commit 5bb6c0e

xplshn  ·  2026-06-16 22:23:04 +0000 UTC
parent 18fa4ce
SH build system - added manpages - large refactor

Signed-off-by: xplshn <anto@xplshn.com.ar>
194 files changed,  +11597, -1902
M README
+180, -38
  1@@ -25,7 +25,8 @@ HDR =\
  2 	shared/passwd.h\
  3 	shared/reboot.h\
  4 	shared/rtc.h\
  5-	shared/proc.h
  6+	shared/proc.h\
  7+	shared/tls.h
  8 
  9 LIBUTFOBJ =\
 10 	shared/libutf/fgetrune.o\
 11@@ -98,9 +99,15 @@ LIBUTILOBJ =\
 12 	shared/libutil/tty.o\
 13 	shared/libutil/fconcat.o\
 14 	shared/libutil/recurse_dir.o\
 15-	shared/libutil/sig.o
 16+	shared/libutil/sig.o\
 17+	shared/libutil/net.o\
 18+	shared/libutil/sysinfo.o\
 19+	shared/libutil/tls.o
 20 
 21-LIB = shared/libutf/libutf.a shared/libutil/libutil.a
 22+LIBREDLINEOBJ =\
 23+	shared/libredline/redline.o
 24+
 25+LIB = shared/libredline/libredline.a shared/libutil/libutil.a shared/libutf/libutf.a
 26 
 27 POSIX_BIN_ALL =\
 28 	cmd/posix/basename\
 29@@ -179,48 +186,46 @@ POSIX_BIN_ALL =\
 30 	cmd/posix/xargs\
 31 	cmd/posix/awk/awk\
 32 	cmd/posix/sh/sh\
 33-	cmd/posix/pax
 34+	cmd/posix/pax\
 35+	cmd/posix/make/make
 36 
 37 LINUX_BIN_ALL =\
 38 	cmd/linux/blkdiscard\
 39 	cmd/linux/chvt\
 40 	cmd/linux/ctrlaltdel\
 41-	cmd/linux/dmesg\
 42 	cmd/linux/eject\
 43-	cmd/linux/fallocate\
 44-	cmd/linux/free\
 45 	cmd/linux/freeramdisk\
 46 	cmd/linux/fsfreeze\
 47 	cmd/linux/hwclock\
 48 	cmd/linux/insmod\
 49 	cmd/linux/lsmod\
 50+	cmd/linux/modprobe\
 51+	cmd/linux/depmod\
 52 	cmd/linux/mkswap\
 53 	cmd/linux/mount\
 54 	cmd/linux/mountpoint\
 55-	cmd/linux/pidof\
 56 	cmd/linux/pivot_root\
 57-	cmd/linux/pwdx\
 58 	cmd/linux/readahead\
 59 	cmd/linux/rmmod\
 60 	cmd/linux/swaplabel\
 61 	cmd/linux/swapoff\
 62 	cmd/linux/swapon\
 63 	cmd/linux/switch_root\
 64+	cmd/linux/tunctl\
 65 	cmd/linux/umount\
 66 	cmd/linux/unshare\
 67-	cmd/linux/uptime\
 68 	cmd/linux/vtallow
 69 
 70 NET_BIN_ALL =\
 71 	cmd/net/netcat\
 72 	cmd/net/tftp\
 73-	cmd/net/tunctl\
 74 	cmd/net/wget\
 75 	cmd/net/ping\
 76 	cmd/net/sdhcp\
 77 	cmd/net/ifconfig\
 78 	cmd/net/host\
 79-	cmd/net/httpd
 80+	cmd/net/httpd\
 81+	cmd/net/ip
 82 
 83 XSI_BIN_ALL =\
 84 	cmd/xsi/mknod\
 85@@ -266,7 +271,17 @@ PSEUDO_BIN_ALL =\
 86 	cmd/pseudo/xinstall\
 87 	cmd/pseudo/yes\
 88 	cmd/pseudo/base64\
 89-	cmd/extra/b3sum
 90+	cmd/extra/b3sum\
 91+	cmd/dev/ar/ar\
 92+	cmd/dev/as/as\
 93+	cmd/dev/ld/ld\
 94+	cmd/dev/cc/cc\
 95+	cmd/pseudo/dmesg\
 96+	cmd/pseudo/fallocate\
 97+	cmd/pseudo/free\
 98+	cmd/pseudo/pidof\
 99+	cmd/pseudo/pwdx\
100+	cmd/pseudo/uptime
101 
102 MAKEOBJ =\
103 	cmd/posix/make/defaults.o\
104@@ -352,45 +367,43 @@ BIN_xargs_1 = cmd/posix/xargs
105 BIN_awk_1 = cmd/posix/awk/awk
106 BIN_sh_1 = cmd/posix/sh/sh
107 BIN_pax_1 = cmd/posix/pax
108+BIN_make_1 = cmd/posix/make/make
109 
110 BIN_blkdiscard_1 = cmd/linux/blkdiscard
111 BIN_chvt_1 = cmd/linux/chvt
112 BIN_ctrlaltdel_1 = cmd/linux/ctrlaltdel
113-BIN_dmesg_1 = cmd/linux/dmesg
114 BIN_eject_1 = cmd/linux/eject
115-BIN_fallocate_1 = cmd/linux/fallocate
116-BIN_free_1 = cmd/linux/free
117 BIN_freeramdisk_1 = cmd/linux/freeramdisk
118 BIN_fsfreeze_1 = cmd/linux/fsfreeze
119 BIN_hwclock_1 = cmd/linux/hwclock
120 BIN_insmod_1 = cmd/linux/insmod
121 BIN_lsmod_1 = cmd/linux/lsmod
122+BIN_modprobe_1 = cmd/linux/modprobe
123+BIN_depmod_1 = cmd/linux/depmod
124 BIN_mkswap_1 = cmd/linux/mkswap
125 BIN_mount_1 = cmd/linux/mount
126 BIN_mountpoint_1 = cmd/linux/mountpoint
127-BIN_pidof_1 = cmd/linux/pidof
128 BIN_pivot_root_1 = cmd/linux/pivot_root
129-BIN_pwdx_1 = cmd/linux/pwdx
130 BIN_readahead_1 = cmd/linux/readahead
131 BIN_rmmod_1 = cmd/linux/rmmod
132 BIN_swaplabel_1 = cmd/linux/swaplabel
133 BIN_swapoff_1 = cmd/linux/swapoff
134 BIN_swapon_1 = cmd/linux/swapon
135 BIN_switch_root_1 = cmd/linux/switch_root
136+BIN_tunctl_1 = cmd/linux/tunctl
137 BIN_umount_1 = cmd/linux/umount
138 BIN_unshare_1 = cmd/linux/unshare
139-BIN_uptime_1 = cmd/linux/uptime
140 BIN_vtallow_1 = cmd/linux/vtallow
141 
142 BIN_netcat_1 = cmd/net/netcat
143 BIN_tftp_1 = cmd/net/tftp
144-BIN_tunctl_1 = cmd/net/tunctl
145 BIN_wget_1 = cmd/net/wget
146 BIN_ping_1 = cmd/net/ping
147 BIN_sdhcp_1 = cmd/net/sdhcp
148 BIN_ifconfig_1 = cmd/net/ifconfig
149 BIN_host_1 = cmd/net/host
150 BIN_httpd_1 = cmd/net/httpd
151+BIN_ip_1 = cmd/net/ip
152 
153 BIN_mknod_1 = cmd/xsi/mknod
154 BIN_passwd_1 = cmd/xsi/passwd
155@@ -435,8 +448,16 @@ BIN_xinstall_1 = cmd/pseudo/xinstall
156 BIN_yes_1 = cmd/pseudo/yes
157 BIN_base64_1 = cmd/pseudo/base64
158 BIN_b3sum_1 = cmd/extra/b3sum
159-
160-BIN_make_tool_1 = cmd/posix/make/make
161+BIN_ar_1 = cmd/dev/ar/ar
162+BIN_as_1 = cmd/dev/as/as
163+BIN_ld_1 = cmd/dev/ld/ld
164+BIN_cc_1 = cmd/dev/cc/cc
165+BIN_dmesg_1 = cmd/pseudo/dmesg
166+BIN_fallocate_1 = cmd/pseudo/fallocate
167+BIN_free_1 = cmd/pseudo/free
168+BIN_pidof_1 = cmd/pseudo/pidof
169+BIN_pwdx_1 = cmd/pseudo/pwdx
170+BIN_uptime_1 = cmd/pseudo/uptime
171 
172 POSIX_BIN = \
173 	$(BIN_basename_$(BUILD_POSIX_BASENAME)) \
174@@ -515,48 +536,46 @@ POSIX_BIN = \
175 	$(BIN_xargs_$(BUILD_POSIX_XARGS)) \
176 	$(BIN_awk_$(BUILD_POSIX_AWK)) \
177 	$(BIN_sh_$(BUILD_POSIX_SH)) \
178-	$(BIN_pax_$(BUILD_POSIX_PAX))
179+	$(BIN_pax_$(BUILD_POSIX_PAX)) \
180+	$(BIN_make_$(BUILD_POSIX_MAKE))
181 
182 LINUX_BIN = \
183 	$(BIN_blkdiscard_$(BUILD_LINUX_BLKDISCARD)) \
184 	$(BIN_chvt_$(BUILD_LINUX_CHVT)) \
185 	$(BIN_ctrlaltdel_$(BUILD_LINUX_CTRLALTDEL)) \
186-	$(BIN_dmesg_$(BUILD_LINUX_DMESG)) \
187 	$(BIN_eject_$(BUILD_LINUX_EJECT)) \
188-	$(BIN_fallocate_$(BUILD_LINUX_FALLOCATE)) \
189-	$(BIN_free_$(BUILD_LINUX_FREE)) \
190 	$(BIN_freeramdisk_$(BUILD_LINUX_FREERAMDISK)) \
191 	$(BIN_fsfreeze_$(BUILD_LINUX_FSFREEZE)) \
192 	$(BIN_hwclock_$(BUILD_LINUX_HWCLOCK)) \
193 	$(BIN_insmod_$(BUILD_LINUX_INSMOD)) \
194 	$(BIN_lsmod_$(BUILD_LINUX_LSMOD)) \
195+	$(BIN_modprobe_$(BUILD_LINUX_MODPROBE)) \
196+	$(BIN_depmod_$(BUILD_LINUX_DEPMOD)) \
197 	$(BIN_mkswap_$(BUILD_LINUX_MKSWAP)) \
198 	$(BIN_mount_$(BUILD_LINUX_MOUNT)) \
199 	$(BIN_mountpoint_$(BUILD_LINUX_MOUNTPOINT)) \
200-	$(BIN_pidof_$(BUILD_LINUX_PIDOF)) \
201 	$(BIN_pivot_root_$(BUILD_LINUX_PIVOT_ROOT)) \
202-	$(BIN_pwdx_$(BUILD_LINUX_PWDX)) \
203 	$(BIN_readahead_$(BUILD_LINUX_READAHEAD)) \
204 	$(BIN_rmmod_$(BUILD_LINUX_RMMOD)) \
205 	$(BIN_swaplabel_$(BUILD_LINUX_SWAPLABEL)) \
206 	$(BIN_swapoff_$(BUILD_LINUX_SWAPOFF)) \
207 	$(BIN_swapon_$(BUILD_LINUX_SWAPON)) \
208 	$(BIN_switch_root_$(BUILD_LINUX_SWITCH_ROOT)) \
209+	$(BIN_tunctl_$(BUILD_LINUX_TUNCTL)) \
210 	$(BIN_umount_$(BUILD_LINUX_UMOUNT)) \
211 	$(BIN_unshare_$(BUILD_LINUX_UNSHARE)) \
212-	$(BIN_uptime_$(BUILD_LINUX_UPTIME)) \
213 	$(BIN_vtallow_$(BUILD_LINUX_VTALLOW))
214 
215 NET_BIN = \
216 	$(BIN_netcat_$(BUILD_NET_NETCAT)) \
217 	$(BIN_tftp_$(BUILD_NET_TFTP)) \
218-	$(BIN_tunctl_$(BUILD_NET_TUNCTL)) \
219 	$(BIN_wget_$(BUILD_NET_WGET)) \
220 	$(BIN_ping_$(BUILD_NET_PING)) \
221 	$(BIN_sdhcp_$(BUILD_NET_SDHCP)) \
222 	$(BIN_ifconfig_$(BUILD_NET_IFCONFIG)) \
223 	$(BIN_host_$(BUILD_NET_HOST)) \
224-	$(BIN_httpd_$(BUILD_NET_HTTPD))
225+	$(BIN_httpd_$(BUILD_NET_HTTPD)) \
226+	$(BIN_ip_$(BUILD_NET_IP))
227 
228 XSI_BIN = \
229 	$(BIN_mknod_$(BUILD_XSI_MKNOD)) \
230@@ -606,13 +625,17 @@ PSEUDO_BIN = \
231 	$(BIN_ar_$(BUILD_DEV_AR)) \
232 	$(BIN_as_$(BUILD_DEV_CC)) \
233 	$(BIN_ld_$(BUILD_DEV_LD)) \
234-	$(BIN_cc_$(BUILD_DEV_CC))
235-
236-MAKE_BIN = $(BIN_make_tool_$(BUILD_MAKE_MAKE))
237+	$(BIN_cc_$(BUILD_DEV_CC)) \
238+	$(BIN_dmesg_$(BUILD_PSEUDO_DMESG)) \
239+	$(BIN_fallocate_$(BUILD_PSEUDO_FALLOCATE)) \
240+	$(BIN_free_$(BUILD_PSEUDO_FREE)) \
241+	$(BIN_pidof_$(BUILD_PSEUDO_PIDOF)) \
242+	$(BIN_pwdx_$(BUILD_PSEUDO_PWDX)) \
243+	$(BIN_uptime_$(BUILD_PSEUDO_UPTIME))
244 
245 OBJ = $(LIBUTFOBJ) $(LIBUTILOBJ) $(MAKEOBJ)
246 
247-all: $(LIB) $(POSIX_BIN) $(LINUX_BIN) $(NET_BIN) $(XSI_BIN) $(PSEUDO_BIN) $(MAKE_BIN)
248+all: $(LIB) $(POSIX_BIN) $(LINUX_BIN) $(NET_BIN) $(XSI_BIN) $(PSEUDO_BIN)
249 
250 $(POSIX_BIN_ALL) $(LINUX_BIN_ALL) $(NET_BIN_ALL) $(XSI_BIN_ALL) $(PSEUDO_BIN_ALL): $(LIB)
251 
252@@ -642,6 +665,10 @@ shared/libutf/libutf.a: $(LIBUTFOBJ)
253 	$(AR) $(ARFLAGS) $@ $?
254 	$(RANLIB) $@
255 
256+shared/libredline/libredline.a: $(LIBREDLINEOBJ)
257+	$(AR) $(ARFLAGS) $@ $?
258+	$(RANLIB) $@
259+
260 shared/libutil/libutil.a: $(LIBUTILOBJ)
261 	$(AR) $(ARFLAGS) $@ $?
262 	$(RANLIB) $@
263@@ -656,14 +683,39 @@ box: $(LIB)
264 	LDFLAGS='$(LDFLAGS)' LDLIBS='$(LDLIBS)' OBJCOPY='$(OBJCOPY)' \
265 	scripts/mkbox
266 
267+.PHONY: man clean
268+
269+scripts/mkman/mkman: scripts/mkman/main.go scripts/mkman/troff.go
270+	cd scripts/mkman && go build -o mkman .
271+
272+man: scripts/mkman/mkman
273+	@for src in $(POSIX_BIN_ALL:=.c) $(PSEUDO_BIN_ALL:=.c); do \
274+		if [ -f "$$src" ] && grep -qE '!man|\?man' "$$src"; then \
275+			base=$$(basename $$src .c); \
276+			mkdir -p man/man1; \
277+			scripts/mkman/mkman -config config.mk -section 1 "$$src" > "man/man1/$$base.1"; \
278+		fi; \
279+	done
280+	@for src in $(LINUX_BIN_ALL:=.c) $(NET_BIN_ALL:=.c) $(XSI_BIN_ALL:=.c); do \
281+		if [ -f "$$src" ] && grep -qE '!man|\?man' "$$src"; then \
282+			base=$$(basename $$src .c); \
283+			mkdir -p man/man8; \
284+			scripts/mkman/mkman -config config.mk -section 8 "$$src" > "man/man8/$$base.8"; \
285+		fi; \
286+	done
287+
288 clean:
289-	rm -f shared/libutf/*.o shared/libutil/*.o cmd/posix/make/*.o cmd/posix/awk/*.o cmd/posix/sh/*.o cmd/extra/*.o
290+	rm -f shared/libutf/*.o shared/libutil/*.o shared/libredline/*.o
291+	rm -f cmd/posix/*.o cmd/posix/make/*.o cmd/posix/awk/*.o cmd/posix/sh/*.o
292+	rm -f cmd/linux/*.o cmd/net/*.o cmd/xsi/*.o cmd/pseudo/*.o
293+	rm -f cmd/extra/*.o cmd/dev/ar/*.o cmd/dev/ld/*.o cmd/dev/cc/*.o cmd/dev/as/*.o
294 	rm -f $(POSIX_BIN_ALL) $(LINUX_BIN_ALL) $(NET_BIN_ALL) $(XSI_BIN_ALL) $(PSEUDO_BIN_ALL) $(LIB)
295 	rm -f cmd/posix/make/make cmd/posix/getconf.h cmd/posix/bc.c
296 	rm -f cmd/posix/awk/awk cmd/posix/awk/maketab cmd/posix/awk/awkgram.tab.c cmd/posix/awk/awkgram.tab.h cmd/posix/awk/proctab.c
297 	rm -f cmd/posix/sh/sh cmd/posix/sh/mknodes cmd/posix/sh/mksyntax
298 	rm -f cmd/posix/sh/syntax.c cmd/posix/sh/syntax.h cmd/posix/sh/nodes.c cmd/posix/sh/nodes.h cmd/posix/sh/builtins.c cmd/posix/sh/builtins.h cmd/posix/sh/token.h
299-	rm -rf aruu-box .box
300+	rm -f cmd/dev/cc/cc1 cmd/dev/cc/cpp cmd/dev/as/as shared/libaruuelf.so
301+	rm -rf aruu-box .box man/man1 man/man8 scripts/mkman/mkman
302 
303 AWKOBJ =\
304 	cmd/posix/awk/b.o\
305@@ -693,7 +745,7 @@ SHOBJ =\
306 	cmd/posix/sh/eval.o\
307 	cmd/posix/sh/exec.o\
308 	cmd/posix/sh/expand.o\
309-	cmd/posix/sh/histedit.o\
310+	cmd/posix/sh/lineedit.o\
311 	cmd/posix/sh/input.o\
312 	cmd/posix/sh/jobs.o\
313 	cmd/posix/sh/kill.o\
314@@ -757,3 +809,93 @@ cmd/posix/sh/%.o: cmd/posix/sh/%.c
315 
316 cmd/posix/sh/sh: $(SHOBJ) $(LIB)
317 	$(CC) $(LDFLAGS) -o $@ $(SHOBJ) $(LIB) $(LDLIBS)
318+
319+cmd/dev/ar/ar: cmd/dev/ar/ar.o $(LIB)
320+	$(CC) $(LDFLAGS) -o $@ cmd/dev/ar/ar.o $(LIB) $(LDLIBS)
321+
322+cmd/dev/ar/%.o: cmd/dev/ar/%.c
323+	$(CC) $(CPPFLAGS) -Icmd/dev/ar $(CFLAGS) -o $@ -c $<
324+
325+LD_OBJ =\
326+	cmd/dev/ld/ld.o
327+
328+cmd/dev/ld/%.o: cmd/dev/ld/%.c
329+	$(CC) $(CPPFLAGS) -DLD_TARGET_X86_64 -Icmd/dev/ld $(CFLAGS) -fPIC -o $@ -c $<
330+
331+shared/libaruuelf.so: cmd/dev/ld/elf.o cmd/dev/ld/x86_64.o cmd/dev/ld/ld_support.o
332+	$(CC) $(LDFLAGS) -shared -o $@ cmd/dev/ld/elf.o cmd/dev/ld/x86_64.o cmd/dev/ld/ld_support.o
333+
334+cmd/dev/ld/ld: $(LD_OBJ) shared/libaruuelf.so $(LIB)
335+	$(CC) $(LDFLAGS) -o $@ $(LD_OBJ) -Lshared -laruuelf $(LIB) $(LDLIBS) -Wl,-rpath,'$$ORIGIN/../../../shared'
336+
337+AS_OBJ =\
338+	cmd/dev/as/as.o\
339+	cmd/dev/as/asm_lex.o\
340+	cmd/dev/as/asm_parse.o\
341+	cmd/dev/as/asm_x86_64.o\
342+	cmd/dev/as/asm_elf.o
343+
344+cmd/dev/as/%.o: cmd/dev/as/%.c
345+	$(CC) $(CPPFLAGS) -Icmd/dev/as -Ishared $(CFLAGS) -o $@ -c $<
346+
347+cmd/dev/as/as: $(AS_OBJ) $(LIB)
348+	$(CC) $(LDFLAGS) -o $@ $(AS_OBJ) $(LIB) $(LDLIBS)
349+
350+cmd/dev/cc/driver.o: cmd/dev/cc/driver.c
351+	$(CC) -Icmd/dev/cc $(CPPFLAGS) $(CFLAGS) -o $@ -c $<
352+
353+cmd/dev/cc/cc: cmd/dev/cc/driver.o cmd/dev/cc/util.o $(LIB) cmd/dev/cc/cc1 cmd/dev/cc/cpp cmd/dev/as/as
354+	$(CC) $(LDFLAGS) -o $@ cmd/dev/cc/driver.o cmd/dev/cc/util.o $(LIB) $(LDLIBS)
355+
356+CC1_OBJ =\
357+	cmd/dev/cc/attr.o\
358+	cmd/dev/cc/decl.o\
359+	cmd/dev/cc/eval.o\
360+	cmd/dev/cc/expr.o\
361+	cmd/dev/cc/init.o\
362+	cmd/dev/cc/cc1.o\
363+	cmd/dev/cc/map.o\
364+	cmd/dev/cc/pp.o\
365+	cmd/dev/cc/qbe.o\
366+	cmd/dev/cc/scan.o\
367+	cmd/dev/cc/scope.o\
368+	cmd/dev/cc/stmt.o\
369+	cmd/dev/cc/targ.o\
370+	cmd/dev/cc/token.o\
371+	cmd/dev/cc/tree.o\
372+	cmd/dev/cc/type.o\
373+	cmd/dev/cc/utf.o\
374+	cmd/dev/cc/util.o
375+
376+CPP_OBJ =\
377+	cmd/dev/cc/attr.o\
378+	cmd/dev/cc/decl.o\
379+	cmd/dev/cc/eval.o\
380+	cmd/dev/cc/expr.o\
381+	cmd/dev/cc/init.o\
382+	cmd/dev/cc/map.o\
383+	cmd/dev/cc/pp.o\
384+	cmd/dev/cc/qbe.o\
385+	cmd/dev/cc/scan.o\
386+	cmd/dev/cc/scope.o\
387+	cmd/dev/cc/stmt.o\
388+	cmd/dev/cc/targ.o\
389+	cmd/dev/cc/token.o\
390+	cmd/dev/cc/tree.o\
391+	cmd/dev/cc/type.o\
392+	cmd/dev/cc/utf.o\
393+	cmd/dev/cc/util.o
394+
395+cmd/dev/cc/%.o: cmd/dev/cc/%.c
396+	$(CC) -Icmd/dev/cc $(CPPFLAGS) $(CFLAGS) -o $@ -c $<
397+
398+cmd/dev/cc/cc1: $(CC1_OBJ) $(LIB)
399+	$(CC) $(LDFLAGS) -o $@ $(CC1_OBJ) $(LIB) $(LDLIBS)
400+
401+cmd/dev/cc/cpp: cmd/dev/cc/cpp.o $(CPP_OBJ) $(LIB)
402+	$(CC) $(LDFLAGS) -o $@ cmd/dev/cc/cpp.o $(CPP_OBJ) $(LIB) $(LDLIBS)
403+
404+
405+
406+
407+
+522, -0
  1@@ -0,0 +1,522 @@
  2+#!/bin/sh
  3+# drives the build without needing a working make to bootstrap
  4+#
  5+set -e
  6+
  7+# per-run work directory, cleaned up on exit
  8+#
  9+WORKDIR="${TMPDIR:-/tmp}/aruu.$$"
 10+mkdir -p "$WORKDIR"
 11+trap 'rm -rf "$WORKDIR"' EXIT INT TERM HUP
 12+
 13+JOBS=1
 14+LOAD=0
 15+TARGET=all
 16+
 17+while [ $# -gt 0 ]; do
 18+	case "$1" in
 19+		-j)  shift; JOBS="${1:?-j requires a number}" ;;
 20+		-j*) JOBS="${1#-j}" ;;
 21+		-l)  shift; LOAD="${1:?-l requires a number}" ;;
 22+		-l*) LOAD="${1#-l}" ;;
 23+		-*)  printf 'unknown flag: %s\n' "$1" >&2; exit 1 ;;
 24+		*)   TARGET="$1" ;;
 25+	esac
 26+	shift
 27+done
 28+
 29+[ -f build.cfg ] || { printf 'build.cfg not found; see the repository README\n' >&2; exit 1; }
 30+# shellcheck disable=SC1091
 31+. ./build.cfg
 32+
 33+# cppflags derived from build.cfg; any feature_* added there is picked up automatically
 34+_feature_flags=""
 35+while IFS= read -r _line; do
 36+	_trimmed=$(printf '%s' "$_line" | tr -d ' \t')
 37+	case "$_trimmed" in
 38+		FEATURE_*=*)
 39+			_key=${_trimmed%%=*}
 40+			eval "_val=\$$_key"
 41+			_feature_flags="$_feature_flags -D${_key}=${_val}"
 42+			;;
 43+	esac
 44+done <<EOF
 45+$(tr ';' '\n' < build.cfg)
 46+EOF
 47+unset _line _key _val _trimmed
 48+CPPFLAGS="-Ishared -DPREFIX=\"$PREFIX\" -D_DEFAULT_SOURCE -D_GNU_SOURCE -D_NETBSD_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_FILE_OFFSET_BITS=64 -DSTD_NON_POSIX$_feature_flags"
 49+if [ "$FEATURE_USE_BEARSSL" = "1" ]; then
 50+	CPPFLAGS="$CPPFLAGS -IexternalRepos/BearSSL/inc"
 51+	LDFLAGS="$LDFLAGS -LexternalRepos/BearSSL/build"
 52+	LDLIBS="$LDLIBS externalRepos/BearSSL/build/libbearssl.a"
 53+fi
 54+if [ "$FEATURE_USE_LIBRESSL" = "1" ]; then
 55+	LDLIBS="$LDLIBS -ltls"
 56+fi
 57+unset _feature_flags
 58+
 59+CC=${CC:-cc}
 60+AR=${AR:-ar}
 61+ARFLAGS=${ARFLAGS:-rc}
 62+RANLIB=${RANLIB:-ranlib}
 63+PREFIX=${PREFIX:-/usr/local}
 64+MANPREFIX=${MANPREFIX:-$PREFIX/share/man}
 65+
 66+# shared headers; every object depends on these
 67+HDR=$(echo shared/*.h)
 68+LIB="shared/libredline/libredline.a shared/libutil/libutil.a shared/libutf/libutf.a"
 69+
 70+# set by multi-file builders for generated headers, cleared after use
 71+EXTRA_HDR=
 72+
 73+# timestamp comparison
 74+#
 75+mtime() {
 76+	stat -c '%Y' "$1" 2>/dev/null || stat -f '%m' "$1" 2>/dev/null || echo 0
 77+}
 78+
 79+newer_than() {
 80+	local s t
 81+	[ -e "$2" ] || return 0
 82+	s=$(mtime "$1"); t=$(mtime "$2")
 83+	[ "$s" -gt "$t" ]
 84+}
 85+
 86+any_newer_than() {
 87+	local target="$1"; shift
 88+	for src do
 89+		newer_than "$src" "$target" && return 0
 90+	done
 91+	return 1
 92+}
 93+
 94+# parallel job queue
 95+#
 96+# objs_for must not run in a subshell; enqueue writes _qn/_queue in the current shell
 97+
 98+_qn=0
 99+_queue=""
100+_objs=""
101+
102+# wraps arg in single quotes, escaping embedded ones, for safe eval
103+shquote() {
104+	printf "'"
105+	printf '%s' "$1" | sed "s/'/'\\\\''/g"
106+	printf "'"
107+}
108+
109+enqueue() {
110+	local desc="$1" cmd="$2"
111+	_qn=$((_qn + 1))
112+	local s="$WORKDIR/j${_qn}.sh"
113+	{
114+		printf '#!/bin/sh\n'
115+		printf 'printf "%%s\\n" %s\n' "$(shquote "$desc")"
116+		printf 'eval %s\n'            "$(shquote "$cmd")"
117+	} > "$s"
118+	_queue="$_queue $_qn"
119+}
120+
121+# silently skipped on systems without /proc/loadavg
122+_wait_for_load() {
123+	[ "$LOAD" -eq 0 ] && return 0
124+	local avg
125+	while true; do
126+		avg=$(awk '{printf "%d", int($1 + 0.5)}' /proc/loadavg 2>/dev/null) || return 0
127+		[ "$avg" -le "$LOAD" ] && return 0
128+		sleep 1
129+	done
130+}
131+
132+drain() {
133+	[ -z "$_queue" ] && return 0
134+	local n
135+	_wait_for_load
136+	if [ "$JOBS" -le 1 ]; then
137+		for n in $_queue; do
138+			sh "$WORKDIR/j${n}.sh" || { printf 'error: build step failed\n' >&2; exit 1; }
139+		done
140+	else
141+		# xargs -P (POSIX 2024), -n 1 so each path becomes its own sh invocation
142+		for n in $_queue; do
143+			printf '%s\n' "$WORKDIR/j${n}.sh"
144+		done | xargs -P "$JOBS" -n 1 sh || { printf 'error: build step failed\n' >&2; exit 1; }
145+	fi
146+	_queue=""
147+}
148+
149+# compile and link primitives
150+#
151+compile_c() {
152+	local src="$1" obj="$2"; shift 2
153+	# shellcheck disable=SC2086
154+	any_newer_than "$obj" "$src" $HDR $EXTRA_HDR || return 0
155+	enqueue "  CC  $obj" "$CC $CPPFLAGS $* $CFLAGS -o $obj -c $src"
156+}
157+
158+link_bin() {
159+	local bin="$1" objs="" libs="" sep=0; shift
160+	for a do
161+		if   [ "$a" = "--" ]; then sep=1
162+		elif [ "$sep" = 0  ]; then objs="$objs $a"
163+		else                        libs="$libs $a"
164+		fi
165+	done
166+	drain
167+	# shellcheck disable=SC2086
168+	any_newer_than "$bin" $objs || return 0
169+	printf '  LD  %s\n' "$bin"
170+	# shellcheck disable=SC2086
171+	eval "$CC $LDFLAGS -o $bin $objs $libs $LDLIBS"
172+}
173+
174+# writes to _objs rather than stdout so the caller avoids a subshell that
175+# would discard enqueued jobs
176+objs_for() {
177+	local dir="$1" skip=" $2 " flags="$3" src base
178+	_objs=""
179+	for src in "$dir"/*.c; do
180+		[ -f "$src" ] || continue
181+		base=${src##*/}
182+		case "$skip" in *" $base "*) continue ;; esac
183+		compile_c "$src" "${src%.c}.o" $flags
184+		_objs="$_objs ${src%.c}.o"
185+	done
186+}
187+
188+# static libraries
189+#
190+# both directories enqueued before the single drain so they compile in parallel
191+build_lib() {
192+	local utf_objs util_objs redline_objs
193+	objs_for shared/libutf "" ""
194+	utf_objs="$_objs"
195+	objs_for shared/libutil "" ""
196+	util_objs="$_objs"
197+	objs_for shared/libredline "" ""
198+	redline_objs="$_objs"
199+	drain
200+	# shellcheck disable=SC2086
201+	any_newer_than shared/libutf/libutf.a $utf_objs && {
202+		printf '  AR  shared/libutf/libutf.a\n'
203+		$AR $ARFLAGS shared/libutf/libutf.a $utf_objs
204+		$RANLIB shared/libutf/libutf.a
205+	}
206+	# shellcheck disable=SC2086
207+	any_newer_than shared/libutil/libutil.a $util_objs && {
208+		printf '  AR  shared/libutil/libutil.a\n'
209+		$AR $ARFLAGS shared/libutil/libutil.a $util_objs
210+		$RANLIB shared/libutil/libutil.a
211+	}
212+	# shellcheck disable=SC2086
213+	any_newer_than shared/libredline/libredline.a $redline_objs && {
214+		printf '  AR  shared/libredline/libredline.a\n'
215+		$AR $ARFLAGS shared/libredline/libredline.a $redline_objs
216+		$RANLIB shared/libredline/libredline.a
217+	}
218+	return 0
219+}
220+
221+# per-tool category builder
222+#
223+cfgvar() {
224+	local cat="$1" base="$2"
225+	[ "$cat" = extra ] && cat=pseudo
226+	printf 'BUILD_%s_%s' "$cat" "$base" | tr 'a-z-' 'A-Z_'
227+}
228+
229+# individual flag overrides group flag
230+cfg_enabled() {
231+	local _var="$1" _v
232+	while [ -n "$_var" ]; do
233+		eval "_v=\${${_var}-UNSET}"
234+		if [ "$_v" = "1" ]; then
235+			return 0
236+		elif [ "$_v" = "0" ]; then
237+			return 1
238+		fi
239+		case "$_var" in
240+			*_[^_]*) _var=$(printf '%s' "$_var" | sed 's/_[^_]*$//') ;;
241+			*)       break ;;
242+		esac
243+	done
244+	return 1
245+}
246+
247+build_simple_tools() {
248+	local cat="$1" src base var staged=""
249+	for src in cmd/"$cat"/*.c; do
250+		[ -f "$src" ] || continue
251+		base=${src##*/}; base=${base%.c}
252+		var=$(cfgvar "$cat" "$base")
253+		cfg_enabled "$var" || continue
254+		compile_c "$src" "${src%.c}.o"
255+		staged="$staged ${src%.c}"
256+	done
257+	drain
258+	local bin
259+	for bin in $staged; do
260+		link_bin "$bin" "${bin}.o" -- $LIB
261+	done
262+}
263+
264+# generated multi-file tools under cmd/posix
265+#
266+build_awk() {
267+	cfg_enabled BUILD_POSIX_AWK || return 0
268+	local dir=cmd/posix/awk
269+
270+	any_newer_than "$dir/awkgram.tab.c" "$dir/awkgram.y" && {
271+		if   command -v yacc  >/dev/null 2>&1; then
272+			printf '  YACC  %s/awkgram.tab.c\n' "$dir"
273+			yacc  -d -o "$dir/awkgram.tab.c" "$dir/awkgram.y"
274+		elif command -v bison >/dev/null 2>&1; then
275+			printf '  BISON %s/awkgram.tab.c\n' "$dir"
276+			bison -d -o "$dir/awkgram.tab.c" "$dir/awkgram.y"
277+		else
278+			printf 'awk: no yacc/bison; using pre-generated awkgram.tab.c\n' >&2
279+		fi
280+	}
281+
282+	any_newer_than "$dir/maketab" "$dir/maketab.c" "$dir/awkgram.tab.h" && {
283+		printf '  CC    %s/maketab\n' "$dir"
284+		eval "$CC $CFLAGS -o $dir/maketab $dir/maketab.c"
285+	}
286+
287+	any_newer_than "$dir/proctab.c" "$dir/maketab" && {
288+		printf '  GEN   %s/proctab.c\n' "$dir"
289+		"$dir/maketab" "$dir/awkgram.tab.h" > "$dir/proctab.c"
290+	}
291+
292+	EXTRA_HDR="$dir/awk.h $dir/awkgram.tab.h $dir/proto.h"
293+	objs_for "$dir" maketab.c "-I$dir"
294+	EXTRA_HDR=
295+	link_bin "$dir/awk" $_objs -- $LIB -lm
296+}
297+
298+build_sh() {
299+	cfg_enabled BUILD_POSIX_SH || return 0
300+	local dir=cmd/posix/sh
301+
302+	any_newer_than "$dir/mknodes" "$dir/mknodes.c" && {
303+		printf '  CC    %s/mknodes\n' "$dir"
304+		eval "$CC $CFLAGS -o $dir/mknodes $dir/mknodes.c"
305+	}
306+
307+	any_newer_than "$dir/mksyntax" "$dir/mksyntax.c" && {
308+		printf '  CC    %s/mksyntax\n' "$dir"
309+		eval "$CC $CPPFLAGS -I$dir $CFLAGS -o $dir/mksyntax $dir/mksyntax.c"
310+	}
311+
312+	any_newer_than "$dir/token.h" "$dir/mktokens" && {
313+		printf '  GEN   %s/token.h\n' "$dir"
314+		(cd "$dir" && sh mktokens)
315+	}
316+
317+	any_newer_than "$dir/syntax.c" "$dir/mksyntax" && {
318+		printf '  GEN   %s/syntax.c\n' "$dir"
319+		(cd "$dir" && ./mksyntax)
320+	}
321+
322+	any_newer_than "$dir/nodes.c" "$dir/mknodes" "$dir/nodetypes" "$dir/nodes.c.pat" && {
323+		printf '  GEN   %s/nodes.c\n' "$dir"
324+		(cd "$dir" && ./mknodes nodetypes nodes.c.pat)
325+	}
326+
327+	any_newer_than "$dir/builtins.c" "$dir/mkbuiltins" "$dir/builtins.def" "$dir/shell.h" && {
328+		printf '  GEN   %s/builtins.c\n' "$dir"
329+		(cd "$dir" && sh mkbuiltins .)
330+	}
331+
332+	EXTRA_HDR="$dir/syntax.h $dir/nodes.h $dir/builtins.h $dir/token.h"
333+	objs_for "$dir" "mknodes.c mksyntax.c" "-DSHELL -I$dir"
334+	EXTRA_HDR=
335+	link_bin "$dir/sh" $_objs -- $LIB
336+}
337+
338+build_make() {
339+	cfg_enabled BUILD_POSIX_MAKE || return 0
340+	local dir=cmd/posix/make
341+	EXTRA_HDR="$dir/make.h"
342+	objs_for "$dir" "" ""
343+	EXTRA_HDR=
344+	link_bin "$dir/make" $_objs -- $LIB
345+}
346+
347+build_posix() {
348+	cfg_enabled BUILD_POSIX_GETCONF && [ ! -f cmd/posix/getconf.h ] && {
349+		printf '  GEN   cmd/posix/getconf.h\n'
350+		scripts/getconf.sh > cmd/posix/getconf.h || { rm -f cmd/posix/getconf.h; exit 1; }
351+	}
352+	build_simple_tools posix
353+	build_awk
354+	build_sh
355+	build_make
356+}
357+
358+# multi-file dev toolchain
359+#
360+build_ar() {
361+	cfg_enabled BUILD_DEV_AR || return 0
362+	local dir=cmd/dev/ar
363+	objs_for "$dir" tinyar.c "-I$dir"
364+	link_bin "$dir/ar" $_objs -- $LIB
365+}
366+
367+build_as() {
368+	cfg_enabled BUILD_DEV_AS || return 0
369+	local dir=cmd/dev/as
370+	objs_for "$dir" "" "-I$dir -Ishared"
371+	link_bin "$dir/as" $_objs -- $LIB
372+}
373+
374+# rpath embeds $ORIGIN so the installed ld finds libaruuelf.so next to itself
375+build_ld() {
376+	cfg_enabled BUILD_DEV_LD || return 0
377+	local dir="cmd/dev/ld"
378+	local cflags="-DLD_TARGET_X86_64 -I$dir -fPIC"
379+	local src
380+
381+	for src in "$dir"/*.c; do
382+		compile_c "$src" "${src%.c}.o" $cflags
383+	done
384+	drain
385+
386+	any_newer_than shared/libaruuelf.so "$dir/elf.o" "$dir/x86_64.o" "$dir/ld_support.o" && {
387+		printf '  LD    shared/libaruuelf.so\n'
388+		eval "$CC $LDFLAGS -shared -o shared/libaruuelf.so $dir/elf.o $dir/x86_64.o $dir/ld_support.o"
389+	}
390+
391+	any_newer_than "$dir/ld" "$dir/ld.o" shared/libaruuelf.so && {
392+		printf '  LD    %s/ld\n' "$dir"
393+		# $ORIGIN is a dynamic-linker token, not a shell variable
394+		eval "$CC $LDFLAGS -o $dir/ld $dir/ld.o -Lshared -laruuelf $LIB $LDLIBS -Wl,-rpath,'\$ORIGIN/../../../shared'"
395+	}
396+	return 0
397+}
398+
399+build_cc() {
400+	cfg_enabled BUILD_DEV_CC || return 0
401+	local dir="cmd/dev/cc"
402+	local cflags="-I$dir"
403+	local common
404+
405+	# compiled once, linked into both cc1 and cpp
406+	objs_for "$dir" "driver.c cc1.c cpp.c" "$cflags"
407+	common="$_objs"
408+	drain
409+
410+	compile_c "$dir/cc1.c"    "$dir/cc1.o"    $cflags
411+	compile_c "$dir/cpp.c"    "$dir/cpp.o"    $cflags
412+	compile_c "$dir/driver.c" "$dir/driver.o" $cflags
413+	drain
414+
415+	link_bin "$dir/cc1" "$dir/cc1.o"    $common -- $LIB
416+	link_bin "$dir/cpp" "$dir/cpp.o"    $common -- $LIB
417+	link_bin "$dir/cc"  "$dir/driver.o" "$dir/util.o" -- $LIB
418+}
419+
420+build_dev() {
421+	build_ar
422+	build_as
423+	build_ld
424+	build_cc
425+}
426+
427+# manual page generation
428+#
429+man_section() {
430+	case "$1" in
431+		*/linux/*|*/net/*|*/xsi/*) printf '8\n' ;;
432+		*)                         printf '1\n' ;;
433+	esac
434+}
435+
436+build_man_for() {
437+	local var="$1" src="$2" base sec out
438+	cfg_enabled "$var" || return 0
439+	[ -x scripts/mkman/mkman ] || { printf 'error: mkman not built\n' >&2; exit 1; }
440+	base=${src##*/}; base=${base%.c}
441+	sec=$(man_section "$src")
442+	out="man/man${sec}/${base}.${sec}"
443+	mkdir -p "man/man${sec}"
444+	any_newer_than "$out" "$src" build.cfg scripts/mkman/mkman || return 0
445+	printf '  MAN   %s\n' "$out"
446+	scripts/mkman/mkman -config build.cfg -section "$sec" "$src" > "$out"
447+}
448+
449+build_man() {
450+	local dir cat src base
451+	if [ ! -x scripts/mkman/mkman ] || any_newer_than scripts/mkman/mkman scripts/mkman/main.go scripts/mkman/troff.go; then
452+		printf '  GO    scripts/mkman/mkman\n'
453+		(cd scripts/mkman && go build -o mkman .)
454+	fi
455+	for dir in cmd/*; do
456+		[ -d "$dir" ] || continue
457+		cat=${dir##*/}
458+		for src in "$dir"/*.c; do
459+			[ -f "$src" ] || continue
460+			base=${src##*/}; base=${base%.c}
461+			build_man_for "$(cfgvar "$cat" "$base")" "$src"
462+		done
463+	done
464+}
465+
466+# install and clean
467+#
468+do_install() {
469+	local bin f sec
470+	mkdir -p "$PREFIX/bin" "$MANPREFIX/man1" "$MANPREFIX/man8"
471+	find cmd -type f ! -name '*.*' -perm -100 | while read -r bin; do
472+		printf '  INSTALL %s/bin/%s\n' "$PREFIX" "${bin##*/}"
473+		cp "$bin" "$PREFIX/bin/${bin##*/}"
474+		chmod 755 "$PREFIX/bin/${bin##*/}"
475+	done
476+	find man/man1 man/man8 -type f -name '*.[18]' 2>/dev/null | while read -r f; do
477+		sec=${f%/*}; sec=${sec##*/}
478+		cp "$f" "$MANPREFIX/${sec}/${f##*/}"
479+	done
480+}
481+
482+do_clean() {
483+	find shared cmd -name '*.o'                   -exec rm -f {} +
484+	find shared    -name '*.a'                    -exec rm -f {} +
485+	find cmd -type f ! -name '*.*' -perm -100     -exec rm -f {} +
486+	rm -f shared/libaruuelf.so
487+	rm -f cmd/posix/bc.c cmd/posix/getconf.h
488+	rm -f cmd/posix/awk/maketab cmd/posix/awk/proctab.c
489+	rm -f cmd/posix/awk/awkgram.tab.c cmd/posix/awk/awkgram.tab.h
490+	rm -f cmd/posix/sh/mknodes cmd/posix/sh/mksyntax
491+	rm -f cmd/posix/sh/token.h cmd/posix/sh/syntax.c cmd/posix/sh/syntax.h
492+	rm -f cmd/posix/sh/nodes.c cmd/posix/sh/nodes.h
493+	rm -f cmd/posix/sh/builtins.c cmd/posix/sh/builtins.h
494+	rm -rf man/man1 man/man8
495+	rm -rf aruu-box .box
496+	rm -f scripts/mkman/mkman
497+}
498+
499+case "$TARGET" in
500+	all)
501+		build_lib
502+		build_posix
503+		build_dev
504+		build_simple_tools linux
505+		build_simple_tools net
506+		build_simple_tools xsi
507+		build_simple_tools pseudo
508+		build_simple_tools extra
509+		;;
510+	lib)             build_lib ;;
511+	posix)           build_lib; build_posix ;;
512+	dev)             build_lib; build_dev ;;
513+	make)            build_lib; build_make ;;
514+	linux|net|xsi|pseudo|extra)
515+	                 build_lib; build_simple_tools "$TARGET" ;;
516+	man)             build_man ;;
517+	install)         do_install ;;
518+	clean)           do_clean ;;
519+	*)
520+		printf 'usage: sh Makefile.sh [-jN] [-lN] [all|clean|install|man|lib|posix|linux|net|xsi|pseudo|extra|dev|make]\n' >&2
521+		exit 1
522+		;;
523+esac
M README
+2, -2
 1@@ -177,8 +177,8 @@ Copyright (c) 2016 Eivind Uggedal [eivind@uggedal.com](mailto:eivind@uggedal.com
 2 Copyright (c) 2016 Mattias Andrée [m@maandree.se](mailto:m@maandree.se)
 3 Copyright (c) 2026 xplshn [anto@xplshn.com.ar](mailto:anto@xplshn.com.ar) (https://github.com/xplshn)
 4 
 5-Portions of this software (specifically utils under net/) are partially
 6-based on code written by Rob Landley
 7+Portions of this software (specifically utils under net/) are partially based on code written by Rob Landley
 8+Our line editing library is partially based on work by Salvatore Sanfilippo (linenoise) and JC Wang (crossline)
 9 The rest are based on SBASE/UBASE source code
10 
11 =~= Argentino Roca Unix Utilities =~= https://github.com/xplshn/aruu =~=
+124, -0
  1@@ -0,0 +1,124 @@
  2+# posix: POSIX 2024 only  common: uncontroversial extras  all: everything except external libs
  3+PRESET_FEATURES=all
  4+
  5+# subset of: posix linux net xsi pseudo dev
  6+PRESET_UTILS="posix linux net xsi pseudo"
  7+
  8+# installation paths
  9+VERSION=2026-06-08T05-53-UTC-03
 10+PREFIX=/usr/local
 11+MANPREFIX=/usr/local/share/man
 12+
 13+# toolchain
 14+CC=cc
 15+CFLAGS="-std=c99 -Wall -Wextra -pedantic"
 16+LDFLAGS=""
 17+LDLIBS="-lcrypt -lresolv"
 18+AR=ar
 19+ARFLAGS="rc"
 20+RANLIB=ranlib
 21+
 22+# --- preset expansion: do not edit below ---
 23+
 24+case "$PRESET_FEATURES" in
 25+posix)
 26+    FEATURE_FIND_DELETE=0;            FEATURE_FIND_QUIT=0
 27+    FEATURE_FIND_EMPTY=0;             FEATURE_FIND_INUM=0
 28+    FEATURE_FIND_SAMEFILE=0;          FEATURE_FIND_MAXDEPTH=0
 29+    FEATURE_FIND_MINDEPTH=0;          FEATURE_FIND_MMIN=0
 30+    FEATURE_FIND_AMIN=0;              FEATURE_FIND_CMIN=0
 31+    FEATURE_FIND_INAME=0;             FEATURE_FIND_IPATH=0
 32+    FEATURE_FIND_REGEX=0;             FEATURE_FIND_PRINT0=0
 33+    FEATURE_SED_INPLACE=0;            FEATURE_SED_PRESERVE_NEWLINE=0
 34+    FEATURE_GREP_CONTEXT=0;           FEATURE_GREP_MAX_COUNT=0
 35+    FEATURE_TAR_CREATE=0;             FEATURE_TAR_EXCLUDE=0
 36+    FEATURE_STAT_FILESYSTEM=0;        FEATURE_STAT_FORMAT=0
 37+    FEATURE_SORT_BIG=0;               FEATURE_SORT_STABLE=0
 38+    FEATURE_OD_ENDIAN=0;
 39+    FEATURE_SH_HISTEDIT=0;            FEATURE_SH_LOCAL=0
 40+    FEATURE_SH_LET=0;                 FEATURE_SH_ULIMIT=0
 41+    FEATURE_SH_SETVAR=0;              FEATURE_SH_WORDEXP=0
 42+    FEATURE_CAL_EXT=0
 43+    FEATURE_LS_COLOR=0
 44+    FEATURE_MODPROBE_SHOW_DEPENDS=0;  FEATURE_MODPROBE_BLACKLIST=0
 45+    FEATURE_MODPROBE_SYSLOG=0;        FEATURE_MODPROBE_DIR_OVERRIDE=0
 46+    FEATURE_DEPMOD_ALIAS=0;           FEATURE_DEPMOD_SYMBOLS=0
 47+    ;;
 48+common)
 49+    FEATURE_FIND_DELETE=1;            FEATURE_FIND_QUIT=1
 50+    FEATURE_FIND_EMPTY=1;             FEATURE_FIND_INUM=1
 51+    FEATURE_FIND_SAMEFILE=1;          FEATURE_FIND_MAXDEPTH=1
 52+    FEATURE_FIND_MINDEPTH=1;          FEATURE_FIND_MMIN=1
 53+    FEATURE_FIND_AMIN=1;              FEATURE_FIND_CMIN=1
 54+    FEATURE_FIND_INAME=1;             FEATURE_FIND_IPATH=1
 55+    FEATURE_FIND_REGEX=1;             FEATURE_FIND_PRINT0=1
 56+    FEATURE_SED_INPLACE=1;            FEATURE_SED_PRESERVE_NEWLINE=1
 57+    FEATURE_GREP_CONTEXT=1;           FEATURE_GREP_MAX_COUNT=1
 58+    FEATURE_TAR_CREATE=1;             FEATURE_TAR_EXCLUDE=1
 59+    FEATURE_STAT_FILESYSTEM=1;        FEATURE_STAT_FORMAT=1
 60+    FEATURE_SORT_BIG=0;               FEATURE_SORT_STABLE=1
 61+    FEATURE_OD_ENDIAN=0;
 62+    FEATURE_SH_HISTEDIT=0;            FEATURE_SH_LOCAL=1
 63+    FEATURE_SH_LET=1;                 FEATURE_SH_ULIMIT=1
 64+    FEATURE_SH_SETVAR=0;              FEATURE_SH_WORDEXP=0
 65+    FEATURE_CAL_EXT=1
 66+    FEATURE_LS_COLOR=1
 67+    FEATURE_MODPROBE_SHOW_DEPENDS=1;  FEATURE_MODPROBE_BLACKLIST=1
 68+    FEATURE_MODPROBE_SYSLOG=1;        FEATURE_MODPROBE_DIR_OVERRIDE=1
 69+    FEATURE_DEPMOD_ALIAS=1;           FEATURE_DEPMOD_SYMBOLS=1
 70+    ;;
 71+all)
 72+    # histedit now included via redline
 73+    FEATURE_FIND_DELETE=1;            FEATURE_FIND_QUIT=1
 74+    FEATURE_FIND_EMPTY=1;             FEATURE_FIND_INUM=1
 75+    FEATURE_FIND_SAMEFILE=1;          FEATURE_FIND_MAXDEPTH=1
 76+    FEATURE_FIND_MINDEPTH=1;          FEATURE_FIND_MMIN=1
 77+    FEATURE_FIND_AMIN=1;              FEATURE_FIND_CMIN=1
 78+    FEATURE_FIND_INAME=1;             FEATURE_FIND_IPATH=1
 79+    FEATURE_FIND_REGEX=1;             FEATURE_FIND_PRINT0=1
 80+    FEATURE_SED_INPLACE=1;            FEATURE_SED_PRESERVE_NEWLINE=1
 81+    FEATURE_GREP_CONTEXT=1;           FEATURE_GREP_MAX_COUNT=1
 82+    FEATURE_TAR_CREATE=1;             FEATURE_TAR_EXCLUDE=1
 83+    FEATURE_STAT_FILESYSTEM=1;        FEATURE_STAT_FORMAT=1
 84+    FEATURE_SORT_BIG=1;               FEATURE_SORT_STABLE=1
 85+    FEATURE_OD_ENDIAN=1;
 86+    FEATURE_SH_HISTEDIT=1;            FEATURE_SH_LOCAL=1
 87+    FEATURE_SH_LET=1;                 FEATURE_SH_ULIMIT=1
 88+    FEATURE_SH_SETVAR=1;              FEATURE_SH_WORDEXP=1
 89+    FEATURE_CAL_EXT=1
 90+    FEATURE_LS_COLOR=1
 91+    FEATURE_MODPROBE_SHOW_DEPENDS=1;  FEATURE_MODPROBE_BLACKLIST=1
 92+    FEATURE_MODPROBE_SYSLOG=1;        FEATURE_MODPROBE_DIR_OVERRIDE=1
 93+    FEATURE_DEPMOD_ALIAS=1;           FEATURE_DEPMOD_SYMBOLS=1
 94+    ;;
 95+*)
 96+    printf 'build.cfg: unknown PRESET_FEATURES value: %s\n' "$PRESET_FEATURES" >&2
 97+    return 1 2>/dev/null || exit 1
 98+    ;;
 99+esac
100+
101+BUILD_POSIX=0; BUILD_LINUX=0; BUILD_NET=0; BUILD_XSI=0
102+BUILD_PSEUDO=0; BUILD_DEV=0
103+for _p in $PRESET_UTILS; do
104+    case "$_p" in
105+        posix)  BUILD_POSIX=1  ;;
106+        linux)  BUILD_LINUX=1  ;;
107+        net)    BUILD_NET=1    ;;
108+        xsi)    BUILD_XSI=1    ;;
109+        pseudo) BUILD_PSEUDO=1 ;;
110+        dev)    BUILD_DEV=1    ;;
111+        *)
112+            printf 'build.cfg: unknown util preset: %s\n' "$_p" >&2
113+            return 1 2>/dev/null || exit 1
114+            ;;
115+    esac
116+done
117+unset _p
118+
119+# --- per-item overrides (after preset expansion) ---
120+# BUILD_POSIX_SH=0       # disable a tool when its group is active
121+# BUILD_DEV_AS=1         # enable a tool whose group isnt in PRESET_UTILS
122+FEATURE_SH_HISTEDIT=1
123+FEATURE_USE_LIBRESSL=0
124+FEATURE_USE_BEARSSL=1
125+# FEATURE_MY_NEW_THING=1 # new FEATURE_* are picked up by Makefile.sh automatically
+10, -0
 1@@ -1,4 +1,10 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+b3sum: compute blake3 checksums
 5+usage: b3sum [-bct] [-l length] [file ...]
 6+
 7+compute and check blake3 message digests
 8+*/
 9 #include "util.h"
10 #include "arg.h"
11 
12@@ -3972,17 +3978,21 @@ main(int argc, char *argv[])
13 	int ret = 0;
14 
15 	ARGBEGIN {
16+	// ?man -b: read in binary mode
17 	case 'b':
18 		mode = "rb";
19 		break;
20+	// ?man -c: check blake3 sums from file
21 	case 'c':
22 		func = check;
23 		break;
24+	// ?man -l length: output digest length in bytes
25 	case 'l':
26 		outlen = strtoul(EARGF(usage()), &end, 10);
27 		if (*end)
28 			usage();
29 		break;
30+	// ?man -t: read in text mode
31 	case 't':
32 		mode = "r";
33 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+blkdiscard: discard sectors on a device
 5+usage: blkdiscard device
 6+
 7+discard sectors on a block device
 8+*/
 9+
10 #include <sys/ioctl.h>
11 #include <sys/mount.h>
12 #include <sys/stat.h>
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+chvt: change foreground virtual terminal
 5+usage: chvt num
 6+
 7+change the active virtual terminal
 8+*/
 9+
10 #include <sys/ioctl.h>
11 #include <sys/types.h>
12 
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+ctrlaltdel: set ctrl-alt-del function
 5+usage: ctrlaltdel -h | -s
 6+
 7+set the behavior of the ctrl-alt-del key combination
 8+*/
 9+
10 #include <sys/syscall.h>
11 
12 #include <stdio.h>
13@@ -19,9 +26,11 @@ main(int argc, char *argv[])
14 	int hflag = 0, sflag = 0, cmd;
15 
16 	ARGBEGIN {
17+	// ?man -h: suppress headers or print help
18 	case 'h':
19 		hflag = 1;
20 		break;
21+	// ?man -s: silent mode or print summary
22 	case 's':
23 		sflag = 1;
24 		break;
+8, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+eject: eject removable media
 5+usage: eject [-t] [device ...]
 6+
 7+eject optical discs or other removable storage media
 8+*/
 9+
10 #include <sys/ioctl.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
13@@ -50,6 +57,7 @@ int
14 main(int argc, char *argv[])
15 {
16 	ARGBEGIN {
17+	// ?man -t: sort or specify timestamp
18 	case 't':
19 		tflag = 1;
20 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+freeramdisk: free ramdisk memory
 5+usage: freeramdisk 
 6+
 7+free memory associated with a ramdisk device
 8+*/
 9+
10 #include <sys/ioctl.h>
11 #include <sys/mount.h>
12 #include <sys/types.h>
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+fsfreeze: suspend access to a filesystem
 5+usage: fsfreeze (-f | -u) mountpoint
 6+
 7+freeze or unfreeze a filesystem to allow safe backups
 8+*/
 9+
10 #include <sys/ioctl.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
13@@ -28,9 +35,11 @@ main(int argc, char *argv[])
14 	int fd;
15 
16 	ARGBEGIN {
17+	// ?man -f: force the operation
18 	case 'f':
19 		fflag = 1;
20 		break;
21+	// ?man -u: unbuffered output
22 	case 'u':
23 		uflag = 1;
24 		break;
+11, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+hwclock: query or set the hardware clock
 5+usage: hwclock [-rsw] [-u] [dev]
 6+
 7+view or adjust the hardware real time clock
 8+*/
 9+
10 #include <sys/ioctl.h>
11 #include <sys/stat.h>
12 #include <sys/time.h>
13@@ -121,15 +128,19 @@ main(int argc, char *argv[])
14 	int wflag = 0;
15 
16 	ARGBEGIN {
17+	// ?man -r: operate recursively
18 	case 'r':
19 		rflag = 1;
20 		break;
21+	// ?man -s: silent mode or print summary
22 	case 's':
23 		sflag = 1;
24 		break;
25+	// ?man -w: wait for completion
26 	case 'w':
27 		wflag = 1;
28 		break;
29+	// ?man -u: unbuffered output
30 	case 'u':
31 		break;
32 	default:
+6, -1
 1@@ -1,4 +1,9 @@
 2-/* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+insmod: insert a module into the Linux kernel
 5+usage: insmod filename [args...]
 6+
 7+insmod inserts a kernel module from filename into the running kernel
 8+*/
 9 #include <sys/stat.h>
10 #include <sys/syscall.h>
11 
+6, -1
 1@@ -1,4 +1,9 @@
 2-/* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+lsmod: show the status of modules in the Linux kernel
 5+usage: lsmod
 6+
 7+lsmod formats and displays /proc/modules, showing currently loaded modules
 8+*/
 9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+lsusb: list usb devices
 5+usage: lsusb 
 6+
 7+display information about usb buses and connected devices
 8+*/
 9+
10 #include <limits.h>
11 #include <stdio.h>
12 #include <stdlib.h>
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+mkswap: set up a swap area
 5+usage: mkswap device
 6+
 7+initialize a linux swap area on a device or file
 8+*/
 9+
10 #include <sys/stat.h>
11 
12 #include <fcntl.h>
+14, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+mount: mount a filesystem
 5+usage: mount [-BMRan] [-t fstype] [-o options] [source] [target]
 6+
 7+mount a filesystem to the directory tree
 8+*/
 9+
10 #include <sys/mount.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
13@@ -200,25 +207,32 @@ main(int argc, char *argv[])
14 	FILE *fp;
15 
16 	ARGBEGIN {
17+	// ?man -B: specify option flag
18 	case 'B':
19 		argflags |= MS_BIND;
20 		break;
21+	// ?man -M: specify option flag
22 	case 'M':
23 		argflags |= MS_MOVE;
24 		break;
25+	// ?man -R: operate recursively on directories
26 	case 'R':
27 		argflags |= MS_REC;
28 		break;
29+	// ?man -a: print or show all entries
30 	case 'a':
31 		aflag = 1;
32 		break;
33+	// ?man -o: specify output file
34 	case 'o':
35 		estrlcat(fsopts, EARGF(usage()), sizeof(fsopts));
36 		parseopts(fsopts, &flags, data, sizeof(data));
37 		break;
38+	// ?man -t: sort or specify timestamp
39 	case 't':
40 		types = EARGF(usage());
41 		break;
42+	// ?man -n: print line numbers or counts
43 	case 'n':
44 		break;
45 	default:
+10, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+mountpoint: check if a directory is a mountpoint
 5+usage: mountpoint [-dqx] target
 6+
 7+determine if a directory is a mountpoint
 8+*/
 9+
10 #include <sys/stat.h>
11 #include <sys/sysmacros.h>
12 #include <sys/types.h>
13@@ -27,12 +34,15 @@ main(int argc, char *argv[])
14 	struct stat st1, st2;
15 
16 	ARGBEGIN {
17+	// ?man -d: specify directory
18 	case 'd':
19 		dflag = 1;
20 		break;
21+	// ?man -q: quiet mode; suppress output
22 	case 'q':
23 		qflag = 1;
24 		break;
25+	// ?man -x: hex format or match whole lines
26 	case 'x':
27 		xflag = 1;
28 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+pivot_root: change the root filesystem
 5+usage: pivot_root new-root put-old
 6+
 7+change the root filesystem of the current process
 8+*/
 9+
10 #include <sys/syscall.h>
11 
12 #include <stdio.h>
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+readahead: read files into page cache
 5+usage: readahead file...
 6+
 7+preload file contents into the page cache
 8+*/
 9+
10 #include <fcntl.h>
11 #include <limits.h>
12 #include <stdio.h>
+9, -1
 1@@ -1,4 +1,12 @@
 2-/* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+rmmod: remove a module from the Linux kernel
 5+usage: rmmod [-fw] module...
 6+
 7+rmmod removes a kernel module from the running kernel
 8+
 9+// ?man -f: force removal of a module even if it is busy or in use
10+// ?man -w: wait for the module to become unused before removing
11+*/
12 #include <sys/syscall.h>
13 
14 #include <fcntl.h>
+8, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+swaplabel: print or change swap label
 5+usage: swaplabel [-L label] device
 6+
 7+display or modify the label and uuid of a swap device
 8+*/
 9+
10 #include <sys/types.h>
11 
12 #include <fcntl.h>
13@@ -33,6 +40,7 @@ main(int argc, char *argv[])
14 	int i;
15 
16 	ARGBEGIN {
17+	// ?man -L: specify option flag
18 	case 'L':
19 		setlabel = 1;
20 		label = EARGF(usage());
+8, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+swapoff: disable swap devices
 5+usage: swapoff -a | device
 6+
 7+disable paging and swapping on specified devices
 8+*/
 9+
10 #include <sys/swap.h>
11 
12 #include <mntent.h>
13@@ -24,6 +31,7 @@ main(int argc, char *argv[])
14 	FILE *fp;
15 
16 	ARGBEGIN {
17+	// ?man -a: print or show all entries
18 	case 'a':
19 		all = 1;
20 		break;
+10, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+swapon: enable swap devices
 5+usage: swapon [-dp] -a | device
 6+
 7+enable paging and swapping on specified devices
 8+*/
 9+
10 #include <sys/swap.h>
11 
12 #include <mntent.h>
13@@ -25,12 +32,15 @@ main(int argc, char *argv[])
14 	FILE *fp;
15 
16 	ARGBEGIN {
17+	// ?man -a: print or show all entries
18 	case 'a':
19 		all = 1;
20 		break;
21+	// ?man -d: specify directory
22 	case 'd':
23 		flags |= SWAP_FLAG_DISCARD;
24 		break;
25+	// ?man -p: preserve file attributes
26 	case 'p':
27 		flags |= SWAP_FLAG_PREFER;
28 		break;
+8, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+switch_root: switch to another root filesystem
 5+usage: switch_root [-c console] [newroot] [init] (PID 1)
 6+
 7+switch to another filesystem as the root of the mount tree
 8+*/
 9+
10 #include <sys/mount.h>
11 #include <sys/stat.h>
12 #include <sys/vfs.h>
13@@ -73,6 +80,7 @@ main(int argc, char *argv[])
14 	struct statfs stfs;
15 
16 	ARGBEGIN {
17+	// ?man -c: print count or perform stdout action
18 	case 'c':
19 		console = EARGF(usage());
20 		break;
+8, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+sysctl: configure kernel parameters
 5+usage: sysctl [-p file] variable[=value]...
 6+
 7+view and modify kernel parameters at runtime
 8+*/
 9+
10 #include <fcntl.h>
11 #include <limits.h>
12 #include <stdio.h>
13@@ -169,6 +176,7 @@ main(int argc, char *argv[])
14 	int r = 0;
15 
16 	ARGBEGIN {
17+	// ?man -p: preserve file attributes
18 	case 'p':
19 		file = EARGF(usage());
20 		break;
R cmd/net/tunctl.c => cmd/linux/tunctl.c
+11, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+tunctl: configure tun/tap interfaces
 5+usage: tunctl [-dtT] [-u owner] [device]
 6+
 7+create or destroy tun/tap network interfaces
 8+*/
 9+
10 #include <sys/ioctl.h>
11 #include <sys/socket.h>
12 #include <sys/stat.h>
13@@ -38,17 +45,21 @@ main(int argc, char *argv[])
14 
15 	ARGBEGIN
16 	{
17+	// ?man -d: specify directory
18 	case 'd':
19 		dflag = 1;
20 		tflag = 0;
21 		break;
22+	// ?man -t: sort or specify timestamp
23 	case 't':
24 		tflag = 1;
25 		dflag = 0;
26 		break;
27+	// ?man -T: specify option flag
28 	case 'T':
29 		Tflag = 1;
30 		break;
31+	// ?man -u: unbuffered output
32 	case 'u':
33 		owner = EARGF(usage());
34 		break;
+12, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+umount: unmount filesystems
 5+usage: umount [-lfn] [-O options] target...
 6+
 7+unmount a filesystem from the directory tree
 8+*/
 9+
10 #include <sys/mount.h>
11 
12 #include <mntent.h>
13@@ -135,18 +142,23 @@ main(int argc, char *argv[])
14 #endif
15 
16 	ARGBEGIN {
17+	// ?man -a: print or show all entries
18 	case 'a':
19 		aflag = 1;
20 		break;
21+	// ?man -f: force the operation
22 	case 'f':
23 		flags |= MNT_FORCE;
24 		break;
25+	// ?man -l: list in long format
26 	case 'l':
27 		flags |= MNT_DETACH;
28 		break;
29+	// ?man -n: print line numbers or counts
30 	case 'n':
31 		break;
32 #ifdef STD_NON_POSIX
33+	// ?man -O: specify option flag
34 	case 'O':
35 		oflag = EARGF(usage());
36 		break;
+13, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+unshare: run program in new namespaces
 5+usage: unshare [-muinpU] cmd [args...]
 6+
 7+run a program with some namespaces unshared from the parent
 8+*/
 9+
10 #include <sched.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13@@ -19,21 +26,27 @@ main(int argc, char *argv[])
14 	int flags = 0;
15 
16 	ARGBEGIN {
17+	// ?man -m: specify mode or limit
18 	case 'm':
19 		flags |= CLONE_NEWNS;
20 		break;
21+	// ?man -u: unbuffered output
22 	case 'u':
23 		flags |= CLONE_NEWUTS;
24 		break;
25+	// ?man -i: interactive mode or prompt for confirmation
26 	case 'i':
27 		flags |= CLONE_NEWIPC;
28 		break;
29+	// ?man -n: print line numbers or counts
30 	case 'n':
31 		flags |= CLONE_NEWNET;
32 		break;
33+	// ?man -p: preserve file attributes
34 	case 'p':
35 		flags |= CLONE_NEWPID;
36 		break;
37+	// ?man -U: specify option flag
38 	case 'U':
39 		flags |= CLONE_NEWUSER;
40 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+vtallow: allow non-root vt access
 5+usage: vtallow n | y
 6+
 7+allow non-root users to access virtual terminal devices
 8+*/
 9+
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #include <sys/ioctl.h>
+8, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+host: dns lookup utility
 5+usage: host [-t type] name [server]
 6+
 7+look up hostnames and IP addresses using dns
 8+*/
 9+
10 #include "util.h"
11 #include "arg.h"
12 
13@@ -269,6 +276,7 @@ main(int argc, char *argv[])
14 	int i, r;
15 
16 	ARGBEGIN {
17+	// ?man -t: sort or specify timestamp
18 	case 't':
19 		tflag = EARGF(usage());
20 		break;
+10, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+httpd: simple http daemon
 5+usage: httpd [-e string] [-d string] [-v] [dir]
 6+
 7+serve static files over http
 8+*/
 9+
10 #include "util.h"
11 #include "arg.h"
12 
13@@ -257,12 +264,15 @@ main(int argc, char *argv[])
14 	char *enc;
15 
16 	ARGBEGIN {
17+	// ?man -e: specify expression or pattern
18 	case 'e':
19 		eflag = EARGF(usage());
20 		break;
21+	// ?man -d: specify directory
22 	case 'd':
23 		dflag = EARGF(usage());
24 		break;
25+	// ?man -v: verbose mode; show progress
26 	case 'v':
27 		break;
28 	default:
+95, -187
  1@@ -1,13 +1,15 @@
  2 /* See LICENSE file for copyright and license details. */
  3-#include "util.h"
  4-#include "arg.h"
  5+/* ?man
  6+ifconfig: configure network interfaces
  7+usage: ifconfig [-a] [interface [action ...]]
  8+
  9+configure network interface parameters and view stats
 10+*/
 11 
 12 #include <arpa/inet.h>
 13 #include <ctype.h>
 14 #include <errno.h>
 15 #include <net/if.h>
 16-#include <net/if_arp.h>
 17-#include <netinet/in.h>
 18 #include <stdio.h>
 19 #include <stdlib.h>
 20 #include <string.h>
 21@@ -16,6 +18,12 @@
 22 #include <sys/types.h>
 23 #include <unistd.h>
 24 
 25+#include "util.h"
 26+
 27+int net_get_interfaces(struct NetInterface **, int *);
 28+int net_get_stats(const char *, struct NetStats *);
 29+int net_set_txqueuelen(const char *, int);
 30+
 31 static void
 32 usage(void)
 33 {
 34@@ -23,200 +31,82 @@ usage(void)
 35 }
 36 
 37 static void
 38-print_ipv6(const char *name)
 39+display_interface(const struct NetInterface *iface)
 40 {
 41-	FILE *fp = fopen("/proc/net/if_inet6", "r");
 42-	char line[256];
 43-	char addr_hex[40];
 44-	char ifname[IFNAMSIZ];
 45-	unsigned int ifindex, plen, scope, flags;
 46-	int i, j;
 47-	char ipv6_str[40];
 48-	const char *scope_str;
 49-
 50-	if (!fp)
 51-		return;
 52-
 53-	while (fgets(line, sizeof(line), fp)) {
 54-		if (sscanf(line, "%32s %x %x %x %x %15s", addr_hex, &ifindex, &plen, &scope, &flags, ifname) == 6) {
 55-			if (strcmp(name, ifname) == 0) {
 56-				j = 0;
 57-				for (i = 0; i < 32; i++) {
 58-					ipv6_str[j++] = addr_hex[i];
 59-					if (i > 0 && i < 31 && (i % 4 == 3))
 60-						ipv6_str[j++] = ':';
 61-				}
 62-				ipv6_str[j] = '\0';
 63-
 64-				scope_str = "Unknown";
 65-				switch (scope) {
 66-				case 0x00: scope_str = "Global"; break;
 67-				case 0x10: scope_str = "Host"; break;
 68-				case 0x20: scope_str = "Link"; break;
 69-				case 0x40: scope_str = "Site"; break;
 70-				case 0x80: scope_str = "Compat"; break;
 71-				}
 72-				printf("        inet6 addr: %s/%d Scope:%s\n", ipv6_str, plen, scope_str);
 73-			}
 74-		}
 75-	}
 76-	fclose(fp);
 77-}
 78-
 79-static void
 80-display_interface(int sock, const char *name)
 81-{
 82-	struct ifreq ifr;
 83-	struct sockaddr_in *sin;
 84-	unsigned char *hwaddr;
 85-	FILE *fp;
 86-	char line[256];
 87-	char *p, *ifname;
 88-	short flags;
 89-	int mtu = 0;
 90-	int metric = 0;
 91-	unsigned long long rx_bytes, rx_packets, rx_errs, rx_drop;
 92-	unsigned long long tx_bytes, tx_packets, tx_errs, tx_drop;
 93-
 94-	memset(&ifr, 0, sizeof(ifr));
 95-	strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
 96-
 97-	if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {
 98-		weprintf("ioctl SIOCGIFFLAGS %s:\n", name);
 99-		return;
100-	}
101-	flags = ifr.ifr_flags;
102-
103-	printf("%-9s ", name);
104-
105-	if (ioctl(sock, SIOCGIFHWADDR, &ifr) >= 0) {
106-		hwaddr = (unsigned char *)ifr.ifr_hwaddr.sa_data;
107-		printf("Link encap:");
108-		switch (ifr.ifr_hwaddr.sa_family) {
109-		case ARPHRD_ETHER:
110-			printf("Ethernet  HWaddr %02x:%02x:%02x:%02x:%02x:%02x\n",
111-			       hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5]);
112-			break;
113-		case ARPHRD_LOOPBACK:
114-			printf("Local Loopback\n");
115-			break;
116-		default:
117-			printf("UNSPEC\n");
118-			break;
119-		}
120+	struct NetStats stats;
121+	char ipv6_str[INET6_ADDRSTRLEN];
122+
123+	printf("%-9s Link encap:", iface->name);
124+	if (iface->has_mac) {
125+		printf("Ethernet  HWaddr %02x:%02x:%02x:%02x:%02x:%02x\n",
126+		       iface->mac[0], iface->mac[1], iface->mac[2],
127+		       iface->mac[3], iface->mac[4], iface->mac[5]);
128+	} else if (iface->flags & IFF_LOOPBACK) {
129+		printf("Local Loopback\n");
130 	} else {
131-		printf("Link encap:UNSPEC\n");
132+		printf("UNSPEC\n");
133 	}
134 
135-	memset(&ifr, 0, sizeof(ifr));
136-	strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
137-	if (ioctl(sock, SIOCGIFADDR, &ifr) >= 0) {
138-		sin = (struct sockaddr_in *)&ifr.ifr_addr;
139-		printf("        inet addr:%s", inet_ntoa(sin->sin_addr));
140-
141-		memset(&ifr, 0, sizeof(ifr));
142-		strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
143-		if (ioctl(sock, SIOCGIFBRDADDR, &ifr) >= 0) {
144-			sin = (struct sockaddr_in *)&ifr.ifr_broadaddr;
145-			printf("  Bcast:%s", inet_ntoa(sin->sin_addr));
146-		}
147+	if (iface->has_ipv4) {
148+		printf("        inet addr:%s", inet_ntoa(iface->ipv4_addr.sin_addr));
149+		printf("  Bcast:%s", inet_ntoa(iface->ipv4_brd.sin_addr));
150+		printf("  Mask:%s\n", inet_ntoa(iface->ipv4_mask.sin_addr));
151+	}
152 
153-		memset(&ifr, 0, sizeof(ifr));
154-		strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
155-		if (ioctl(sock, SIOCGIFNETMASK, &ifr) >= 0) {
156-			sin = (struct sockaddr_in *)&ifr.ifr_netmask;
157-			printf("  Mask:%s", inet_ntoa(sin->sin_addr));
158+	if (iface->has_ipv6) {
159+		if (inet_ntop(AF_INET6, &iface->ipv6_addr.sin6_addr, ipv6_str, sizeof(ipv6_str))) {
160+			printf("        inet6 addr: %s\n", ipv6_str);
161 		}
162-		printf("\n");
163 	}
164 
165-	print_ipv6(name);
166-
167 	printf("        ");
168-	if (flags & IFF_UP) printf("UP ");
169-	if (flags & IFF_BROADCAST) printf("BROADCAST ");
170-	if (flags & IFF_DEBUG) printf("DEBUG ");
171-	if (flags & IFF_LOOPBACK) printf("LOOPBACK ");
172-	if (flags & IFF_POINTOPOINT) printf("POINTOPOINT ");
173-	if (flags & IFF_RUNNING) printf("RUNNING ");
174-	if (flags & IFF_NOARP) printf("NOARP ");
175-	if (flags & IFF_PROMISC) printf("PROMISC ");
176-	if (flags & IFF_ALLMULTI) printf("ALLMULTI ");
177-	if (flags & IFF_MULTICAST) printf("MULTICAST ");
178-
179-	memset(&ifr, 0, sizeof(ifr));
180-	strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
181-	if (ioctl(sock, SIOCGIFMTU, &ifr) >= 0)
182-		mtu = ifr.ifr_mtu;
183-	if (ioctl(sock, SIOCGIFMETRIC, &ifr) >= 0)
184-		metric = ifr.ifr_metric;
185-	printf(" MTU:%d  Metric:%d\n", mtu, metric);
186-
187-	fp = fopen("/proc/net/dev", "r");
188-	if (fp) {
189-		while (fgets(line, sizeof(line), fp)) {
190-			p = strchr(line, ':');
191-			if (p) {
192-				*p = '\0';
193-				ifname = line;
194-				while (isspace(*ifname))
195-					ifname++;
196-				if (strcmp(ifname, name) == 0) {
197-					if (sscanf(p + 1, "%llu %llu %llu %llu %*u %*u %*u %*u %llu %llu %llu %llu",
198-					           &rx_bytes, &rx_packets, &rx_errs, &rx_drop,
199-					           &tx_bytes, &tx_packets, &tx_errs, &tx_drop) == 8) {
200-						printf("        RX packets:%llu errors:%llu dropped:%llu overruns:0 frame:0\n",
201-						       rx_packets, rx_errs, rx_drop);
202-						printf("        TX packets:%llu errors:%llu dropped:%llu overruns:0 carrier:0\n",
203-						       tx_packets, tx_errs, tx_drop);
204-						printf("        RX bytes:%llu  TX bytes:%llu\n", rx_bytes, tx_bytes);
205-					}
206-					break;
207-				}
208-			}
209-		}
210-		fclose(fp);
211+	if (iface->flags & IFF_UP) printf("UP ");
212+	if (iface->flags & IFF_BROADCAST) printf("BROADCAST ");
213+	if (iface->flags & IFF_DEBUG) printf("DEBUG ");
214+	if (iface->flags & IFF_LOOPBACK) printf("LOOPBACK ");
215+	if (iface->flags & IFF_POINTOPOINT) printf("POINTOPOINT ");
216+	if (iface->flags & IFF_RUNNING) printf("RUNNING ");
217+	if (iface->flags & IFF_NOARP) printf("NOARP ");
218+	if (iface->flags & IFF_PROMISC) printf("PROMISC ");
219+	if (iface->flags & IFF_ALLMULTI) printf("ALLMULTI ");
220+	if (iface->flags & IFF_MULTICAST) printf("MULTICAST ");
221+
222+	printf(" MTU:%d  Metric:%d\n", iface->mtu, iface->metric);
223+
224+	if (net_get_stats(iface->name, &stats) >= 0) {
225+		printf("        RX packets:%llu errors:%llu dropped:%llu overruns:0 frame:0\n",
226+		       stats.rx_packets, stats.rx_errs, stats.rx_drop);
227+		printf("        TX packets:%llu errors:%llu dropped:%llu overruns:0 carrier:0\n",
228+		       stats.tx_packets, stats.tx_errs, stats.tx_drop);
229+		printf("        RX bytes:%llu  TX bytes:%llu\n",
230+		       stats.rx_bytes, stats.tx_bytes);
231 	}
232 	printf("\n");
233 }
234 
235 static void
236-list_interfaces(int sock, int all)
237+list_interfaces(int all)
238 {
239-	FILE *fp = fopen("/proc/net/dev", "r");
240-	struct ifreq ifr;
241-	char line[256];
242-	char *p, *name;
243-	int line_num = 0;
244-
245-	if (!fp)
246-		eprintf("fopen /proc/net/dev:\n");
247-
248-	while (fgets(line, sizeof(line), fp)) {
249-		line_num++;
250-		if (line_num <= 2)
251-			continue;
252-		p = strchr(line, ':');
253-		if (p) {
254-			*p = '\0';
255-			name = line;
256-			while (isspace(*name))
257-				name++;
258-			memset(&ifr, 0, sizeof(ifr));
259-			strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
260-			if (ioctl(sock, SIOCGIFFLAGS, &ifr) >= 0) {
261-				if (all || (ifr.ifr_flags & IFF_UP)) {
262-					display_interface(sock, name);
263-				}
264-			}
265+	struct NetInterface *ifaces = NULL;
266+	int count = 0;
267+	int i;
268+
269+	if (net_get_interfaces(&ifaces, &count) < 0)
270+		eprintf("net_get_interfaces:");
271+
272+	for (i = 0; i < count; i++) {
273+		if (all || (ifaces[i].flags & IFF_UP)) {
274+			display_interface(&ifaces[i]);
275 		}
276 	}
277-	fclose(fp);
278+	free(ifaces);
279 }
280 
281 int
282 main(int argc, char *argv[])
283 {
284+	struct NetInterface *ifaces = NULL;
285+	const struct NetInterface *iface = NULL;
286 	struct ifreq ifr;
287 	struct sockaddr_in *sin;
288 	char *name;
289@@ -226,9 +116,11 @@ main(int argc, char *argv[])
290 	int sock = -1;
291 	int i = 1;
292 	int prefix;
293+	int count = 0;
294 	unsigned int mask;
295 
296 	ARGBEGIN {
297+	// ?man -a: print or show all entries
298 	case 'a':
299 		aflag = 1;
300 		break;
301@@ -236,26 +128,37 @@ main(int argc, char *argv[])
302 		usage();
303 	} ARGEND
304 
305-	sock = socket(AF_INET, SOCK_DGRAM, 0);
306-	if (sock < 0)
307-		eprintf("socket:\n");
308-
309 	if (argc == 0) {
310-		list_interfaces(sock, aflag);
311-		close(sock);
312+		list_interfaces(aflag);
313 		return 0;
314 	}
315 
316 	name = argv[0];
317 
318+	if (net_get_interfaces(&ifaces, &count) < 0)
319+		eprintf("net_get_interfaces:");
320+
321+	for (prefix = 0; prefix < count; prefix++) {
322+		if (strcmp(ifaces[prefix].name, name) == 0) {
323+			iface = &ifaces[prefix];
324+			break;
325+		}
326+	}
327+
328 	if (argc == 1) {
329-		display_interface(sock, name);
330-		close(sock);
331+		if (!iface)
332+			eprintf("interface %s not found\n", name);
333+		display_interface(iface);
334+		free(ifaces);
335 		return 0;
336 	}
337 
338+	sock = socket(AF_INET, SOCK_DGRAM, 0);
339+	if (sock < 0)
340+		eprintf("socket:");
341+
342 	memset(&ifr, 0, sizeof(ifr));
343-	strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
344+	strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
345 
346 	while (i < argc) {
347 		arg = argv[i];
348@@ -301,8 +204,8 @@ main(int argc, char *argv[])
349 			i += 2;
350 		} else if (strcmp(arg, "txqueuelen") == 0) {
351 			if (i + 1 >= argc) eprintf("txqueuelen needs a value\n");
352-			ifr.ifr_qlen = atoi(argv[i + 1]);
353-			if (ioctl(sock, SIOCSIFTXQLEN, &ifr) < 0) eprintf("ioctl SIOCSIFTXQLEN:");
354+			if (net_set_txqueuelen(name, atoi(argv[i + 1])) < 0)
355+				eprintf("net_set_txqueuelen:");
356 			i += 2;
357 		} else if (strcmp(arg, "dstaddr") == 0 || strcmp(arg, "pointopoint") == 0) {
358 			if (i + 1 >= argc) eprintf("%s needs an address\n", arg);
359@@ -387,6 +290,11 @@ main(int argc, char *argv[])
360 		}
361 	}
362 
363+	free(ifaces);
364 	close(sock);
365+
366+	if (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))
367+		return 1;
368+
369 	return 0;
370 }
+269, -0
  1@@ -0,0 +1,269 @@
  2+/* See LICENSE file for copyright and license details. */
  3+/* ?man
  4+ip: show or configure routing and devices
  5+usage: ip [addr | link | route] [args...]
  6+
  7+configure and view network devices, routing, and tunnels
  8+*/
  9+
 10+#include <arpa/inet.h>
 11+#include <ctype.h>
 12+#include <errno.h>
 13+#include <net/if.h>
 14+#include <stdio.h>
 15+#include <stdlib.h>
 16+#include <string.h>
 17+#include <sys/socket.h>
 18+#include <sys/types.h>
 19+#include <unistd.h>
 20+
 21+#include "util.h"
 22+
 23+int net_get_interfaces(struct NetInterface **, int *);
 24+int net_get_stats(const char *, struct NetStats *);
 25+int net_set_flags(const char *, unsigned int, int);
 26+int net_set_mtu(const char *, int);
 27+int net_set_mac(const char *, const unsigned char *);
 28+int net_add_addr(const char *, const char *, int);
 29+int net_del_addr(const char *, const char *, int);
 30+int net_show_routes(void);
 31+
 32+static void
 33+usage(void)
 34+{
 35+	eprintf("usage: %s [addr | link | route] [args...]\n", argv0);
 36+}
 37+
 38+static void
 39+parse_mac(const char *str, unsigned char mac[6])
 40+{
 41+	unsigned int val;
 42+	int i;
 43+	const char *p = str;
 44+
 45+	for (i = 0; i < 6; i++) {
 46+		if (i > 0) {
 47+			if (*p != ':')
 48+				eprintf("invalid mac address: %s\n", str);
 49+			p++;
 50+		}
 51+		if (sscanf(p, "%2x", &val) != 1)
 52+			eprintf("invalid mac address: %s\n", str);
 53+		mac[i] = val;
 54+		p += 2;
 55+	}
 56+}
 57+
 58+static void
 59+print_link_iface(const struct NetInterface *iface)
 60+{
 61+	printf("%d: %s: mtu %d ", iface->metric, iface->name, iface->mtu);
 62+	if (iface->flags & IFF_UP) printf("UP ");
 63+	if (iface->flags & IFF_LOOPBACK) printf("LOOPBACK ");
 64+	if (iface->flags & IFF_POINTOPOINT) printf("POINTOPOINT ");
 65+	if (iface->flags & IFF_RUNNING) printf("RUNNING ");
 66+	printf("\n");
 67+	if (iface->has_mac) {
 68+		printf("    link/ether %02x:%02x:%02x:%02x:%02x:%02x\n",
 69+		       iface->mac[0], iface->mac[1], iface->mac[2],
 70+		       iface->mac[3], iface->mac[4], iface->mac[5]);
 71+	}
 72+}
 73+
 74+static void
 75+print_addr_iface(const struct NetInterface *iface)
 76+{
 77+	char ipv6_str[INET6_ADDRSTRLEN];
 78+
 79+	print_link_iface(iface);
 80+	if (iface->has_ipv4) {
 81+		printf("    inet %s netmask %s\n",
 82+		       inet_ntoa(iface->ipv4_addr.sin_addr),
 83+		       inet_ntoa(iface->ipv4_mask.sin_addr));
 84+	}
 85+	if (iface->has_ipv6) {
 86+		if (inet_ntop(AF_INET6, &iface->ipv6_addr.sin6_addr, ipv6_str, sizeof(ipv6_str))) {
 87+			printf("    inet6 %s\n", ipv6_str);
 88+		}
 89+	}
 90+}
 91+
 92+static int
 93+do_addr(int argc, char *argv[])
 94+{
 95+	struct NetInterface *ifaces = NULL;
 96+	char *cmd, *ip, *dev, *slash;
 97+	int count = 0;
 98+	int prefix = -1;
 99+	int i, r;
100+
101+	if (argc == 0)
102+		cmd = "show";
103+	else
104+		cmd = argv[0];
105+
106+	if (strcmp(cmd, "show") == 0 || strcmp(cmd, "list") == 0) {
107+		dev = NULL;
108+		if (argc > 1) {
109+			if (strcmp(argv[1], "dev") == 0 && argc > 2)
110+				dev = argv[2];
111+			else
112+				dev = argv[1];
113+		}
114+		if (net_get_interfaces(&ifaces, &count) < 0)
115+			eprintf("net_get_interfaces:");
116+		for (i = 0; i < count; i++) {
117+			if (!dev || strcmp(ifaces[i].name, dev) == 0)
118+				print_addr_iface(&ifaces[i]);
119+		}
120+		free(ifaces);
121+		return 0;
122+	}
123+
124+	if (strcmp(cmd, "add") == 0 || strcmp(cmd, "del") == 0) {
125+		if (argc < 4)
126+			eprintf("usage: ip addr %s <ip>/<prefix> dev <interface>\n", cmd);
127+		ip = argv[1];
128+		dev = NULL;
129+		for (i = 2; i < argc; i++) {
130+			if (strcmp(argv[i], "dev") == 0 && i + 1 < argc) {
131+				dev = argv[i + 1];
132+				break;
133+			}
134+		}
135+		if (!dev)
136+			eprintf("missing dev parameter\n");
137+
138+		slash = strchr(ip, '/');
139+		if (slash) {
140+			*slash = '\0';
141+			prefix = atoi(slash + 1);
142+		}
143+
144+		if (strcmp(cmd, "add") == 0)
145+			r = net_add_addr(dev, ip, prefix);
146+		else
147+			r = net_del_addr(dev, ip, prefix);
148+
149+		if (r < 0)
150+			eprintf("net_%s_addr:", cmd);
151+		return 0;
152+	}
153+
154+	usage();
155+	return 1;
156+}
157+
158+static int
159+do_link(int argc, char *argv[])
160+{
161+	struct NetInterface *ifaces = NULL;
162+	char *cmd, *dev, *mac_str;
163+	int count = 0;
164+	int i, mtu;
165+	unsigned char mac[6];
166+
167+	if (argc == 0)
168+		cmd = "show";
169+	else
170+		cmd = argv[0];
171+
172+	if (strcmp(cmd, "show") == 0 || strcmp(cmd, "list") == 0) {
173+		dev = NULL;
174+		if (argc > 1) {
175+			if (strcmp(argv[1], "dev") == 0 && argc > 2)
176+				dev = argv[2];
177+			else
178+				dev = argv[1];
179+		}
180+		if (net_get_interfaces(&ifaces, &count) < 0)
181+			eprintf("net_get_interfaces:");
182+		for (i = 0; i < count; i++) {
183+			if (!dev || strcmp(ifaces[i].name, dev) == 0)
184+				print_link_iface(&ifaces[i]);
185+		}
186+		free(ifaces);
187+		return 0;
188+	}
189+
190+	if (strcmp(cmd, "set") == 0) {
191+		if (argc < 3)
192+			eprintf("usage: ip link set <interface> [up | down | mtu <mtu> | address <mac>]\n");
193+		dev = argv[1];
194+		for (i = 2; i < argc; i++) {
195+			if (strcmp(argv[i], "up") == 0) {
196+				if (net_set_flags(dev, IFF_UP, 1) < 0)
197+					eprintf("net_set_flags up:");
198+			} else if (strcmp(argv[i], "down") == 0) {
199+				if (net_set_flags(dev, IFF_UP, 0) < 0)
200+					eprintf("net_set_flags down:");
201+			} else if (strcmp(argv[i], "mtu") == 0 && i + 1 < argc) {
202+				mtu = atoi(argv[i + 1]);
203+				if (net_set_mtu(dev, mtu) < 0)
204+					eprintf("net_set_mtu:");
205+				i++;
206+			} else if (strcmp(argv[i], "address") == 0 && i + 1 < argc) {
207+				mac_str = argv[i + 1];
208+				parse_mac(mac_str, mac);
209+				if (net_set_mac(dev, mac) < 0)
210+					eprintf("net_set_mac:");
211+				i++;
212+			}
213+		}
214+		return 0;
215+	}
216+
217+	usage();
218+	return 1;
219+}
220+
221+static int
222+do_route(int argc, char *argv[])
223+{
224+	char *cmd;
225+
226+	if (argc == 0)
227+		cmd = "show";
228+	else
229+		cmd = argv[0];
230+
231+	if (strcmp(cmd, "show") == 0 || strcmp(cmd, "list") == 0) {
232+		if (net_show_routes() < 0)
233+			eprintf("net_show_routes:");
234+		return 0;
235+	}
236+
237+	usage();
238+	return 1;
239+}
240+
241+int
242+main(int argc, char *argv[])
243+{
244+	char *obj;
245+
246+	ARGBEGIN {
247+	default:
248+		usage();
249+	} ARGEND
250+
251+	if (argc == 0)
252+		usage();
253+
254+	obj = argv[0];
255+
256+	if (strcmp(obj, "addr") == 0 || strcmp(obj, "address") == 0) {
257+		return do_addr(argc - 1, argv + 1);
258+	} else if (strcmp(obj, "link") == 0) {
259+		return do_link(argc - 1, argv + 1);
260+	} else if (strcmp(obj, "route") == 0) {
261+		return do_route(argc - 1, argv + 1);
262+	}
263+
264+	usage();
265+
266+	if (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))
267+		return 1;
268+
269+	return 0;
270+}
+10, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+netcat: read and write data across network connections
 5+usage: netcat [-lu] [-p localport] [host] [port]
 6+
 7+arbitrary data transmission over tcp or udp
 8+*/
 9+
10 #include <sys/socket.h>
11 #include <sys/types.h>
12 
13@@ -65,12 +72,15 @@ main(int argc, char *argv[])
14 
15 	ARGBEGIN
16 	{
17+	// ?man -l: list in long format
18 	case 'l':
19 		lflag = 1;
20 		break;
21+	// ?man -p: preserve file attributes
22 	case 'p':
23 		local_port = EARGF(usage());
24 		break;
25+	// ?man -u: unbuffered output
26 	case 'u':
27 		uflag = 1;
28 		break;
+13, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+ping: send icmp echo requests
 5+usage: ping [-c count] [-i interval] [-s size] [-t ttl] [-w deadline] [-q] host
 6+
 7+send icmp echo requests to verify network connectivity
 8+*/
 9+
10 #include "util.h"
11 #include "arg.h"
12 
13@@ -136,21 +143,27 @@ main(int argc, char *argv[])
14 	socklen_t fromlen;
15 
16 	ARGBEGIN {
17+	// ?man -c: print count or perform stdout action
18 	case 'c':
19 		cflag = EARGF(usage());
20 		break;
21+	// ?man -i: interactive mode or prompt for confirmation
22 	case 'i':
23 		iflag = EARGF(usage());
24 		break;
25+	// ?man -s: silent mode or print summary
26 	case 's':
27 		sflag = EARGF(usage());
28 		break;
29+	// ?man -t: sort or specify timestamp
30 	case 't':
31 		tflag = EARGF(usage());
32 		break;
33+	// ?man -w: wait for completion
34 	case 'w':
35 		wflag = EARGF(usage());
36 		break;
37+	// ?man -q: quiet mode; suppress output
38 	case 'q':
39 		qflag = 1;
40 		break;
+11, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+tftp: tftp client
 5+usage: tftp -h host [-p port] [-x | -c] file
 6+
 7+transfer files to and from a remote tftp server
 8+*/
 9+
10 #include <sys/time.h>
11 #include <sys/types.h>
12 #include <sys/socket.h>
13@@ -256,15 +263,19 @@ main(int argc, char *argv[])
14 	int ret;
15 
16 	ARGBEGIN {
17+	// ?man -h: suppress headers or print help
18 	case 'h':
19 		host = EARGF(usage());
20 		break;
21+	// ?man -p: preserve file attributes
22 	case 'p':
23 		port = EARGF(usage());
24 		break;
25+	// ?man -x: hex format or match whole lines
26 	case 'x':
27 		fn = getfile;
28 		break;
29+	// ?man -c: print count or perform stdout action
30 	case 'c':
31 		fn = putfile;
32 		break;
+266, -69
  1@@ -1,6 +1,14 @@
  2-/* See LICENSE file for copyright and license details. */
  3+/* see license file for copyright and license details */
  4+/* ?man
  5+wget: retrieve files from the web
  6+usage: wget [-cqS] [-O file] [-P dir] [-T timeout] [-U user_agent] [-post-data data] [-post-file file] [-header header] [-no-check-certificate] [-spider] url
  7+
  8+download files over http or https
  9+*/
 10+
 11 #include "util.h"
 12 #include "arg.h"
 13+#include "tls.h"
 14 
 15 #include <arpa/inet.h>
 16 #include <ctype.h>
 17@@ -12,20 +20,44 @@
 18 #include <stdlib.h>
 19 #include <string.h>
 20 #include <sys/socket.h>
 21+#include <sys/stat.h>
 22 #include <sys/types.h>
 23 #include <unistd.h>
 24 
 25 struct Stream {
 26-	int fd;
 27+	struct TlsSocket *ts;
 28 	char buf[8192];
 29 	size_t len;
 30 	size_t idx;
 31 };
 32 
 33+static int qflag = 0;
 34+static int Sflag = 0;
 35+static int cflag = 0;
 36+static int spider = 0;
 37+static int no_check_certificate = 0;
 38+static int timeout_sec = 900;
 39+static char *Pflag = NULL;
 40+static char *Oflag = NULL;
 41+static char *user_agent = "wget/aruu";
 42+static char *post_data = NULL;
 43+static char *post_file = NULL;
 44+static char **custom_headers = NULL;
 45+static size_t custom_headers_num = 0;
 46+
 47 static void
 48 usage(void)
 49 {
 50-	eprintf("usage: %s [-O file] [-p post_data] [-r limit] url\n", argv0);
 51+	eprintf("usage: %s [-cqS] [-O file] [-P dir] [-T timeout] [-U user_agent] "
 52+	        "[-post-data data] [-post-file file] [-header header] "
 53+	        "[-no-check-certificate] [-spider] url\n", argv0);
 54+}
 55+
 56+static void
 57+add_header(const char *hdr)
 58+{
 59+	custom_headers = ereallocarray(custom_headers, custom_headers_num + 1, sizeof(*custom_headers));
 60+	custom_headers[custom_headers_num++] = estrdup(hdr);
 61 }
 62 
 63 static int
 64@@ -40,7 +72,8 @@ dial(const char *host, const char *port)
 65 
 66 	r = getaddrinfo(host, port, &hints, &res);
 67 	if (r != 0) {
 68-		weprintf("getaddrinfo %s:%s: %s\n", host, port, gai_strerror(r));
 69+		if (!qflag)
 70+			weprintf("getaddrinfo %s:%s: %s\n", host, port, gai_strerror(r));
 71 		return -1;
 72 	}
 73 
 74@@ -48,6 +81,13 @@ dial(const char *host, const char *port)
 75 		fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
 76 		if (fd < 0)
 77 			continue;
 78+		if (timeout_sec > 0) {
 79+			struct timeval tv;
 80+			tv.tv_sec = timeout_sec;
 81+			tv.tv_usec = 0;
 82+			setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
 83+			setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
 84+		}
 85 		if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0)
 86 			break;
 87 		close(fd);
 88@@ -59,14 +99,16 @@ dial(const char *host, const char *port)
 89 }
 90 
 91 static void
 92-parse_url(char *url, char **host, char **port, char **path)
 93+parse_url(char *url, char **host, char **port, char **path, int *is_tls)
 94 {
 95 	char *p, *ss;
 96 
 97+	*is_tls = 0;
 98 	if (strncasecmp(url, "http://", 7) == 0) {
 99 		url += 7;
100 	} else if (strncasecmp(url, "https://", 8) == 0) {
101 		url += 8;
102+		*is_tls = 1;
103 	} else {
104 		eprintf("unsupported protocol or invalid url: %s\n", url);
105 	}
106@@ -90,7 +132,7 @@ parse_url(char *url, char **host, char **port, char **path)
107 			if (*ss == ':')
108 				*port = ss + 1;
109 			else
110-				*port = "80";
111+				*port = *is_tls ? "443" : "80";
112 		} else {
113 			eprintf("invalid ipv6 literal: %s\n", *host);
114 		}
115@@ -100,7 +142,7 @@ parse_url(char *url, char **host, char **port, char **path)
116 			*p = '\0';
117 			*port = p + 1;
118 		} else {
119-			*port = "80";
120+			*port = *is_tls ? "443" : "80";
121 		}
122 	}
123 }
124@@ -136,7 +178,7 @@ stream_getc(struct Stream *s)
125 		return (unsigned char)s->buf[s->idx++];
126 	}
127 	s->idx = 0;
128-	r = read(s->fd, s->buf, sizeof(s->buf));
129+	r = tls_read(s->ts, s->buf, sizeof(s->buf));
130 	if (r <= 0) {
131 		s->len = 0;
132 		return EOF;
133@@ -161,7 +203,7 @@ stream_read(struct Stream *s, void *ptr, size_t size)
134 			total += n;
135 		} else {
136 			s->idx = 0;
137-			r = read(s->fd, s->buf, sizeof(s->buf));
138+			r = tls_read(s->ts, s->buf, sizeof(s->buf));
139 			if (r <= 0) {
140 				s->len = 0;
141 				break;
142@@ -242,13 +284,24 @@ read_non_chunked(struct Stream *s, int out_fd, long long content_len)
143 	}
144 }
145 
146+static void
147+req_printf(struct TlsSocket *ts, const char *fmt, ...)
148+{
149+	va_list ap;
150+	char buf[1024];
151+	int len;
152+
153+	va_start(ap, fmt);
154+	len = vsnprintf(buf, sizeof(buf), fmt, ap);
155+	va_end(ap);
156+	if (len > 0)
157+		tls_write(ts, buf, len);
158+}
159+
160 int
161 main(int argc, char *argv[])
162 {
163 	struct Stream s;
164-	char *Oflag = NULL;
165-	char *pflag = NULL;
166-	char *rflag = NULL;
167 	char *url, *host, *port, *path, *loc;
168 	char *curr_host, *curr_port, *curr_path;
169 	char *new_url;
170@@ -258,7 +311,7 @@ main(int argc, char *argv[])
171 	char *out_name;
172 	int redirects = 0;
173 	int max_redirects = 20;
174-	int sock = -1;
175+	int sock_fd = -1;
176 	int out_fd = 1;
177 	int chunked;
178 	int status;
179@@ -267,16 +320,83 @@ main(int argc, char *argv[])
180 	ssize_t n;
181 	size_t dir_len;
182 	char *last_slash;
183+	int is_tls = 0;
184+	struct TlsSocket *tls_sock = NULL;
185+	off_t resume_offset = 0;
186+	int out_mode = O_WRONLY | O_CREAT | O_TRUNC;
187+	long long post_len = 0;
188+	int post_fd = -1;
189+	size_t i;
190 
191 	ARGBEGIN {
192+	// ?man -O: specify output file path
193 	case 'O':
194 		Oflag = EARGF(usage());
195 		break;
196-	case 'p':
197-		pflag = EARGF(usage());
198+	// ?man -P: specify output directory prefix
199+	case 'P':
200+		Pflag = EARGF(usage());
201+		break;
202+	// ?man -T: set network read and connect timeout
203+	case 'T':
204+		timeout_sec = estrtonum(EARGF(usage()), 0, 100000);
205+		break;
206+	// ?man -U: set User-Agent header
207+	case 'U':
208+		user_agent = EARGF(usage());
209 		break;
210-	case 'r':
211-		rflag = EARGF(usage());
212+	// ?man -c: continue retrieval of aborted transfer
213+	case 'c':
214+		cflag = 1;
215+		break;
216+	// ?man -q: quiet mode to suppress stderr output
217+	case 'q':
218+		qflag = 1;
219+		break;
220+	// ?man -S: print server response headers to stderr
221+	case 'S':
222+		Sflag = 1;
223+		break;
224+	case '-':
225+		if (strcmp(argv[0], "-no-check-certificate") == 0) {
226+			no_check_certificate = 1;
227+			brk_ = 1;
228+		} else if (strncmp(argv[0], "-header=", 8) == 0) {
229+			add_header(argv[0] + 8);
230+			brk_ = 1;
231+		} else if (strcmp(argv[0], "-header") == 0) {
232+			brk_ = 1;
233+			if (!argv[1])
234+				usage();
235+			add_header(argv[1]);
236+			argv++;
237+			argc--;
238+		} else if (strncmp(argv[0], "-post-data=", 11) == 0) {
239+			post_data = argv[0] + 11;
240+			brk_ = 1;
241+		} else if (strcmp(argv[0], "-post-data") == 0) {
242+			brk_ = 1;
243+			if (!argv[1])
244+				usage();
245+			post_data = argv[1];
246+			argv++;
247+			argc--;
248+		} else if (strncmp(argv[0], "-post-file=", 11) == 0) {
249+			post_file = argv[0] + 11;
250+			brk_ = 1;
251+		} else if (strcmp(argv[0], "-post-file") == 0) {
252+			brk_ = 1;
253+			if (!argv[1])
254+				usage();
255+			post_file = argv[1];
256+			argv++;
257+			argc--;
258+		} else if (strcmp(argv[0], "-spider") == 0) {
259+			spider = 1;
260+			brk_ = 1;
261+		} else {
262+			usage();
263+		}
264 		break;
265 	default:
266 		usage();
267@@ -285,47 +405,109 @@ main(int argc, char *argv[])
268 	if (argc < 1)
269 		usage();
270 
271-	if (rflag)
272-		max_redirects = estrtonum(rflag, 0, 1000);
273-
274 	url = estrdup(argv[0]);
275 
276-	while (sock < 0) {
277+	/* determine output filename early to check for resume */
278+	out_name = NULL;
279+	if (Oflag) {
280+		out_name = Oflag;
281+	} else {
282+		last_slash = strrchr(url, '/');
283+		if (last_slash && *(last_slash + 1))
284+			out_name = last_slash + 1;
285+		else
286+			out_name = "index.html";
287+
288+		if (Pflag) {
289+			char *tmp = emalloc(strlen(Pflag) + 1 + strlen(out_name) + 1);
290+			sprintf(tmp, "%s/%s", Pflag, out_name);
291+			out_name = tmp;
292+		}
293+	}
294+
295+	if (cflag && out_name && strcmp(out_name, "-") != 0) {
296+		struct stat st;
297+		if (stat(out_name, &st) == 0 && S_ISREG(st.st_mode)) {
298+			resume_offset = st.st_size;
299+		}
300+	}
301+
302+	if (post_data) {
303+		post_len = strlen(post_data);
304+	} else if (post_file) {
305+		struct stat st;
306+		post_fd = open(post_file, O_RDONLY);
307+		if (post_fd < 0)
308+			eprintf("open %s:\n", post_file);
309+		if (fstat(post_fd, &st) < 0)
310+			eprintf("stat %s:\n", post_file);
311+		post_len = st.st_size;
312+	}
313+
314+	while (!tls_sock) {
315 		if (redirects > max_redirects)
316 			eprintf("too many redirects\n");
317 
318-		/* parse url and backup original parts for relative redirect resolves */
319 		curr_host = curr_port = curr_path = NULL;
320-		parse_url(url, &curr_host, &curr_port, &curr_path);
321+		parse_url(url, &curr_host, &curr_port, &curr_path, &is_tls);
322 
323 		host = estrdup(curr_host);
324 		port = estrdup(curr_port);
325 		path = estrdup(curr_path);
326 
327-		sock = dial(host, port);
328-		if (sock < 0)
329+		sock_fd = dial(host, port);
330+		if (sock_fd < 0)
331 			eprintf("failed to connect to %s:%s\n", host, port);
332 
333-		if (pflag) {
334-			dprintf(sock, "POST /%s HTTP/1.1\r\n"
335-			              "Host: %s\r\n"
336-			              "User-Agent: wget/aruu\r\n"
337-#ifdef STD_NON_POSIX
338-			              "Accept: */*\r\n"
339-#endif
340-			              "Content-Length: %zu\r\n"
341-			              "Connection: close\r\n\r\n",
342-			              path, host, strlen(pflag));
343-			writeall(sock, pflag, strlen(pflag));
344-		} else {
345-			dprintf(sock, "GET /%s HTTP/1.1\r\n"
346-			              "Host: %s\r\n"
347-			              "User-Agent: wget/aruu\r\n"
348-#ifdef STD_NON_POSIX
349-			              "Accept: */*\r\n"
350-#endif
351-			              "Connection: close\r\n\r\n",
352-			              path, host);
353+		tls_sock = tls_connect(sock_fd, host, !no_check_certificate, is_tls);
354+		if (!tls_sock) {
355+			close(sock_fd);
356+			eprintf("failed to establish TLS connection with %s\n", host);
357+		}
358+
359+		/* send http request */
360+		const char *method = spider ? "HEAD" : ((post_data || post_file) ? "POST" : "GET");
361+		req_printf(tls_sock, "%s /%s HTTP/1.1\r\n", method, path);
362+		req_printf(tls_sock, "Host: %s\r\n", host);
363+		req_printf(tls_sock, "User-Agent: %s\r\n", user_agent);
364+		req_printf(tls_sock, "Connection: close\r\n");
365+
366+		if (resume_offset > 0) {
367+			req_printf(tls_sock, "Range: bytes=%lld-\r\n", (long long)resume_offset);
368+		}
369+
370+		if (post_data || post_file) {
371+			int has_ct = 0;
372+			for (i = 0; i < custom_headers_num; i++) {
373+				if (strncasecmp(custom_headers[i], "Content-Type:", 13) == 0) {
374+					has_ct = 1;
375+					break;
376+				}
377+			}
378+			if (!has_ct) {
379+				req_printf(tls_sock, "Content-Type: application/x-www-form-urlencoded\r\n");
380+			}
381+			req_printf(tls_sock, "Content-Length: %lld\r\n", post_len);
382+		}
383+
384+		for (i = 0; i < custom_headers_num; i++) {
385+			req_printf(tls_sock, "%s\r\n", custom_headers[i]);
386+		}
387+
388+		req_printf(tls_sock, "\r\n");
389+
390+		if (post_data) {
391+			tls_write(tls_sock, post_data, strlen(post_data));
392+		} else if (post_file) {
393+			char io_buf[8192];
394+			ssize_t r;
395+			while ((r = read(post_fd, io_buf, sizeof(io_buf))) > 0) {
396+				if (tls_write(tls_sock, io_buf, r) < 0) {
397+					eprintf("failed to write post data:\n");
398+				}
399+			}
400+			close(post_fd);
401+			post_fd = -1;
402 		}
403 
404 		/* read headers */
405@@ -333,7 +515,7 @@ main(int argc, char *argv[])
406 		header_end = NULL;
407 		memset(s.buf, 0, sizeof(s.buf));
408 		while (total_read < sizeof(s.buf) - 1) {
409-			n = read(sock, s.buf + total_read, sizeof(s.buf) - 1 - total_read);
410+			n = tls_read(tls_sock, s.buf + total_read, sizeof(s.buf) - 1 - total_read);
411 			if (n <= 0) {
412 				if (n < 0)
413 					eprintf("read socket:\n");
414@@ -351,11 +533,14 @@ main(int argc, char *argv[])
415 			eprintf("http header too large or not found\n");
416 
417 		*header_end = '\0';
418-		s.fd = sock;
419+		s.ts = tls_sock;
420 		s.len = total_read;
421 		s.idx = (header_end + 4) - s.buf;
422 
423-		/* parse HTTP status code */
424+		if (Sflag) {
425+			fprintf(stderr, "%s\n\n", s.buf);
426+		}
427+
428 		if (strncasecmp(s.buf, "HTTP/1.1 ", 9) != 0 &&
429 		    strncasecmp(s.buf, "HTTP/1.0 ", 9) != 0) {
430 			eprintf("invalid http response: %s\n", s.buf);
431@@ -372,14 +557,14 @@ main(int argc, char *argv[])
432 				new_url = estrdup(loc);
433 			} else if (loc[0] == '/') {
434 				new_url = emalloc(8 + strlen(host) + strlen(port) + strlen(loc) + 2);
435-				sprintf(new_url, "http://%s:%s%s", host, port, loc);
436+				sprintf(new_url, "%s://%s:%s%s", is_tls ? "https" : "http", host, port, loc);
437 			} else {
438 				last_slash = strrchr(path, '/');
439 				dir_len = 0;
440 				if (last_slash)
441 					dir_len = last_slash - path + 1;
442 				new_url = emalloc(8 + strlen(host) + strlen(port) + 1 + dir_len + strlen(loc) + 2);
443-				sprintf(new_url, "http://%s:%s/", host, port);
444+				sprintf(new_url, "%s://%s:%s/", is_tls ? "https" : "http", host, port);
445 				if (dir_len > 0)
446 					strncat(new_url, path, dir_len);
447 				strcat(new_url, loc);
448@@ -388,10 +573,23 @@ main(int argc, char *argv[])
449 			free(loc);
450 			free(url);
451 			url = new_url;
452-			close(sock);
453-			sock = -1;
454+			tls_close(tls_sock, 1);
455+			tls_sock = NULL;
456 			redirects++;
457-		} else if (status != 200) {
458+		} else if (status == 206) {
459+			out_mode = O_WRONLY | O_CREAT | O_APPEND;
460+		} else if (status == 200) {
461+			out_mode = O_WRONLY | O_CREAT | O_TRUNC;
462+		} else if (status == 416) {
463+			if (!qflag)
464+				weprintf("file already fully retrieved or range invalid\n");
465+			tls_close(tls_sock, 1);
466+			free(url);
467+			free(host);
468+			free(port);
469+			free(path);
470+			return 0;
471+		} else {
472 			eprintf("server returned status: %d\n", status);
473 		}
474 
475@@ -400,7 +598,12 @@ main(int argc, char *argv[])
476 		free(path);
477 	}
478 
479-	/* parse headers for content length and chunked encoding */
480+	if (spider) {
481+		tls_close(tls_sock, 1);
482+		free(url);
483+		return 0;
484+	}
485+
486 	cl_str = find_header(s.buf, "Content-Length:");
487 	content_len = -1;
488 	if (cl_str) {
489@@ -416,34 +619,28 @@ main(int argc, char *argv[])
490 		free(te_str);
491 	}
492 
493-	/* open output file */
494-	out_name = NULL;
495-	if (Oflag) {
496-		out_name = Oflag;
497-	} else {
498-		last_slash = strrchr(url, '/');
499-		if (last_slash && *(last_slash + 1))
500-			out_name = last_slash + 1;
501-		else
502-			out_name = "index.html";
503-	}
504-
505 	if (strcmp(out_name, "-") != 0) {
506-		out_fd = open(out_name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
507+		out_fd = open(out_name, out_mode, 0644);
508 		if (out_fd < 0)
509 			eprintf("open %s:\n", out_name);
510 	}
511 
512-	/* read and write body */
513 	if (chunked)
514 		read_chunked(&s, out_fd);
515 	else
516 		read_non_chunked(&s, out_fd, content_len);
517 
518-	close(sock);
519+	tls_close(tls_sock, 1);
520 	if (out_fd != 1)
521 		close(out_fd);
522+	if (Oflag != out_name && Pflag)
523+		free(out_name);
524 	free(url);
525 
526+	for (i = 0; i < custom_headers_num; i++) {
527+		free(custom_headers[i]);
528+	}
529+	free(custom_headers);
530+
531 	return 0;
532 }
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+basename: strip directory and suffix from filenames
 5+usage: basename path [suffix]
 6+
 7+print filename with leading directories and optional suffix removed
 8+*/
 9+
10 #include <libgen.h>
11 #include <stdio.h>
12 #include <string.h>
+15, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+cal: display a calendar
 5+usage: cal [-1 | -3 | -y | -n num]
 6+
 7+print a formatted calendar of the specified month or year
 8+*/
 9+
10 #include <limits.h>
11 #include <stdint.h>
12 #include <stdio.h>
13@@ -175,9 +182,11 @@ main(int argc, char *argv[])
14 
15 	ARGBEGIN {
16 #ifdef FEATURE_CAL_EXT
17+	// ?man -1: specify option flag
18 	case '1':
19 		nmons = 1;
20 		break;
21+	// ?man -3: specify option flag
22 	case '3':
23 		nmons = 3;
24 		if (--month == 0) {
25@@ -185,22 +194,28 @@ main(int argc, char *argv[])
26 			year--;
27 		}
28 		break;
29+	// ?man -c: print count or perform stdout action
30 	case 'c':
31 		ncols = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
32 		break;
33+	// ?man -f: force the operation
34 	case 'f':
35 		fday = estrtonum(EARGF(usage()), 0, 6);
36 		break;
37+	// ?man -m: specify mode or limit
38 	case 'm': /* Monday */
39 		fday = 1;
40 		break;
41+	// ?man -n: print line numbers or counts
42 	case 'n':
43 		nmons = estrtonum(EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
44 		break;
45+	// ?man -s: silent mode or print summary
46 	case 's': /* Sunday */
47 		fday = 0;
48 		break;
49 #endif
50+	// ?man -y: specify option flag
51 	case 'y':
52 		month = 1;
53 		nmons = 12;
+21, -0
 1@@ -1,4 +1,13 @@
 2 /* See LICENSE file for copyright and license details. */
 3+
 4+/* ?man
 5+cat: concatenate files and print to standard output
 6+usage: cat [-u] [file ...]
 7+
 8+cat reads each file in sequence and writes it to standard output
 9+if no file is given, or a file is -, standard input is read
10+*/
11+
12 #include <fcntl.h>
13 #include <string.h>
14 #include <unistd.h>
15@@ -18,6 +27,7 @@ main(int argc, char *argv[])
16 
17 	ARGBEGIN {
18 	case 'u':
19+		// ?man -u: ignored; accepted for posix compatibility; output is always unbuffered
20 		break;
21 	default:
22 		usage();
23@@ -48,5 +58,16 @@ main(int argc, char *argv[])
24 		}
25 	}
26 
27+	/* ?man
28+	## Exit status
29+
30+	cat exits 0 on success, and >0 if an error occurs reading any file
31+	or writing to standard output
32+
33+	## See also
34+
35+	cp(1), dd(1)
36+	*/
37+
38 	return ret;
39 }
+12, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+chgrp: change group ownership
 5+usage: chgrp [-h] [-R [-H | -L | -P]] group file ...
 6+
 7+change the group ownership of files and directories
 8+*/
 9+
10 #include <sys/stat.h>
11 
12 #include <errno.h>
13@@ -43,14 +50,19 @@ main(int argc, char *argv[])
14 	struct recursor r = { .fn = chgrp, .maxdepth = 1, .follow = 'P' };
15 
16 	ARGBEGIN {
17+	// ?man -h: affect symbolic links instead of referenced files
18 	case 'h':
19 		hflag = 1;
20 		break;
21+	// ?man -R: change group ownership recursively
22 	case 'R':
23 		r.maxdepth = 0;
24 		break;
25+	// ?man -H: specify option flag
26 	case 'H':
27+	// ?man -L: specify option flag
28 	case 'L':
29+	// ?man -P: specify option flag
30 	case 'P':
31 		r.follow = ARGC();
32 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+chmod: change file modes
 5+usage: chmod [-R] mode file ...
 6+
 7+change the file mode bits of files and directories
 8+*/
 9+
10 #include <fcntl.h>
11 #include <sys/stat.h>
12 
+13, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+chown: change ownership
 5+usage: chown [-h] [-R [-H | -L | -P]] owner[:[group]] file ...
 6+
 7+change the user and group ownership of files and directories
 8+*/
 9+
10 #include <errno.h>
11 #include <fcntl.h>
12 #include <grp.h>
13@@ -51,15 +58,21 @@ main(int argc, char *argv[])
14 	char *owner, *group;
15 
16 	ARGBEGIN {
17+	// ?man -h: affect symbolic links instead of referenced files
18 	case 'h':
19 		hflag = 1;
20 		break;
21+	// ?man -r: operate recursively
22 	case 'r':
23+	// ?man -R: change files and directories recursively
24 	case 'R':
25 		r.maxdepth = 0;
26 		break;
27+	// ?man -H: specify option flag
28 	case 'H':
29+	// ?man -L: specify option flag
30 	case 'L':
31+	// ?man -P: specify option flag
32 	case 'P':
33 		r.follow = ARGC();
34 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+cksum: checksum and count bytes
 5+usage: cksum [file ...]
 6+
 7+print crc checksum and byte counts for files
 8+*/
 9+
10 #include <fcntl.h>
11 #include <inttypes.h>
12 #include <stdio.h>
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+cmp: compare two files
 5+usage: cmp [-l | -s] file1 file2
 6+
 7+compare two files byte by byte
 8+*/
 9+
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13@@ -19,9 +26,11 @@ main(int argc, char *argv[])
14 	int ret = 0, lflag = 0, sflag = 0, same = 1, b[2];
15 
16 	ARGBEGIN {
17+	// ?man -l: list in long format
18 	case 'l':
19 		lflag = 1;
20 		break;
21+	// ?man -s: silent mode or print summary
22 	case 's':
23 		sflag = 1;
24 		break;
+10, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+comm: compare two sorted files
 5+usage: comm [-123] file1 file2
 6+
 7+compare two sorted files line by line
 8+*/
 9+
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13@@ -39,8 +46,11 @@ main(int argc, char *argv[])
14 	int ret = 0, i, diff = 0, seenline = 0;
15 
16 	ARGBEGIN {
17+	// ?man -1: specify option flag
18 	case '1':
19+	// ?man -2: specify option flag
20 	case '2':
21+	// ?man -3: specify option flag
22 	case '3':
23 		show &= 0x07 ^ (1 << (ARGC() - '1'));
24 		break;
+17, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+cp: copy files and directories
 5+usage: cp [-afipv] [-R [-H | -L | -P]] source ... dest
 6+
 7+copy files and directories to a destination
 8+*/
 9+
10 #include <sys/stat.h>
11 
12 #include "fs.h"
13@@ -16,28 +23,38 @@ main(int argc, char *argv[])
14 	struct stat st;
15 
16 	ARGBEGIN {
17+	// ?man -i: prompt before overwriting existing files
18 	case 'i':
19 		cp_iflag = 1;
20 		break;
21+	// ?man -a: archive mode; equivalent to -dpR
22 	case 'a':
23 		cp_follow = 'P';
24 		cp_aflag = cp_pflag = cp_rflag = 1;
25 		break;
26+	// ?man -f: force copy by removing existing destination files
27 	case 'f':
28 		cp_fflag = 1;
29 		break;
30+	// ?man -p: preserve file attributes
31 	case 'p':
32 		cp_pflag = 1;
33 		break;
34+	// ?man -r: copy directories recursively
35 	case 'r':
36+	// ?man -R: copy directories recursively
37 	case 'R':
38 		cp_rflag = 1;
39 		break;
40+	// ?man -v: verbose mode; show progress
41 	case 'v':
42 		cp_vflag = 1;
43 		break;
44+	// ?man -H: specify option flag
45 	case 'H':
46+	// ?man -L: specify option flag
47 	case 'L':
48+	// ?man -P: specify option flag
49 	case 'P':
50 		cp_follow = ARGC();
51 		break;
+13, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+cut: cut out fields from lines
 5+usage: cut -b list [-n] [file ...]
 6+
 7+print selected parts of lines from files
 8+*/
 9+
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13@@ -166,21 +173,27 @@ main(int argc, char *argv[])
14 	int ret = 0;
15 
16 	ARGBEGIN {
17+	// ?man -b: specify block size or base directory
18 	case 'b':
19+	// ?man -c: print count or perform stdout action
20 	case 'c':
21+	// ?man -f: force the operation
22 	case 'f':
23 		mode = ARGC();
24 		parselist(EARGF(usage()));
25 		break;
26+	// ?man -d: specify directory
27 	case 'd':
28 		delim = EARGF(usage());
29 		if (!*delim)
30 			eprintf("empty delimiter\n");
31 		delimlen = unescape(delim);
32 		break;
33+	// ?man -n: print line numbers or counts
34 	case 'n':
35 		nflag = 1;
36 		break;
37+	// ?man -s: silent mode or print summary
38 	case 's':
39 		sflag = 1;
40 		break;
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+date: print or set system date and time
 5+usage: date [-u] [-d time] [+format | mmddHHMM[[CC]yy]]
 6+
 7+display or configure the system date and time
 8+*/
 9+
10 #include <ctype.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13@@ -73,9 +80,11 @@ main(int argc, char *argv[])
14 		eprintf("time:");
15 
16 	ARGBEGIN {
17+	// ?man -d: specify directory
18 	case 'd':
19 		t = estrtonum(EARGF(usage()), 0, LLONG_MAX);
20 		break;
21+	// ?man -u: unbuffered output
22 	case 'u':
23 		if (setenv("TZ", "UTC0", 1) < 0)
24 			eprintf("setenv:");
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+dd: convert and copy a file
 5+usage: dd [operand...]
 6+
 7+copy a file while performing conversions
 8+*/
 9+
10 #include <ctype.h>
11 #include <fcntl.h>
12 #include <inttypes.h>
+12, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+df: report disk space usage
 5+usage: df [-a]
 6+
 7+display free and used disk space on filesystems
 8+*/
 9+
10 #include <sys/statvfs.h>
11 
12 #include <mntent.h>
13@@ -97,19 +104,24 @@ main(int argc, char *argv[])
14 	int ret = 0;
15 
16 	ARGBEGIN {
17+	// ?man -a: print or show all entries
18 	case 'a':
19 		aflag = 1;
20 		break;
21+	// ?man -h: suppress headers or print help
22 	case 'h':
23 		hflag = 1;
24 		kflag = 0;
25 		break;
26+	// ?man -k: specify option flag
27 	case 'k':
28 		kflag = 1;
29 		hflag = 0;
30 		blksize = 1024;
31 		break;
32+	// ?man -s: silent mode or print summary
33 	case 's':
34+	// ?man -i: interactive mode or prompt for confirmation
35 	case 'i':
36 		eprintf("not implemented\n");
37 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+dirname: strip non-directory suffix from filename
 5+usage: dirname path
 6+
 7+print the directory portion of a pathname
 8+*/
 9+
10 #include <libgen.h>
11 #include <stdio.h>
12 
+16, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+du: estimate file space usage
 5+usage: du [-a | -s] [-d depth] [-h] [-k] [-H | -L | -P] [-x] [file ...]
 6+
 7+display disk space used by files and directories
 8+*/
 9+
10 #include <sys/stat.h>
11 #include <sys/types.h>
12 
13@@ -117,27 +124,36 @@ main(int argc, char *argv[])
14 	char *bsize;
15 
16 	ARGBEGIN {
17+	// ?man -a: print or show all entries
18 	case 'a':
19 		aflag = 1;
20 		break;
21+	// ?man -d: specify directory
22 	case 'd':
23 		dflag = 1;
24 		maxdepth = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
25 		break;
26+	// ?man -h: suppress headers or print help
27 	case 'h':
28 		hflag = 1;
29 		break;
30+	// ?man -k: specify option flag
31 	case 'k':
32 		kflag = 1;
33 		break;
34+	// ?man -s: silent mode or print summary
35 	case 's':
36 		sflag = 1;
37 		break;
38+	// ?man -x: hex format or match whole lines
39 	case 'x':
40 		r.flags |= SAMEDEV;
41 		break;
42+	// ?man -H: specify option flag
43 	case 'H':
44+	// ?man -L: specify option flag
45 	case 'L':
46+	// ?man -P: specify option flag
47 	case 'P':
48 		r.follow = ARGC();
49 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+echo: write arguments to stdout
 5+usage: echo
 6+
 7+print the specified arguments to standard output
 8+*/
 9+
10 #include <stdio.h>
11 #include <string.h>
12 #include "util.h"
+11, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+ed: line editor
 5+usage: ed [-s] [-p] [file]
 6+
 7+simple text line editor
 8+*/
 9+
10 #include <sys/stat.h>
11 #include <fcntl.h>
12 #include <regex.h>
13@@ -1332,6 +1339,7 @@ repeat:
14 		goto print;
15 	case 'l':
16 	case 'n':
17+	// ?man -p: preserve file attributes
18 	case 'p':
19 		back(cmd);
20 		chkprint(1);
21@@ -1390,6 +1398,7 @@ repeat:
22 			error("nothing to undo");
23 		undo();
24 		break;
25+	// ?man -s: silent mode or print summary
26 	case 's':
27 		deflines(curln, curln);
28 		c = input();
29@@ -1700,10 +1709,12 @@ int
30 main(int argc, char *argv[])
31 {
32 	ARGBEGIN {
33+	// ?man -p: preserve file attributes
34 	case 'p':
35 		prompt = EARGF(usage());
36 		optprompt = 1;
37 		break;
38+	// ?man -s: silent mode or print summary
39 	case 's':
40 		optdiag = 0;
41 		break;
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+env: run command in modified environment
 5+usage: env [-i] [-u var] ... [var=value] ... [cmd [arg ...]]
 6+
 7+set environment variables and run a command
 8+*/
 9+
10 #include <errno.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13@@ -21,9 +28,11 @@ main(int argc, char *argv[])
14 	int savederrno;
15 
16 	ARGBEGIN {
17+	// ?man -i: interactive mode or prompt for confirmation
18 	case 'i':
19 		*environ = NULL;
20 		break;
21+	// ?man -u: unbuffered output
22 	case 'u':
23 		if (unsetenv(EARGF(usage())) < 0)
24 			eprintf("unsetenv:");
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+expand: convert tabs to spaces
 5+usage: expand [-i] [-t tablist] [file ...]
 6+
 7+convert tab characters to space characters
 8+*/
 9+
10 #include <stdint.h>
11 #include <stdlib.h>
12 #include <string.h>
13@@ -93,9 +100,11 @@ main(int argc, char *argv[])
14 	char *tl = "8";
15 
16 	ARGBEGIN {
17+	// ?man -i: interactive mode or prompt for confirmation
18 	case 'i':
19 		iflag = 1;
20 		break;
21+	// ?man -t: sort or specify timestamp
22 	case 't':
23 		tl = EARGF(usage());
24 		if (!*tl)
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+expr: evaluate expression
 5+usage: expr
 6+
 7+evaluate a command line expression and print the result
 8+*/
 9+
10 #include <limits.h>
11 #include <stdio.h>
12 #include <stdlib.h>
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+false: return unsuccessful exit status
 5+usage: false
 6+
 7+exit with status 1 representing failure
 8+*/
 9+
10 int
11 main(void)
12 {
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+find: search for files
 5+usage: find [-H | -L] path ... [expression ...]
 6+
 7+search for files in a directory hierarchy
 8+*/
 9+
10 #include "config.h"
11 #include "util.h"
12 
13@@ -1429,10 +1436,12 @@ main(int argc, char **argv)
14 	gflags.mindepth = -1;
15 
16 	ARGBEGIN {
17+	// ?man -H: specify option flag
18 	case 'H':
19 		gflags.h = 1;
20 		gflags.l = 0;
21 		break;
22+	// ?man -L: specify option flag
23 	case 'L':
24 		gflags.l = 1;
25 		gflags.h = 0;
+10, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+fold: wrap lines to fit width
 5+usage: fold [-bs] [-w num | -num] [FILE ...]
 6+
 7+wrap input lines to fit a specified width
 8+*/
 9+
10 #include <ctype.h>
11 #include <stdint.h>
12 #include <stdio.h>
13@@ -89,12 +96,15 @@ main(int argc, char *argv[])
14 	int ret = 0;
15 
16 	ARGBEGIN {
17+	// ?man -b: specify block size or base directory
18 	case 'b':
19 		bflag = 1;
20 		break;
21+	// ?man -s: silent mode or print summary
22 	case 's':
23 		sflag = 1;
24 		break;
25+	// ?man -w: wait for completion
26 	case 'w':
27 		width = estrtonum(EARGF(usage()), 1, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
28 		break;
+8, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+getconf: query configuration variables
 5+usage: getconf [-v spec] var [path]
 6+
 7+query system configuration variables
 8+*/
 9+
10 #include <errno.h>
11 #include <unistd.h>
12 #include <limits.h>
13@@ -29,6 +36,7 @@ main(int argc, char *argv[])
14 	char *str;
15 
16 	ARGBEGIN {
17+	// ?man -v: verbose mode; show progress
18 	case 'v':
19 		/* ignore */
20 		EARGF(usage());
+0, -605
  1@@ -1,605 +0,0 @@
  2-static const struct var confstr_l[] = {
  3-#ifdef _CS_PATH
  4-	{"PATH",	_CS_PATH},
  5-#endif
  6-#ifdef _CS_POSIX_V7_ILP32_OFF32_CFLAGS
  7-	{"POSIX_V7_ILP32_OFF32_CFLAGS",	_CS_POSIX_V7_ILP32_OFF32_CFLAGS},
  8-#endif
  9-#ifdef _CS_POSIX_V7_ILP32_OFF32_LDFLAGS
 10-	{"POSIX_V7_ILP32_OFF32_LDFLAGS",	_CS_POSIX_V7_ILP32_OFF32_LDFLAGS},
 11-#endif
 12-#ifdef _CS_POSIX_V7_ILP32_OFF32_LIBS
 13-	{"POSIX_V7_ILP32_OFF32_LIBS",	_CS_POSIX_V7_ILP32_OFF32_LIBS},
 14-#endif
 15-#ifdef _CS_POSIX_V7_ILP32_OFFBIG_CFLAGS
 16-	{"POSIX_V7_ILP32_OFFBIG_CFLAGS",	_CS_POSIX_V7_ILP32_OFFBIG_CFLAGS},
 17-#endif
 18-#ifdef _CS_POSIX_V7_ILP32_OFFBIG_LDFLAGS
 19-	{"POSIX_V7_ILP32_OFFBIG_LDFLAGS",	_CS_POSIX_V7_ILP32_OFFBIG_LDFLAGS},
 20-#endif
 21-#ifdef _CS_POSIX_V7_ILP32_OFFBIG_LIBS
 22-	{"POSIX_V7_ILP32_OFFBIG_LIBS",	_CS_POSIX_V7_ILP32_OFFBIG_LIBS},
 23-#endif
 24-#ifdef _CS_POSIX_V7_LP64_OFF64_CFLAGS
 25-	{"POSIX_V7_LP64_OFF64_CFLAGS",	_CS_POSIX_V7_LP64_OFF64_CFLAGS},
 26-#endif
 27-#ifdef _CS_POSIX_V7_LP64_OFF64_LDFLAGS
 28-	{"POSIX_V7_LP64_OFF64_LDFLAGS",	_CS_POSIX_V7_LP64_OFF64_LDFLAGS},
 29-#endif
 30-#ifdef _CS_POSIX_V7_LP64_OFF64_LIBS
 31-	{"POSIX_V7_LP64_OFF64_LIBS",	_CS_POSIX_V7_LP64_OFF64_LIBS},
 32-#endif
 33-#ifdef _CS_POSIX_V7_LPBIG_OFFBIG_CFLAGS
 34-	{"POSIX_V7_LPBIG_OFFBIG_CFLAGS",	_CS_POSIX_V7_LPBIG_OFFBIG_CFLAGS},
 35-#endif
 36-#ifdef _CS_POSIX_V7_LPBIG_OFFBIG_LDFLAGS
 37-	{"POSIX_V7_LPBIG_OFFBIG_LDFLAGS",	_CS_POSIX_V7_LPBIG_OFFBIG_LDFLAGS},
 38-#endif
 39-#ifdef _CS_POSIX_V7_LPBIG_OFFBIG_LIBS
 40-	{"POSIX_V7_LPBIG_OFFBIG_LIBS",	_CS_POSIX_V7_LPBIG_OFFBIG_LIBS},
 41-#endif
 42-#ifdef _CS_POSIX_V7_THREADS_CFLAGS
 43-	{"POSIX_V7_THREADS_CFLAGS",	_CS_POSIX_V7_THREADS_CFLAGS},
 44-#endif
 45-#ifdef _CS_POSIX_V7_THREADS_LDFLAGS
 46-	{"POSIX_V7_THREADS_LDFLAGS",	_CS_POSIX_V7_THREADS_LDFLAGS},
 47-#endif
 48-#ifdef _CS_POSIX_V7_WIDTH_RESTRICTED_ENVS
 49-	{"POSIX_V7_WIDTH_RESTRICTED_ENVS",	_CS_POSIX_V7_WIDTH_RESTRICTED_ENVS},
 50-#endif
 51-#ifdef _CS_V7_ENV
 52-	{"V7_ENV",	_CS_V7_ENV},
 53-#endif
 54-};
 55-static const struct var limits_l[] = {
 56-#ifdef _POSIX_CLOCKRES_MIN
 57-	{"_POSIX_CLOCKRES_MIN",	_POSIX_CLOCKRES_MIN},
 58-#endif
 59-#ifdef _POSIX_AIO_LISTIO_MAX
 60-	{"_POSIX_AIO_LISTIO_MAX",	_POSIX_AIO_LISTIO_MAX},
 61-#endif
 62-#ifdef _POSIX_AIO_MAX
 63-	{"_POSIX_AIO_MAX",	_POSIX_AIO_MAX},
 64-#endif
 65-#ifdef _POSIX_ARG_MAX
 66-	{"_POSIX_ARG_MAX",	_POSIX_ARG_MAX},
 67-#endif
 68-#ifdef _POSIX_CHILD_MAX
 69-	{"_POSIX_CHILD_MAX",	_POSIX_CHILD_MAX},
 70-#endif
 71-#ifdef _POSIX_DELAYTIMER_MAX
 72-	{"_POSIX_DELAYTIMER_MAX",	_POSIX_DELAYTIMER_MAX},
 73-#endif
 74-#ifdef _POSIX_HOST_NAME_MAX
 75-	{"_POSIX_HOST_NAME_MAX",	_POSIX_HOST_NAME_MAX},
 76-#endif
 77-#ifdef _POSIX_LINK_MAX
 78-	{"_POSIX_LINK_MAX",	_POSIX_LINK_MAX},
 79-#endif
 80-#ifdef _POSIX_LOGIN_NAME_MAX
 81-	{"_POSIX_LOGIN_NAME_MAX",	_POSIX_LOGIN_NAME_MAX},
 82-#endif
 83-#ifdef _POSIX_MAX_CANON
 84-	{"_POSIX_MAX_CANON",	_POSIX_MAX_CANON},
 85-#endif
 86-#ifdef _POSIX_MAX_INPUT
 87-	{"_POSIX_MAX_INPUT",	_POSIX_MAX_INPUT},
 88-#endif
 89-#ifdef _POSIX_MQ_OPEN_MAX
 90-	{"_POSIX_MQ_OPEN_MAX",	_POSIX_MQ_OPEN_MAX},
 91-#endif
 92-#ifdef _POSIX_MQ_PRIO_MAX
 93-	{"_POSIX_MQ_PRIO_MAX",	_POSIX_MQ_PRIO_MAX},
 94-#endif
 95-#ifdef _POSIX_NAME_MAX
 96-	{"_POSIX_NAME_MAX",	_POSIX_NAME_MAX},
 97-#endif
 98-#ifdef _POSIX_NGROUPS_MAX
 99-	{"_POSIX_NGROUPS_MAX",	_POSIX_NGROUPS_MAX},
100-#endif
101-#ifdef _POSIX_OPEN_MAX
102-	{"_POSIX_OPEN_MAX",	_POSIX_OPEN_MAX},
103-#endif
104-#ifdef _POSIX_PATH_MAX
105-	{"_POSIX_PATH_MAX",	_POSIX_PATH_MAX},
106-#endif
107-#ifdef _POSIX_PIPE_BUF
108-	{"_POSIX_PIPE_BUF",	_POSIX_PIPE_BUF},
109-#endif
110-#ifdef _POSIX_RE_DUP_MAX
111-	{"_POSIX_RE_DUP_MAX",	_POSIX_RE_DUP_MAX},
112-#endif
113-#ifdef _POSIX_RTSIG_MAX
114-	{"_POSIX_RTSIG_MAX",	_POSIX_RTSIG_MAX},
115-#endif
116-#ifdef _POSIX_SEM_NSEMS_MAX
117-	{"_POSIX_SEM_NSEMS_MAX",	_POSIX_SEM_NSEMS_MAX},
118-#endif
119-#ifdef _POSIX_SEM_VALUE_MAX
120-	{"_POSIX_SEM_VALUE_MAX",	_POSIX_SEM_VALUE_MAX},
121-#endif
122-#ifdef _POSIX_SIGQUEUE_MAX
123-	{"_POSIX_SIGQUEUE_MAX",	_POSIX_SIGQUEUE_MAX},
124-#endif
125-#ifdef _POSIX_SSIZE_MAX
126-	{"_POSIX_SSIZE_MAX",	_POSIX_SSIZE_MAX},
127-#endif
128-#ifdef _POSIX_SS_REPL_MAX
129-	{"_POSIX_SS_REPL_MAX",	_POSIX_SS_REPL_MAX},
130-#endif
131-#ifdef _POSIX_STREAM_MAX
132-	{"_POSIX_STREAM_MAX",	_POSIX_STREAM_MAX},
133-#endif
134-#ifdef _POSIX_SYMLINK_MAX
135-	{"_POSIX_SYMLINK_MAX",	_POSIX_SYMLINK_MAX},
136-#endif
137-#ifdef _POSIX_SYMLOOP_MAX
138-	{"_POSIX_SYMLOOP_MAX",	_POSIX_SYMLOOP_MAX},
139-#endif
140-#ifdef _POSIX_THREAD_DESTRUCTOR_ITERATIONS
141-	{"_POSIX_THREAD_DESTRUCTOR_ITERATIONS",	_POSIX_THREAD_DESTRUCTOR_ITERATIONS},
142-#endif
143-#ifdef _POSIX_THREAD_KEYS_MAX
144-	{"_POSIX_THREAD_KEYS_MAX",	_POSIX_THREAD_KEYS_MAX},
145-#endif
146-#ifdef _POSIX_THREAD_THREADS_MAX
147-	{"_POSIX_THREAD_THREADS_MAX",	_POSIX_THREAD_THREADS_MAX},
148-#endif
149-#ifdef _POSIX_TIMER_MAX
150-	{"_POSIX_TIMER_MAX",	_POSIX_TIMER_MAX},
151-#endif
152-#ifdef _POSIX_TTY_NAME_MAX
153-	{"_POSIX_TTY_NAME_MAX",	_POSIX_TTY_NAME_MAX},
154-#endif
155-#ifdef _POSIX_TZNAME_MAX
156-	{"_POSIX_TZNAME_MAX",	_POSIX_TZNAME_MAX},
157-#endif
158-#ifdef _POSIX2_BC_BASE_MAX
159-	{"_POSIX2_BC_BASE_MAX",	_POSIX2_BC_BASE_MAX},
160-#endif
161-#ifdef _POSIX2_BC_DIM_MAX
162-	{"_POSIX2_BC_DIM_MAX",	_POSIX2_BC_DIM_MAX},
163-#endif
164-#ifdef _POSIX2_BC_SCALE_MAX
165-	{"_POSIX2_BC_SCALE_MAX",	_POSIX2_BC_SCALE_MAX},
166-#endif
167-#ifdef _POSIX2_BC_STRING_MAX
168-	{"_POSIX2_BC_STRING_MAX",	_POSIX2_BC_STRING_MAX},
169-#endif
170-#ifdef _POSIX2_CHARCLASS_NAME_MAX
171-	{"_POSIX2_CHARCLASS_NAME_MAX",	_POSIX2_CHARCLASS_NAME_MAX},
172-#endif
173-#ifdef _POSIX2_COLL_WEIGHTS_MAX
174-	{"_POSIX2_COLL_WEIGHTS_MAX",	_POSIX2_COLL_WEIGHTS_MAX},
175-#endif
176-#ifdef _POSIX2_EXPR_NEST_MAX
177-	{"_POSIX2_EXPR_NEST_MAX",	_POSIX2_EXPR_NEST_MAX},
178-#endif
179-#ifdef _POSIX2_LINE_MAX
180-	{"_POSIX2_LINE_MAX",	_POSIX2_LINE_MAX},
181-#endif
182-#ifdef _POSIX2_RE_DUP_MAX
183-	{"_POSIX2_RE_DUP_MAX",	_POSIX2_RE_DUP_MAX},
184-#endif
185-};
186-static const struct var sysconf_l[] = {
187-#ifdef _SC_AIO_LISTIO_MAX
188-	{"AIO_LISTIO_MAX",	_SC_AIO_LISTIO_MAX},
189-#endif
190-#ifdef _SC_AIO_MAX
191-	{"AIO_MAX",	_SC_AIO_MAX},
192-#endif
193-#ifdef _SC_AIO_PRIO_DELTA_MAX
194-	{"AIO_PRIO_DELTA_MAX",	_SC_AIO_PRIO_DELTA_MAX},
195-#endif
196-#ifdef _SC_ARG_MAX
197-	{"ARG_MAX",	_SC_ARG_MAX},
198-#endif
199-#ifdef _SC_ATEXIT_MAX
200-	{"ATEXIT_MAX",	_SC_ATEXIT_MAX},
201-#endif
202-#ifdef _SC_BC_BASE_MAX
203-	{"BC_BASE_MAX",	_SC_BC_BASE_MAX},
204-#endif
205-#ifdef _SC_BC_DIM_MAX
206-	{"BC_DIM_MAX",	_SC_BC_DIM_MAX},
207-#endif
208-#ifdef _SC_BC_SCALE_MAX
209-	{"BC_SCALE_MAX",	_SC_BC_SCALE_MAX},
210-#endif
211-#ifdef _SC_BC_STRING_MAX
212-	{"BC_STRING_MAX",	_SC_BC_STRING_MAX},
213-#endif
214-#ifdef _SC_CHILD_MAX
215-	{"CHILD_MAX",	_SC_CHILD_MAX},
216-#endif
217-#ifdef _SC_COLL_WEIGHTS_MAX
218-	{"COLL_WEIGHTS_MAX",	_SC_COLL_WEIGHTS_MAX},
219-#endif
220-#ifdef _SC_DELAYTIMER_MAX
221-	{"DELAYTIMER_MAX",	_SC_DELAYTIMER_MAX},
222-#endif
223-#ifdef _SC_EXPR_NEST_MAX
224-	{"EXPR_NEST_MAX",	_SC_EXPR_NEST_MAX},
225-#endif
226-#ifdef _SC_HOST_NAME_MAX
227-	{"HOST_NAME_MAX",	_SC_HOST_NAME_MAX},
228-#endif
229-#ifdef _SC_IOV_MAX
230-	{"IOV_MAX",	_SC_IOV_MAX},
231-#endif
232-#ifdef _SC_LINE_MAX
233-	{"LINE_MAX",	_SC_LINE_MAX},
234-#endif
235-#ifdef _SC_LOGIN_NAME_MAX
236-	{"LOGIN_NAME_MAX",	_SC_LOGIN_NAME_MAX},
237-#endif
238-#ifdef _SC_NGROUPS_MAX
239-	{"NGROUPS_MAX",	_SC_NGROUPS_MAX},
240-#endif
241-#ifdef _SC_MQ_OPEN_MAX
242-	{"MQ_OPEN_MAX",	_SC_MQ_OPEN_MAX},
243-#endif
244-#ifdef _SC_MQ_PRIO_MAX
245-	{"MQ_PRIO_MAX",	_SC_MQ_PRIO_MAX},
246-#endif
247-#ifdef _SC_OPEN_MAX
248-	{"OPEN_MAX",	_SC_OPEN_MAX},
249-#endif
250-#ifdef _SC_ADVISORY_INFO
251-	{"_POSIX_ADVISORY_INFO",	_SC_ADVISORY_INFO},
252-#endif
253-#ifdef _SC_BARRIERS
254-	{"_POSIX_BARRIERS",	_SC_BARRIERS},
255-#endif
256-#ifdef _SC_ASYNCHRONOUS_IO
257-	{"_POSIX_ASYNCHRONOUS_IO",	_SC_ASYNCHRONOUS_IO},
258-#endif
259-#ifdef _SC_CLOCK_SELECTION
260-	{"_POSIX_CLOCK_SELECTION",	_SC_CLOCK_SELECTION},
261-#endif
262-#ifdef _SC_CPUTIME
263-	{"_POSIX_CPUTIME",	_SC_CPUTIME},
264-#endif
265-#ifdef _SC_FSYNC
266-	{"_POSIX_FSYNC",	_SC_FSYNC},
267-#endif
268-#ifdef _SC_IPV6
269-	{"_POSIX_IPV6",	_SC_IPV6},
270-#endif
271-#ifdef _SC_JOB_CONTROL
272-	{"_POSIX_JOB_CONTROL",	_SC_JOB_CONTROL},
273-#endif
274-#ifdef _SC_MAPPED_FILES
275-	{"_POSIX_MAPPED_FILES",	_SC_MAPPED_FILES},
276-#endif
277-#ifdef _SC_MEMLOCK
278-	{"_POSIX_MEMLOCK",	_SC_MEMLOCK},
279-#endif
280-#ifdef _SC_MEMLOCK_RANGE
281-	{"_POSIX_MEMLOCK_RANGE",	_SC_MEMLOCK_RANGE},
282-#endif
283-#ifdef _SC_MEMORY_PROTECTION
284-	{"_POSIX_MEMORY_PROTECTION",	_SC_MEMORY_PROTECTION},
285-#endif
286-#ifdef _SC_MESSAGE_PASSING
287-	{"_POSIX_MESSAGE_PASSING",	_SC_MESSAGE_PASSING},
288-#endif
289-#ifdef _SC_MONOTONIC_CLOCK
290-	{"_POSIX_MONOTONIC_CLOCK",	_SC_MONOTONIC_CLOCK},
291-#endif
292-#ifdef _SC_PRIORITIZED_IO
293-	{"_POSIX_PRIORITIZED_IO",	_SC_PRIORITIZED_IO},
294-#endif
295-#ifdef _SC_PRIORITY_SCHEDULING
296-	{"_POSIX_PRIORITY_SCHEDULING",	_SC_PRIORITY_SCHEDULING},
297-#endif
298-#ifdef _SC_RAW_SOCKETS
299-	{"_POSIX_RAW_SOCKETS",	_SC_RAW_SOCKETS},
300-#endif
301-#ifdef _SC_READER_WRITER_LOCKS
302-	{"_POSIX_READER_WRITER_LOCKS",	_SC_READER_WRITER_LOCKS},
303-#endif
304-#ifdef _SC_REALTIME_SIGNALS
305-	{"_POSIX_REALTIME_SIGNALS",	_SC_REALTIME_SIGNALS},
306-#endif
307-#ifdef _SC_REGEXP
308-	{"_POSIX_REGEXP",	_SC_REGEXP},
309-#endif
310-#ifdef _SC_SAVED_IDS
311-	{"_POSIX_SAVED_IDS",	_SC_SAVED_IDS},
312-#endif
313-#ifdef _SC_SEMAPHORES
314-	{"_POSIX_SEMAPHORES",	_SC_SEMAPHORES},
315-#endif
316-#ifdef _SC_SHARED_MEMORY_OBJECTS
317-	{"_POSIX_SHARED_MEMORY_OBJECTS",	_SC_SHARED_MEMORY_OBJECTS},
318-#endif
319-#ifdef _SC_SHELL
320-	{"_POSIX_SHELL",	_SC_SHELL},
321-#endif
322-#ifdef _SC_SPAWN
323-	{"_POSIX_SPAWN",	_SC_SPAWN},
324-#endif
325-#ifdef _SC_SPIN_LOCKS
326-	{"_POSIX_SPIN_LOCKS",	_SC_SPIN_LOCKS},
327-#endif
328-#ifdef _SC_SPORADIC_SERVER
329-	{"_POSIX_SPORADIC_SERVER",	_SC_SPORADIC_SERVER},
330-#endif
331-#ifdef _SC_SS_REPL_MAX
332-	{"_POSIX_SS_REPL_MAX",	_SC_SS_REPL_MAX},
333-#endif
334-#ifdef _SC_SYNCHRONIZED_IO
335-	{"_POSIX_SYNCHRONIZED_IO",	_SC_SYNCHRONIZED_IO},
336-#endif
337-#ifdef _SC_THREAD_ATTR_STACKADDR
338-	{"_POSIX_THREAD_ATTR_STACKADDR",	_SC_THREAD_ATTR_STACKADDR},
339-#endif
340-#ifdef _SC_THREAD_ATTR_STACKSIZE
341-	{"_POSIX_THREAD_ATTR_STACKSIZE",	_SC_THREAD_ATTR_STACKSIZE},
342-#endif
343-#ifdef _SC_THREAD_CPUTIME
344-	{"_POSIX_THREAD_CPUTIME",	_SC_THREAD_CPUTIME},
345-#endif
346-#ifdef _SC_THREAD_PRIO_INHERIT
347-	{"_POSIX_THREAD_PRIO_INHERIT",	_SC_THREAD_PRIO_INHERIT},
348-#endif
349-#ifdef _SC_THREAD_PRIO_PROTECT
350-	{"_POSIX_THREAD_PRIO_PROTECT",	_SC_THREAD_PRIO_PROTECT},
351-#endif
352-#ifdef _SC_THREAD_PRIORITY_SCHEDULING
353-	{"_POSIX_THREAD_PRIORITY_SCHEDULING",	_SC_THREAD_PRIORITY_SCHEDULING},
354-#endif
355-#ifdef _SC_THREAD_PROCESS_SHARED
356-	{"_POSIX_THREAD_PROCESS_SHARED",	_SC_THREAD_PROCESS_SHARED},
357-#endif
358-#ifdef _SC_THREAD_ROBUST_PRIO_INHERIT
359-	{"_POSIX_THREAD_ROBUST_PRIO_INHERIT",	_SC_THREAD_ROBUST_PRIO_INHERIT},
360-#endif
361-#ifdef _SC_THREAD_ROBUST_PRIO_PROTECT
362-	{"_POSIX_THREAD_ROBUST_PRIO_PROTECT",	_SC_THREAD_ROBUST_PRIO_PROTECT},
363-#endif
364-#ifdef _SC_THREAD_SAFE_FUNCTIONS
365-	{"_POSIX_THREAD_SAFE_FUNCTIONS",	_SC_THREAD_SAFE_FUNCTIONS},
366-#endif
367-#ifdef _SC_THREAD_SPORADIC_SERVER
368-	{"_POSIX_THREAD_SPORADIC_SERVER",	_SC_THREAD_SPORADIC_SERVER},
369-#endif
370-#ifdef _SC_THREADS
371-	{"_POSIX_THREADS",	_SC_THREADS},
372-#endif
373-#ifdef _SC_TIMEOUTS
374-	{"_POSIX_TIMEOUTS",	_SC_TIMEOUTS},
375-#endif
376-#ifdef _SC_TIMERS
377-	{"_POSIX_TIMERS",	_SC_TIMERS},
378-#endif
379-#ifdef _SC_TRACE
380-	{"_POSIX_TRACE",	_SC_TRACE},
381-#endif
382-#ifdef _SC_TRACE_EVENT_FILTER
383-	{"_POSIX_TRACE_EVENT_FILTER",	_SC_TRACE_EVENT_FILTER},
384-#endif
385-#ifdef _SC_TRACE_EVENT_NAME_MAX
386-	{"_POSIX_TRACE_EVENT_NAME_MAX",	_SC_TRACE_EVENT_NAME_MAX},
387-#endif
388-#ifdef _SC_TRACE_INHERIT
389-	{"_POSIX_TRACE_INHERIT",	_SC_TRACE_INHERIT},
390-#endif
391-#ifdef _SC_TRACE_LOG
392-	{"_POSIX_TRACE_LOG",	_SC_TRACE_LOG},
393-#endif
394-#ifdef _SC_TRACE_NAME_MAX
395-	{"_POSIX_TRACE_NAME_MAX",	_SC_TRACE_NAME_MAX},
396-#endif
397-#ifdef _SC_TRACE_SYS_MAX
398-	{"_POSIX_TRACE_SYS_MAX",	_SC_TRACE_SYS_MAX},
399-#endif
400-#ifdef _SC_TRACE_USER_EVENT_MAX
401-	{"_POSIX_TRACE_USER_EVENT_MAX",	_SC_TRACE_USER_EVENT_MAX},
402-#endif
403-#ifdef _SC_TYPED_MEMORY_OBJECTS
404-	{"_POSIX_TYPED_MEMORY_OBJECTS",	_SC_TYPED_MEMORY_OBJECTS},
405-#endif
406-#ifdef _SC_VERSION
407-	{"_POSIX_VERSION",	_SC_VERSION},
408-#endif
409-#ifdef _SC_V7_ILP32_OFF32
410-	{"_POSIX_V7_ILP32_OFF32",	_SC_V7_ILP32_OFF32},
411-#endif
412-#ifdef _SC_V7_ILP32_OFFBIG
413-	{"_POSIX_V7_ILP32_OFFBIG",	_SC_V7_ILP32_OFFBIG},
414-#endif
415-#ifdef _SC_V7_LP64_OFF64
416-	{"_POSIX_V7_LP64_OFF64",	_SC_V7_LP64_OFF64},
417-#endif
418-#ifdef _SC_V7_LPBIG_OFFBIG
419-	{"_POSIX_V7_LPBIG_OFFBIG",	_SC_V7_LPBIG_OFFBIG},
420-#endif
421-#ifdef _SC_2_C_BIND
422-	{"_POSIX2_C_BIND",	_SC_2_C_BIND},
423-#endif
424-#ifdef _SC_2_C_DEV
425-	{"_POSIX2_C_DEV",	_SC_2_C_DEV},
426-#endif
427-#ifdef _SC_2_CHAR_TERM
428-	{"_POSIX2_CHAR_TERM",	_SC_2_CHAR_TERM},
429-#endif
430-#ifdef _SC_2_FORT_DEV
431-	{"_POSIX2_FORT_DEV",	_SC_2_FORT_DEV},
432-#endif
433-#ifdef _SC_2_FORT_RUN
434-	{"_POSIX2_FORT_RUN",	_SC_2_FORT_RUN},
435-#endif
436-#ifdef _SC_2_LOCALEDEF
437-	{"_POSIX2_LOCALEDEF",	_SC_2_LOCALEDEF},
438-#endif
439-#ifdef _SC_2_PBS
440-	{"_POSIX2_PBS",	_SC_2_PBS},
441-#endif
442-#ifdef _SC_2_PBS_ACCOUNTING
443-	{"_POSIX2_PBS_ACCOUNTING",	_SC_2_PBS_ACCOUNTING},
444-#endif
445-#ifdef _SC_2_PBS_CHECKPOINT
446-	{"_POSIX2_PBS_CHECKPOINT",	_SC_2_PBS_CHECKPOINT},
447-#endif
448-#ifdef _SC_2_PBS_LOCATE
449-	{"_POSIX2_PBS_LOCATE",	_SC_2_PBS_LOCATE},
450-#endif
451-#ifdef _SC_2_PBS_MESSAGE
452-	{"_POSIX2_PBS_MESSAGE",	_SC_2_PBS_MESSAGE},
453-#endif
454-#ifdef _SC_2_PBS_TRACK
455-	{"_POSIX2_PBS_TRACK",	_SC_2_PBS_TRACK},
456-#endif
457-#ifdef _SC_2_SW_DEV
458-	{"_POSIX2_SW_DEV",	_SC_2_SW_DEV},
459-#endif
460-#ifdef _SC_2_UPE
461-	{"_POSIX2_UPE",	_SC_2_UPE},
462-#endif
463-#ifdef _SC_2_VERSION
464-	{"_POSIX2_VERSION",	_SC_2_VERSION},
465-#endif
466-#ifdef _SC_PAGE_SIZE
467-	{"PAGE_SIZE",	_SC_PAGE_SIZE},
468-#endif
469-#ifdef _SC_PAGESIZE
470-	{"PAGESIZE",	_SC_PAGESIZE},
471-#endif
472-#ifdef _SC_THREAD_DESTRUCTOR_ITERATIONS
473-	{"PTHREAD_DESTRUCTOR_ITERATIONS",	_SC_THREAD_DESTRUCTOR_ITERATIONS},
474-#endif
475-#ifdef _SC_THREAD_KEYS_MAX
476-	{"PTHREAD_KEYS_MAX",	_SC_THREAD_KEYS_MAX},
477-#endif
478-#ifdef _SC_THREAD_STACK_MIN
479-	{"PTHREAD_STACK_MIN",	_SC_THREAD_STACK_MIN},
480-#endif
481-#ifdef _SC_THREAD_THREADS_MAX
482-	{"PTHREAD_THREADS_MAX",	_SC_THREAD_THREADS_MAX},
483-#endif
484-#ifdef _SC_RE_DUP_MAX
485-	{"RE_DUP_MAX",	_SC_RE_DUP_MAX},
486-#endif
487-#ifdef _SC_RTSIG_MAX
488-	{"RTSIG_MAX",	_SC_RTSIG_MAX},
489-#endif
490-#ifdef _SC_SEM_NSEMS_MAX
491-	{"SEM_NSEMS_MAX",	_SC_SEM_NSEMS_MAX},
492-#endif
493-#ifdef _SC_SEM_VALUE_MAX
494-	{"SEM_VALUE_MAX",	_SC_SEM_VALUE_MAX},
495-#endif
496-#ifdef _SC_SIGQUEUE_MAX
497-	{"SIGQUEUE_MAX",	_SC_SIGQUEUE_MAX},
498-#endif
499-#ifdef _SC_STREAM_MAX
500-	{"STREAM_MAX",	_SC_STREAM_MAX},
501-#endif
502-#ifdef _SC_SYMLOOP_MAX
503-	{"SYMLOOP_MAX",	_SC_SYMLOOP_MAX},
504-#endif
505-#ifdef _SC_TIMER_MAX
506-	{"TIMER_MAX",	_SC_TIMER_MAX},
507-#endif
508-#ifdef _SC_TTY_NAME_MAX
509-	{"TTY_NAME_MAX",	_SC_TTY_NAME_MAX},
510-#endif
511-#ifdef _SC_TZNAME_MAX
512-	{"TZNAME_MAX",	_SC_TZNAME_MAX},
513-#endif
514-#ifdef _SC_XOPEN_CRYPT
515-	{"_XOPEN_CRYPT",	_SC_XOPEN_CRYPT},
516-#endif
517-#ifdef _SC_XOPEN_ENH_I18N
518-	{"_XOPEN_ENH_I18N",	_SC_XOPEN_ENH_I18N},
519-#endif
520-#ifdef _SC_XOPEN_REALTIME
521-	{"_XOPEN_REALTIME",	_SC_XOPEN_REALTIME},
522-#endif
523-#ifdef _SC_XOPEN_REALTIME_THREADS
524-	{"_XOPEN_REALTIME_THREADS",	_SC_XOPEN_REALTIME_THREADS},
525-#endif
526-#ifdef _SC_XOPEN_SHM
527-	{"_XOPEN_SHM",	_SC_XOPEN_SHM},
528-#endif
529-#ifdef _SC_XOPEN_STREAMS
530-	{"_XOPEN_STREAMS",	_SC_XOPEN_STREAMS},
531-#endif
532-#ifdef _SC_XOPEN_UNIX
533-	{"_XOPEN_UNIX",	_SC_XOPEN_UNIX},
534-#endif
535-#ifdef _SC_XOPEN_UUCP
536-	{"_XOPEN_UUCP",	_SC_XOPEN_UUCP},
537-#endif
538-#ifdef _SC_XOPEN_VERSION
539-	{"_XOPEN_VERSION",	_SC_XOPEN_VERSION},
540-#endif
541-};
542-static const struct var pathconf_l[] = {
543-#ifdef _PC_FILESIZEBITS
544-	{"FILESIZEBITS",	_PC_FILESIZEBITS},
545-#endif
546-#ifdef _PC_LINK_MAX
547-	{"LINK_MAX",	_PC_LINK_MAX},
548-#endif
549-#ifdef _PC_MAX_CANON
550-	{"MAX_CANON",	_PC_MAX_CANON},
551-#endif
552-#ifdef _PC_MAX_INPUT
553-	{"MAX_INPUT",	_PC_MAX_INPUT},
554-#endif
555-#ifdef _PC_NAME_MAX
556-	{"NAME_MAX",	_PC_NAME_MAX},
557-#endif
558-#ifdef _PC_PATH_MAX
559-	{"PATH_MAX",	_PC_PATH_MAX},
560-#endif
561-#ifdef _PC_PIPE_BUF
562-	{"PIPE_BUF",	_PC_PIPE_BUF},
563-#endif
564-#ifdef _PC_2_SYMLINKS
565-	{"POSIX2_SYMLINKS",	_PC_2_SYMLINKS},
566-#endif
567-#ifdef _PC_ALLOC_SIZE_MIN
568-	{"POSIX_ALLOC_SIZE_MIN",	_PC_ALLOC_SIZE_MIN},
569-#endif
570-#ifdef _PC_REC_INCR_XFER_SIZE
571-	{"POSIX_REC_INCR_XFER_SIZE",	_PC_REC_INCR_XFER_SIZE},
572-#endif
573-#ifdef _PC_REC_MAX_XFER_SIZE
574-	{"POSIX_REC_MAX_XFER_SIZE",	_PC_REC_MAX_XFER_SIZE},
575-#endif
576-#ifdef _PC_REC_MIN_XFER_SIZE
577-	{"POSIX_REC_MIN_XFER_SIZE",	_PC_REC_MIN_XFER_SIZE},
578-#endif
579-#ifdef _PC_REC_XFER_ALIGN
580-	{"POSIX_REC_XFER_ALIGN",	_PC_REC_XFER_ALIGN},
581-#endif
582-#ifdef _PC_SYMLINK_MAX
583-	{"SYMLINK_MAX",	_PC_SYMLINK_MAX},
584-#endif
585-#ifdef _PC_CHOWN_RESTRICTED
586-	{"_POSIX_CHOWN_RESTRICTED",	_PC_CHOWN_RESTRICTED},
587-#endif
588-#ifdef _PC_NO_TRUNC
589-	{"_POSIX_NO_TRUNC",	_PC_NO_TRUNC},
590-#endif
591-#ifdef _PC_VDISABLE
592-	{"_POSIX_VDISABLE",	_PC_VDISABLE},
593-#endif
594-#ifdef _PC_ASYNC_IO
595-	{"_POSIX_ASYNC_IO",	_PC_ASYNC_IO},
596-#endif
597-#ifdef _PC_PRIO_IO
598-	{"_POSIX_PRIO_IO",	_PC_PRIO_IO},
599-#endif
600-#ifdef _PC_SYNC_IO
601-	{"_POSIX_SYNC_IO",	_PC_SYNC_IO},
602-#endif
603-#ifdef _PC_TIMESTAMP_RESOLUTION
604-	{"_POSIX_TIMESTAMP_RESOLUTION",	_PC_TIMESTAMP_RESOLUTION},
605-#endif
606-};
+47, -0
  1@@ -1,3 +1,11 @@
  2+/* ?man
  3+grep: search files for a pattern
  4+usage: grep [-EFHchilnqsvwx] [-e pattern] [-f file] [pattern] [file ...]
  5+
  6+grep searches the named input files for lines matching the given pattern
  7+if no files are named, or a file is -, standard input is searched
  8+by default, matching lines are written to standard output
  9+*/
 10 #include "config.h"
 11 #include "queue.h"
 12 #include "util.h"
 13@@ -255,12 +263,15 @@ main(int argc, char *argv[])
 14 	ARGBEGIN {
 15 #if FEATURE_GREP_CONTEXT
 16 	case 'A':
 17+		// ?man -A num: print num lines of trailing context after each match
 18 		Aflag = estrtonum(EARGF(usage()), 0, LONG_MAX);
 19 		break;
 20 	case 'B':
 21+		// ?man -B num: print num lines of leading context before each match
 22 		Bflag = estrtonum(EARGF(usage()), 0, LONG_MAX);
 23 		break;
 24 	case 'C':
 25+		// ?man -C num: print num lines of context before and after each match; equivalent to -A num -B num
 26 		Aflag = Bflag = estrtonum(EARGF(usage()), 0, LONG_MAX);
 27 		break;
 28 	ARGNUM:
 29@@ -269,24 +280,29 @@ main(int argc, char *argv[])
 30 #endif
 31 #if FEATURE_GREP_MAX_COUNT
 32 	case 'm':
 33+		// ?man -m num: stop reading a file after num matching lines
 34 		mval = estrtonum(EARGF(usage()), 0, LONG_MAX);
 35 		break;
 36 #endif
 37 	case 'E':
 38+		// ?man -E: interpret pattern as an extended regular expression
 39 		Eflag = 1;
 40 		Fflag = 0;
 41 		flags |= REG_EXTENDED;
 42 		break;
 43 	case 'F':
 44+		// ?man -F: interpret pattern as a list of fixed strings separated by newlines
 45 		Fflag = 1;
 46 		Eflag = 0;
 47 		flags &= ~REG_EXTENDED;
 48 		break;
 49 	case 'H':
 50+		// ?man -H: always print the file name with matching lines
 51 		Hflag = 1;
 52 		hflag = 0;
 53 		break;
 54 	case 'e':
 55+		// ?man -e pattern: specify a pattern to match; may be given multiple times
 56 		arg = EARGF(usage());
 57 		if (!(fp = fmemopen(arg, strlen(arg) + 1, "r")))
 58 			eprintf("fmemopen:");
 59@@ -295,6 +311,7 @@ main(int argc, char *argv[])
 60 		eflag = 1;
 61 		break;
 62 	case 'f':
 63+		// ?man -f file: read patterns from file, one per line
 64 		arg = EARGF(usage());
 65 		fp = fopen(arg, "r");
 66 		if (!fp)
 67@@ -304,29 +321,42 @@ main(int argc, char *argv[])
 68 		fflag = 1;
 69 		break;
 70 	case 'h':
 71+		// ?man -h: never print file names with matching lines
 72 		hflag = 1;
 73 		Hflag = 0;
 74 		break;
 75 	case 'c':
 76+		// ?man -c: print only a count of matching lines per file
 77+		/* FALLTHROUGH */
 78 	case 'l':
 79+		// ?man -l: print only the names of files with at least one matching line
 80+		/* FALLTHROUGH */
 81 	case 'n':
 82+		// ?man -n: prefix each matching line with its line number within its file
 83+		/* FALLTHROUGH */
 84 	case 'q':
 85+		// ?man -q: quiet mode; exit immediately with status 0 on first match and write nothing
 86 		mode = ARGC();
 87 		break;
 88 	case 'i':
 89+		// ?man -i: perform case-insensitive matching
 90 		flags |= REG_ICASE;
 91 		iflag = 1;
 92 		break;
 93 	case 's':
 94+		// ?man -s: suppress error messages about nonexistent or unreadable files
 95 		sflag = 1;
 96 		break;
 97 	case 'v':
 98+		// ?man -v: invert the sense of matching to select non-matching lines
 99 		vflag = 1;
100 		break;
101 	case 'w':
102+		// ?man -w: match only whole words
103 		wflag = 1;
104 		break;
105 	case 'x':
106+		// ?man -x: match only whole lines
107 		xflag = 1;
108 		break;
109 	default:
110@@ -375,5 +405,22 @@ main(int argc, char *argv[])
111 	if (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))
112 		match = Error;
113 
114+	/* ?man
115+	## Exit status
116+
117+	0
118+	: one or more lines matched in at least one file
119+
120+	1
121+	: no lines matched in any file
122+
123+	2
124+	: an error occurred
125+
126+	## See also
127+
128+	sed(1), awk(1)
129+	*/
130+
131 	return match;
132 }
+8, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+head: output first part of files
 5+usage: head [-num | -n num] [file ...]
 6+
 7+print the first lines or bytes of files
 8+*/
 9+
10 #include <stdint.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13@@ -36,6 +43,7 @@ main(int argc, char *argv[])
14 	int ret = 0, newline = 0, many = 0;
15 
16 	ARGBEGIN {
17+	// ?man -n: print line numbers or counts
18 	case 'n':
19 		n = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
20 		break;
+11, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+id: print user and group ids
 5+usage: id [-n] [-g | -u | -G] [user | uid]
 6+
 7+display real and effective user and group identities
 8+*/
 9+
10 #include <sys/types.h>
11 
12 #include <ctype.h>
13@@ -137,15 +144,19 @@ int
14 main(int argc, char *argv[])
15 {
16 	ARGBEGIN {
17+	// ?man -g: specify option flag
18 	case 'g':
19 		gflag = 1;
20 		break;
21+	// ?man -u: unbuffered output
22 	case 'u':
23 		uflag = 1;
24 		break;
25+	// ?man -G: specify option flag
26 	case 'G':
27 		Gflag = 1;
28 		break;
29+	// ?man -n: print line numbers or counts
30 	case 'n':
31 		nflag = 1;
32 		break;
+14, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+join: join lines on common field
 5+usage: join [-1 field] [-2 field] [-o list] [-e string]
 6+
 7+join lines of two sorted files on a common field
 8+*/
 9+
10 #include <ctype.h>
11 #include <stdint.h>
12 #include <stdio.h>
13@@ -458,12 +465,15 @@ main(int argc, char *argv[])
14 	char *fno;
15 
16 	ARGBEGIN {
17+	// ?man -1: specify option flag
18 	case '1':
19 		jf[0] = estrtonum(EARGF(usage()), 1, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
20 		break;
21+	// ?man -2: specify option flag
22 	case '2':
23 		jf[1] = estrtonum(EARGF(usage()), 1, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
24 		break;
25+	// ?man -a: print or show all entries
26 	case 'a':
27 		fno = EARGF(usage());
28 		if (strcmp(fno, "1") == 0)
29@@ -473,17 +483,21 @@ main(int argc, char *argv[])
30 		else
31 			usage();
32 		break;
33+	// ?man -e: specify expression or pattern
34 	case 'e':
35 		replace = EARGF(usage());
36 		break;
37+	// ?man -o: specify output file
38 	case 'o':
39 		oflag = 1;
40 		initolist(&output);
41 		makeolist(&output, EARGF(usage()));
42 		break;
43+	// ?man -t: sort or specify timestamp
44 	case 't':
45 		sep = EARGF(usage());
46 		break;
47+	// ?man -v: verbose mode; show progress
48 	case 'v':
49 		pairs = 0;
50 		fno = EARGF(usage());
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+kill: send signal to process
 5+usage: kill [-s signame | -num | -signame] pid ...
 6+
 7+send a specified signal to processes by pid
 8+*/
 9+
10 #include "sig.h"
11 #include "util.h"
12 
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+link: create a link to a file
 5+usage: link target name
 6+
 7+create a hard link to an existing file
 8+*/
 9+
10 #include <unistd.h>
11 
12 #include "util.h"
+11, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+ln: make links between files
 5+usage: ln [-f] [-L | -P | -s] target [name]
 6+
 7+create hard or symbolic links between files
 8+*/
 9+
10 #include <sys/stat.h>
11 
12 #include <errno.h>
13@@ -25,15 +32,19 @@ main(int argc, char *argv[])
14 	struct stat st, tst;
15 
16 	ARGBEGIN {
17+	// ?man -f: force creation of links by removing existing destination files
18 	case 'f':
19 		fflag = 1;
20 		break;
21+	// ?man -L: specify option flag
22 	case 'L':
23 		flags |= AT_SYMLINK_FOLLOW;
24 		break;
25+	// ?man -P: specify option flag
26 	case 'P':
27 		flags &= ~AT_SYMLINK_FOLLOW;
28 		break;
29+	// ?man -s: make symbolic links instead of hard links
30 	case 's':
31 		sflag = 1;
32 		break;
+11, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+logger: log messages
 5+usage: logger [-is] [-p priority] [-t tag] [message ...]
 6+
 7+add messages to the system log
 8+*/
 9+
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13@@ -51,15 +58,19 @@ main(int argc, char *argv[])
14 	char *buf = NULL, *tag = NULL;
15 
16 	ARGBEGIN {
17+	// ?man -i: interactive mode or prompt for confirmation
18 	case 'i':
19 		logflags |= LOG_PID;
20 		break;
21+	// ?man -p: preserve file attributes
22 	case 'p':
23 		priority = decodepri(EARGF(usage()));
24 		break;
25+	// ?man -s: silent mode or print summary
26 	case 's':
27 		logflags |= LOG_PERROR;
28 		break;
29+	// ?man -t: sort or specify timestamp
30 	case 't':
31 		tag = EARGF(usage());
32 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+logname: print login name
 5+usage: logname 
 6+
 7+display the user login name
 8+*/
 9+
10 #include <stdio.h>
11 #include <unistd.h>
12 
+83, -7
  1@@ -1,4 +1,11 @@
  2-/* See LICENSE file for copyright and license details. */
  3+/* see license file for copyright and license details */
  4+/* ?man
  5+ls: list directory contents
  6+usage: ls [-1ACacdFfGghiLlnopqRrtUu] [--color[=always|never|auto]] [file ...]
  7+
  8+list information about files and directories
  9+*/
 10+
 11 #include <sys/stat.h>
 12 #include <sys/types.h>
 13 #ifndef major
 14@@ -58,6 +65,9 @@ static int first = 1;
 15 static char sort = 0;
 16 static int showdirs;
 17 
 18+static int gflag = 0;
 19+static int oflag = 0;
 20+
 21 static int Cflag   = 0;
 22 static int one_flag = 0;
 23 static int termwidth = 80;
 24@@ -152,17 +162,17 @@ printname(const char *name)
 25 	}
 26 }
 27 
 28-#if FEATURE_LS_COLOR
 29 static int
 30 should_color(void)
 31 {
 32+#if FEATURE_LS_COLOR
 33 	if (color_mode == COLOR_ALWAYS)
 34 		return 1;
 35 	if (color_mode == COLOR_AUTO)
 36 		return isatty(STDOUT_FILENO);
 37+#endif
 38 	return 0;
 39 }
 40-#endif
 41 
 42 static void
 43 printname_colored(const char *name, mode_t mode)
 44@@ -287,7 +297,7 @@ printcols(const struct entry *ents, size_t n)
 45 					printf("%lu ", (unsigned long)ents[idx].ino);
 46 				printname_colored(ents[idx].name, ents[idx].mode);
 47 				fputs(indicator(ents[idx].mode), stdout);
 48-				
 49+
 50 				if (c < ncols - 1 && (c + 1) * nrows + r < (int)n) {
 51 					int pad = colwidths[c] - w + 2;
 52 					while (pad-- > 0)
 53@@ -368,7 +378,11 @@ output(const struct entry *ent)
 54 		strftime(buf, sizeof(buf), fmt, tm);
 55 	else
 56 		snprintf(buf, sizeof(buf), "%lld", (long long)(ent->t.tv_sec));
 57-	printf("%s %4ld %-8.8s %-8.8s ", mode, (long)ent->nlink, pwname, grname);
 58+	printf("%s %4ld ", mode, (long)ent->nlink);
 59+	if (!gflag)
 60+		printf("%-8.8s ", pwname);
 61+	if (!oflag)
 62+		printf("%-8.8s ", grname);
 63 
 64 	if (S_ISBLK(ent->mode) || S_ISCHR(ent->mode))
 65 		printf("%4u, %4u ", major(ent->rdev), minor(ent->rdev));
 66@@ -397,9 +411,11 @@ entcmp(const void *va, const void *vb)
 67 	const struct entry *a = va, *b = vb;
 68 
 69 	switch (sort) {
 70+	// ?man -S: sort by file size
 71 	case 'S':
 72 		cmp = b->size - a->size;
 73 		break;
 74+	// ?man -t: sort by modification time
 75 	case 't':
 76 		if (!(cmp = b->t.tv_sec - a->t.tv_sec))
 77 			cmp = b->t.tv_nsec - a->t.tv_nsec;
 78@@ -439,7 +455,7 @@ lsdir(const char *path, const struct entry *dir)
 79 
 80 		ents = ereallocarray(ents, ++n, sizeof(*ents));
 81 		mkent(&ents[n - 1], estrdup(d->d_name), Fflag || iflag ||
 82-		    lflag || pflag || Rflag || sort, Lflag);
 83+		    lflag || pflag || Rflag || sort || should_color(), Lflag);
 84 	}
 85 
 86 	closedir(dp);
 87@@ -537,7 +553,7 @@ ls(const char *path, const struct entry *ent, int listdir)
 88 static void
 89 usage(void)
 90 {
 91-	eprintf("usage: %s [-1ACacdFfHhiLlnpqRrtUu] [file ...]\n", argv0);
 92+	eprintf("usage: %s [-1ACacdFfGghiLlnopqRrtUu] [--color[=always|never|auto]] [file ...]\n", argv0);
 93 }
 94 
 95 int
 96@@ -545,96 +561,156 @@ main(int argc, char *argv[])
 97 {
 98 	struct entry ent, *dents, *fents;
 99 	size_t i, ds, fs;
100+#if FEATURE_LS_COLOR
101+	char *val;
102+#endif
103 
104 	if (isatty(STDOUT_FILENO))
105 		Cflag = 1;
106 	else
107 		one_flag = 1;
108 
109+#if FEATURE_LS_COLOR
110+	if ((val = getenv("CLICOLOR_FORCE"))) {
111+		if (*val && strcmp(val, "0") != 0)
112+			color_mode = COLOR_ALWAYS;
113+		else
114+			color_mode = COLOR_NEVER;
115+	} else if ((val = getenv("CLICOLOR"))) {
116+		if (*val && strcmp(val, "0") != 0)
117+			color_mode = COLOR_AUTO;
118+		else
119+			color_mode = COLOR_NEVER;
120+	}
121+#endif
122+
123 	tree = ereallocarray(NULL, PATH_MAX, sizeof(*tree));
124 
125 	ARGBEGIN {
126+	// ?man -1: list one file per line
127 	case '1':
128 		one_flag = 1;
129 		Cflag = 0;
130 		lflag = 0;
131 		break;
132+	// ?man -A: list all entries except dot and dot dot
133 	case 'A':
134 		Aflag = 1;
135 		break;
136+	// ?man -a: list all entries including those starting with a dot
137 	case 'a':
138 		aflag = 1;
139 		break;
140+	// ?man -c: sort by ctime or use ctime for long listing
141 	case 'c':
142 		cflag = 1;
143 		uflag = 0;
144 		break;
145+	// ?man -C: list entries in columns sorted vertically
146 	case 'C':
147 		Cflag = 1;
148 		one_flag = 0;
149 		lflag = 0;
150 		break;
151+	// ?man -d: list directory entries instead of their contents
152 	case 'd':
153 		dflag = 1;
154 		break;
155+	// ?man -f: do not sort and enable a and U
156 	case 'f':
157 		aflag = 1;
158 		fflag = 1;
159 		Uflag = 1;
160 		break;
161+	// ?man -F: append type indicators
162 	case 'F':
163 		Fflag = 1;
164 		break;
165+#if FEATURE_LS_COLOR
166+	// ?man -G: enable colored output
167+	case 'G':
168+		color_mode = COLOR_AUTO;
169+		break;
170+#endif
171+	// ?man -g: list in long format without owner name
172+	case 'g':
173+		gflag = 1;
174+		lflag = 1;
175+		Cflag = 0;
176+		one_flag = 0;
177+		break;
178+	// ?man -H: follow symlinks on the command line
179 	case 'H':
180 		Hflag = 1;
181 		break;
182+	// ?man -h: print human readable sizes
183 	case 'h':
184 		hflag = 1;
185 		break;
186+	// ?man -i: print inode number of each file
187 	case 'i':
188 		iflag = 1;
189 		break;
190+	// ?man -L: follow all symlinks
191 	case 'L':
192 		Lflag = 1;
193 		break;
194+	// ?man -l: use a long listing format
195 	case 'l':
196 		lflag = 1;
197 		Cflag = 0;
198 		one_flag = 0;
199 		break;
200+	// ?man -n: list numeric uids and gids
201 	case 'n':
202 		lflag = 1;
203 		nflag = 1;
204 		Cflag = 0;
205 		one_flag = 0;
206 		break;
207+	// ?man -o: list in long format without group name
208+	case 'o':
209+		oflag = 1;
210+		lflag = 1;
211+		Cflag = 0;
212+		one_flag = 0;
213+		break;
214+	// ?man -p: append slash indicator to directories
215 	case 'p':
216 		pflag = 1;
217 		break;
218+	// ?man -q: print non printable characters as question marks
219 	case 'q':
220 		qflag = 1;
221 		break;
222+	// ?man -R: list subdirectories recursively
223 	case 'R':
224 		Rflag = 1;
225 		break;
226+	// ?man -r: reverse sort order
227 	case 'r':
228 		rflag = 1;
229 		break;
230+	// ?man -S: sort by file size
231 	case 'S':
232 		sort = 'S';
233 		break;
234+	// ?man -t: sort by modification time
235 	case 't':
236 		sort = 't';
237 		break;
238+	// ?man -U: do not sort
239 	case 'U':
240 		Uflag = 1;
241 		break;
242+	// ?man -u: sort by atime or use atime for long listing
243 	case 'u':
244 		uflag = 1;
245 		cflag = 0;
246 		break;
247 	case '-':
248 #if FEATURE_LS_COLOR
249+		// ?man --color [when]: control coloring
250 		if (strncmp(argv[0], "-color", 6) == 0) {
251 			char *val = NULL;
252 			if (argv[0][6] == '=') {
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+mesg: control write access
 5+usage: mesg [n|y]
 6+
 7+allow or disallow other users to write to the terminal
 8+*/
 9+
10 #include <sys/stat.h>
11 #include <sys/types.h>
12 
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+mkdir: create directories
 5+usage: mkdir [-p] [-m mode] name ...
 6+
 7+create directories at specified paths
 8+*/
 9+
10 #include <sys/stat.h>
11 
12 #include <errno.h>
13@@ -22,9 +29,11 @@ main(int argc, char *argv[])
14 	mode = 0777 & ~mask;
15 
16 	ARGBEGIN {
17+	// ?man -p: create parent directories as needed
18 	case 'p':
19 		pflag = 1;
20 		break;
21+	// ?man -m: set file mode bits for created directories
22 	case 'm':
23 		mode = parsemode(EARGF(usage()), 0777, mask);
24 		break;
+8, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+mkfifo: make fifos
 5+usage: mkfifo [-m mode] name ...
 6+
 7+create named pipes at specified paths
 8+*/
 9+
10 #include <sys/stat.h>
11 
12 #include <stdlib.h>
13@@ -18,6 +25,7 @@ main(int argc, char *argv[])
14 	int ret = 0;
15 
16 	ARGBEGIN {
17+	// ?man -m: specify mode or limit
18 	case 'm':
19 		mode = parsemode(EARGF(usage()), mode, umask(0));
20 		break;
+8, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+mv: move or rename files
 5+usage: mv [-f] source ... dest
 6+
 7+move or rename files and directories
 8+*/
 9+
10 #include <sys/stat.h>
11 
12 #include <errno.h>
13@@ -47,6 +54,7 @@ main(int argc, char *argv[])
14 	struct stat st;
15 
16 	ARGBEGIN {
17+	// ?man -f: do not prompt before overwriting
18 	case 'f':
19 		break;
20 	default:
+8, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+nice: run command with modified priority
 5+usage: nice [-n inc] cmd [arg ...]
 6+
 7+run a command with modified scheduling priority
 8+*/
 9+
10 #include <sys/resource.h>
11 
12 #include <errno.h>
13@@ -27,6 +34,7 @@ main(int argc, char *argv[])
14 	int val = 10, r, savederrno;
15 
16 	ARGBEGIN {
17+	// ?man -n: print line numbers or counts
18 	case 'n':
19 		val = estrtonum(EARGF(usage()), PRIO_MIN, PRIO_MAX);
20 		break;
+20, -1
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+nl: number lines
 5+usage: nl [-p] [-b type] [-d delim] [-f type]
 6+
 7+number the lines of files
 8+*/
 9+
10 #include <limits.h>
11 #include <stdint.h>
12 #include <stdio.h>
13@@ -71,7 +78,8 @@ nl(const char *fname, FILE *fp)
14 			if (line.data[0] != '\n')
15 				donumber = 1;
16 			break;
17-		case 'p':
18+		// ?man -p: preserve file attributes
19+	case 'p':
20 			if (!regexec(preg + section, line.data, 0, NULL, 0))
21 				donumber = 1;
22 			break;
23@@ -124,6 +132,7 @@ main(int argc, char *argv[])
24 	char *d, *formattype, *formatblit;
25 
26 	ARGBEGIN {
27+	// ?man -d: specify directory
28 	case 'd':
29 		switch (utflen((d = EARGF(usage())))) {
30 		case 0:
31@@ -142,21 +151,27 @@ main(int argc, char *argv[])
32 			break;
33 		}
34 		break;
35+	// ?man -f: force the operation
36 	case 'f':
37 		type[0] = getlinetype(EARGF(usage()), preg);
38 		break;
39+	// ?man -b: specify block size or base directory
40 	case 'b':
41 		type[1] = getlinetype(EARGF(usage()), preg + 1);
42 		break;
43+	// ?man -h: suppress headers or print help
44 	case 'h':
45 		type[2] = getlinetype(EARGF(usage()), preg + 2);
46 		break;
47+	// ?man -i: interactive mode or prompt for confirmation
48 	case 'i':
49 		incr = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
50 		break;
51+	// ?man -l: list in long format
52 	case 'l':
53 		blines = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
54 		break;
55+	// ?man -n: print line numbers or counts
56 	case 'n':
57 		formattype = EARGF(usage());
58 		estrlcpy(format, "%", sizeof(format));
59@@ -174,16 +189,20 @@ main(int argc, char *argv[])
60 		estrlcat(format, formatblit, sizeof(format));
61 		estrlcat(format, "*ld", sizeof(format));
62 		break;
63+	// ?man -p: preserve file attributes
64 	case 'p':
65 		pflag = 1;
66 		break;
67+	// ?man -s: silent mode or print summary
68 	case 's':
69 		sep = EARGF(usage());
70 		seplen = unescape(sep);
71 		break;
72+	// ?man -v: verbose mode; show progress
73 	case 'v':
74 		startnum = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
75 		break;
76+	// ?man -w: wait for completion
77 	case 'w':
78 		width = estrtonum(EARGF(usage()), 1, INT_MAX);
79 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+nohup: run command immune to hangups
 5+usage: nohup cmd [arg ...]
 6+
 7+run a command that persists after logging out
 8+*/
 9+
10 #include <sys/stat.h>
11 
12 #include <errno.h>
+41, -10
  1@@ -1,4 +1,11 @@
  2 /* See LICENSE file for copyright and license details. */
  3+/* ?man
  4+od: dump files in formats
  5+usage: od [-bdosvx] [-A addressformat]
  6+
  7+display file contents in octal, hex, or other formats
  8+*/
  9+
 10 #include "queue.h"
 11 #include "util.h"
 12 
 13@@ -59,6 +66,7 @@ printchunk(const unsigned char *s, unsigned char format, size_t len)
 14 	};
 15 
 16 	switch (format) {
 17+	// ?man -a: print or show all entries
 18 	case 'a':
 19 		c = *s & ~128; /* clear high bit as required by standard */
 20 		if (c < LEN(namedict) || c == 127) {
 21@@ -67,6 +75,7 @@ printchunk(const unsigned char *s, unsigned char format, size_t len)
 22 			printf(" %3c", c);
 23 		}
 24 		break;
 25+	// ?man -c: print count or perform stdout action
 26 	case 'c':
 27 		if (strchr("\a\b\t\n\v\f\r\0", *s)) {
 28 			printf(" %3s", escdict[*s]);
 29@@ -220,69 +229,89 @@ main(int argc, char *argv[])
 30 	big_endian = (*(uint16_t *)"\0\xff" == 0xff);
 31 
 32 	ARGBEGIN {
 33+	// ?man -A: specify option flag
 34 	case 'A':
 35 		s = EARGF(usage());
 36 		if (strlen(s) != 1 || !strchr("doxn", s[0]))
 37 			usage();
 38 		addr_format = s[0];
 39 		break;
 40+	// ?man -b: specify block size or base directory
 41 	case 'b':
 42 		addtype('o', 1);
 43 		break;
 44+	// ?man -d: specify directory
 45 	case 'd':
 46 		addtype('u', 2);
 47 		break;
 48 #if FEATURE_OD_ENDIAN
 49+	// ?man -E: specify option flag
 50 	case 'E':
 51+	// ?man -e: specify expression or pattern
 52 	case 'e':
 53 		big_endian = (ARGC() == 'E');
 54 		break;
 55 #endif
 56+	// ?man -j: specify option flag
 57 	case 'j':
 58 		if ((skip = parseoffset(EARGF(usage()))) < 0)
 59 			usage();
 60 		break;
 61+	// ?man -N: specify option flag
 62 	case 'N':
 63 		if ((max = parseoffset(EARGF(usage()))) < 0)
 64 			usage();
 65 		break;
 66+	// ?man -o: specify output file
 67 	case 'o':
 68 		addtype('o', 2);
 69 		break;
 70+	// ?man -s: silent mode or print summary
 71 	case 's':
 72 		addtype('d', 2);
 73 		break;
 74+	// ?man -t: sort or specify timestamp
 75 	case 't':
 76 		s = EARGF(usage());
 77 		for (; *s; s++) {
 78 			switch (*s) {
 79-			case 'a':
 80-			case 'c':
 81+			// ?man -a: print or show all entries
 82+	case 'a':
 83+			// ?man -c: print count or perform stdout action
 84+	case 'c':
 85 				addtype(*s, 1);
 86 				break;
 87-			case 'd':
 88-			case 'o':
 89-			case 'u':
 90-			case 'x':
 91+			// ?man -d: specify directory
 92+	case 'd':
 93+			// ?man -o: specify output file
 94+	case 'o':
 95+			// ?man -u: unbuffered output
 96+	case 'u':
 97+			// ?man -x: hex format or match whole lines
 98+	case 'x':
 99 				fmt_char = *s;
100 				if (isdigit((unsigned char)*(s + 1))) {
101 					len = strtol(s + 1, &end, 10);
102 					s = end - 1;
103 				} else {
104 					switch (*(s + 1)) {
105-					case 'C':
106+					// ?man -C: specify option flag
107+	case 'C':
108 						len = sizeof(char);
109 						s++;
110 						break;
111-					case 'S':
112+					// ?man -S: specify option flag
113+	case 'S':
114 						len = sizeof(short);
115 						s++;
116 						break;
117-					case 'I':
118+					// ?man -I: specify option flag
119+	case 'I':
120 						len = sizeof(int);
121 						s++;
122 						break;
123-					case 'L':
124+					// ?man -L: specify option flag
125+	case 'L':
126 						len = sizeof(long);
127 						s++;
128 						break;
129@@ -297,9 +326,11 @@ main(int argc, char *argv[])
130 			}
131 		}
132 		break;
133+	// ?man -v: verbose mode; show progress
134 	case 'v':
135 		/* always set, use uniq(1) to handle duplicate lines */
136 		break;
137+	// ?man -x: hex format or match whole lines
138 	case 'x':
139 		addtype('x', 2);
140 		break;
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+paste: merge lines of files
 5+usage: paste [-s] [-d list] file ...
 6+
 7+merge corresponding lines of files side by side
 8+*/
 9+
10 #include <stdlib.h>
11 #include <string.h>
12 
13@@ -94,9 +101,11 @@ main(int argc, char *argv[])
14 	char *delim = "\t";
15 
16 	ARGBEGIN {
17+	// ?man -s: silent mode or print summary
18 	case 's':
19 		seq = 1;
20 		break;
21+	// ?man -d: specify directory
22 	case 'd':
23 		delim = EARGF(usage());
24 		delim_bytelen = unescape(delim);
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+pathchk: check pathnames
 5+usage: pathchk [-pP] filename...
 6+
 7+verify that pathnames are valid and portable
 8+*/
 9+
10 #include <sys/stat.h>
11 
12 #include <errno.h>
13@@ -84,9 +91,11 @@ main(int argc, char *argv[])
14 	int ret = 0;
15 
16 	ARGBEGIN {
17+	// ?man -p: preserve file attributes
18 	case 'p':
19 		most = 1;
20 		break;
21+	// ?man -P: specify option flag
22 	case 'P':
23 		extra = 1;
24 		break;
+49, -9
  1@@ -1,3 +1,10 @@
  2+/* ?man
  3+pax: portable archive interchange
  4+usage: pax 
  5+
  6+read, write, and list member files of archive files
  7+*/
  8+
  9 /* taken from: https://github.com/michaelforney/pax */
 10 #ifndef _GNU_SOURCE
 11 #define _GNU_SOURCE  /* needed for major/minor (non-posix) */
 12@@ -844,10 +851,12 @@ readpax(struct bufio *f, struct header *h)
 13 		case 'g':
 14 			readexthdr(f, &globexthdr, h->size);
 15 			break;
 16-		case 'x':
 17+		// ?man -x: hex format or match whole lines
 18+	case 'x':
 19 			readexthdr(f, &exthdr, h->size);
 20 			break;
 21-		case 'L':
 22+		// ?man -L: specify option flag
 23+	case 'L':
 24 			if ((exthdr.delete | opt.delete) & PATH)
 25 				break;
 26 			readgnuhdr(f, &exthdr.pathbuf, h->size);
 27@@ -2080,8 +2089,10 @@ parsereplstr(char *str)
 28 	for (;;) {
 29 		switch (*++str) {
 30 		case 'g': r->global = 1; break;
 31-		case 'p': r->print = 1; break;
 32-		case 's': r->symlink = 0; break;
 33+		// ?man -p: preserve file attributes
 34+	case 'p': r->print = 1; break;
 35+		// ?man -s: silent mode or print summary
 36+	case 's': r->symlink = 0; break;
 37 		case 'S': r->symlink = 1; break;
 38 		case 0: goto done;
 39 		}
 40@@ -2341,84 +2352,113 @@ main(int argc, char *argv[])
 41 	size_t l;
 42 
 43 	ARGBEGIN {
 44+	// ?man -a: print or show all entries
 45 	case 'a':
 46 		aflag = 1;
 47 		break;
 48+	// ?man -b: specify block size or base directory
 49 	case 'b':
 50 		EARGF(usage());
 51 		break;
 52+	// ?man -c: print count or perform stdout action
 53 	case 'c':
 54 		cflag = 1;
 55 		break;
 56+	// ?man -d: specify directory
 57 	case 'd':
 58 		dflag = 1;
 59 		break;
 60+	// ?man -f: force the operation
 61 	case 'f':
 62 		name = EARGF(usage());
 63 		break;
 64+	// ?man -H: specify option flag
 65 	case 'H':
 66 		follow = 'H';
 67 		break;
 68+	// ?man -i: interactive mode or prompt for confirmation
 69 	case 'i':
 70 		iflag = 1;
 71 		break;
 72+	// ?man -j: specify option flag
 73 	case 'j':
 74 		algo = "bzip2";
 75 		break;
 76+	// ?man -J: specify option flag
 77 	case 'J':
 78 		algo = "xz";
 79 		break;
 80+	// ?man -k: specify option flag
 81 	case 'k':
 82 		kflag = 1;
 83 		break;
 84+	// ?man -l: list in long format
 85 	case 'l':
 86 		lflag = 1;
 87 		break;
 88+	// ?man -L: specify option flag
 89 	case 'L':
 90 		follow = 'L';
 91 		break;
 92+	// ?man -n: print line numbers or counts
 93 	case 'n':
 94 		nflag = 1;
 95 		break;
 96+	// ?man -o: specify output file
 97 	case 'o':
 98 		parseopts(EARGF(usage()));
 99 		break;
100+	// ?man -p: preserve file attributes
101 	case 'p':
102 		for (arg = EARGF(usage()); *arg; ++arg) {
103 			switch (*arg) {
104-			case 'a': preserve &= ~ATIME; break;
105-			case 'e': preserve = ~0; break;
106-			case 'm': preserve &= ~MTIME; break;
107-			case 'o': preserve |= UID | GID; break;
108-			case 'p': preserve |= MODE; break;
109+			// ?man -a: print or show all entries
110+	case 'a': preserve &= ~ATIME; break;
111+			// ?man -e: specify expression or pattern
112+	case 'e': preserve = ~0; break;
113+			// ?man -m: specify mode or limit
114+	case 'm': preserve &= ~MTIME; break;
115+			// ?man -o: specify output file
116+	case 'o': preserve |= UID | GID; break;
117+			// ?man -p: preserve file attributes
118+	case 'p': preserve |= MODE; break;
119 			default: fatal("unknown -p option");
120 			}
121 		}
122 		break;
123+	// ?man -r: operate recursively
124 	case 'r':
125 		mode |= READ;
126 		break;
127+	// ?man -s: silent mode or print summary
128 	case 's':
129 		parsereplstr(EARGF(usage()));
130 		break;
131+	// ?man -t: sort or specify timestamp
132 	case 't':
133 		tflag = 1;
134 		break;
135+	// ?man -u: unbuffered output
136 	case 'u':
137 		uflag = 1;
138 		break;
139+	// ?man -v: verbose mode; show progress
140 	case 'v':
141 		vflag = 1;
142 		break;
143+	// ?man -w: wait for completion
144 	case 'w':
145 		mode |= WRITE;
146 		break;
147+	// ?man -x: hex format or match whole lines
148 	case 'x':
149 		format = EARGF(usage());
150 		break;
151+	// ?man -X: specify option flag
152 	case 'X':
153 		Xflag = 1;
154 		break;
155+	// ?man -z: specify option flag
156 	case 'z':
157 		algo = "gzip";
158 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+printf: format and print data
 5+usage: printf format [arg ...]
 6+
 7+format and print arguments to standard output
 8+*/
 9+
10 #include <ctype.h>
11 #include <errno.h>
12 #include <limits.h>
+12, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+ps: report process status
 5+usage: ps [-aAdef]
 6+
 7+display information about active system processes
 8+*/
 9+
10 #include <sys/ioctl.h>
11 #include <sys/sysinfo.h>
12 
13@@ -152,18 +159,23 @@ int
14 main(int argc, char *argv[])
15 {
16 	ARGBEGIN {
17+	// ?man -a: print or show all entries
18 	case 'a':
19 		flags |= PS_aflag;
20 		break;
21+	// ?man -A: specify option flag
22 	case 'A':
23 		flags |= PS_Aflag;
24 		break;
25+	// ?man -d: specify directory
26 	case 'd':
27 		flags |= PS_dflag;
28 		break;
29+	// ?man -e: specify expression or pattern
30 	case 'e':
31 		flags |= PS_Aflag;
32 		break;
33+	// ?man -f: force the operation
34 	case 'f':
35 		flags |= PS_fflag;
36 		break;
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+pwd: print working directory
 5+usage: pwd [-LP]
 6+
 7+display the pathname of the current working directory
 8+*/
 9+
10 #include <sys/stat.h>
11 
12 #include <stdio.h>
13@@ -34,7 +41,9 @@ main(int argc, char *argv[])
14 	char mode = 'L';
15 
16 	ARGBEGIN {
17+	// ?man -L: specify option flag
18 	case 'L':
19+	// ?man -P: specify option flag
20 	case 'P':
21 		mode = ARGC();
22 		break;
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+readlink: print value of symlink
 5+usage: readlink [-fn] path
 6+
 7+display the target of a symbolic link
 8+*/
 9+
10 #include <limits.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13@@ -30,10 +37,12 @@ main(int argc, char *argv[])
14 	ARGBEGIN
15 	{
16 #ifdef STD_NON_POSIX
17+	// ?man -f: force the operation
18 	case 'f':
19 		fflag = 1;
20 		break;
21 #endif
22+	// ?man -n: print line numbers or counts
23 	case 'n':
24 		nflag = 1;
25 		break;
+11, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+renice: alter priority of processes
 5+usage: renice -n num [-g | -p | -u] id ...
 6+
 7+change the scheduling priority of running processes
 8+*/
 9+
10 #include <sys/resource.h>
11 
12 #include <errno.h>
13@@ -50,15 +57,19 @@ main(int argc, char *argv[])
14 	int who;
15 
16 	ARGBEGIN {
17+	// ?man -n: print line numbers or counts
18 	case 'n':
19 		adj = EARGF(usage());
20 		break;
21+	// ?man -g: specify option flag
22 	case 'g':
23 		which = PRIO_PGRP;
24 		break;
25+	// ?man -p: preserve file attributes
26 	case 'p':
27 		which = PRIO_PROCESS;
28 		break;
29+	// ?man -u: unbuffered output
30 	case 'u':
31 		which = PRIO_USER;
32 		break;
+11, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+rm: remove files
 5+usage: rm [-f] [-iRr] file ...
 6+
 7+remove files and directory hierarchies
 8+*/
 9+
10 #include <fcntl.h>
11 #include  <string.h>
12 
13@@ -51,13 +58,17 @@ main(int argc, char *argv[])
14 	struct recursor r = { .fn = rm, .maxdepth = 1, .follow = 'P' };
15 
16 	ARGBEGIN {
17+	// ?man -f: ignore nonexistent files and never prompt
18 	case 'f':
19 		r.flags |= SILENT | IGNORE;
20 		break;
21+	// ?man -i: prompt before every removal
22 	case 'i':
23 		r.flags |= CONFIRM;
24 		break;
25+	// ?man -R: remove directories and their contents recursively
26 	case 'R':
27+	// ?man -r: remove directories and their contents recursively
28 	case 'r':
29 		r.maxdepth = 0;
30 		break;
+8, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+rmdir: remove empty directories
 5+usage: rmdir [-p] dir ...
 6+
 7+remove empty directories from the filesystem
 8+*/
 9+
10 #include <libgen.h>
11 #include <string.h>
12 #include <unistd.h>
13@@ -18,6 +25,7 @@ main(int argc, char *argv[])
14 	char *d;
15 
16 	ARGBEGIN {
17+	// ?man -p: remove parent directories if they are also empty
18 	case 'p':
19 		pflag = 1;
20 		break;
+13, -0
 1@@ -1,3 +1,10 @@
 2+/* ?man
 3+sed: stream editor
 4+usage: sed [-nrE] script [file ...]
 5+
 6+stream editor for filtering and transforming text
 7+*/
 8+
 9 /* FIXME: summary
10  * decide whether we enforce valid UTF-8, right now it's enforced in certain
11  *     parts of the script, but not the input...
12@@ -1754,24 +1761,30 @@ main(int argc, char *argv[])
13 	int script = 0;
14 
15 	ARGBEGIN {
16+	// ?man -n: print line numbers or counts
17 	case 'n':
18 		gflags.n = 1;
19 		break;
20+	// ?man -r: operate recursively
21 	case 'r':
22+	// ?man -E: specify option flag
23 	case 'E':
24 		gflags.E = 1;
25 		break;
26+	// ?man -e: specify expression or pattern
27 	case 'e':
28 		arg = EARGF(usage());
29 		compile(arg, 0);
30 		script = 1;
31 		break;
32+	// ?man -f: force the operation
33 	case 'f':
34 		arg = EARGF(usage());
35 		compile(arg, 1);
36 		script = 1;
37 		break;
38 #if FEATURE_SED_INPLACE
39+	// ?man -i: interactive mode or prompt for confirmation
40 	case 'i':
41 		iflag = 1;
42 		if (argv[0][1] != '\0') {
+1, -1
1@@ -63,7 +63,7 @@
2 #include "show.h"
3 #include "mystring.h"
4 #ifndef NO_HISTORY
5-#include "myhistedit.h"
6+#include "lineedit.h"
7 #endif
8 
9 
+0, -802
  1@@ -1,802 +0,0 @@
  2-/*-
  3- * SPDX-License-Identifier: BSD-3-Clause
  4- *
  5- * Copyright (c) 1993
  6- *	The Regents of the University of California.  All rights reserved.
  7- *
  8- * This code is derived from software contributed to Berkeley by
  9- * Kenneth Almquist.
 10- *
 11- * Redistribution and use in source and binary forms, with or without
 12- * modification, are permitted provided that the following conditions
 13- * are met:
 14- * 1. Redistributions of source code must retain the above copyright
 15- *    notice, this list of conditions and the following disclaimer.
 16- * 2. Redistributions in binary form must reproduce the above copyright
 17- *    notice, this list of conditions and the following disclaimer in the
 18- *    documentation and/or other materials provided with the distribution.
 19- * 3. Neither the name of the University nor the names of its contributors
 20- *    may be used to endorse or promote products derived from this software
 21- *    without specific prior written permission.
 22- *
 23- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 24- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 25- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 26- * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 27- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 28- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 29- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 30- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 31- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 32- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 33- * SUCH DAMAGE.
 34- */
 35-
 36-#if !FEATURE_SH_HISTEDIT
 37-#ifndef NO_HISTORY
 38-#define NO_HISTORY
 39-#endif
 40-#endif
 41-
 42-#include "shell.h"
 43-#include "alias.h"
 44-#include "builtins.h"
 45-#include "error.h"
 46-#include "eval.h"
 47-#include "exec.h"
 48-#include "main.h"
 49-#include "memalloc.h"
 50-#include "mystring.h"
 51-#include "options.h"
 52-#include "output.h"
 53-#include "parser.h"
 54-#include "var.h"
 55-
 56-#ifndef NO_HISTORY
 57-#include "myhistedit.h"
 58-
 59-#include <sys/param.h>
 60-#include <sys/stat.h>
 61-
 62-#include <dirent.h>
 63-#include <errno.h>
 64-#include <fcntl.h>
 65-#include <limits.h>
 66-#include <paths.h>
 67-#include <stdio.h>
 68-#include <stdlib.h>
 69-#include <unistd.h>
 70-
 71-#define MAXHISTLOOPS	4	/* max recursions through fc */
 72-#define DEFEDITOR	"ed"	/* default editor *should* be $EDITOR */
 73-
 74-History *hist;	/* history cookie */
 75-EditLine *el;	/* editline cookie */
 76-int displayhist;
 77-static int savehist;
 78-static FILE *el_in, *el_out;
 79-static int in_command_completion;
 80-
 81-static char *fc_replace(const char *, char *, char *);
 82-static int not_fcnumber(const char *);
 83-static int str_to_event(const char *, int);
 84-static int comparator(const void *, const void *, void *);
 85-static char **sh_matches(const char *, int, int);
 86-static const char *append_char_function(const char *);
 87-static unsigned char sh_complete(EditLine *, int);
 88-
 89-static const char *
 90-get_histfile(void)
 91-{
 92-	const char *histfile;
 93-
 94-	/* don't try to save if the history size is 0 */
 95-	if (hist == NULL || !strcmp(histsizeval(), "0"))
 96-		return (NULL);
 97-	histfile = expandstr("${HISTFILE-${HOME-}/.sh_history}");
 98-
 99-	if (histfile[0] == '\0')
100-		return (NULL);
101-	return (histfile);
102-}
103-
104-void
105-histsave(void)
106-{
107-	HistEvent he;
108-	char *histtmpname = NULL;
109-	const char *histfile;
110-	int fd;
111-	FILE *f;
112-
113-	if (!savehist || (histfile = get_histfile()) == NULL)
114-		return;
115-	INTOFF;
116-	asprintf(&histtmpname, "%s.XXXXXXXXXX", histfile);
117-	if (histtmpname == NULL) {
118-		INTON;
119-		return;
120-	}
121-	fd = mkstemp(histtmpname);
122-	if (fd == -1 || (f = fdopen(fd, "w")) == NULL) {
123-		free(histtmpname);
124-		INTON;
125-		return;
126-	}
127-	if (history(hist, &he, H_SAVE_FP, f) < 1 ||
128-	    rename(histtmpname, histfile) == -1)
129-		unlink(histtmpname);
130-	fclose(f);
131-	free(histtmpname);
132-	INTON;
133-
134-}
135-
136-void
137-histload(void)
138-{
139-	const char *histfile;
140-	HistEvent he;
141-
142-	if ((histfile = get_histfile()) == NULL)
143-		return;
144-	errno = 0;
145-	if (history(hist, &he, H_LOAD, histfile) != -1 || errno == ENOENT)
146-		savehist = 1;
147-}
148-
149-/*
150- * Set history and editing status.  Called whenever the status may
151- * have changed (figures out what to do).
152- */
153-void
154-histedit(void)
155-{
156-
157-#define editing (Eflag || Vflag)
158-
159-	if (iflag) {
160-		if (!hist) {
161-			/*
162-			 * turn history on
163-			 */
164-			INTOFF;
165-			hist = history_init();
166-			INTON;
167-
168-			if (hist != NULL)
169-				sethistsize(histsizeval());
170-			else
171-				out2fmt_flush("sh: can't initialize history\n");
172-		}
173-		if (editing && !el && isatty(0)) { /* && isatty(2) ??? */
174-			/*
175-			 * turn editing on
176-			 */
177-			char *term;
178-
179-			INTOFF;
180-			if (el_in == NULL)
181-				el_in = fdopen(0, "r");
182-			if (el_out == NULL)
183-				el_out = fdopen(2, "w");
184-			if (el_in == NULL || el_out == NULL)
185-				goto bad;
186-			term = lookupvar("TERM");
187-			if (term)
188-				setenv("TERM", term, 1);
189-			else
190-				unsetenv("TERM");
191-			el = el_init(arg0, el_in, el_out, el_out);
192-			if (el != NULL) {
193-				if (hist)
194-					el_set(el, EL_HIST, history, hist);
195-				el_set(el, EL_PROMPT_ESC, getprompt, '\001');
196-				el_set(el, EL_ADDFN, "sh-complete",
197-				    "Filename completion",
198-				    sh_complete);
199-			} else {
200-bad:
201-				out2fmt_flush("sh: can't initialize editing\n");
202-			}
203-			INTON;
204-		} else if (!editing && el) {
205-			INTOFF;
206-			el_end(el);
207-			el = NULL;
208-			INTON;
209-		}
210-		if (el) {
211-			if (Vflag)
212-				el_set(el, EL_EDITOR, "vi");
213-			else if (Eflag) {
214-				el_set(el, EL_EDITOR, "emacs");
215-			}
216-			el_set(el, EL_BIND, "^I", "sh-complete", NULL);
217-			el_source(el, NULL);
218-		}
219-	} else {
220-		INTOFF;
221-		if (el) {	/* no editing if not interactive */
222-			el_end(el);
223-			el = NULL;
224-		}
225-		if (hist) {
226-			history_end(hist);
227-			hist = NULL;
228-		}
229-		INTON;
230-	}
231-}
232-
233-
234-void
235-sethistsize(const char *hs)
236-{
237-	int histsize;
238-	HistEvent he;
239-
240-	if (hist != NULL) {
241-		if (hs == NULL || !is_number(hs))
242-			histsize = 128;
243-		else
244-			histsize = atoi(hs);
245-		history(hist, &he, H_SETSIZE, histsize);
246-		history(hist, &he, H_SETUNIQUE, 1);
247-	}
248-}
249-
250-void
251-setterm(const char *term)
252-{
253-	if (rootshell && el != NULL && term != NULL)
254-		el_set(el, EL_TERMINAL, term);
255-}
256-
257-int
258-histcmd(int argc, char **argv __unused)
259-{
260-	const char *editor = NULL;
261-	HistEvent he;
262-	int lflg = 0, nflg = 0, rflg = 0, sflg = 0;
263-	int i, retval;
264-	const char *firststr, *laststr;
265-	int first, last, direction;
266-	char *pat = NULL, *repl = NULL;
267-	static int active = 0;
268-	struct jmploc jmploc;
269-	struct jmploc *savehandler;
270-	char editfilestr[PATH_MAX];
271-	char *volatile editfile;
272-	FILE *efp = NULL;
273-	int oldhistnum;
274-
275-	if (hist == NULL)
276-		error("history not active");
277-
278-	if (argc == 1)
279-		error("missing history argument");
280-
281-	while (not_fcnumber(*argptr))
282-		do {
283-			switch (nextopt("e:lnrs")) {
284-			case 'e':
285-				editor = shoptarg;
286-				break;
287-			case 'l':
288-				lflg = 1;
289-				break;
290-			case 'n':
291-				nflg = 1;
292-				break;
293-			case 'r':
294-				rflg = 1;
295-				break;
296-			case 's':
297-				sflg = 1;
298-				break;
299-			case '\0':
300-				goto operands;
301-			}
302-		} while (nextopt_optptr != NULL);
303-operands:
304-	savehandler = handler;
305-	/*
306-	 * If executing...
307-	 */
308-	if (lflg == 0 || editor || sflg) {
309-		lflg = 0;	/* ignore */
310-		editfile = NULL;
311-		/*
312-		 * Catch interrupts to reset active counter and
313-		 * cleanup temp files.
314-		 */
315-		if (setjmp(jmploc.loc)) {
316-			active = 0;
317-			if (editfile)
318-				unlink(editfile);
319-			handler = savehandler;
320-			longjmp(handler->loc, 1);
321-		}
322-		handler = &jmploc;
323-		if (++active > MAXHISTLOOPS) {
324-			active = 0;
325-			displayhist = 0;
326-			error("called recursively too many times");
327-		}
328-		/*
329-		 * Set editor.
330-		 */
331-		if (sflg == 0) {
332-			if (editor == NULL &&
333-			    (editor = bltinlookup("FCEDIT", 1)) == NULL &&
334-			    (editor = bltinlookup("EDITOR", 1)) == NULL)
335-				editor = DEFEDITOR;
336-			if (editor[0] == '-' && editor[1] == '\0') {
337-				sflg = 1;	/* no edit */
338-				editor = NULL;
339-			}
340-		}
341-	}
342-
343-	/*
344-	 * If executing, parse [old=new] now
345-	 */
346-	if (lflg == 0 && *argptr != NULL &&
347-	     ((repl = strchr(*argptr, '=')) != NULL)) {
348-		pat = *argptr;
349-		*repl++ = '\0';
350-		argptr++;
351-	}
352-	/*
353-	 * determine [first] and [last]
354-	 */
355-	if (*argptr == NULL) {
356-		firststr = lflg ? "-16" : "-1";
357-		laststr = "-1";
358-	} else if (argptr[1] == NULL) {
359-		firststr = argptr[0];
360-		laststr = lflg ? "-1" : argptr[0];
361-	} else if (argptr[2] == NULL) {
362-		firststr = argptr[0];
363-		laststr = argptr[1];
364-	} else
365-		error("too many arguments");
366-	/*
367-	 * Turn into event numbers.
368-	 */
369-	first = str_to_event(firststr, 0);
370-	last = str_to_event(laststr, 1);
371-
372-	if (rflg) {
373-		i = last;
374-		last = first;
375-		first = i;
376-	}
377-	/*
378-	 * XXX - this should not depend on the event numbers
379-	 * always increasing.  Add sequence numbers or offset
380-	 * to the history element in next (diskbased) release.
381-	 */
382-	direction = first < last ? H_PREV : H_NEXT;
383-
384-	/*
385-	 * If editing, grab a temp file.
386-	 */
387-	if (editor) {
388-		int fd;
389-		INTOFF;		/* easier */
390-		sprintf(editfilestr, "%s/_shXXXXXX", _PATH_TMP);
391-		if ((fd = mkstemp(editfilestr)) < 0)
392-			error("can't create temporary file %s", editfile);
393-		editfile = editfilestr;
394-		if ((efp = fdopen(fd, "w")) == NULL) {
395-			close(fd);
396-			error("Out of space");
397-		}
398-	}
399-
400-	/*
401-	 * Loop through selected history events.  If listing or executing,
402-	 * do it now.  Otherwise, put into temp file and call the editor
403-	 * after.
404-	 *
405-	 * The history interface needs rethinking, as the following
406-	 * convolutions will demonstrate.
407-	 */
408-	history(hist, &he, H_FIRST);
409-	retval = history(hist, &he, H_NEXT_EVENT, first);
410-	for (;retval != -1; retval = history(hist, &he, direction)) {
411-		if (lflg) {
412-			if (!nflg)
413-				out1fmt("%5d ", he.num);
414-			out1str(he.str);
415-		} else {
416-			const char *s = pat ?
417-			   fc_replace(he.str, pat, repl) : he.str;
418-
419-			if (sflg) {
420-				if (displayhist) {
421-					out2str(s);
422-					flushout(out2);
423-				}
424-				evalstring(s, 0);
425-				if (displayhist && hist) {
426-					/*
427-					 *  XXX what about recursive and
428-					 *  relative histnums.
429-					 */
430-					oldhistnum = he.num;
431-					history(hist, &he, H_ENTER, s);
432-					/*
433-					 * XXX H_ENTER moves the internal
434-					 * cursor, set it back to the current
435-					 * entry.
436-					 */
437-					history(hist, &he,
438-					    H_NEXT_EVENT, oldhistnum);
439-				}
440-			} else
441-				fputs(s, efp);
442-		}
443-		/*
444-		 * At end?  (if we were to lose last, we'd sure be
445-		 * messed up).
446-		 */
447-		if (he.num == last)
448-			break;
449-	}
450-	if (editor) {
451-		char *editcmd;
452-
453-		fclose(efp);
454-		INTON;
455-		editcmd = stalloc(strlen(editor) + strlen(editfile) + 2);
456-		sprintf(editcmd, "%s %s", editor, editfile);
457-		evalstring(editcmd, 0);	/* XXX - should use no JC command */
458-		readcmdfile(editfile, 0 /* verify */);	/* XXX - should read back - quick tst */
459-		unlink(editfile);
460-	}
461-
462-	if (lflg == 0 && active > 0)
463-		--active;
464-	if (displayhist)
465-		displayhist = 0;
466-	handler = savehandler;
467-	return 0;
468-}
469-
470-static char *
471-fc_replace(const char *s, char *p, char *r)
472-{
473-	char *dest;
474-	int plen = strlen(p);
475-
476-	STARTSTACKSTR(dest);
477-	while (*s) {
478-		if (*s == *p && strncmp(s, p, plen) == 0) {
479-			STPUTS(r, dest);
480-			s += plen;
481-			*p = '\0';	/* so no more matches */
482-		} else
483-			STPUTC(*s++, dest);
484-	}
485-	STPUTC('\0', dest);
486-	dest = grabstackstr(dest);
487-
488-	return (dest);
489-}
490-
491-static int
492-not_fcnumber(const char *s)
493-{
494-	if (s == NULL)
495-		return (0);
496-	if (*s == '-')
497-		s++;
498-	return (!is_number(s));
499-}
500-
501-static int
502-str_to_event(const char *str, int last)
503-{
504-	HistEvent he;
505-	const char *s = str;
506-	int relative = 0;
507-	int i, retval;
508-
509-	retval = history(hist, &he, H_FIRST);
510-	switch (*s) {
511-	case '-':
512-		relative = 1;
513-		/*FALLTHROUGH*/
514-	case '+':
515-		s++;
516-	}
517-	if (is_number(s)) {
518-		i = atoi(s);
519-		if (relative) {
520-			while (retval != -1 && i--) {
521-				retval = history(hist, &he, H_NEXT);
522-			}
523-			if (retval == -1)
524-				retval = history(hist, &he, H_LAST);
525-		} else {
526-			retval = history(hist, &he, H_NEXT_EVENT, i);
527-			if (retval == -1) {
528-				/*
529-				 * the notion of first and last is
530-				 * backwards to that of the history package
531-				 */
532-				retval = history(hist, &he, last ? H_FIRST : H_LAST);
533-			}
534-		}
535-		if (retval == -1)
536-			error("history number %s not found (internal error)",
537-			       str);
538-	} else {
539-		/*
540-		 * pattern
541-		 */
542-		retval = history(hist, &he, H_PREV_STR, str);
543-		if (retval == -1)
544-			error("history pattern not found: %s", str);
545-	}
546-	return (he.num);
547-}
548-
549-int
550-bindcmd(int argc, char **argv)
551-{
552-	int ret;
553-	FILE *old;
554-	FILE *out;
555-
556-	if (el == NULL)
557-		error("line editing is disabled");
558-
559-	INTOFF;
560-
561-	out = out1fp();
562-	if (out == NULL)
563-		error("Out of space");
564-
565-	el_get(el, EL_GETFP, 1, &old);
566-	el_set(el, EL_SETFP, 1, out);
567-
568-	ret = el_parse(el, argc, __DECONST(const char **, argv));
569-
570-	el_set(el, EL_SETFP, 1, old);
571-
572-	fclose(out);
573-
574-	if (argc > 1 && argv[1][0] == '-' &&
575-	    memchr("ve", argv[1][1], 2) != NULL) {
576-		Vflag = argv[1][1] == 'v';
577-		Eflag = !Vflag;
578-		histedit();
579-	}
580-
581-	INTON;
582-
583-	return ret;
584-}
585-
586-/*
587- * Comparator function for qsort(). The use of curpos here is to skip
588- * characters that we already know to compare equal (common prefix).
589- */
590-static int
591-comparator(const void *a, const void *b, void *thunk)
592-{
593-	size_t curpos = (intptr_t)thunk;
594-
595-	return (strcmp(*(char *const *)a + curpos,
596-		*(char *const *)b + curpos));
597-}
598-
599-static char
600-**add_match(char **matches, size_t i, size_t *size, char *match_copy)
601-{
602-	if (match_copy == NULL)
603-		return (NULL);
604-	matches[i] = match_copy;
605-	if (i >= *size - 1) {
606-		*size *= 2;
607-		matches = reallocarray(matches, *size, sizeof(matches[0]));
608-	}
609-
610-	return (matches);
611-}
612-
613-/*
614- * This function is passed to libedit's fn_complete2(). The library will use
615- * it instead of its standard function that finds matching files in current
616- * directory. If we're at the start of the line, we want to look for
617- * available commands from all paths in $PATH.
618- */
619-static char
620-**sh_matches(const char *text, int start, int end)
621-{
622-	char *free_path = NULL, *path;
623-	const char *dirname;
624-	char **matches = NULL, **rmatches;
625-	size_t i = 0, size = 16, uniq;
626-	size_t curpos = end - start, lcstring = -1;
627-	struct cmdentry e;
628-
629-	in_command_completion = 0;
630-	if (start > 0 || memchr("/.~", text[0], 3) != NULL)
631-		return (NULL);
632-	in_command_completion = 1;
633-	if ((free_path = path = strdup(pathval())) == NULL)
634-		goto out;
635-	if ((matches = malloc(size * sizeof(matches[0]))) == NULL)
636-		goto out;
637-	while ((dirname = strsep(&path, ":")) != NULL) {
638-		struct dirent *entry;
639-		DIR *dir;
640-		int dfd;
641-
642-		dir = opendir(dirname[0] == '\0' ? "." : dirname);
643-		if (dir == NULL)
644-			continue;
645-		if ((dfd = dirfd(dir)) == -1) {
646-			closedir(dir);
647-			continue;
648-		}
649-		while ((entry = readdir(dir)) != NULL) {
650-			struct stat statb;
651-
652-			if (strncmp(entry->d_name, text, curpos) != 0)
653-				continue;
654-			if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK) {
655-				if (fstatat(dfd, entry->d_name, &statb, 0) == -1)
656-					continue;
657-				if (!S_ISREG(statb.st_mode))
658-					continue;
659-			} else if (entry->d_type != DT_REG)
660-				continue;
661-			rmatches = add_match(matches, ++i, &size,
662-				strdup(entry->d_name));
663-			if (rmatches == NULL) {
664-				closedir(dir);
665-				goto out;
666-			}
667-			matches = rmatches;
668-		}
669-		closedir(dir);
670-	}
671-	for (const unsigned char *bp = builtincmd; *bp != 0; bp += 2 + bp[0]) {
672-		if (curpos > bp[0] || memcmp(bp + 2, text, curpos) != 0)
673-			continue;
674-		rmatches = add_match(matches, ++i, &size, strndup(bp + 2, bp[0]));
675-		if (rmatches == NULL)
676-			goto out;
677-		matches = rmatches;
678-	}
679-	for (const struct alias *ap = NULL; (ap = iteralias(ap)) != NULL;) {
680-		if (strncmp(ap->name, text, curpos) != 0)
681-			continue;
682-		rmatches = add_match(matches, ++i, &size, strdup(ap->name));
683-		if (rmatches == NULL)
684-			goto out;
685-		matches = rmatches;
686-	}
687-	for (const void *a = NULL; (a = itercmd(a, &e)) != NULL;) {
688-		if (e.cmdtype != CMDFUNCTION)
689-			continue;
690-		if (strncmp(e.cmdname, text, curpos) != 0)
691-			continue;
692-		rmatches = add_match(matches, ++i, &size, strdup(e.cmdname));
693-		if (rmatches == NULL)
694-			goto out;
695-		matches = rmatches;
696-	}
697-out:
698-	free(free_path);
699-	if (i == 0) {
700-		free(matches);
701-		return (NULL);
702-	}
703-	uniq = 1;
704-	if (i > 1) {
705-		qsort_s(matches + 1, i, sizeof(matches[0]), comparator,
706-			(void *)(intptr_t)curpos);
707-		for (size_t k = 2; k <= i; k++) {
708-			const char *l = matches[uniq] + curpos;
709-			const char *r = matches[k] + curpos;
710-			size_t common = 0;
711-
712-			while (*l != '\0' && *r != '\0' && *l == *r)
713-				(void)l++, r++, common++;
714-			if (common < lcstring)
715-				lcstring = common;
716-			if (*l == *r)
717-				free(matches[k]);
718-			else
719-				matches[++uniq] = matches[k];
720-		}
721-	}
722-	matches[uniq + 1] = NULL;
723-	/*
724-	 * matches[0] is special: it's not a real matching file name but
725-	 * a common prefix for all matching names. It can't be null, unlike
726-	 * any other element of the array. When strings matches[0] and
727-	 * matches[1] compare equal and matches[2] is null that means to
728-	 * libedit that there is only a single match. It will then replace
729-	 * user input with possibly escaped string in matches[0] which is the
730-	 * reason to copy the full name of the only match.
731-	 */
732-	if (uniq == 1)
733-		matches[0] = strdup(matches[1]);
734-	else if (lcstring != (size_t)-1)
735-		matches[0] = strndup(matches[1], curpos + lcstring);
736-	else
737-		matches[0] = strdup(text);
738-	if (matches[0] == NULL) {
739-		for (size_t k = 1; k <= uniq; k++)
740-			free(matches[k]);
741-		free(matches);
742-		return (NULL);
743-	}
744-	return (matches);
745-}
746-
747-/*
748- * If we don't specify this function as app_func in the call to fn_complete2,
749- * libedit will use the default one, which adds a " " to plain files and
750- * a "/" to directories regardless of whether it's a command name or a plain
751- * path (relative or absolute). We never want to add "/" to commands.
752- *
753- * For example, after I did "mkdir rmdir", "rmdi" would be autocompleted to
754- * "rmdir/" instead of "rmdir ".
755- */
756-static const char *
757-append_char_function(const char *name)
758-{
759-	struct stat stbuf;
760-	char *expname = name[0] == '~' ? fn_tilde_expand(name) : NULL;
761-	const char *rs;
762-
763-	if (!in_command_completion &&
764-	    stat(expname ? expname : name, &stbuf) == 0 &&
765-	    S_ISDIR(stbuf.st_mode))
766-		rs = "/";
767-	else
768-		rs = " ";
769-	free(expname);
770-	return (rs);
771-}
772-
773-/*
774- * This is passed to el_set(el, EL_ADDFN, ...) so that it's possible to
775- * bind a key (tab by default) to execute the function.
776- */
777-unsigned char
778-sh_complete(EditLine *sel, int ch __unused)
779-{
780-	return (unsigned char)fn_complete2(sel, NULL, sh_matches,
781-		L" \t\n\"\\'`@$><=;|&{(", NULL, append_char_function,
782-		(size_t)100, NULL, &((int) {0}), NULL, NULL, FN_QUOTE_MATCH);
783-}
784-
785-#else
786-
787-int
788-histcmd(int argc __unused, char **argv __unused)
789-{
790-
791-	error("not compiled with history support");
792-	/*NOTREACHED*/
793-	return (0);
794-}
795-
796-int
797-bindcmd(int argc __unused, char **argv __unused)
798-{
799-
800-	error("not compiled with line editing support");
801-	return (0);
802-}
803-#endif
+38, -29
 1@@ -54,7 +54,7 @@
 2 #include "alias.h"
 3 #include "parser.h"
 4 #ifndef NO_HISTORY
 5-#include "myhistedit.h"
 6+#include "lineedit.h"
 7 #endif
 8 #include "trap.h"
 9 
10@@ -132,30 +132,34 @@ preadfd(void)
11 
12 	retry:
13 #ifndef NO_HISTORY
14-	if (parsefile->fd == 0 && el) {
15-		const char *line;
16-
17-		el_resize(el);
18-		line = el_gets(el, &nr);
19-		if (nr > 0 && parsefile->bufsize < (size_t)nr + 1) {
20-			size_t bufsize;
21-
22-			INTOFF;
23-			if (parsefile->buf != basebuf) {
24-				ckfree(parsefile->buf);
25-				parsefile->buf = NULL;
26-				parsefile->bufsize = 0;
27+	if (parsefile->fd == 0 && sh_history_enabled) {
28+		char *line;
29+
30+		line = redline(getprompt(NULL));
31+		if (line != NULL) {
32+			nr = strlen(line) + 1;
33+			if (parsefile->bufsize < (size_t)nr + 1) {
34+				size_t bufsize;
35+
36+				INTOFF;
37+				if (parsefile->buf != basebuf) {
38+					ckfree(parsefile->buf);
39+					parsefile->buf = NULL;
40+					parsefile->bufsize = 0;
41+				}
42+				bufsize = (size_t)nr + BUFSIZ + 1;
43+				bufsize -= bufsize % BUFSIZ;
44+				parsefile->buf = ckmalloc(bufsize);
45+				parsefile->bufsize = bufsize;
46+				INTON;
47 			}
48-			bufsize = (size_t)nr + BUFSIZ + 1;
49-			bufsize -= bufsize % BUFSIZ;
50-			parsefile->buf = ckmalloc(bufsize);
51-			parsefile->bufsize = bufsize;
52-			INTON;
53+			memcpy(parsefile->buf, line, nr - 1);
54+			parsefile->buf[nr - 1] = '\n';
55+			parsefile->buf[nr] = '\0';
56+			free(line);
57+		} else {
58+			nr = 0;
59 		}
60-		if (nr > 0 && line != NULL)
61-			memcpy(parsefile->buf, line, nr);
62-		else
63-			nr = nr ? -1 : 0;
64 	} else
65 #endif
66 	nr = read(parsefile->fd, parsefile->buf, parsefile->bufsize - 1);
67@@ -249,13 +253,18 @@ preadbuffer(void)
68 	*q = '\0';
69 
70 #ifndef NO_HISTORY
71-	if (parsefile->fd == 0 && hist &&
72+	if (parsefile->fd == 0 && sh_history_enabled &&
73 	    parsenextc[strspn(parsenextc, " \t\n")] != '\0') {
74-		HistEvent he;
75-		INTOFF;
76-		history(hist, &he, whichprompt == 1 ? H_ENTER : H_ADD,
77-		    parsenextc);
78-		INTON;
79+		char *histline = strdup(parsenextc);
80+		if (histline) {
81+			char *nl = strchr(histline, '\n');
82+			if (nl)
83+				*nl = '\0';
84+			INTOFF;
85+			redlineHistoryAdd(histline);
86+			INTON;
87+			free(histline);
88+		}
89 	}
90 #endif
91 
+844, -0
  1@@ -0,0 +1,844 @@
  2+/*-
  3+ * SPDX-License-Identifier: BSD-3-Clause
  4+ *
  5+ * Copyright (c) 1993
  6+ *	The Regents of the University of California.  All rights reserved.
  7+ *
  8+ * This code is derived from software contributed to Berkeley by
  9+ * Kenneth Almquist.
 10+ *
 11+ * Redistribution and use in source and binary forms, with or without
 12+ * modification, are permitted provided that the following conditions
 13+ * are met:
 14+ * 1. Redistributions of source code must retain the above copyright
 15+ *    notice, this list of conditions and the following disclaimer.
 16+ * 2. Redistributions in binary form must reproduce the above copyright
 17+ *    notice, this list of conditions and the following disclaimer in the
 18+ *    documentation and/or other materials provided with the distribution.
 19+ * 3. Neither the name of the University nor the names of its contributors
 20+ *    may be used to promote products derived from this software
 21+ *    without specific prior written permission.
 22+ *
 23+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 24+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 25+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 26+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 27+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 28+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 29+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 30+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 31+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 32+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 33+ * SUCH DAMAGE.
 34+ */
 35+
 36+#if !FEATURE_SH_HISTEDIT
 37+#ifndef NO_HISTORY
 38+#define NO_HISTORY
 39+#endif
 40+#endif
 41+
 42+#include "util.h"
 43+#include "shell.h"
 44+#include "alias.h"
 45+#include "builtins.h"
 46+#include "error.h"
 47+#include "eval.h"
 48+#include "exec.h"
 49+#include "main.h"
 50+#include "memalloc.h"
 51+#include "mystring.h"
 52+#include "options.h"
 53+#include "output.h"
 54+#include "parser.h"
 55+#include "var.h"
 56+
 57+#ifndef NO_HISTORY
 58+#include "lineedit.h"
 59+
 60+#include <sys/param.h>
 61+#include <sys/stat.h>
 62+
 63+#include <dirent.h>
 64+#include <errno.h>
 65+#include <fcntl.h>
 66+#include <limits.h>
 67+#include <paths.h>
 68+#include <pwd.h>
 69+#include <stdio.h>
 70+#include <stdlib.h>
 71+#include <string.h>
 72+#include <unistd.h>
 73+
 74+#define MAXHISTLOOPS	4
 75+#define DEFEDITOR	"ed"
 76+#define VTABSIZE 39
 77+
 78+extern struct var *vartab[VTABSIZE];
 79+
 80+int sh_history_enabled = 0;
 81+int displayhist = 0;
 82+static int savehist = 0;
 83+
 84+static char *fc_replace(const char *, char *, char *);
 85+static int not_fcnumber(const char *);
 86+static int str_to_event(const char *, int);
 87+
 88+static char *
 89+escape_filename(const char *filename)
 90+{
 91+	size_t len;
 92+	size_t i;
 93+	size_t j;
 94+	char *escaped;
 95+	const char *special;
 96+
 97+	len = 0;
 98+	special = " \t\n\"'\\$&|;<>()*?[]!{}";
 99+	for (i = 0; filename[i] != '\0'; i++) {
100+		if (strchr(special, filename[i]) != NULL)
101+			len += 2;
102+		else
103+			len += 1;
104+	}
105+
106+	escaped = malloc(len + 1);
107+	j = 0;
108+	for (i = 0; filename[i] != '\0'; i++) {
109+		if (strchr(special, filename[i]) != NULL) {
110+			escaped[j++] = '\\';
111+			escaped[j++] = filename[i];
112+		} else {
113+			escaped[j++] = filename[i];
114+		}
115+	}
116+	escaped[j] = '\0';
117+	return escaped;
118+}
119+
120+static char *
121+unescape_filename(const char *filename)
122+{
123+	size_t len;
124+	char *unescaped;
125+	size_t i;
126+	size_t j;
127+
128+	len = strlen(filename);
129+	unescaped = malloc(len + 1);
130+	i = 0;
131+	j = 0;
132+	while (i < len) {
133+		if (filename[i] == '\\' && i + 1 < len) {
134+			unescaped[j++] = filename[i+1];
135+			i += 2;
136+		} else {
137+			unescaped[j++] = filename[i];
138+			i++;
139+		}
140+	}
141+	unescaped[j] = '\0';
142+	return unescaped;
143+}
144+
145+static void
146+complete_tildes(const char *word, struct redlineCompletions *lc)
147+{
148+	struct passwd *pw;
149+	char completed[512];
150+	const char *user_prefix;
151+	size_t prefix_len;
152+
153+	user_prefix = word + 1;
154+	prefix_len = strlen(user_prefix);
155+
156+	setpwent();
157+	while ((pw = getpwent()) != NULL) {
158+		if (strncmp(pw->pw_name, user_prefix, prefix_len) == 0) {
159+			snprintf(completed, sizeof(completed), "~%s/", pw->pw_name);
160+			redlineAddCompletion(lc, completed);
161+		}
162+	}
163+	endpwent();
164+}
165+
166+static void
167+complete_variables(const char *word, struct redlineCompletions *lc)
168+{
169+	struct var **vpp;
170+	struct var *vp;
171+	char name[256];
172+	char completed[512];
173+	const char *var_prefix;
174+	size_t prefix_len;
175+	char *eq;
176+	size_t name_len;
177+
178+	var_prefix = word + 1;
179+	prefix_len = strlen(var_prefix);
180+
181+	for (vpp = vartab; vpp < vartab + VTABSIZE; vpp++) {
182+		for (vp = *vpp; vp; vp = vp->next) {
183+			if (!(vp->flags & VUNSET)) {
184+				eq = strchr(vp->text, '=');
185+				if (eq) {
186+					name_len = eq - vp->text;
187+					if (name_len < sizeof(name)) {
188+						memcpy(name, vp->text, name_len);
189+						name[name_len] = '\0';
190+						if (strncmp(name, var_prefix, prefix_len) == 0) {
191+							snprintf(completed, sizeof(completed), "$%s ", name);
192+							redlineAddCompletion(lc, completed);
193+						}
194+					}
195+				}
196+			}
197+		}
198+	}
199+}
200+
201+static const char *
202+get_histfile(void)
203+{
204+	const char *histfile;
205+
206+	if (!strcmp(histsizeval(), "0"))
207+		return (NULL);
208+	histfile = expandstr("${HISTFILE-${HOME-}/.sh_history}");
209+
210+	if (histfile[0] == '\0')
211+		return (NULL);
212+	return (histfile);
213+}
214+
215+void
216+histsave(void)
217+{
218+	const char *histfile;
219+
220+	if (!savehist || (histfile = get_histfile()) == NULL)
221+		return;
222+	INTOFF;
223+	redlineHistorySave(histfile);
224+	INTON;
225+}
226+
227+void
228+histload(void)
229+{
230+	const char *histfile;
231+
232+	if ((histfile = get_histfile()) == NULL)
233+		return;
234+	errno = 0;
235+	if (redlineHistoryLoad(histfile) != -1 || errno == ENOENT)
236+		savehist = 1;
237+}
238+
239+static void
240+find_completions_recurse(const char *fs_dir, const char *user_prefix,
241+    char **comps, int comp_idx, int comp_count, int is_cmd, struct redlineCompletions *lc)
242+{
243+	DIR *dir;
244+	struct dirent *de;
245+	struct stat st;
246+	char next_fs[4096];
247+	char next_user[4096];
248+	size_t len;
249+
250+	if (comp_idx == comp_count) {
251+		/* reached the end of components, check if the path exists */
252+		if (stat(fs_dir, &st) == 0) {
253+			if (is_cmd && !S_ISDIR(st.st_mode) && access(fs_dir, X_OK) != 0) {
254+				return;
255+			}
256+			snprintf(next_user, sizeof(next_user), "%s", user_prefix);
257+			if (S_ISDIR(st.st_mode)) {
258+				/* if it is a directory and doesnt end with slash, add slash */
259+				len = strlen(next_user);
260+				if (len > 0 && next_user[len - 1] != '/') {
261+					strlcat(next_user, "/", sizeof(next_user));
262+				}
263+			} else {
264+				/* file, add space */
265+				strlcat(next_user, " ", sizeof(next_user));
266+			}
267+			redlineAddCompletion(lc, next_user);
268+		}
269+		return;
270+	}
271+
272+	if (strcmp(comps[comp_idx], ".") == 0 || strcmp(comps[comp_idx], "..") == 0) {
273+		/* construct filesystem path */
274+		if (strcmp(fs_dir, "/") == 0) {
275+			snprintf(next_fs, sizeof(next_fs), "/%s", comps[comp_idx]);
276+		} else if (strcmp(fs_dir, ".") == 0) {
277+			snprintf(next_fs, sizeof(next_fs), "./%s", comps[comp_idx]);
278+		} else {
279+			snprintf(next_fs, sizeof(next_fs), "%s/%s", fs_dir, comps[comp_idx]);
280+		}
281+
282+		/* construct user visible path */
283+		if (strcmp(user_prefix, "/") == 0) {
284+			snprintf(next_user, sizeof(next_user), "/%s", comps[comp_idx]);
285+		} else if (strcmp(user_prefix, "~/") == 0) {
286+			snprintf(next_user, sizeof(next_user), "~/%s", comps[comp_idx]);
287+		} else if (user_prefix[0] == '\0') {
288+			snprintf(next_user, sizeof(next_user), "%s", comps[comp_idx]);
289+		} else {
290+			len = strlen(user_prefix);
291+			if (user_prefix[len - 1] == '/') {
292+				snprintf(next_user, sizeof(next_user), "%s%s", user_prefix, comps[comp_idx]);
293+			} else {
294+				snprintf(next_user, sizeof(next_user), "%s/%s", user_prefix, comps[comp_idx]);
295+			}
296+		}
297+
298+		find_completions_recurse(next_fs, next_user, comps, comp_idx + 1, comp_count, is_cmd, lc);
299+		return;
300+	}
301+
302+	dir = opendir(fs_dir);
303+	if (!dir)
304+		return;
305+
306+	while ((de = readdir(dir)) != NULL) {
307+		if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
308+			continue;
309+
310+		if (strncmp(de->d_name, comps[comp_idx], strlen(comps[comp_idx])) == 0) {
311+			char *escaped_name = escape_filename(de->d_name);
312+			/* construct filesystem path */
313+			if (strcmp(fs_dir, "/") == 0) {
314+				snprintf(next_fs, sizeof(next_fs), "/%s", de->d_name);
315+			} else if (strcmp(fs_dir, ".") == 0) {
316+				snprintf(next_fs, sizeof(next_fs), "./%s", de->d_name);
317+			} else {
318+				snprintf(next_fs, sizeof(next_fs), "%s/%s", fs_dir, de->d_name);
319+			}
320+
321+			/* middle components must be directories */
322+			if (comp_idx < comp_count - 1) {
323+				if (stat(next_fs, &st) != 0 || !S_ISDIR(st.st_mode)) {
324+					free(escaped_name);
325+					continue;
326+				}
327+			}
328+
329+			/* construct user-visible path */
330+			if (strcmp(user_prefix, "/") == 0) {
331+				snprintf(next_user, sizeof(next_user), "/%s", escaped_name);
332+			} else if (strcmp(user_prefix, "~/") == 0) {
333+				snprintf(next_user, sizeof(next_user), "~/%s", escaped_name);
334+			} else if (user_prefix[0] == '\0') {
335+				snprintf(next_user, sizeof(next_user), "%s", escaped_name);
336+			} else {
337+				len = strlen(user_prefix);
338+				if (user_prefix[len - 1] == '/') {
339+					snprintf(next_user, sizeof(next_user), "%s%s", user_prefix, escaped_name);
340+				} else {
341+					snprintf(next_user, sizeof(next_user), "%s/%s", user_prefix, escaped_name);
342+				}
343+			}
344+
345+			find_completions_recurse(next_fs, next_user, comps, comp_idx + 1, comp_count, is_cmd, lc);
346+			free(escaped_name);
347+		}
348+	}
349+	closedir(dir);
350+}
351+
352+/* complete matching files in the filesystem */
353+static void
354+complete_files(const char *word, int is_cmd, struct redlineCompletions *lc)
355+{
356+	char *path_to_split;
357+	const char *fs_dir;
358+	const char *user_prefix;
359+	const char *home;
360+	char *path_copy;
361+	char *p;
362+	char *comps[128];
363+	int comp_count;
364+	char *unescaped_word;
365+
366+	path_to_split = NULL;
367+	fs_dir = ".";
368+	user_prefix = "";
369+	home = getenv("HOME");
370+	comp_count = 0;
371+	unescaped_word = unescape_filename(word);
372+
373+	if (unescaped_word[0] == '~') {
374+		if (unescaped_word[1] == '/' || unescaped_word[1] == '\0') {
375+			fs_dir = home ? home : "/";
376+			user_prefix = "~/";
377+			path_to_split = (unescaped_word[1] == '\0') ? "" : (char *)(unescaped_word + 2);
378+		} else {
379+			/* ~username is not supported for abbreviation, fallback to home */
380+			fs_dir = home ? home : "/";
381+			user_prefix = "~/";
382+			path_to_split = (char *)(unescaped_word + 1);
383+		}
384+	} else if (unescaped_word[0] == '/') {
385+		fs_dir = "/";
386+		user_prefix = "/";
387+		path_to_split = (char *)(unescaped_word + 1);
388+	} else {
389+		fs_dir = ".";
390+		user_prefix = "";
391+		path_to_split = (char *)unescaped_word;
392+	}
393+
394+	path_copy = estrdup(path_to_split);
395+	p = path_copy;
396+	if (*p != '\0') {
397+		comps[comp_count++] = p;
398+		while (*p != '\0') {
399+			if (*p == '/') {
400+				*p = '\0';
401+				p++;
402+				while (*p == '/')
403+					p++;
404+				if (*p == '\0') {
405+					comps[comp_count++] = p;
406+					break;
407+				}
408+				comps[comp_count++] = p;
409+			} else {
410+				p++;
411+			}
412+		}
413+	} else {
414+		/* empty path_to_split (e.g. exactly ~ or exactly / or empty word) */
415+		comps[comp_count++] = p;
416+	}
417+
418+	find_completions_recurse(fs_dir, user_prefix, comps, 0, comp_count, is_cmd, lc);
419+	free(path_copy);
420+	free(unescaped_word);
421+}
422+
423+/* complete matching executable commands and builtins */
424+static void
425+complete_commands(const char *word, struct redlineCompletions *lc)
426+{
427+	char *free_path = NULL, *path;
428+	const char *dirname;
429+	struct cmdentry e;
430+	const struct alias *ap = NULL;
431+	const unsigned char *bp = builtincmd;
432+	const void *a = NULL;
433+	DIR *dir;
434+	struct dirent *entry;
435+	int dfd;
436+	struct stat statb;
437+	char completed[512];
438+
439+	while ((ap = iteralias(ap)) != NULL) {
440+		if (strncmp(ap->name, word, strlen(word)) == 0) {
441+			snprintf(completed, sizeof(completed), "%s ", ap->name);
442+			redlineAddCompletion(lc, completed);
443+		}
444+	}
445+
446+	while (bp && *bp != 0) {
447+		if (strncmp((const char *)(bp + 2), word, strlen(word)) == 0) {
448+			snprintf(completed, sizeof(completed), "%.*s ", (int)bp[0], bp + 2);
449+			redlineAddCompletion(lc, completed);
450+		}
451+		bp += 2 + bp[0];
452+	}
453+
454+	while ((a = itercmd(a, &e)) != NULL) {
455+		if (e.cmdtype == CMDFUNCTION && strncmp(e.cmdname, word, strlen(word)) == 0) {
456+			snprintf(completed, sizeof(completed), "%s ", e.cmdname);
457+			redlineAddCompletion(lc, completed);
458+		}
459+	}
460+
461+	path = pathval();
462+	if (path) {
463+		free_path = path = estrdup(path);
464+		while ((dirname = strsep(&path, ":")) != NULL) {
465+			dir = opendir(dirname[0] == '\0' ? "." : dirname);
466+			if (dir == NULL)
467+				continue;
468+			dfd = dirfd(dir);
469+			if (dfd == -1) {
470+				closedir(dir);
471+				continue;
472+			}
473+			while ((entry = readdir(dir)) != NULL) {
474+				if (strncmp(entry->d_name, word, strlen(word)) != 0)
475+					continue;
476+				if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK) {
477+					if (fstatat(dfd, entry->d_name, &statb, 0) == -1)
478+						continue;
479+					if (!S_ISREG(statb.st_mode))
480+						continue;
481+				} else if (entry->d_type != DT_REG) {
482+					continue;
483+				}
484+				snprintf(completed, sizeof(completed), "%s ", entry->d_name);
485+				redlineAddCompletion(lc, completed);
486+			}
487+			closedir(dir);
488+		}
489+		free(free_path);
490+	}
491+}
492+
493+/* main completion callback called by redline library */
494+static void
495+sh_complete_callback(const char *buf, struct redlineCompletions *lc)
496+{
497+	const char *word;
498+	int start;
499+	int is_cmd;
500+	int p;
501+	struct redlineCompletions temp_lc;
502+	char line_prefix[4096];
503+	char full_completion[4096];
504+	size_t i;
505+
506+	start = strlen(buf);
507+	while (start > 0) {
508+		char c = buf[start - 1];
509+		if (c == ' ' || c == '\t' || c == '\n' || c == '"' || c == '\'' || c == '`' ||
510+		    c == '@' || c == '$' || c == '>' || c == '<' || c == '=' || c == ';' ||
511+		    c == '|' || c == '&' || c == '{' || c == '(') {
512+			if (start > 1 && buf[start - 2] == '\\') {
513+				start -= 2;
514+				continue;
515+			}
516+			break;
517+		} else if (c == '\\') {
518+			break;
519+		}
520+		start--;
521+	}
522+	word = buf + start;
523+
524+	is_cmd = 0;
525+	if (start == 0) {
526+		is_cmd = 1;
527+	} else {
528+		p = start;
529+		while (p > 0 && (buf[p - 1] == ' ' || buf[p - 1] == '\t'))
530+			p--;
531+		if (p == 0 || strchr(";&|({`\n", buf[p - 1]) != NULL)
532+			is_cmd = 1;
533+	}
534+
535+	if (start >= (int)sizeof(line_prefix))
536+		return;
537+	snprintf(line_prefix, sizeof(line_prefix), "%.*s", start, buf);
538+
539+	temp_lc.len = 0;
540+	temp_lc.cvec = NULL;
541+
542+	if (word[0] == '$') {
543+		complete_variables(word, &temp_lc);
544+	} else if (word[0] == '~' && strchr(word, '/') == NULL) {
545+		complete_tildes(word, &temp_lc);
546+	} else if (is_cmd && strchr(word, '/') == NULL && word[0] != '~' && word[0] != '.') {
547+		complete_commands(word, &temp_lc);
548+	} else {
549+		complete_files(word, is_cmd, &temp_lc);
550+	}
551+
552+	for (i = 0; i < temp_lc.len; i++) {
553+		snprintf(full_completion, sizeof(full_completion), "%s%s", line_prefix, temp_lc.cvec[i]);
554+		redlineAddCompletion(lc, full_completion);
555+	}
556+
557+	for (i = 0; i < temp_lc.len; i++) {
558+		free(temp_lc.cvec[i]);
559+	}
560+	free(temp_lc.cvec);
561+}
562+
563+void
564+histedit(void)
565+{
566+	sh_history_enabled = (iflag && (Eflag || Vflag));
567+	if (sh_history_enabled) {
568+		redlineSetCompletionCallback(sh_complete_callback);
569+		redlineSetMultiLine(1);
570+	}
571+}
572+
573+void
574+sethistsize(const char *hs)
575+{
576+	int histsize;
577+
578+	if (hs == NULL || !is_number(hs))
579+		histsize = 128;
580+	else
581+		histsize = atoi(hs);
582+	redlineHistorySetMaxLen(histsize);
583+}
584+
585+void
586+setterm(const char *term __unused)
587+{
588+}
589+
590+int
591+histcmd(int argc, char **argv __unused)
592+{
593+	const char *editor = NULL;
594+	int lflg = 0, nflg = 0, rflg = 0, sflg = 0;
595+	int i;
596+	const char *firststr, *laststr;
597+	int first, last;
598+	char *pat = NULL, *repl = NULL;
599+	static int active = 0;
600+	struct jmploc jmploc;
601+	struct jmploc *savehandler;
602+	char editfilestr[PATH_MAX];
603+	char *volatile editfile;
604+	FILE *efp = NULL;
605+	int dir;
606+
607+	if (redlineHistoryLen() == 0)
608+		error("history not active");
609+
610+	if (argc == 1)
611+		error("missing history argument");
612+
613+	while (not_fcnumber(*argptr))
614+		do {
615+			switch (nextopt("e:lnrs")) {
616+			case 'e':
617+				editor = shoptarg;
618+				break;
619+			case 'l':
620+				lflg = 1;
621+				break;
622+			case 'n':
623+				nflg = 1;
624+				break;
625+			case 'r':
626+				rflg = 1;
627+				break;
628+			case 's':
629+				sflg = 1;
630+				break;
631+			case '\0':
632+				goto operands;
633+			}
634+		} while (nextopt_optptr != NULL);
635+operands:
636+	savehandler = handler;
637+	if (lflg == 0 || editor || sflg) {
638+		lflg = 0;
639+		editfile = NULL;
640+		if (setjmp(jmploc.loc)) {
641+			active = 0;
642+			if (editfile)
643+				unlink(editfile);
644+			handler = savehandler;
645+			longjmp(handler->loc, 1);
646+		}
647+		handler = &jmploc;
648+		if (++active > MAXHISTLOOPS) {
649+			active = 0;
650+			displayhist = 0;
651+			error("called recursively too many times");
652+		}
653+		if (sflg == 0) {
654+			if (editor == NULL &&
655+			    (editor = bltinlookup("FCEDIT", 1)) == NULL &&
656+			    (editor = bltinlookup("EDITOR", 1)) == NULL)
657+				editor = DEFEDITOR;
658+			if (editor[0] == '-' && editor[1] == '\0') {
659+				sflg = 1;
660+				editor = NULL;
661+			}
662+		}
663+	}
664+
665+	if (lflg == 0 && *argptr != NULL &&
666+	     ((repl = strchr(*argptr, '=')) != NULL)) {
667+		pat = *argptr;
668+		*repl++ = '\0';
669+		argptr++;
670+	}
671+
672+	if (*argptr == NULL) {
673+		firststr = lflg ? "-16" : "-1";
674+		laststr = "-1";
675+	} else if (argptr[1] == NULL) {
676+		firststr = argptr[0];
677+		laststr = lflg ? "-1" : argptr[0];
678+	} else if (argptr[2] == NULL) {
679+		firststr = argptr[0];
680+		laststr = argptr[1];
681+	} else {
682+		error("too many arguments");
683+	}
684+
685+	first = str_to_event(firststr, 0);
686+	last = str_to_event(laststr, 1);
687+
688+	if (rflg) {
689+		i = last;
690+		last = first;
691+		first = i;
692+	}
693+
694+	if (editor) {
695+		int fd;
696+		INTOFF;
697+		sprintf(editfilestr, "%s/_shXXXXXX", _PATH_TMP);
698+		if ((fd = mkstemp(editfilestr)) < 0)
699+			error("can't create temporary file %s", editfile);
700+		editfile = editfilestr;
701+		if ((efp = fdopen(fd, "w")) == NULL) {
702+			close(fd);
703+			error("Out of space");
704+		}
705+	}
706+
707+	dir = (first <= last) ? 1 : -1;
708+	for (i = first; ; i += dir) {
709+		if (i < 1 || i > redlineHistoryLen())
710+			continue;
711+		const char *hstr = redlineHistoryGet(i - 1);
712+		if (lflg) {
713+			if (!nflg)
714+				out1fmt("%5d ", i);
715+			out1fmt("%s\n", hstr);
716+		} else {
717+			const char *s = pat ? fc_replace(hstr, pat, repl) : hstr;
718+			if (sflg) {
719+				if (displayhist) {
720+					out2fmt_flush("%s\n", s);
721+				}
722+				evalstring(s, 0);
723+				if (displayhist) {
724+					redlineHistoryAdd(s);
725+				}
726+			} else {
727+				fprintf(efp, "%s\n", s);
728+			}
729+		}
730+		if (i == last)
731+			break;
732+	}
733+
734+	if (editor) {
735+		char *editcmd;
736+
737+		fclose(efp);
738+		INTON;
739+		editcmd = stalloc(strlen(editor) + strlen(editfile) + 2);
740+		sprintf(editcmd, "%s %s", editor, editfile);
741+		evalstring(editcmd, 0);
742+		readcmdfile(editfile, 0);
743+		unlink(editfile);
744+	}
745+
746+	if (lflg == 0 && active > 0)
747+		--active;
748+	if (displayhist)
749+		displayhist = 0;
750+	handler = savehandler;
751+	return 0;
752+}
753+
754+static char *
755+fc_replace(const char *s, char *p, char *r)
756+{
757+	char *dest;
758+	int plen = strlen(p);
759+
760+	STARTSTACKSTR(dest);
761+	while (*s) {
762+		if (*s == *p && strncmp(s, p, plen) == 0) {
763+			STPUTS(r, dest);
764+			s += plen;
765+			*p = '\0';
766+		} else
767+			STPUTC(*s++, dest);
768+	}
769+	STPUTC('\0', dest);
770+	dest = grabstackstr(dest);
771+
772+	return (dest);
773+}
774+
775+static int
776+not_fcnumber(const char *s)
777+{
778+	if (s == NULL)
779+		return (0);
780+	if (*s == '-')
781+		s++;
782+	return (!is_number(s));
783+}
784+
785+static int
786+str_to_event(const char *str, int last_fallback)
787+{
788+	int relative = 0;
789+	int i;
790+	const char *s = str;
791+
792+	if (s == NULL) {
793+		return last_fallback ? redlineHistoryLen() : (redlineHistoryLen() > 16 ? redlineHistoryLen() - 15 : 1);
794+	}
795+
796+	switch (*s) {
797+	case '-':
798+		relative = 1;
799+		s++;
800+		break;
801+	case '+':
802+		s++;
803+		break;
804+	}
805+
806+	if (is_number(s)) {
807+		i = atoi(s);
808+		if (relative) {
809+			return redlineHistoryLen() - i;
810+		}
811+		return i;
812+	}
813+
814+	for (i = redlineHistoryLen() - 1; i >= 0; i--) {
815+		if (strncmp(redlineHistoryGet(i), str, strlen(str)) == 0) {
816+			return i + 1;
817+		}
818+	}
819+	error("history pattern not found: %s", str);
820+	return 0;
821+}
822+
823+int
824+bindcmd(int argc __unused, char **argv __unused)
825+{
826+	error("not compiled with line editing support");
827+	return (0);
828+}
829+
830+#else
831+
832+int
833+histcmd(int argc __unused, char **argv __unused)
834+{
835+	error("not compiled with history support");
836+	return (0);
837+}
838+
839+int
840+bindcmd(int argc __unused, char **argv __unused)
841+{
842+	error("not compiled with line editing support");
843+	return (0);
844+}
845+#endif
+28, -0
 1@@ -0,0 +1,28 @@
 2+/* see license file for copyright and license details */
 3+#ifndef LINEEDIT_H_
 4+#define LINEEDIT_H_
 5+
 6+#if FEATURE_SH_HISTEDIT
 7+
 8+#include "redline.h"
 9+
10+extern int sh_history_enabled;
11+extern int displayhist;
12+
13+void histedit(void);
14+void sethistsize(const char *s);
15+void setterm(const char *t);
16+void histload(void);
17+void histsave(void);
18+
19+#else
20+
21+#define histedit()        ((void)0)
22+#define sethistsize(s)    ((void)0)
23+#define setterm(t)        ((void)0)
24+#define histload()        ((void)0)
25+#define histsave()        ((void)0)
26+
27+#endif
28+
29+#endif /* LINEEDIT_H_ */
+1, -1
1@@ -65,7 +65,7 @@
2 #include "redir.h"
3 #include "builtins.h"
4 #ifndef NO_HISTORY
5-#include "myhistedit.h"
6+#include "lineedit.h"
7 #endif
8 
9 int rootpid;
+49, -2
 1@@ -51,6 +51,7 @@
 2 #include <unistd.h>
 3 
 4 #include "shell.h"
 5+#include "redline.h"
 6 
 7 #ifndef timespeccmp
 8 #define timespeccmp(tvp, uvp, cmp) \
 9@@ -183,17 +184,39 @@ readcmd(int argc __unused, char **argv __unused)
10 	ssize_t nread;
11 	int sig;
12 	struct fdctx fdctx;
13+#if FEATURE_SH_HISTEDIT
14+	int eflag;
15+	char *rl_line;
16+	char *rl_line_ptr;
17+	size_t rl_idx;
18+#endif
19 
20 	rflag = 0;
21 	prompt = NULL;
22 	timeout = -1;
23-	while ((i = nextopt("erp:t:")) != '\0') {
24+#if FEATURE_SH_HISTEDIT
25+	eflag = 0;
26+	rl_line = NULL;
27+	rl_line_ptr = NULL;
28+	rl_idx = 0;
29+#endif
30+
31+	while ((i = nextopt(
32+#if FEATURE_SH_HISTEDIT
33+		"erp:t:"
34+#else
35+		"rp:t:"
36+#endif
37+	)) != '\0') {
38 		switch(i) {
39 		case 'p':
40 			prompt = shoptarg;
41 			break;
42+#if FEATURE_SH_HISTEDIT
43 		case 'e':
44+			eflag = 1;
45 			break;
46+#endif
47 		case 'r':
48 			rflag = 1;
49 			break;
50@@ -224,6 +247,12 @@ readcmd(int argc __unused, char **argv __unused)
51 			break;
52 		}
53 	}
54+#if FEATURE_SH_HISTEDIT
55+	if (eflag && isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
56+		rl_line = redline(prompt ? prompt : "");
57+		rl_line_ptr = rl_line;
58+	} else
59+#endif
60 	if (prompt && isatty(0)) {
61 		out2str(prompt);
62 		flushall();
63@@ -279,7 +308,22 @@ readcmd(int argc __unused, char **argv __unused)
64 	fdctx_init(STDIN_FILENO, &fdctx);
65 	for (;;) {
66 		c = 0;
67-		nread = fdgetc(&fdctx, &c);
68+#if FEATURE_SH_HISTEDIT
69+		if (rl_line_ptr) {
70+			c = rl_line_ptr[rl_idx];
71+			if (c == '\0') {
72+				c = '\n';
73+				nread = 1;
74+				rl_line_ptr = NULL;
75+			} else {
76+				rl_idx++;
77+				nread = 1;
78+			}
79+		} else
80+#endif
81+		{
82+			nread = fdgetc(&fdctx, &c);
83+		}
84 		if (nread == -1) {
85 			if (errno == EINTR) {
86 				sig = pendingsig;
87@@ -380,6 +424,9 @@ readcmd(int argc __unused, char **argv __unused)
88 	/* Set any remaining args to "" */
89 	while (*++ap != NULL)
90 		setvar(*ap, "", 0);
91+#if FEATURE_SH_HISTEDIT
92+	free(rl_line);
93+#endif
94 	return status;
95 }
96 
+3, -0
 1@@ -127,6 +127,9 @@ main(int argc __unused, char **argv __unused)
 2 	/* Generate the #define statements in the header file */
 3 	fputs("/* Syntax classes */\n", hfile);
 4 	for (i = 0 ; synclass[i].name ; i++) {
 5+		if (strcmp(synclass[i].name, "CEOF") == 0) {
 6+			fputs("#ifdef CEOF\n#undef CEOF\n#endif\n", hfile);
 7+		}
 8 		sprintf(buf, "#define %s %d", synclass[i].name, i);
 9 		fputs(buf, hfile);
10 		for (pos = strlen(buf) ; pos < 32 ; pos = (pos + 8) & ~07)
+0, -37
 1@@ -1,37 +0,0 @@
 2-#ifndef MYHISTEDIT_H_
 3-#define MYHISTEDIT_H_
 4-
 5-#if FEATURE_SH_HISTEDIT
 6-
 7-#include <histedit.h>
 8-#include <filecomplete.h>
 9-
10-extern History *hist;
11-extern EditLine *el;
12-
13-void histedit(void);
14-void sethistsize(const char *);
15-void setterm(const char *);
16-void histload(void);
17-void histsave(void);
18-
19-#else
20-
21-/* stubbed histedit definitions */
22-typedef void History;
23-typedef void EditLine;
24-
25-extern History *hist;
26-extern EditLine *el;
27-
28-#define histedit() ((void)0)
29-#define sethistsize(s) ((void)0)
30-#define setterm(t) ((void)0)
31-#define histload() ((void)0)
32-#define histsave() ((void)0)
33-
34-#endif
35-
36-extern int displayhist;
37-
38-#endif /* !MYHISTEDIT_H_ */
+1, -1
1@@ -52,7 +52,7 @@
2 #include "mystring.h"
3 #include "builtins.h"
4 #ifndef NO_HISTORY
5-#include "myhistedit.h"
6+#include "lineedit.h"
7 #endif
8 
9 char *arg0;			/* value of $0 */
+5, -3
 1@@ -63,7 +63,7 @@ size_t xstrlcpy(char *, const char *, size_t);
 2 #include "main.h"
 3 #include "jobs.h"
 4 #ifndef NO_HISTORY
 5-#include "myhistedit.h"
 6+#include "lineedit.h"
 7 #endif
 8 
 9 /*
10@@ -1944,7 +1944,7 @@ setprompt(int which)
11 		return;
12 
13 #ifndef NO_HISTORY
14-	if (!el)
15+	if (!sh_history_enabled)
16 #endif
17 	{
18 		out2str(getprompt(NULL));
19@@ -2045,7 +2045,9 @@ getprompt(void *unused __unused)
20 		fmt = "";
21 		break;
22 	case 1:
23-		fmt = ps1val();
24+		fmt = expandstr(ps1val());
25+		if (fmt == NULL)
26+			fmt = ps1val();
27 		break;
28 	case 2:
29 		fmt = ps2val();
+1, -1
1@@ -47,7 +47,7 @@
2 #include "mystring.h"
3 #include "builtins.h"
4 #ifndef NO_HISTORY
5-#include "myhistedit.h"
6+#include "lineedit.h"
7 #endif
8 
9 #include <signal.h>
+2, -2
 1@@ -48,7 +48,7 @@
 2 #include "parser.h"
 3 #include "builtins.h"
 4 #ifndef NO_HISTORY
 5-#include "myhistedit.h"
 6+#include "lineedit.h"
 7 #endif
 8 
 9 #include <langinfo.h>
10@@ -123,7 +123,7 @@ static const struct varinit varinit[] = {
11 	  NULL }
12 };
13 
14-static struct var *vartab[VTABSIZE];
15+struct var *vartab[VTABSIZE];
16 
17 static const char *const locale_names[7] = {
18 	"LC_COLLATE", "LC_CTYPE", "LC_MONETARY",
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+sleep: delay for a duration
 5+usage: sleep num
 6+
 7+pause execution for a specified amount of time
 8+*/
 9+
10 #include <unistd.h>
11 
12 #include "util.h"
+34, -6
  1@@ -1,4 +1,11 @@
  2 /* See LICENSE file for copyright and license details. */
  3+/* ?man
  4+sort: sort lines
  5+usage: sort [-Cbcdfimnru
  6+
  7+sort or merge lines of text files
  8+*/
  9+
 10 #include "queue.h"
 11 #include "text.h"
 12 #include "utf.h"
 13@@ -303,22 +310,28 @@ parse_flags(char **s, int *flags, int bflag)
 14 {
 15 	while (isalpha((int)**s)) {
 16 		switch (*((*s)++)) {
 17-		case 'b':
 18+		// ?man -b: specify block size or base directory
 19+	case 'b':
 20 			*flags |= bflag;
 21 			break;
 22-		case 'd':
 23+		// ?man -d: specify directory
 24+	case 'd':
 25 			*flags |= MOD_D;
 26 			break;
 27-		case 'f':
 28+		// ?man -f: force the operation
 29+	case 'f':
 30 			*flags |= MOD_F;
 31 			break;
 32-		case 'i':
 33+		// ?man -i: interactive mode or prompt for confirmation
 34+	case 'i':
 35 			*flags |= MOD_I;
 36 			break;
 37-		case 'n':
 38+		// ?man -n: print line numbers or counts
 39+	case 'n':
 40 			*flags |= MOD_N;
 41 			break;
 42-		case 'r':
 43+		// ?man -r: operate recursively
 44+	case 'r':
 45 			*flags |= MOD_R;
 46 			break;
 47 		default:
 48@@ -397,27 +410,35 @@ main(int argc, char *argv[])
 49 	char *outfile = NULL;
 50 
 51 	ARGBEGIN {
 52+	// ?man -C: specify option flag
 53 	case 'C':
 54 		Cflag = 1;
 55 		break;
 56+	// ?man -b: specify block size or base directory
 57 	case 'b':
 58 		global_flags |= MOD_STARTB | MOD_ENDB;
 59 		break;
 60+	// ?man -c: print count or perform stdout action
 61 	case 'c':
 62 		cflag = 1;
 63 		break;
 64+	// ?man -d: specify directory
 65 	case 'd':
 66 		global_flags |= MOD_D;
 67 		break;
 68+	// ?man -f: force the operation
 69 	case 'f':
 70 		global_flags |= MOD_F;
 71 		break;
 72+	// ?man -i: interactive mode or prompt for confirmation
 73 	case 'i':
 74 		global_flags |= MOD_I;
 75 		break;
 76+	// ?man -k: specify option flag
 77 	case 'k':
 78 		addkeydef(EARGF(usage()), global_flags);
 79 		break;
 80+	// ?man -m: specify mode or limit
 81 	case 'm':
 82 		/* more or less for free, but for performance-reasons,
 83 		 * we should keep this flag in mind and maybe some later
 84@@ -425,30 +446,37 @@ main(int argc, char *argv[])
 85 		 * while merging large sorted files.
 86 		 */
 87 		break;
 88+	// ?man -n: print line numbers or counts
 89 	case 'n':
 90 		global_flags |= MOD_N;
 91 		break;
 92+	// ?man -o: specify output file
 93 	case 'o':
 94 		outfile = EARGF(usage());
 95 		break;
 96+	// ?man -r: operate recursively
 97 	case 'r':
 98 		global_flags |= MOD_R;
 99 		break;
100 #if FEATURE_SORT_STABLE
101+	// ?man -s: silent mode or print summary
102 	case 's':
103 		sflag = 1;
104 		break;
105 #endif
106+	// ?man -t: sort or specify timestamp
107 	case 't':
108 		fieldsep = EARGF(usage());
109 		if (!*fieldsep)
110 			eprintf("empty delimiter\n");
111 		fieldseplen = unescape(fieldsep);
112 		break;
113+	// ?man -u: unbuffered output
114 	case 'u':
115 		uflag = 1;
116 		break;
117 #if FEATURE_SORT_BIG
118+	// ?man -z: specify option flag
119 	case 'z':
120 		zflag = 1;
121 		break;
+11, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+split: split file into pieces
 5+usage: split [-a num] [-b num[k|m|g] | -l num] [-d]
 6+
 7+split a file into fixed-size pieces
 8+*/
 9+
10 #include <ctype.h>
11 #include <stdint.h>
12 #include <stdio.h>
13@@ -53,9 +60,11 @@ main(int argc, char *argv[])
14 	char name[NAME_MAX + 1], *prefix = "x", *file = NULL;
15 
16 	ARGBEGIN {
17+	// ?man -a: print or show all entries
18 	case 'a':
19 		slen = estrtonum(EARGF(usage()), 0, INT_MAX);
20 		break;
21+	// ?man -b: specify block size or base directory
22 	case 'b':
23 		always = 1;
24 		if ((size = parseoffset(EARGF(usage()))) < 0)
25@@ -63,10 +72,12 @@ main(int argc, char *argv[])
26 		if (!size)
27 			eprintf("size needs to be positive\n");
28 		break;
29+	// ?man -d: specify directory
30 	case 'd':
31 		base = 10;
32 		start = '0';
33 		break;
34+	// ?man -l: list in long format
35 	case 'l':
36 		always = 0;
37 		size = estrtonum(EARGF(usage()), 1, MIN(LLONG_MAX, SSIZE_MAX));
+23, -6
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+tail: output last part of files
 5+usage: tail [-f] [-c num | -m num | -n num | -num] [file ...]
 6+
 7+print the last lines or bytes of files
 8+*/
 9+
10 #include <sys/stat.h>
11 
12 #include <fcntl.h>
13@@ -27,13 +34,15 @@ dropinit(int fd, const char *fname, size_t count)
14 	count--;  /* numbering starts at 1 */
15 	while (count && (n = read(fd, buf, sizeof(buf))) > 0) {
16 		switch (mode) {
17-		case 'n':  /* lines */
18+		// ?man -n: print line numbers or counts
19+	case 'n':  /* lines */
20 			for (p = buf; count && n > 0; p++, n--) {
21 				if (*p == '\n')
22 					count--;
23 			}
24 			break;
25-		case 'c':  /* bytes */
26+		// ?man -c: print count or perform stdout action
27+	case 'c':  /* bytes */
28 			if (count > (size_t)n) {
29 				count -= n;
30 			} else {
31@@ -42,7 +51,8 @@ dropinit(int fd, const char *fname, size_t count)
32 				count = 0;
33 			}
34 			break;
35-		case 'm':  /* runes */
36+		// ?man -m: specify mode or limit
37+	case 'm':  /* runes */
38 			for (p = buf; count && n > 0; p += nr, n -= nr, count--) {
39 				nr = charntorune(&r, p, n);
40 				if (!nr) {
41@@ -103,7 +113,8 @@ taketail(int fd, const char *fname, size_t count)
42 			break;
43 		len += n;
44 		switch (mode) {
45-		case 'n':  /* lines */
46+		// ?man -n: print line numbers or counts
47+	case 'n':  /* lines */
48 			/* ignore the last character; if it is a newline, it
49 			 * ends the last line */
50 			for (p = buf + len - 2, left = count; p >= buf; p--) {
51@@ -116,10 +127,12 @@ taketail(int fd, const char *fname, size_t count)
52 				}
53 			}
54 			break;
55-		case 'c':  /* bytes */
56+		// ?man -c: print count or perform stdout action
57+	case 'c':  /* bytes */
58 			p = count < len ? buf + len - count : buf;
59 			break;
60-		case 'm':  /* runes */
61+		// ?man -m: specify mode or limit
62+	case 'm':  /* runes */
63 			for (p = buf + len - 1, left = count; p >= buf; p--) {
64 				/* skip utf-8 continuation bytes */
65 				if (UTF8_POINT(*p))
66@@ -157,11 +170,15 @@ main(int argc, char *argv[])
67 	int (*tail)(int, const char *, size_t) = taketail;
68 
69 	ARGBEGIN {
70+	// ?man -f: force the operation
71 	case 'f':
72 		fflag = 1;
73 		break;
74+	// ?man -c: print count or perform stdout action
75 	case 'c':
76+	// ?man -m: specify mode or limit
77 	case 'm':
78+	// ?man -n: print line numbers or counts
79 	case 'n':
80 		mode = ARGC();
81 		numstr = EARGF(usage());
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+tee: duplicate input
 5+usage: tee [-ai] [file ...]
 6+
 7+read from standard input and write to standard output and files
 8+*/
 9+
10 #include <fcntl.h>
11 #include <signal.h>
12 #include <unistd.h>
13@@ -21,9 +28,11 @@ main(int argc, char *argv[])
14 	char buf[BUFSIZ];
15 
16 	ARGBEGIN {
17+	// ?man -a: print or show all entries
18 	case 'a':
19 		aflag = O_APPEND;
20 		break;
21+	// ?man -i: interactive mode or prompt for confirmation
22 	case 'i':
23 		iflag = 1;
24 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+test: evaluate condition
 5+usage: test
 6+
 7+check file types and compare values, returning 0 or 1
 8+*/
 9+
10 #include <sys/stat.h>
11 
12 #include <ctype.h>
+8, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+time: time command execution
 5+usage: time [-p] cmd [arg ...]
 6+
 7+run a command and report its execution duration
 8+*/
 9+
10 #include <sys/times.h>
11 #include <sys/wait.h>
12 
13@@ -24,6 +31,7 @@ main(int argc, char *argv[])
14 	int status, savederrno, ret = 0;
15 
16 	ARGBEGIN {
17+	// ?man -p: preserve file attributes
18 	case 'p':
19 		break;
20 	default:
+14, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+touch: change file timestamps
 5+usage: touch [-acm] [-d time | -r ref_file | -t time | -T time]
 6+
 7+update the access and modification times of files
 8+*/
 9+
10 #include <sys/stat.h>
11 
12 #include <errno.h>
13@@ -112,20 +119,26 @@ main(int argc, char *argv[])
14 	char *ref = NULL;
15 
16 	ARGBEGIN {
17+	// ?man -a: print or show all entries
18 	case 'a':
19 		aflag = 1;
20 		break;
21+	// ?man -c: print count or perform stdout action
22 	case 'c':
23 		cflag = 1;
24 		break;
25+	// ?man -d: specify directory
26 	case 'd':
27+	// ?man -t: sort or specify timestamp
28 	case 't':
29 		times[0].tv_sec = parsetime(EARGF(usage()));
30 		times[0].tv_nsec = 0;
31 		break;
32+	// ?man -m: specify mode or limit
33 	case 'm':
34 		mflag = 1;
35 		break;
36+	// ?man -r: operate recursively
37 	case 'r':
38 		ref = EARGF(usage());
39 		if (stat(ref, &st) < 0)
40@@ -133,6 +146,7 @@ main(int argc, char *argv[])
41 		times[0] = st.st_atim;
42 		times[1] = st.st_mtim;
43 		break;
44+	// ?man -T: specify option flag
45 	case 'T':
46 		times[0].tv_sec = estrtonum(EARGF(usage()), 0, LLONG_MAX);
47 		times[0].tv_nsec = 0;
+11, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+tr: translate characters
 5+usage: tr [-cCds] set1 [set2]
 6+
 7+translate, squeeze, or delete characters from standard input
 8+*/
 9+
10 #include <stdlib.h>
11 
12 #include "utf.h"
13@@ -195,13 +202,17 @@ main(int argc, char *argv[])
14 	int ret = 0;
15 
16 	ARGBEGIN {
17+	// ?man -c: print count or perform stdout action
18 	case 'c':
19+	// ?man -C: specify option flag
20 	case 'C':
21 		cflag = 1;
22 		break;
23+	// ?man -d: specify directory
24 	case 'd':
25 		dflag = 1;
26 		break;
27+	// ?man -s: silent mode or print summary
28 	case 's':
29 		sflag = 1;
30 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+true: return successful exit status
 5+usage: true
 6+
 7+exit with status 0 representing success
 8+*/
 9+
10 int
11 main(void)
12 {
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+tsort: topological sort
 5+usage: tsort [file]
 6+
 7+perform a topological sort on input pairs
 8+*/
 9+
10 #include <stdio.h>
11 #include <string.h>
12 #include <stdlib.h>
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+tty: print terminal filename
 5+usage: tty 
 6+
 7+display the filename of the terminal connected to stdin
 8+*/
 9+
10 #include <stdio.h>
11 #include <unistd.h>
12 
+13, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+uname: print system info
 5+usage: uname [-amnrsv]
 6+
 7+display system hostname, kernel name, release, and architecture
 8+*/
 9+
10 #include <sys/utsname.h>
11 
12 #include <stdio.h>
13@@ -18,21 +25,27 @@ main(int argc, char *argv[])
14 	int mflag = 0, nflag = 0, rflag = 0, sflag = 0, vflag = 0;
15 
16 	ARGBEGIN {
17+	// ?man -a: print or show all entries
18 	case 'a':
19 		mflag = nflag = rflag = sflag = vflag = 1;
20 		break;
21+	// ?man -m: specify mode or limit
22 	case 'm':
23 		mflag = 1;
24 		break;
25+	// ?man -n: print line numbers or counts
26 	case 'n':
27 		nflag = 1;
28 		break;
29+	// ?man -r: operate recursively
30 	case 'r':
31 		rflag = 1;
32 		break;
33+	// ?man -s: silent mode or print summary
34 	case 's':
35 		sflag = 1;
36 		break;
37+	// ?man -v: verbose mode; show progress
38 	case 'v':
39 		vflag = 1;
40 		break;
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+unexpand: convert spaces to tabs
 5+usage: unexpand [-a] [-t tablist] [file ...]
 6+
 7+convert space characters to tab characters
 8+*/
 9+
10 #include <stdint.h>
11 #include <stdlib.h>
12 #include <string.h>
13@@ -136,11 +143,13 @@ main(int argc, char *argv[])
14 	char *tl = "8";
15 
16 	ARGBEGIN {
17+	// ?man -t: sort or specify timestamp
18 	case 't':
19 		tl = EARGF(usage());
20 		if (!*tl)
21 			eprintf("tablist cannot be empty\n");
22 		/* fallthrough */
23+	// ?man -a: print or show all entries
24 	case 'a':
25 		aflag = 1;
26 		break;
+12, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+uniq: report duplicate lines
 5+usage: uniq [-c] [-d | -u] [-f fields] [-s chars]
 6+
 7+filter out repeated lines from sorted files
 8+*/
 9+
10 #include <ctype.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13@@ -105,18 +112,23 @@ main(int argc, char *argv[])
14 	char *fname[2] = { "<stdin>", "<stdout>" };
15 
16 	ARGBEGIN {
17+	// ?man -c: print count or perform stdout action
18 	case 'c':
19 		countfmt = "%7ld ";
20 		break;
21+	// ?man -d: specify directory
22 	case 'd':
23 		dflag = 1;
24 		break;
25+	// ?man -u: unbuffered output
26 	case 'u':
27 		uflag = 1;
28 		break;
29+	// ?man -f: force the operation
30 	case 'f':
31 		fskip = estrtonum(EARGF(usage()), 0, INT_MAX);
32 		break;
33+	// ?man -s: silent mode or print summary
34 	case 's':
35 		sskip = estrtonum(EARGF(usage()), 0, INT_MAX);
36 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+unlink: remove a file
 5+usage: unlink file
 6+
 7+call the unlink system call to remove a file
 8+*/
 9+
10 #include <unistd.h>
11 
12 #include "util.h"
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+uudecode: decode uuencoded file
 5+usage: uudecode [-m] [-o output] [file]
 6+
 7+decode a file created by uuencode
 8+*/
 9+
10 #include <sys/stat.h>
11 
12 #include <errno.h>
13@@ -234,9 +241,11 @@ main(int argc, char *argv[])
14 	void (*d) (FILE *, FILE *) = NULL;
15 
16 	ARGBEGIN {
17+	// ?man -m: specify mode or limit
18 	case 'm':
19 		mflag = 1; /* accepted but unused (autodetect file type) */
20 		break;
21+	// ?man -o: specify output file
22 	case 'o':
23 		oflag = 1;
24 		ofname = EARGF(usage());
+8, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+uuencode: encode binary file
 5+usage: uuencode [-m] [file] name
 6+
 7+encode a binary file into ascii text
 8+*/
 9+
10 #include <sys/stat.h>
11 
12 #include <stdio.h>
13@@ -104,6 +111,7 @@ main(int argc, char *argv[])
14 	int ret = 0;
15 
16 	ARGBEGIN {
17+	// ?man -m: specify mode or limit
18 	case 'm':
19 		uuencode_f = uuencodeb64;
20 		break;
+11, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+wc: count lines, words, and bytes
 5+usage: wc [-c | -m] [-lw] [file ...]
 6+
 7+display the number of lines, words, and bytes in files
 8+*/
 9+
10 #include <string.h>
11 
12 #include "utf.h"
13@@ -74,15 +81,19 @@ main(int argc, char *argv[])
14 	int ret = 0;
15 
16 	ARGBEGIN {
17+	// ?man -c: print count or perform stdout action
18 	case 'c':
19 		cmode = 'c';
20 		break;
21+	// ?man -m: specify mode or limit
22 	case 'm':
23 		cmode = 'm';
24 		break;
25+	// ?man -l: list in long format
26 	case 'l':
27 		lflag = 1;
28 		break;
29+	// ?man -w: wait for completion
30 	case 'w':
31 		wflag = 1;
32 		break;
+21, -5
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+who: show logged in users
 5+usage: who [-ml]
 6+
 7+display a list of users currently logged into the system
 8+*/
 9+
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13@@ -21,11 +28,14 @@ main(int argc, char *argv[])
14 	struct utmp usr;
15 	FILE *ufp;
16 	char timebuf[sizeof "yyyy-mm-dd hh:mm"];
17+	char line_buf[sizeof(usr.ut_line) + 1];
18+	char name_buf[sizeof(usr.ut_name) + 1];
19 	char *tty, *ttmp;
20 	int mflag = 0, lflag = 0;
21 	time_t t;
22 
23 	ARGBEGIN {
24+	// ?man -m: specify mode or limit
25 	case 'm':
26 		mflag = 1;
27 		tty = ttyname(0);
28@@ -34,6 +44,7 @@ main(int argc, char *argv[])
29 		if ((ttmp = strrchr(tty, '/')))
30 			tty = ttmp+1;
31 		break;
32+	// ?man -l: list in long format
33 	case 'l':
34 		lflag = 1;
35 		break;
36@@ -48,16 +59,21 @@ main(int argc, char *argv[])
37 		eprintf("fopen: %s:", UTMP_PATH);
38 
39 	while (fread(&usr, sizeof(usr), 1, ufp) == 1) {
40-		if (!*usr.ut_name || !*usr.ut_line ||
41-		    usr.ut_line[0] == '~')
42+		memcpy(line_buf, usr.ut_line, sizeof(usr.ut_line));
43+		line_buf[sizeof(usr.ut_line)] = '\0';
44+		memcpy(name_buf, usr.ut_name, sizeof(usr.ut_name));
45+		name_buf[sizeof(usr.ut_name)] = '\0';
46+
47+		if (!*name_buf || !*line_buf ||
48+		    line_buf[0] == '~')
49 			continue;
50-		if (mflag != 0 && strcmp(usr.ut_line, tty) != 0)
51+		if (mflag != 0 && strcmp(line_buf, tty) != 0)
52 			continue;
53-		if (!!strcmp(usr.ut_name, "LOGIN") == lflag)
54+		if (!!strcmp(name_buf, "LOGIN") == lflag)
55 			continue;
56 		t = usr.ut_time;
57 		strftime(timebuf, sizeof timebuf, "%Y-%m-%d %H:%M", localtime(&t));
58-		printf("%-8s %-12s %-16s\n", usr.ut_name, usr.ut_line, timebuf);
59+		printf("%-8s %-12s %-16s\n", name_buf, line_buf, timebuf);
60 	}
61 	fclose(ufp);
62 	return 0;
+18, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+xargs: build and run command lines
 5+usage: xargs [-0prtx] [-E eofstr] [-I replstr] [-L maxlines] [-n
 6+
 7+execute commands built from standard input arguments
 8+*/
 9+
10 #include <sys/wait.h>
11 
12 #include <errno.h>
13@@ -278,33 +285,42 @@ main(int argc, char *argv[])
14 
15 	ARGBEGIN
16 	{
17+	// ?man -0: specify option flag
18 	case '0':
19 		nulflag = 1;
20 		break;
21+	// ?man -n: print line numbers or counts
22 	case 'n':
23 		nflag = 1;
24 		maxargs =
25 			estrtonum(EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
26 		break;
27+	// ?man -p: preserve file attributes
28 	case 'p':
29 		pflag = 1;
30 		break;
31+	// ?man -r: operate recursively
32 	case 'r':
33 		rflag = 1;
34 		break;
35+	// ?man -s: silent mode or print summary
36 	case 's':
37 		argmaxsz =
38 			estrtonum(EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
39 		break;
40+	// ?man -t: sort or specify timestamp
41 	case 't':
42 		tflag = 1;
43 		break;
44+	// ?man -x: hex format or match whole lines
45 	case 'x':
46 		xflag = 1;
47 		break;
48+	// ?man -E: specify option flag
49 	case 'E':
50 		eofstr = EARGF(usage());
51 		break;
52+	// ?man -I: specify option flag
53 	case 'I':
54 		Iflag = 1;
55 		xflag = 1;
56@@ -312,11 +328,13 @@ main(int argc, char *argv[])
57 		maxargs = 1;
58 		replstr = EARGF(usage());
59 		break;
60+	// ?man -L: specify option flag
61 	case 'L':
62 		Lflag = 1;
63 		maxlines =
64 			estrtonum(EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
65 		break;
66+	// ?man -P: specify option flag
67 	case 'P':
68 		maxprocs =
69 			estrtonum(EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
+10, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+base64: encode or decode base64
 5+usage: base64 [-d] [-i] [-w cols] [file]
 6+
 7+encode or decode data in base64 format
 8+*/
 9+
10 #include "util.h"
11 #include "arg.h"
12 
13@@ -142,12 +149,15 @@ main(int argc, char *argv[])
14 	fp = stdin;
15 
16 	ARGBEGIN {
17+	// ?man -d: specify directory
18 	case 'd':
19 		dflag = 1;
20 		break;
21+	// ?man -i: interactive mode or prompt for confirmation
22 	case 'i':
23 		iflag = 1;
24 		break;
25+	// ?man -w: wait for completion
26 	case 'w':
27 		wrap = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SSIZE_MAX));
28 		break;
+0, -0
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+chroot: run command in new root
 5+usage: chroot dir [cmd [arg ...]]
 6+
 7+run a command or shell with a substitute root directory
 8+*/
 9+
10 #include <errno.h>
11 #include <stdlib.h>
12 #include <unistd.h>
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+clear: clear terminal screen
 5+usage: clear 
 6+
 7+clear the terminal screen and scrollback buffer
 8+*/
 9+
10 #include <stdio.h>
11 
12 #include "util.h"
+8, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+cols: format columns
 5+usage: cols [-c num] [file ...]
 6+
 7+format standard input into vertical columns
 8+*/
 9+
10 #include <sys/ioctl.h>
11 
12 #include <limits.h>
13@@ -28,6 +35,7 @@ main(int argc, char *argv[])
14 	char *p;
15 
16 	ARGBEGIN {
17+	// ?man -c: print count or perform stdout action
18 	case 'c':
19 		cflag = 1;
20 		chars = estrtonum(EARGF(usage()), 1, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+cron: cron daemon
 5+usage: cron [-f file] [-n]
 6+
 7+daemon to run scheduled background commands
 8+*/
 9+
10 #include <sys/types.h>
11 #include <sys/wait.h>
12 
13@@ -498,9 +505,11 @@ main(int argc, char *argv[])
14 	struct sigaction sa;
15 
16 	ARGBEGIN {
17+	// ?man -n: print line numbers or counts
18 	case 'n':
19 		nflag = 1;
20 		break;
21+	// ?man -f: force the operation
22 	case 'f':
23 		config = EARGF(usage());
24 		break;
R cmd/linux/dmesg.c => cmd/pseudo/dmesg.c
+32, -19
 1@@ -1,5 +1,10 @@
 2 /* See LICENSE file for copyright and license details. */
 3-#include <sys/klog.h>
 4+/* ?man
 5+dmesg: print kernel ring buffer
 6+usage: dmesg [-Ccr] [-n level]
 7+
 8+display or control the kernel ring buffer messages
 9+*/
10 
11 #include <stdio.h>
12 #include <stdlib.h>
13@@ -8,12 +13,9 @@
14 
15 #include "util.h"
16 
17-enum {
18-	SYSLOG_ACTION_READ_ALL = 3,
19-	SYSLOG_ACTION_CLEAR = 5,
20-	SYSLOG_ACTION_CONSOLE_LEVEL = 8,
21-	SYSLOG_ACTION_SIZE_BUFFER = 10
22-};
23+ssize_t get_dmesg(char *, size_t);
24+int clear_dmesg(void);
25+int set_console_level(int);
26 
27 static void
28 dmesg_show(const void *buf, size_t n)
29@@ -37,45 +39,56 @@ usage(void)
30 int
31 main(int argc, char *argv[])
32 {
33-	int n;
34+	ssize_t n;
35 	char *buf;
36 	int cflag = 0;
37 	long level;
38 
39 	ARGBEGIN {
40+	// ?man -C: specify option flag
41 	case 'C':
42-		if (klogctl(SYSLOG_ACTION_CLEAR, NULL, 0) < 0)
43-			eprintf("klogctl:");
44+		if (clear_dmesg() < 0)
45+			eprintf("clear_dmesg:");
46 		return 0;
47+	// ?man -c: print count or perform stdout action
48 	case 'c':
49 		cflag = 1;
50 		break;
51+	// ?man -r: operate recursively
52 	case 'r':
53 		break;
54+	// ?man -n: print line numbers or counts
55 	case 'n':
56 		level = estrtol(EARGF(usage()), 10);
57-		if (klogctl(SYSLOG_ACTION_CONSOLE_LEVEL, NULL, level) < 0)
58-			eprintf("klogctl:");
59+		if (set_console_level(level) < 0)
60+			eprintf("set_console_level:");
61 		return 0;
62 	default:
63 		usage();
64 	} ARGEND;
65 
66-	n = klogctl(SYSLOG_ACTION_SIZE_BUFFER, NULL, 0);
67-	if (n < 0)
68-		eprintf("klogctl:");
69+	if (argc)
70+		usage();
71+
72+	n = get_dmesg(NULL, 0);
73+	if (n <= 0)
74+		n = 16384;
75 
76 	buf = emalloc(n);
77 
78-	n = klogctl(SYSLOG_ACTION_READ_ALL, buf, n);
79+	n = get_dmesg(buf, n);
80 	if (n < 0)
81-		eprintf("klogctl:");
82+		eprintf("get_dmesg:");
83 
84 	dmesg_show(buf, n);
85 
86-	if (cflag && klogctl(SYSLOG_ACTION_CLEAR, NULL, 0) < 0)
87-		eprintf("klogctl:");
88+	if (cflag && clear_dmesg() < 0)
89+		eprintf("clear_dmesg:");
90 
91 	free(buf);
92+
93+	if (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))
94+		return 1;
95+
96 	return 0;
97 }
R cmd/linux/fallocate.c => cmd/pseudo/fallocate.c
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+fallocate: preallocate file space
 5+usage: fallocate [-o num] -l num file ...
 6+
 7+preallocate or deallocate space to a file
 8+*/
 9+
10 #include <sys/stat.h>
11 
12 #include <fcntl.h>
13@@ -23,9 +30,11 @@ main(int argc, char *argv[])
14 	off_t size = 0, offset = 0;
15 
16 	ARGBEGIN {
17+	// ?man -l: list in long format
18 	case 'l':
19 		size = estrtonum(EARGF(usage()), 1, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
20 		break;
21+	// ?man -o: specify output file
22 	case 'o':
23 		offset = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
24 		break;
+12, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+flock: manage locks
 5+usage: flock [-nosux] file cmd [arg ...]
 6+
 7+acquire or release locks from shell scripts
 8+*/
 9+
10 #include <sys/file.h>
11 #include <sys/wait.h>
12 
13@@ -22,18 +29,23 @@ main(int argc, char *argv[])
14 	pid_t pid;
15 
16 	ARGBEGIN {
17+	// ?man -n: print line numbers or counts
18 	case 'n':
19 		nonblk = LOCK_NB;
20 		break;
21+	// ?man -o: specify output file
22 	case 'o':
23 		oflag = 1;
24 		break;
25+	// ?man -s: silent mode or print summary
26 	case 's':
27 		flags = LOCK_SH;
28 		break;
29+	// ?man -u: unbuffered output
30 	case 'u':
31 		flags = LOCK_UN;
32 		break;
33+	// ?man -x: hex format or match whole lines
34 	case 'x':
35 		flags = LOCK_EX;
36 		break;
R cmd/linux/free.c => cmd/pseudo/free.c
+33, -16
 1@@ -1,11 +1,18 @@
 2 /* See LICENSE file for copyright and license details. */
 3-#include <sys/sysinfo.h>
 4+/* ?man
 5+free: display memory usage
 6+usage: free [-bkmg]
 7+
 8+display the amount of free and used memory in the system
 9+*/
10 
11 #include <stdio.h>
12 #include <stdlib.h>
13 
14 #include "util.h"
15 
16+int get_meminfo(struct MemInfo *);
17+
18 static unsigned int mem_unit = 1;
19 static unsigned int unit_shift;
20 
21@@ -24,22 +31,22 @@ usage(void)
22 int
23 main(int argc, char *argv[])
24 {
25-	struct sysinfo info;
26-
27-	if (sysinfo(&info) < 0)
28-		eprintf("sysinfo:");
29-	mem_unit = info.mem_unit ? info.mem_unit : 1;
30+	struct MemInfo mi;
31 
32 	ARGBEGIN {
33+	// ?man -b: specify block size or base directory
34 	case 'b':
35 		unit_shift = 0;
36 		break;
37+	// ?man -k: specify option flag
38 	case 'k':
39 		unit_shift = 10;
40 		break;
41+	// ?man -m: specify mode or limit
42 	case 'm':
43 		unit_shift = 20;
44 		break;
45+	// ?man -g: specify option flag
46 	case 'g':
47 		unit_shift = 30;
48 		break;
49@@ -47,6 +54,12 @@ main(int argc, char *argv[])
50 		usage();
51 	} ARGEND;
52 
53+	if (argc)
54+		usage();
55+
56+	if (get_meminfo(&mi) < 0)
57+		eprintf("get_meminfo:");
58+
59 	printf("     %13s%13s%13s%13s%13s\n",
60 	       "total",
61 	       "used",
62@@ -54,19 +67,23 @@ main(int argc, char *argv[])
63 	       "shared", "buffers");
64 	printf("Mem: ");
65 	printf("%13llu%13llu%13llu%13llu%13llu\n",
66-	       scale(info.totalram),
67-	       scale(info.totalram - info.freeram),
68-	       scale(info.freeram),
69-	       scale(info.sharedram),
70-	       scale(info.bufferram));
71+	       scale(mi.total),
72+	       scale(mi.total - mi.free),
73+	       scale(mi.free),
74+	       scale(mi.shared),
75+	       scale(mi.buffers));
76 	printf("-/+ buffers/cache:");
77 	printf("%13llu%13llu\n",
78-	       scale(info.totalram - info.freeram - info.bufferram),
79-	       scale(info.freeram + info.bufferram));
80+	       scale(mi.total - mi.free - mi.buffers),
81+	       scale(mi.free + mi.buffers));
82 	printf("Swap:");
83 	printf("%13llu%13llu%13llu\n",
84-	       scale(info.totalswap),
85-	       scale(info.totalswap - info.freeswap),
86-	       scale(info.freeswap));
87+	       scale(mi.totalswap),
88+	       scale(mi.totalswap - mi.freeswap),
89+	       scale(mi.freeswap));
90+
91+	if (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))
92+		return 1;
93+
94 	return 0;
95 }
+11, -1
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+getty: set terminal mode
 5+usage: getty [tty] [term] [cmd] [args...]
 6+
 7+set terminal line discipline, speed, and mode
 8+*/
 9+
10 #include <sys/ioctl.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
13@@ -29,6 +36,7 @@ main(int argc, char *argv[])
14 {
15 	char term[128], logname[LOGIN_NAME_MAX], c;
16 	char hostname[HOST_NAME_MAX + 1];
17+	char ut_line_buf[UT_LINESIZE + 1];
18 	struct utmp usr;
19 	struct sigaction sa;
20 	FILE *fp;
21@@ -97,7 +105,9 @@ main(int argc, char *argv[])
22 				break;
23 			if (usr.ut_line[0] == '\0')
24 				continue;
25-			if (strcmp(usr.ut_line, tty) != 0)
26+			memcpy(ut_line_buf, usr.ut_line, UT_LINESIZE);
27+			ut_line_buf[UT_LINESIZE] = '\0';
28+			if (strcmp(ut_line_buf, tty) != 0)
29 				continue;
30 			memset(&usr, 0, sizeof(usr));
31 			fseek(fp, pos, SEEK_SET);
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+halt: halt or reboot
 5+usage: halt [-pr]
 6+
 7+halt, poweroff, or reboot the machine
 8+*/
 9+
10 #include <sys/syscall.h>
11 
12 #include <stdio.h>
13@@ -21,9 +28,11 @@ main(int argc, char *argv[])
14 	int cmd = LINUX_REBOOT_CMD_HALT;
15 
16 	ARGBEGIN {
17+	// ?man -p: preserve file attributes
18 	case 'p':
19 		pflag = 1;
20 		break;
21+	// ?man -r: operate recursively
22 	case 'r':
23 		rflag = 1;
24 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+hostname: show or set hostname
 5+usage: hostname [name]
 6+
 7+display or configure the system hostname
 8+*/
 9+
10 #include <stdio.h>
11 #include <string.h>
12 #include <unistd.h>
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+killall5: send signal to all processes
 5+usage: killall5 [-o pid1
 6+
 7+send a signal to all processes except kernel threads
 8+*/
 9+
10 #include <dirent.h>
11 #include <limits.h>
12 #include <signal.h>
13@@ -49,6 +56,7 @@ main(int argc, char *argv[])
14 	size_t i;
15 
16 	ARGBEGIN {
17+	// ?man -s: silent mode or print summary
18 	case 's':
19 		v = EARGF(usage());
20 		sig = strtol(v, &end, 0);
21@@ -63,6 +71,7 @@ main(int argc, char *argv[])
22 		if (i == LEN(sigs))
23 			eprintf("%s: unknown signal\n", v);
24 		break;
25+	// ?man -o: specify output file
26 	case 'o':
27 		oflag = 1;
28 		arg = EARGF(usage());
+11, -1
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+last: show last logged in users
 5+usage: last [user]
 6+
 7+display a list of recent user logins
 8+*/
 9+
10 #include <errno.h>
11 #include <libgen.h>
12 #include <paths.h>
13@@ -25,6 +32,7 @@ main(int argc, char **argv)
14 	FILE *fp;
15 	struct utmp ut;
16 	char *user, *file, *prog;
17+	char ut_name_buf[UT_NAMESIZE + 1];
18 	time_t t;
19 
20 	ARGBEGIN {
21@@ -49,8 +57,10 @@ main(int argc, char **argv)
22 		eprintf("fopen %s:", file);
23 
24 	while (fread(&ut, sizeof(ut), 1, fp) == 1) {
25+		memcpy(ut_name_buf, ut.ut_name, UT_NAMESIZE);
26+		ut_name_buf[UT_NAMESIZE] = '\0';
27 		if (ut.ut_type != USER_PROCESS ||
28-		    (user && strcmp(user, ut.ut_name))) {
29+		    (user && strcmp(user, ut_name_buf))) {
30 			continue;
31 		}
32 
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+lastlog: report recent logins
 5+usage: lastlog
 6+
 7+display the most recent login times of users
 8+*/
 9+
10 #include <errno.h>
11 #include <paths.h>
12 #include <pwd.h>
+8, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+login: begin terminal session
 5+usage: login [-p] username
 6+
 7+authenticate and start a session on the system
 8+*/
 9+
10 #include <sys/ioctl.h>
11 #include <sys/types.h>
12 
13@@ -78,6 +85,7 @@ main(int argc, char *argv[])
14 	int pflag = 0;
15 
16 	ARGBEGIN {
17+	// ?man -p: preserve file attributes
18 	case 'p':
19 		pflag = 1;
20 		break;
+10, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+md5sum: compute md5 checksums
 5+usage: md5sum [-c] [file ...]
 6+
 7+compute and check md5 message digests
 8+*/
 9+
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <stdint.h>
13@@ -28,10 +35,13 @@ main(int argc, char *argv[])
14 	uint8_t md[MD5_DIGEST_LENGTH];
15 
16 	ARGBEGIN {
17+	// ?man -b: specify block size or base directory
18 	case 'b':
19+	// ?man -t: sort or specify timestamp
20 	case 't':
21 		/* ignore */
22 		break;
23+	// ?man -c: print count or perform stdout action
24 	case 'c':
25 		cryptfunc = cryptcheck;
26 		break;
+12, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+mktemp: create temporary file
 5+usage: mktemp [-dqtu] [-p directory] [template]
 6+
 7+create a temporary file or directory safely
 8+*/
 9+
10 #include <libgen.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13@@ -22,19 +29,24 @@ main(int argc, char *argv[])
14 	size_t len;
15 
16 	ARGBEGIN {
17+	// ?man -d: specify directory
18 	case 'd':
19 		dflag = 1;
20 		break;
21+	// ?man -p: preserve file attributes
22 	case 'p':
23 		pflag = 1;
24 		pdir = EARGF(usage());
25 		break;
26+	// ?man -q: quiet mode; suppress output
27 	case 'q':
28 		qflag = 1;
29 		break;
30+	// ?man -t: sort or specify timestamp
31 	case 't':
32 		tflag = 1;
33 		break;
34+	// ?man -u: unbuffered output
35 	case 'u':
36 		uflag = 1;
37 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+nologin: politely refuse login
 5+usage: nologin
 6+
 7+politely refuse a login attempt by exiting with status 1
 8+*/
 9+
10 #include <fcntl.h>
11 #include <stdio.h>
12 #include <unistd.h>
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+pagesize: print system page size
 5+usage: pagesize 
 6+
 7+display the size of a page in memory
 8+*/
 9+
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <unistd.h>
R cmd/linux/pidof.c => cmd/pseudo/pidof.c
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+pidof: find process ids
 5+usage: pidof [-o pid1
 6+
 7+find the process identity numbers of running programs
 8+*/
 9+
10 #include <sys/types.h>
11 
12 #include <dirent.h>
13@@ -39,9 +46,11 @@ main(int argc, char *argv[])
14 	struct pidentry *pe;
15 
16 	ARGBEGIN {
17+	// ?man -s: silent mode or print summary
18 	case 's':
19 		sflag = 1;
20 		break;
21+	// ?man -o: specify output file
22 	case 'o':
23 		oflag = 1;
24 		arg = EARGF(usage());
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+printenv: print environment variables
 5+usage: printenv [var ...]
 6+
 7+display all or part of the current environment
 8+*/
 9+
10 #include <stdio.h>
11 #include <stdlib.h>
12 
R cmd/linux/pwdx.c => cmd/pseudo/pwdx.c
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+pwdx: print working directory of process
 5+usage: pwdx pid...
 6+
 7+display the current working directory of a process by pid
 8+*/
 9+
10 #include <errno.h>
11 #include <limits.h>
12 #include <stdio.h>
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+respawn: restart command on exit
 5+usage: respawn [-l fifo] [-d N] cmd [args...]
 6+
 7+run a command and restart it automatically when it exits
 8+*/
 9+
10 #include <sys/stat.h>
11 #include <sys/time.h>
12 #include <sys/types.h>
13@@ -42,9 +49,11 @@ main(int argc, char *argv[])
14 	int polln;
15 
16 	ARGBEGIN {
17+	// ?man -d: specify directory
18 	case 'd':
19 		delay = estrtol(EARGF(usage()), 0);
20 		break;
21+	// ?man -l: list in long format
22 	case 'l':
23 		fifo = EARGF(usage());
24 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+rev: reverse lines
 5+usage: rev [file ...]
 6+
 7+reverse the order of characters in each line of input
 8+*/
 9+
10 #include <stdio.h>
11 #include <string.h>
12 #include <unistd.h>
+13, -2
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+seq: print sequence of numbers
 5+usage: seq [-f fmt] [-s sep] [-w]
 6+
 7+print a sequence of numbers from start to end
 8+*/
 9+
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13@@ -9,7 +16,7 @@ static int
14 digitsleft(const char *d)
15 {
16 	int shift;
17-	char *exp;
18+	const char *exp;
19 
20 	if (*d == '+')
21 		d++;
22@@ -23,7 +30,7 @@ static int
23 digitsright(const char *d)
24 {
25 	int shift, after;
26-	char *exp;
27+	const char *exp;
28 
29 	exp = strpbrk(d, "eE");
30 	shift = exp ? estrtonum(&exp[1], INT_MIN, INT_MAX) : 0;
31@@ -58,6 +65,7 @@ format:
32 		fmt++;
33 
34 	switch (*fmt) {
35+	// ?man -f: force the operation
36 	case 'f': case 'F':
37 	case 'g': case 'G':
38 	case 'e': case 'E':
39@@ -85,14 +93,17 @@ main(int argc, char *argv[])
40 	const char *starts = "1", *steps = "1", *ends = "1", *sep = "\n";
41 
42 	ARGBEGIN {
43+	// ?man -f: force the operation
44 	case 'f':
45 		if (!validfmt(tmp=EARGF(usage())))
46 			eprintf("%s: invalid format\n", tmp);
47 		fmt = tmp;
48 		break;
49+	// ?man -s: silent mode or print summary
50 	case 's':
51 		sep = EARGF(usage());
52 		break;
53+	// ?man -w: wait for completion
54 	case 'w':
55 		wflag = 1;
56 		break;
+8, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+setsid: run in new session
 5+usage: setsid [-f] cmd [arg ...]
 6+
 7+run a program in a new session
 8+*/
 9+
10 #include <errno.h>
11 #include <unistd.h>
12 
13@@ -18,6 +25,7 @@ main(int argc, char *argv[])
14 	int savederrno;
15 
16 	ARGBEGIN {
17+	// ?man -f: force the operation
18 	case 'f':
19 		fflag = 1;
20 		break;
+10, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+sha1sum: compute sha1 checksums
 5+usage: sha1sum [-c] [file ...]
 6+
 7+compute and check sha1 message digests
 8+*/
 9+
10 #include <stdint.h>
11 #include <stdio.h>
12 
13@@ -27,10 +34,13 @@ main(int argc, char *argv[])
14 	uint8_t md[SHA1_DIGEST_LENGTH];
15 
16 	ARGBEGIN {
17+	// ?man -b: specify block size or base directory
18 	case 'b':
19+	// ?man -t: sort or specify timestamp
20 	case 't':
21 		/* ignore */
22 		break;
23+	// ?man -c: print count or perform stdout action
24 	case 'c':
25 		cryptfunc = cryptcheck;
26 		break;
+10, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+sha224sum: compute sha224 checksums
 5+usage: sha224sum [-c] [file ...]
 6+
 7+compute and check sha224 message digests
 8+*/
 9+
10 #include <stdint.h>
11 #include <stdio.h>
12 
13@@ -27,10 +34,13 @@ main(int argc, char *argv[])
14 	uint8_t md[SHA224_DIGEST_LENGTH];
15 
16 	ARGBEGIN {
17+	// ?man -b: specify block size or base directory
18 	case 'b':
19+	// ?man -t: sort or specify timestamp
20 	case 't':
21 		/* ignore */
22 		break;
23+	// ?man -c: print count or perform stdout action
24 	case 'c':
25 		cryptfunc = cryptcheck;
26 		break;
+10, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+sha256sum: compute sha256 checksums
 5+usage: sha256sum [-c] [file ...]
 6+
 7+compute and check sha256 message digests
 8+*/
 9+
10 #include <stdint.h>
11 #include <stdio.h>
12 
13@@ -27,10 +34,13 @@ main(int argc, char *argv[])
14 	uint8_t md[SHA256_DIGEST_LENGTH];
15 
16 	ARGBEGIN {
17+	// ?man -b: specify block size or base directory
18 	case 'b':
19+	// ?man -t: sort or specify timestamp
20 	case 't':
21 		/* ignore */
22 		break;
23+	// ?man -c: print count or perform stdout action
24 	case 'c':
25 		cryptfunc = cryptcheck;
26 		break;
+10, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+sha384sum: compute sha384 checksums
 5+usage: sha384sum [-c] [file ...]
 6+
 7+compute and check sha384 message digests
 8+*/
 9+
10 #include <stdint.h>
11 #include <stdio.h>
12 
13@@ -27,10 +34,13 @@ main(int argc, char *argv[])
14 	uint8_t md[SHA384_DIGEST_LENGTH];
15 
16 	ARGBEGIN {
17+	// ?man -b: specify block size or base directory
18 	case 'b':
19+	// ?man -t: sort or specify timestamp
20 	case 't':
21 		/* ignore */
22 		break;
23+	// ?man -c: print count or perform stdout action
24 	case 'c':
25 		cryptfunc = cryptcheck;
26 		break;
+10, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+sha512-224sum: compute sha512/224 checksums
 5+usage: sha512-224sum [-c] [file ...]
 6+
 7+compute and check sha512/224 message digests
 8+*/
 9+
10 #include <stdint.h>
11 #include <stdio.h>
12 
13@@ -27,10 +34,13 @@ main(int argc, char *argv[])
14 	uint8_t md[SHA512_224_DIGEST_LENGTH];
15 
16 	ARGBEGIN {
17+	// ?man -b: specify block size or base directory
18 	case 'b':
19+	// ?man -t: sort or specify timestamp
20 	case 't':
21 		/* ignore */
22 		break;
23+	// ?man -c: print count or perform stdout action
24 	case 'c':
25 		cryptfunc = cryptcheck;
26 		break;
+10, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+sha512-256sum: compute sha512/256 checksums
 5+usage: sha512-256sum [-c] [file ...]
 6+
 7+compute and check sha512/256 message digests
 8+*/
 9+
10 #include <stdint.h>
11 #include <stdio.h>
12 
13@@ -27,10 +34,13 @@ main(int argc, char *argv[])
14 	uint8_t md[SHA512_256_DIGEST_LENGTH];
15 
16 	ARGBEGIN {
17+	// ?man -b: specify block size or base directory
18 	case 'b':
19+	// ?man -t: sort or specify timestamp
20 	case 't':
21 		/* ignore */
22 		break;
23+	// ?man -c: print count or perform stdout action
24 	case 'c':
25 		cryptfunc = cryptcheck;
26 		break;
+10, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+sha512sum: compute sha512 checksums
 5+usage: sha512sum [-c] [file ...]
 6+
 7+compute and check sha512 message digests
 8+*/
 9+
10 #include <stdint.h>
11 #include <stdio.h>
12 
13@@ -27,10 +34,13 @@ main(int argc, char *argv[])
14 	uint8_t md[SHA512_DIGEST_LENGTH];
15 
16 	ARGBEGIN {
17+	// ?man -b: specify block size or base directory
18 	case 'b':
19+	// ?man -t: sort or specify timestamp
20 	case 't':
21 		/* ignore */
22 		break;
23+	// ?man -c: print count or perform stdout action
24 	case 'c':
25 		cryptfunc = cryptcheck;
26 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+sponge: soak up input
 5+usage: sponge file
 6+
 7+soak up standard input and write to a file
 8+*/
 9+
10 #include <fcntl.h>
11 #include <stdlib.h>
12 #include <unistd.h>
+27, -8
  1@@ -1,3 +1,10 @@
  2+/* ?man
  3+stat: display file status
  4+usage: stat [-L] [-t]
  5+
  6+display file or filesystem status information
  7+*/
  8+
  9 #include "config.h"
 10 #include "util.h"
 11 
 12@@ -56,10 +63,12 @@ print_custom(const char *file, struct stat *st, const char *fmt)
 13 			p++;
 14 			switch (*p) {
 15 			case 'n':  putchar('\n'); break;
 16-			case 't':  putchar('\t'); break;
 17+			// ?man -t: sort or specify timestamp
 18+	case 't':  putchar('\t'); break;
 19 			case 'r':  putchar('\r'); break;
 20 			case 'b':  putchar('\b'); break;
 21-			case 'f':  putchar('\f'); break;
 22+			// ?man -f: force the operation
 23+	case 'f':  putchar('\f'); break;
 24 			case '\\': putchar('\\'); break;
 25 			case '\0': return;
 26 			default:   putchar(*p);  break;
 27@@ -169,7 +178,8 @@ print_custom(const char *file, struct stat *st, const char *fmt)
 28 			fmt_spec[fmt_len] = '\0';
 29 			printf(fmt_spec, (long long)st->st_size);
 30 			break;
 31-		case 't':
 32+		// ?man -t: sort or specify timestamp
 33+	case 't':
 34 			fmt_spec[fmt_len++] = 'x';
 35 			fmt_spec[fmt_len] = '\0';
 36 			printf(fmt_spec, major(st->st_rdev));
 37@@ -273,10 +283,12 @@ print_fs_custom(const char *file, struct statfs *st, const char *fmt)
 38 			p++;
 39 			switch (*p) {
 40 			case 'n':  putchar('\n'); break;
 41-			case 't':  putchar('\t'); break;
 42+			// ?man -t: sort or specify timestamp
 43+	case 't':  putchar('\t'); break;
 44 			case 'r':  putchar('\r'); break;
 45 			case 'b':  putchar('\b'); break;
 46-			case 'f':  putchar('\f'); break;
 47+			// ?man -f: force the operation
 48+	case 'f':  putchar('\f'); break;
 49 			case '\\': putchar('\\'); break;
 50 			case '\0': return;
 51 			default:   putchar(*p);  break;
 52@@ -322,7 +334,8 @@ print_fs_custom(const char *file, struct statfs *st, const char *fmt)
 53 			fmt_spec[fmt_len] = '\0';
 54 			printf(fmt_spec, (long long)st->f_blocks);
 55 			break;
 56-		case 'c':
 57+		// ?man -c: print count or perform stdout action
 58+	case 'c':
 59 			fmt_spec[fmt_len++] = 'l';
 60 			fmt_spec[fmt_len++] = 'l';
 61 			fmt_spec[fmt_len++] = 'd';
 62@@ -336,7 +349,8 @@ print_fs_custom(const char *file, struct statfs *st, const char *fmt)
 63 			fmt_spec[fmt_len] = '\0';
 64 			printf(fmt_spec, (long long)st->f_ffree);
 65 			break;
 66-		case 'f':
 67+		// ?man -f: force the operation
 68+	case 'f':
 69 			fmt_spec[fmt_len++] = 'l';
 70 			fmt_spec[fmt_len++] = 'l';
 71 			fmt_spec[fmt_len++] = 'd';
 72@@ -373,7 +387,8 @@ print_fs_custom(const char *file, struct statfs *st, const char *fmt)
 73 			fmt_spec[fmt_len] = '\0';
 74 			printf(fmt_spec, (long)st->f_frsize ? (long)st->f_frsize : (long)st->f_bsize);
 75 			break;
 76-		case 't':
 77+		// ?man -t: sort or specify timestamp
 78+	case 't':
 79 			fmt_spec[fmt_len++] = 'l';
 80 			fmt_spec[fmt_len++] = 'x';
 81 			fmt_spec[fmt_len] = '\0';
 82@@ -463,19 +478,23 @@ main(int argc, char *argv[])
 83 	void (*showstat)(const char *, struct stat *) = show_stat;
 84 
 85 	ARGBEGIN {
 86+	// ?man -L: specify option flag
 87 	case 'L':
 88 		fn = stat;
 89 		fnname = "stat";
 90 		break;
 91+	// ?man -t: sort or specify timestamp
 92 	case 't':
 93 		showstat = show_stat_terse;
 94 		break;
 95 #if FEATURE_STAT_FILESYSTEM
 96+	// ?man -f: force the operation
 97 	case 'f':
 98 		fflag = 1;
 99 		break;
100 #endif
101 #if FEATURE_STAT_FORMAT
102+	// ?man -c: print count or perform stdout action
103 	case 'c':
104 		format = EARGF(usage());
105 		break;
+25, -1
 1@@ -1,3 +1,10 @@
 2+/* ?man
 3+tar: tape archiver
 4+usage: tar [x | t | -x | -t] [-C dir] [-J | -Z | -a | -j | -z] [-m] [-p]
 5+
 6+manipulate tape archive files
 7+*/
 8+
 9 #include "config.h"
10 #include "fs.h"
11 #include "utf.h"
12@@ -421,7 +428,7 @@ unarchive(char *fname, ssize_t l, char b[BLKSIZ])
13 	if (fd != -1) {
14 		for (; l > 0; l -= BLKSIZ)
15 			if (eread(tarfd, b, BLKSIZ) > 0)
16-				ewrite(fd, b, MIN(l, BLKSIZ));
17+				ewrite(fd, b, MIN(l, (ssize_t)BLKSIZ));
18 		close(fd);
19 	}
20 
21@@ -670,38 +677,52 @@ main(int argc, char *argv[])
22 		*(argv[1]+1) ? *argv[1] = '-' : (*++argv = argv0, --argc);
23 
24 	ARGBEGIN {
25+	// ?man -x: extract files from an archive
26 	case 'x':
27 #if FEATURE_TAR_CREATE
28+	// ?man -c: create a new archive
29 	case 'c':
30 #endif
31+	// ?man -t: list the contents of an archive
32 	case 't':
33 		mode = ARGC();
34 		break;
35+	// ?man -C: specify option flag
36 	case 'C':
37 		dir = EARGF(usage());
38 		break;
39+	// ?man -f: specify archive file
40 	case 'f':
41 		file = EARGF(usage());
42 		break;
43+	// ?man -m: specify mode or limit
44 	case 'm':
45 		mflag = 1;
46 		break;
47+	// ?man -J: specify option flag
48 	case 'J':
49+	// ?man -Z: specify option flag
50 	case 'Z':
51+	// ?man -a: print or show all entries
52 	case 'a':
53+	// ?man -j: specify option flag
54 	case 'j':
55+	// ?man -z: specify option flag
56 	case 'z':
57 		filtermode = ARGC();
58 		filtertool = filtertools[filtermode];
59 		break;
60+	// ?man -h: suppress headers or print help
61 	case 'h':
62 #if FEATURE_TAR_CREATE
63 		r.follow = 'L';
64 #endif
65 		break;
66+	// ?man -v: verbosely list files processed
67 	case 'v':
68 		vflag = 1;
69 		break;
70+	// ?man -p: preserve file attributes
71 	case 'p':
72 		break;  /* do nothing as already default behaviour */
73 #if FEATURE_TAR_EXCLUDE
74@@ -724,6 +745,7 @@ main(int argc, char *argv[])
75 
76 	switch (mode) {
77 #if FEATURE_TAR_CREATE
78+	// ?man -c: create a new archive
79 	case 'c':
80 		if (!argc)
81 			usage();
82@@ -747,7 +769,9 @@ main(int argc, char *argv[])
83 			recurse(AT_FDCWD, *argv, NULL, &r);
84 		break;
85 #endif
86+	// ?man -t: list the contents of an archive
87 	case 't':
88+	// ?man -x: extract files from an archive
89 	case 'x':
90 		tarfd = 0;
91 		if (file && *file != '-') {
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+truncate: set file size
 5+usage: truncate [-c] -s size file...
 6+
 7+shrink or extend a file to a specified size
 8+*/
 9+
10 #include <sys/stat.h>
11 
12 #include <fcntl.h>
13@@ -22,10 +29,12 @@ main(int argc, char *argv[])
14 	long size = 0;
15 
16 	ARGBEGIN {
17+	// ?man -s: silent mode or print summary
18 	case 's':
19 		sflag = 1;
20 		size = estrtol(EARGF(usage()), 10);
21 		break;
22+	// ?man -c: print count or perform stdout action
23 	case 'c':
24 		cflag = 1;
25 		break;
R cmd/linux/uptime.c => cmd/pseudo/uptime.c
+29, -12
 1@@ -1,5 +1,10 @@
 2 /* See LICENSE file for copyright and license details. */
 3-#include <sys/sysinfo.h>
 4+/* ?man
 5+uptime: show system uptime
 6+usage: uptime 
 7+
 8+display how long the system has been running and load averages
 9+*/
10 
11 #include <stdio.h>
12 #include <stdlib.h>
13@@ -10,6 +15,9 @@
14 #include "config.h"
15 #include "util.h"
16 
17+int get_uptime(long *);
18+int get_loads(double *);
19+
20 static void
21 usage(void)
22 {
23@@ -21,7 +29,8 @@ main(int argc, char *argv[])
24 {
25 	struct utmpx utx;
26 	FILE *ufp;
27-	struct sysinfo info;
28+	long uptime;
29+	double loads[3];
30 	time_t tmptime;
31 	struct tm *now;
32 	unsigned int days, hours, minutes;
33@@ -33,16 +42,23 @@ main(int argc, char *argv[])
34 		usage();
35 	} ARGEND;
36 
37-	if (sysinfo(&info) < 0)
38-		eprintf("sysinfo:");
39+	if (argc)
40+		usage();
41+
42+	if (get_uptime(&uptime) < 0)
43+		eprintf("get_uptime:");
44+	if (get_loads(loads) < 0)
45+		eprintf("get_loads:");
46+
47 	time(&tmptime);
48 	now = localtime(&tmptime);
49 	printf(" %02d:%02d:%02d up ", now->tm_hour, now->tm_min, now->tm_sec);
50-	info.uptime /= 60;
51-	minutes = info.uptime % 60;
52-	info.uptime /= 60;
53-	hours = info.uptime % 24;
54-	days = info.uptime / 24;
55+
56+	uptime /= 60;
57+	minutes = uptime % 60;
58+	uptime /= 60;
59+	hours = uptime % 24;
60+	days = uptime / 24;
61 	if (days)
62 		printf("%d day%s, ", days, days != 1 ? "s" : "");
63 	if (hours)
64@@ -65,9 +81,10 @@ main(int argc, char *argv[])
65 	}
66 
67 	printf(" load average: %.02f, %.02f, %.02f\n",
68-	       info.loads[0] / 65536.0f,
69-	       info.loads[1] / 65536.0f,
70-	       info.loads[2] / 65536.0f);
71+	       loads[0], loads[1], loads[2]);
72+
73+	if (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))
74+		return 1;
75 
76 	return 0;
77 }
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+watch: execute periodically
 5+usage: watch [-t] [-n interval] command
 6+
 7+run a program periodically, showing output fullscreen
 8+*/
 9+
10 #include <errno.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13@@ -22,8 +29,10 @@ main(int argc, char *argv[])
14 	int i;
15 
16 	ARGBEGIN {
17+	// ?man -t: sort or specify timestamp
18 	case 't':
19 		break;
20+	// ?man -n: print line numbers or counts
21 	case 'n':
22 		period = strtof(EARGF(usage()), &end);
23 		if (*end != '\0' || errno != 0)
+8, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+which: locate a command
 5+usage: which [-a] name ...
 6+
 7+find the path of executable files in PATH
 8+*/
 9+
10 #include <sys/stat.h>
11 #include <sys/types.h>
12 
13@@ -75,6 +82,7 @@ main(int argc, char *argv[])
14 	int found = 0, foundall = 1;
15 
16 	ARGBEGIN {
17+	// ?man -a: print or show all entries
18 	case 'a':
19 		aflag = 1;
20 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+whoami: print effective user name
 5+usage: whoami 
 6+
 7+display the effective user name of the current process
 8+*/
 9+
10 #include <errno.h>
11 #include <stdio.h>
12 #include <unistd.h>
+15, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+xinstall: copy files and set attributes
 5+usage: xinstall [-g group] [-o owner] [-m mode] (-d dir ... | [-D] (-t dest source ... | source ... dest))
 6+
 7+copy files and set their permissions and ownership
 8+*/
 9+
10 #include <grp.h>
11 #include <pwd.h>
12 #include <errno.h>
13@@ -96,27 +103,35 @@ main(int argc, char *argv[])
14 	char *p;
15 
16 	ARGBEGIN {
17+	// ?man -c: print count or perform stdout action
18 	case 'c':
19 		/* no-op for compatibility */
20 		break;
21+	// ?man -d: specify directory
22 	case 'd':
23 		dflag = 1;
24 		break;
25+	// ?man -D: specify option flag
26 	case 'D':
27 		Dflag = 1;
28 		break;
29+	// ?man -s: silent mode or print summary
30 	case 's':
31 		/* no-op for compatibility */
32 		break;
33+	// ?man -g: specify option flag
34 	case 'g':
35 		gflag = EARGF(usage());
36 		break;
37+	// ?man -o: specify output file
38 	case 'o':
39 		oflag = EARGF(usage());
40 		break;
41+	// ?man -m: specify mode or limit
42 	case 'm':
43 		mflag = EARGF(usage());
44 		break;
45+	// ?man -t: sort or specify timestamp
46 	case 't':
47 		tflag = EARGF(usage());
48 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+yes: output string repeatedly
 5+usage: yes [string]
 6+
 7+output a string repeatedly until terminated
 8+*/
 9+
10 #include <stdio.h>
11 
12 #include "util.h"
+8, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+mknod: create special files
 5+usage: mknod [-m mode] name b|c|u major minor
 6+
 7+create block or character special files
 8+*/
 9+
10 #include <sys/stat.h>
11 #include <sys/types.h>
12 #ifndef makedev
13@@ -28,6 +35,7 @@ main(int argc, char *argv[])
14 	dev_t dev;
15 
16 	ARGBEGIN {
17+	// ?man -m: specify mode or limit
18 	case 'm':
19 		mode = parsemode(EARGF(usage()), mode, umask(0));
20 		break;
+7, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+passwd: change user password
 5+usage: passwd [username]
 6+
 7+update a user authentication password
 8+*/
 9+
10 #include <sys/ioctl.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
+9, -0
 1@@ -1,4 +1,11 @@
 2 /* See LICENSE file for copyright and license details. */
 3+/* ?man
 4+su: run command with substitute user id
 5+usage: su [-lp] [username]
 6+
 7+run a shell or command with another user id
 8+*/
 9+
10 #include <sys/types.h>
11 
12 #include <errno.h>
13@@ -33,9 +40,11 @@ main(int argc, char *argv[])
14 	uid_t uid;
15 
16 	ARGBEGIN {
17+	// ?man -l: list in long format
18 	case 'l':
19 		lflag = 1;
20 		break;
21+	// ?man -p: preserve file attributes
22 	case 'p':
23 		pflag = 1;
24 		break;
+44, -14
  1@@ -13,7 +13,7 @@ BUILD_LINUX  = 1
  2 BUILD_NET    = 1
  3 BUILD_XSI    = 1
  4 BUILD_PSEUDO = 1
  5-BUILD_MAKE   = 1
  6+BUILD_DEV    = 0
  7 
  8 # posix tools
  9 BUILD_POSIX_BASENAME = $(BUILD_POSIX)
 10@@ -98,42 +98,39 @@ BUILD_POSIX_PAX = $(BUILD_POSIX)
 11 BUILD_LINUX_BLKDISCARD = $(BUILD_LINUX)
 12 BUILD_LINUX_CHVT = $(BUILD_LINUX)
 13 BUILD_LINUX_CTRLALTDEL = $(BUILD_LINUX)
 14-BUILD_LINUX_DMESG = $(BUILD_LINUX)
 15 BUILD_LINUX_EJECT = $(BUILD_LINUX)
 16-BUILD_LINUX_FALLOCATE = $(BUILD_LINUX)
 17-BUILD_LINUX_FREE = $(BUILD_LINUX)
 18 BUILD_LINUX_FREERAMDISK = $(BUILD_LINUX)
 19 BUILD_LINUX_FSFREEZE = $(BUILD_LINUX)
 20 BUILD_LINUX_HWCLOCK = $(BUILD_LINUX)
 21 BUILD_LINUX_INSMOD = $(BUILD_LINUX)
 22 BUILD_LINUX_LSMOD = $(BUILD_LINUX)
 23+BUILD_LINUX_MODPROBE = $(BUILD_LINUX)
 24+BUILD_LINUX_DEPMOD = $(BUILD_LINUX)
 25 BUILD_LINUX_MKSWAP = $(BUILD_LINUX)
 26 BUILD_LINUX_MOUNT = $(BUILD_LINUX)
 27 BUILD_LINUX_MOUNTPOINT = $(BUILD_LINUX)
 28-BUILD_LINUX_PIDOF = $(BUILD_LINUX)
 29 BUILD_LINUX_PIVOT_ROOT = $(BUILD_LINUX)
 30-BUILD_LINUX_PWDX = $(BUILD_LINUX)
 31 BUILD_LINUX_READAHEAD = $(BUILD_LINUX)
 32 BUILD_LINUX_RMMOD = $(BUILD_LINUX)
 33 BUILD_LINUX_SWAPLABEL = $(BUILD_LINUX)
 34 BUILD_LINUX_SWAPOFF = $(BUILD_LINUX)
 35 BUILD_LINUX_SWAPON = $(BUILD_LINUX)
 36 BUILD_LINUX_SWITCH_ROOT = $(BUILD_LINUX)
 37+BUILD_LINUX_TUNCTL = $(BUILD_LINUX)
 38 BUILD_LINUX_UMOUNT = $(BUILD_LINUX)
 39 BUILD_LINUX_UNSHARE = $(BUILD_LINUX)
 40-BUILD_LINUX_UPTIME = $(BUILD_LINUX)
 41 BUILD_LINUX_VTALLOW = $(BUILD_LINUX)
 42 
 43 # net tools
 44 BUILD_NET_NETCAT = $(BUILD_NET)
 45 BUILD_NET_TFTP = $(BUILD_NET)
 46-BUILD_NET_TUNCTL = $(BUILD_NET)
 47 BUILD_NET_WGET = $(BUILD_NET)
 48 BUILD_NET_PING = $(BUILD_NET)
 49 BUILD_NET_SDHCP = $(BUILD_NET)
 50 BUILD_NET_IFCONFIG = $(BUILD_NET)
 51 BUILD_NET_HOST = $(BUILD_NET)
 52 BUILD_NET_HTTPD = $(BUILD_NET)
 53+BUILD_NET_IP = $(BUILD_NET)
 54 
 55 # xsi tools
 56 BUILD_XSI_MKNOD = $(BUILD_XSI)
 57@@ -180,9 +177,18 @@ BUILD_PSEUDO_XINSTALL = $(BUILD_PSEUDO)
 58 BUILD_PSEUDO_YES = $(BUILD_PSEUDO)
 59 BUILD_PSEUDO_BASE64 = $(BUILD_PSEUDO)
 60 BUILD_PSEUDO_B3SUM = $(BUILD_PSEUDO)
 61+BUILD_PSEUDO_DMESG = $(BUILD_PSEUDO)
 62+BUILD_PSEUDO_FALLOCATE = $(BUILD_PSEUDO)
 63+BUILD_PSEUDO_FREE = $(BUILD_PSEUDO)
 64+BUILD_PSEUDO_PIDOF = $(BUILD_PSEUDO)
 65+BUILD_PSEUDO_PWDX = $(BUILD_PSEUDO)
 66+BUILD_PSEUDO_UPTIME = $(BUILD_PSEUDO)
 67+BUILD_DEV_AR = $(BUILD_DEV)
 68+BUILD_DEV_LD = $(BUILD_DEV)
 69+BUILD_DEV_CC = $(BUILD_DEV)
 70 
 71 # make tool
 72-BUILD_MAKE_MAKE = $(BUILD_MAKE)
 73+BUILD_POSIX_MAKE = $(BUILD_POSIX)
 74 
 75 # feature gates for specific utilities
 76 FEATURE_FIND_DELETE     = 1
 77@@ -209,7 +215,7 @@ FEATURE_STAT_FORMAT     = 1
 78 FEATURE_SORT_BIG        = 1
 79 FEATURE_SORT_STABLE     = 1
 80 FEATURE_OD_ENDIAN       = 1
 81-FEATURE_SH_HISTEDIT     = 0
 82+FEATURE_SH_HISTEDIT = 1
 83 FEATURE_SH_LOCAL        = 1
 84 FEATURE_SH_LET          = 1
 85 FEATURE_SH_ULIMIT       = 1
 86@@ -218,6 +224,15 @@ FEATURE_SH_WORDEXP      = 1
 87 FEATURE_CAL_EXT         = 1
 88 FEATURE_SED_PRESERVE_NEWLINE = 1
 89 FEATURE_LS_COLOR             = 1
 90+FEATURE_MODPROBE_SHOW_DEPENDS = 1
 91+FEATURE_MODPROBE_BLACKLIST   = 1
 92+FEATURE_MODPROBE_SYSLOG      = 1
 93+FEATURE_MODPROBE_DIR_OVERRIDE = 1
 94+FEATURE_DEPMOD_ALIAS   = 1
 95+FEATURE_DEPMOD_SYMBOLS = 1
 96+FEATURE_USE_LIBRESSL = 0
 97+FEATURE_USE_BEARSSL = 1
 98+
 99 
100 CPPFLAGS =\
101 	-Ishared\
102@@ -261,10 +276,25 @@ CPPFLAGS =\
103 	-DFEATURE_SH_WORDEXP=$(FEATURE_SH_WORDEXP)\
104 	-DFEATURE_CAL_EXT=$(FEATURE_CAL_EXT)\
105 	-DFEATURE_SED_PRESERVE_NEWLINE=$(FEATURE_SED_PRESERVE_NEWLINE)\
106-	-DFEATURE_LS_COLOR=$(FEATURE_LS_COLOR)
107+	-DFEATURE_LS_COLOR=$(FEATURE_LS_COLOR)\
108+	-DFEATURE_MODPROBE_SHOW_DEPENDS=$(FEATURE_MODPROBE_SHOW_DEPENDS)\
109+	-DFEATURE_MODPROBE_BLACKLIST=$(FEATURE_MODPROBE_BLACKLIST)\
110+	-DFEATURE_MODPROBE_SYSLOG=$(FEATURE_MODPROBE_SYSLOG)\
111+	-DFEATURE_MODPROBE_DIR_OVERRIDE=$(FEATURE_MODPROBE_DIR_OVERRIDE)\
112+	-DFEATURE_DEPMOD_ALIAS=$(FEATURE_DEPMOD_ALIAS)\
113+	-DFEATURE_DEPMOD_SYMBOLS=$(FEATURE_DEPMOD_SYMBOLS)\
114+	-DFEATURE_USE_LIBRESSL=$(FEATURE_USE_LIBRESSL)\
115+	-DFEATURE_USE_BEARSSL=$(FEATURE_USE_BEARSSL) $(CPPFLAGS_TLS)
116 
117 CFLAGS   = -std=c99 -Wall -Wextra -pedantic
118-LDFLAGS  =
119+LDFLAGS  = $(LDFLAGS_TLS)
120 LDLIBS_HISTEDIT_0 =
121-LDLIBS_HISTEDIT_1 = -ledit
122-LDLIBS   = -lcrypt -lresolv $(LDLIBS_HISTEDIT_$(FEATURE_SH_HISTEDIT))
123+LDLIBS_HISTEDIT_1 =
124+LDLIBS_TLS_LIBRESSL_1 = -ltls
125+LDLIBS_TLS_BEARSSL_1  = externalRepos/BearSSL/build/libbearssl.a
126+LDLIBS_TLS = $(LDLIBS_TLS_LIBRESSL_$(FEATURE_USE_LIBRESSL)) $(LDLIBS_TLS_BEARSSL_$(FEATURE_USE_BEARSSL))
127+CPPFLAGS_TLS_BEARSSL_1 = -IexternalRepos/BearSSL/inc
128+LDFLAGS_TLS_BEARSSL_1  = -LexternalRepos/BearSSL/build
129+CPPFLAGS_TLS = $(CPPFLAGS_TLS_BEARSSL_$(FEATURE_USE_BEARSSL))
130+LDFLAGS_TLS  = $(LDFLAGS_TLS_BEARSSL_$(FEATURE_USE_BEARSSL))
131+LDLIBS   = -lcrypt -lresolv $(LDLIBS_HISTEDIT_$(FEATURE_SH_HISTEDIT)) $(LDLIBS_TLS)
+1, -1
1@@ -189,7 +189,7 @@ compile_sh() {
2 		die "mktokens failed"
3 
4 	for s in alias arith_yacc arith_yylex cd echo error eval exec \
5-	          expand histedit input jobs kill mail main memalloc \
6+	          expand lineedit input jobs kill mail main memalloc \
7 	          miscbltin mystring options output parser printf redir \
8 	          show test trap var builtins nodes syntax; do
9 		out="${BDIR}/sh_${s}.o"
+1135, -0
   1@@ -0,0 +1,1135 @@
   2+package djot
   3+
   4+import (
   5+	"bytes"
   6+	"strconv"
   7+	"strings"
   8+)
   9+
  10+type Assembler struct {
  11+	doc   *Document
  12+	stack []int32
  13+
  14+	pendingAttr  []Attribute
  15+	sectionStack []int16
  16+
  17+	references     map[string]Reference
  18+	lastClosedNode int32
  19+
  20+	footnoteLabels  map[string]int
  21+	usedFootnotes   []string
  22+	footnoteContent map[string]int32
  23+
  24+	usedIDs    map[string]bool
  25+	headingIDs map[int32]string
  26+
  27+	currentTableAlignments []uint32
  28+}
  29+
  30+func (a *Assembler) Assemble(events []Event) {
  31+	a.doc.Nodes = make([]Node, 0, len(events)/2+8)
  32+	a.doc.Attributes = make([]Attribute, 0, 16)
  33+	a.doc.References = make(map[string]Reference)
  34+	a.doc.Extra = make([]byte, 0, 64)
  35+	a.stack = make([]int32, 0, 16)
  36+	a.pendingAttr = make([]Attribute, 0, 8)
  37+	a.sectionStack = make([]int16, 0, 8)
  38+	a.references = make(map[string]Reference)
  39+	a.lastClosedNode = -1
  40+	a.footnoteLabels = make(map[string]int)
  41+	a.usedFootnotes = make([]string, 0, 8)
  42+	a.footnoteContent = make(map[string]int32)
  43+	a.usedIDs = make(map[string]bool)
  44+	a.headingIDs = make(map[int32]string)
  45+
  46+	a.collectReferences(events)
  47+	a.collectHeadingReferences(events)
  48+
  49+	rootIdx := a.addNode(NodeDoc, 0, 0)
  50+	a.stack = append(a.stack, rootIdx)
  51+
  52+	type listTightState struct {
  53+		nodeIdx    int32
  54+		blanklines bool
  55+	}
  56+	var listTightStack []listTightState
  57+
  58+	i := 0
  59+	for i < len(events) {
  60+		ev := events[i]
  61+
  62+		if len(listTightStack) > 0 {
  63+			top := &listTightStack[len(listTightStack)-1]
  64+			switch ev.Type {
  65+			case EvBlankLine:
  66+				top.blanklines = true
  67+			case EvOpenList, EvCloseList:
  68+				top.blanklines = false
  69+			case EvOpenListItem, EvCloseListItem:
  70+			default:
  71+				if top.blanklines {
  72+					a.doc.Nodes[top.nodeIdx].Data &= ^DataListTight
  73+				}
  74+				top.blanklines = false
  75+			}
  76+		}
  77+
  78+		switch ev.Type {
  79+		case EvOpenAttributes:
  80+			i = a.parseAttributes(events, i)
  81+		case EvOpenInlineAttributes:
  82+			i = a.parseAttributes(events, i)
  83+			if a.lastClosedNode != -1 && len(a.pendingAttr) > 0 {
  84+				if a.doc.Nodes[a.lastClosedNode].Type == NodeStr {
  85+					nodeIdx := a.lastClosedNode
  86+					content := a.doc.Source[a.doc.Nodes[nodeIdx].Start : a.doc.Nodes[nodeIdx].End+1]
  87+					if len(content) == 0 || isSpace(content[len(content)-1]) {
  88+						a.pendingAttr = a.pendingAttr[:0]
  89+					} else {
  90+						lastWordStart := -1
  91+						for j := len(content) - 1; j >= 0; j-- {
  92+							if isSpace(content[j]) {
  93+								lastWordStart = j + 1
  94+								break
  95+							}
  96+						}
  97+						if lastWordStart == -1 {
  98+							lastWordStart = 0
  99+						}
 100+
 101+						if lastWordStart > 0 {
 102+							origStart := a.doc.Nodes[nodeIdx].Start
 103+							origEnd := a.doc.Nodes[nodeIdx].End
 104+							newEnd := origStart + int32(lastWordStart) - 1
 105+
 106+							spanIdx := a.addNode(NodeSpan, origStart+int32(lastWordStart), origEnd)
 107+							wordIdx := a.addNode(NodeStr, origStart+int32(lastWordStart), origEnd)
 108+
 109+							a.doc.Nodes[nodeIdx].End = newEnd
 110+							a.doc.Nodes[spanIdx].Child = wordIdx
 111+
 112+							parentIdx := a.stack[len(a.stack)-1]
 113+							a.linkToParent(parentIdx, spanIdx)
 114+
 115+							a.lastClosedNode = spanIdx
 116+						} else if lastWordStart == 0 {
 117+							origStart := a.doc.Nodes[nodeIdx].Start
 118+							origEnd := a.doc.Nodes[nodeIdx].End
 119+							childIdx := a.addNode(NodeStr, origStart, origEnd)
 120+							a.doc.Nodes[nodeIdx].Type = NodeSpan
 121+							a.doc.Nodes[nodeIdx].Child = childIdx
 122+						}
 123+					}
 124+				}
 125+				a.applyPendingAttr(a.lastClosedNode)
 126+			} else {
 127+				a.pendingAttr = a.pendingAttr[:0]
 128+			}
 129+		case EvOpenPara:
 130+			idx := a.openNode(NodePara, ev.Start)
 131+			a.applyPendingAttr(idx)
 132+		case EvClosePara:
 133+			a.lastClosedNode = a.closeNode(ev.End)
 134+		case EvOpenHeading:
 135+			level := int16(ev.End - ev.Start + 1)
 136+			a.handleHeadingOpen(level, ev.Start)
 137+		case EvCloseHeading:
 138+			idx := a.closeNode(ev.End)
 139+			a.generateID(idx, a.doc.Nodes[idx].Start)
 140+			a.lastClosedNode = idx
 141+		case EvOpenBlockQuote:
 142+			idx := a.openNode(NodeBlockQuote, ev.Start)
 143+			a.applyPendingAttr(idx)
 144+		case EvCloseBlockQuote:
 145+			a.lastClosedNode = a.closeNode(ev.End)
 146+		case EvOpenList:
 147+			marker := byte(ev.End)
 148+			ntype := NodeBulletList
 149+			if isOrderedMarker(marker) {
 150+				ntype = NodeOrderedList
 151+			} else if marker == ':' {
 152+				ntype = NodeDefinitionList
 153+			}
 154+			idx := a.openNode(ntype, ev.Start)
 155+			a.applyPendingAttr(idx)
 156+			a.doc.Nodes[idx].Data |= DataListTight
 157+			a.setStyleFromMarker(idx, marker)
 158+			listTightStack = append(listTightStack, listTightState{nodeIdx: idx})
 159+
 160+			if ntype == NodeOrderedList && i+1 < len(events) && events[i+1].Type == EvNone {
 161+				i++
 162+				startNum := int(events[i].End)
 163+				if startNum != 1 {
 164+					a.doc.Nodes[idx].Data |= uint32(startNum) << 16
 165+				}
 166+			}
 167+		case EvCloseList:
 168+			a.lastClosedNode = a.closeNode(ev.Start)
 169+			if len(listTightStack) > 0 {
 170+				listTightStack = listTightStack[:len(listTightStack)-1]
 171+			}
 172+		case EvOpenListItem:
 173+			pIdx := a.stack[len(a.stack)-1]
 174+			ntype := NodeListItem
 175+			if a.doc.Nodes[pIdx].Type == NodeDefinitionList {
 176+				ntype = NodeDefinitionListItem
 177+			}
 178+			idx := a.openNode(ntype, ev.Start)
 179+			a.applyPendingAttr(idx)
 180+			if ev.End > 0 && ntype == NodeListItem {
 181+				a.doc.Nodes[idx].Type = NodeTaskListItem
 182+				if ev.End == 2 {
 183+					a.doc.Nodes[idx].Data |= DataTaskChecked
 184+				}
 185+				pIdx := a.stack[len(a.stack)-2]
 186+				if a.doc.Nodes[pIdx].Type == NodeBulletList {
 187+					a.doc.Nodes[pIdx].Type = NodeTaskList
 188+				}
 189+			}
 190+		case EvCloseListItem:
 191+			idx := a.stack[len(a.stack)-1]
 192+			if a.doc.Nodes[idx].Type == NodeDefinitionListItem {
 193+				a.handleDefinitionListItem(idx)
 194+			}
 195+			a.lastClosedNode = a.closeNode(ev.End)
 196+		case EvOpenCodeBlock, EvOpenRawBlock:
 197+			ntype := NodeCodeBlock
 198+			closeType := EvCloseCodeBlock
 199+			if ev.Type == EvOpenRawBlock {
 200+				ntype = NodeRawBlock
 201+				closeType = EvCloseRawBlock
 202+			}
 203+			idx := a.openNode(ntype, ev.Start)
 204+			a.applyPendingAttr(idx)
 205+
 206+			j := i + 1
 207+			contentStart := int32(-1)
 208+			foundStartMarker := false
 209+			for j < len(events) {
 210+				if !foundStartMarker && events[j].Type == EvNone {
 211+					foundStartMarker = true
 212+				} else if events[j].Type == EvStr {
 213+					if contentStart == -1 {
 214+						contentStart = events[j].Start
 215+					}
 216+				} else if events[j].Type == closeType {
 217+					break
 218+				}
 219+				j++
 220+			}
 221+
 222+			if contentStart != -1 {
 223+				var buf bytes.Buffer
 224+				for k := i + 1; k < j; k++ {
 225+					if events[k].Type == EvStr {
 226+						buf.Write(a.doc.Source[events[k].Start : events[k].End+1])
 227+					}
 228+				}
 229+				extraStart := int32(len(a.doc.Extra))
 230+				a.doc.Extra = append(a.doc.Extra, buf.Bytes()...)
 231+				a.doc.Nodes[idx].Start = ^extraStart
 232+				a.doc.Nodes[idx].End = int32(len(a.doc.Extra)) - 1
 233+			} else {
 234+				a.doc.Nodes[idx].Start = -1
 235+				a.doc.Nodes[idx].End = -1
 236+			}
 237+
 238+			if ntype == NodeRawBlock && idx != -1 {
 239+				node := &a.doc.Nodes[idx]
 240+				if node.Attr != -1 {
 241+					for k := uint16(0); k < node.AttrCount; k++ {
 242+						attr := &a.doc.Attributes[node.Attr+int32(k)]
 243+						if attr.KeyStart == -2 {
 244+							val := string(a.getAttrVal(*attr))
 245+							if strings.HasPrefix(val, "=") {
 246+								attr.KeyStart = -3
 247+							}
 248+						}
 249+					}
 250+				}
 251+			}
 252+			a.closeNode(ev.End)
 253+			i = j
 254+		case EvOpenDiv:
 255+			idx := a.openNode(NodeDiv, ev.Start)
 256+			a.applyPendingAttr(idx)
 257+		case EvCloseDiv:
 258+			a.lastClosedNode = a.closeNode(ev.End)
 259+		case EvOpenTable:
 260+			idx := a.openNode(NodeTable, ev.Start)
 261+			a.applyPendingAttr(idx)
 262+		case EvCloseTable:
 263+			a.lastClosedNode = a.closeNode(ev.End)
 264+		case EvOpenCaption:
 265+			idx := int32(-1)
 266+			if a.lastClosedNode != -1 && a.doc.Nodes[a.lastClosedNode].Type == NodeTable {
 267+				tableIdx := a.lastClosedNode
 268+				captionIdx := a.addNode(NodeCaption, ev.Start, -1)
 269+				a.linkToParent(tableIdx, captionIdx)
 270+				a.stack = append(a.stack, captionIdx)
 271+				a.lastClosedNode = -1
 272+				idx = captionIdx
 273+			} else {
 274+				idx = a.openNode(NodeCaption, ev.Start)
 275+			}
 276+			a.applyPendingAttr(idx)
 277+		case EvCloseCaption:
 278+			a.lastClosedNode = a.closeNode(ev.End)
 279+		case EvOpenRow:
 280+			a.openNode(NodeRow, ev.Start)
 281+		case EvCloseRow:
 282+			a.lastClosedNode = a.closeNode(ev.End)
 283+		case EvOpenCell:
 284+			idx := a.openNode(NodeCell, ev.Start)
 285+			a.applyPendingAttr(idx)
 286+
 287+			col := 0
 288+			curr := a.doc.Nodes[a.stack[len(a.stack)-2]].Child
 289+			for curr != -1 && curr != idx {
 290+				curr = a.doc.Nodes[curr].Next
 291+				col++
 292+			}
 293+			if col < len(a.currentTableAlignments) {
 294+				a.doc.Nodes[idx].Data &= ^(DataAlignLeft | DataAlignCenter | DataAlignRight)
 295+				a.doc.Nodes[idx].Data |= a.currentTableAlignments[col]
 296+			}
 297+		case EvCloseCell:
 298+			a.lastClosedNode = a.closeNode(ev.End)
 299+		case EvOpenFootnote:
 300+			j := i + 1
 301+			label := ""
 302+			if j < len(events) && events[j].Type == EvStr {
 303+				content := a.doc.Source[events[j].Start : events[j].End+1]
 304+				if len(content) > 0 && content[0] == '[' && content[1] == '^' {
 305+					closeIdx := bytes.IndexByte(content, ']')
 306+					if closeIdx != -1 {
 307+						label = normalizeLabel(string(content[2:closeIdx]))
 308+					}
 309+				}
 310+			}
 311+			idx := a.openNode(NodeFootnote, ev.Start)
 312+			if label != "" {
 313+				a.footnoteContent[label] = idx
 314+			}
 315+			a.applyPendingAttr(idx)
 316+		case EvCloseFootnote:
 317+			a.lastClosedNode = a.closeNode(ev.End)
 318+		case EvOpenReferenceDefinition:
 319+			idx := a.openNode(NodeReference, ev.Start)
 320+			a.applyPendingAttr(idx)
 321+		case EvCloseReferenceDefinition:
 322+			a.closeNode(ev.End)
 323+		case EvOpenLinkText:
 324+			idx := a.openNode(NodeLink, ev.Start)
 325+			a.applyPendingAttr(idx)
 326+		case EvCloseLinkText:
 327+			a.lastClosedNode = a.closeNode(ev.End)
 328+		case EvOpenImageText:
 329+			idx := a.openNode(NodeImage, ev.Start)
 330+			a.applyPendingAttr(idx)
 331+		case EvCloseImageText:
 332+			a.lastClosedNode = a.closeNode(ev.End)
 333+		case EvOpenSpan:
 334+			idx := a.openNode(NodeSpan, ev.Start)
 335+			a.applyPendingAttr(idx)
 336+		case EvCloseSpan:
 337+			a.lastClosedNode = a.closeNode(ev.End)
 338+		case EvOpenDoubleQuoted:
 339+			a.openNode(NodeDoubleQuoted, ev.Start)
 340+		case EvCloseDoubleQuoted:
 341+			a.lastClosedNode = a.closeNode(ev.End)
 342+		case EvOpenSingleQuoted:
 343+			a.openNode(NodeSingleQuoted, ev.Start)
 344+		case EvCloseSingleQuoted:
 345+			a.lastClosedNode = a.closeNode(ev.End)
 346+		case EvOpenMark:
 347+			idx := a.openNode(NodeMark, ev.Start)
 348+			a.applyPendingAttr(idx)
 349+		case EvCloseMark:
 350+			a.lastClosedNode = a.closeNode(ev.End)
 351+		case EvOpenInsert:
 352+			idx := a.openNode(NodeInsert, ev.Start)
 353+			a.applyPendingAttr(idx)
 354+		case EvCloseInsert:
 355+			a.lastClosedNode = a.closeNode(ev.End)
 356+		case EvOpenDelete:
 357+			idx := a.openNode(NodeDelete, ev.Start)
 358+			a.applyPendingAttr(idx)
 359+		case EvCloseDelete:
 360+			a.lastClosedNode = a.closeNode(ev.End)
 361+		case EvOpenDestination:
 362+			a.addLeafNode(NodeUrl, ev.Start, ev.End)
 363+		case EvUrl, EvEmail:
 364+			idx := a.openNode(NodeLink, ev.Start)
 365+			a.applyPendingAttr(idx)
 366+
 367+			destType := NodeUrl
 368+			if ev.Type == EvEmail {
 369+				destType = NodeEmail
 370+			}
 371+			destIdx := a.addNode(destType, ev.Start, ev.End)
 372+			a.linkToParent(idx, destIdx)
 373+
 374+			strIdx := a.addNode(NodeStr, ev.Start, ev.End)
 375+			a.linkToParent(idx, strIdx)
 376+
 377+			a.lastClosedNode = a.closeNode(ev.End)
 378+		case EvOpenReference:
 379+			label := string(a.doc.Source[ev.Start : ev.End+1])
 380+			if label == "" {
 381+				var buf strings.Builder
 382+				a.collectPlainText(a.stack[len(a.stack)-1], &buf)
 383+				label = buf.String()
 384+			}
 385+			label = normalizeLabel(label)
 386+			if ref, ok := a.references[label]; ok {
 387+				a.addLeafNode(NodeUrl, ref.DestStart, ref.DestEnd)
 388+				if len(ref.Attributes) > 0 {
 389+					parentIdx := a.stack[len(a.stack)-1]
 390+					a.pendingAttr = append(a.pendingAttr, ref.Attributes...)
 391+					a.applyPendingAttr(parentIdx)
 392+				}
 393+			}
 394+		case EvFootnoteReference:
 395+			label := string(a.doc.Source[ev.Start+2 : ev.End])
 396+			num, ok := a.footnoteLabels[label]
 397+			if !ok {
 398+				num = len(a.usedFootnotes) + 1
 399+				a.footnoteLabels[label] = num
 400+				a.usedFootnotes = append(a.usedFootnotes, label)
 401+			}
 402+			idx := a.addNode(NodeFootnoteReference, ev.Start, ev.End)
 403+			a.doc.Nodes[idx].Data = uint32(num)
 404+			parentIdx := a.stack[len(a.stack)-1]
 405+			a.linkToParent(parentIdx, idx)
 406+		case EvSmartPunctuation:
 407+			a.addLeafNode(NodeSmartPunctuation, ev.Start, ev.End)
 408+		case EvInlineMath:
 409+			a.addLeafNode(NodeInlineMath, ev.Start, ev.End)
 410+		case EvDisplayMath:
 411+			a.addLeafNode(NodeDisplayMath, ev.Start, ev.End)
 412+		case EvRawInline:
 413+			idx := a.addNode(NodeRawInline, ev.Start, ev.End)
 414+			a.linkToParent(a.stack[len(a.stack)-1], idx)
 415+			a.lastClosedNode = idx
 416+			i++
 417+			for i < len(events) && events[i].Type != EvRawInline {
 418+				i++
 419+			}
 420+		case EvOpenVerbatim:
 421+			a.openNode(NodeVerbatim, ev.Start)
 422+		case EvCloseVerbatim:
 423+			a.lastClosedNode = a.closeNode(ev.End)
 424+		case EvSoftBreak:
 425+			a.addLeafNode(NodeSoftBreak, ev.Start, ev.End)
 426+		case EvHardBreak:
 427+			a.addLeafNode(NodeHardBreak, ev.Start, ev.End)
 428+		case EvNonBreakingSpace:
 429+			a.addLeafNode(NodeNonBreakingSpace, ev.Start, ev.End)
 430+		case EvThematicBreak:
 431+			idx := a.addNode(NodeThematicBreak, ev.Start, ev.End)
 432+			a.linkToParent(a.stack[len(a.stack)-1], idx)
 433+			a.applyPendingAttr(idx)
 434+		case EvBlankLine:
 435+		case EvStr:
 436+			skipThisStr := false
 437+			if i > 0 && events[i-1].Type == EvOpenFootnote {
 438+				content := a.doc.Source[ev.Start : ev.End+1]
 439+				if len(content) > 0 && content[0] == '[' && len(content) > 1 && content[1] == '^' {
 440+					if bytes.Contains(content, []byte("]:")) {
 441+						skipThisStr = true
 442+					}
 443+				}
 444+			}
 445+			if !skipThisStr {
 446+				tipIdx := a.stack[len(a.stack)-1]
 447+				if a.doc.Nodes[tipIdx].Type == NodeTable {
 448+					a.handleTableSeparator(ev.Start, ev.End)
 449+				} else {
 450+					a.addLeafNode(NodeStr, ev.Start, ev.End)
 451+				}
 452+			}
 453+		case EvSymb:
 454+			a.addLeafNode(NodeSymb, ev.Start, ev.End)
 455+		case EvOpenEmph:
 456+			idx := a.openNode(NodeEmph, ev.Start)
 457+			a.applyPendingAttr(idx)
 458+		case EvCloseEmph:
 459+			a.lastClosedNode = a.closeNode(ev.End)
 460+		case EvOpenStrong:
 461+			idx := a.openNode(NodeStrong, ev.Start)
 462+			a.applyPendingAttr(idx)
 463+		case EvCloseStrong:
 464+			a.lastClosedNode = a.closeNode(ev.End)
 465+		case EvEscape:
 466+			a.addLeafNode(NodeStr, ev.Start+1, ev.End)
 467+		case EvOpenSuperscript:
 468+			a.openNode(NodeSuperscript, ev.Start)
 469+		case EvCloseSuperscript:
 470+			a.lastClosedNode = a.closeNode(ev.End)
 471+		case EvOpenSubscript:
 472+			a.openNode(NodeSubscript, ev.Start)
 473+		case EvCloseSubscript:
 474+			a.lastClosedNode = a.closeNode(ev.End)
 475+		}
 476+		i++
 477+	}
 478+
 479+	for _, label := range a.usedFootnotes {
 480+		if contentIdx, ok := a.footnoteContent[label]; ok {
 481+			a.linkToParent(rootIdx, contentIdx)
 482+		}
 483+	}
 484+
 485+	a.doc.UsedFootnotes = a.usedFootnotes
 486+	a.doc.FootnoteContent = a.footnoteContent
 487+	a.doc.References = a.references
 488+
 489+	if len(a.doc.Source) > 0 {
 490+		a.doc.Nodes[rootIdx].End = int32(len(a.doc.Source) - 1)
 491+	}
 492+}
 493+
 494+func normalizeLabel(label string) string {
 495+	return strings.Join(strings.Fields(label), " ")
 496+}
 497+
 498+func unescapeBytes(b []byte) []byte {
 499+	var res []byte
 500+	escaped := false
 501+	for _, ch := range b {
 502+		if escaped {
 503+			res = append(res, ch)
 504+			escaped = false
 505+		} else if ch == '\\' {
 506+			escaped = true
 507+		} else {
 508+			res = append(res, ch)
 509+		}
 510+	}
 511+	return res
 512+}
 513+
 514+func (a *Assembler) collectReferences(events []Event) {
 515+	var pendingAttr []Attribute
 516+	for i := 0; i < len(events); i++ {
 517+		ev := events[i]
 518+		switch ev.Type {
 519+		case EvOpenAttributes, EvOpenInlineAttributes:
 520+			i = a.parseAttributesTo(events, i, &pendingAttr)
 521+			continue
 522+		case EvOpenPara, EvOpenBlockQuote, EvOpenList, EvOpenListItem, EvOpenCodeBlock, EvOpenDiv, EvOpenTable, EvOpenCaption, EvOpenRow, EvOpenCell, EvOpenHeading:
 523+			pendingAttr = pendingAttr[:0]
 524+		case EvOpenReferenceDefinition:
 525+			var content string
 526+			j := i + 1
 527+			for j < len(events) && events[j].Type != EvCloseReferenceDefinition {
 528+				if events[j].Type == EvStr || events[j].Type == EvText {
 529+					content = string(a.doc.Source[events[j].Start : events[j].End+1])
 530+				}
 531+				j++
 532+			}
 533+
 534+			if content == "" {
 535+				lineStart := events[i].Start
 536+				lineEnd := lineStart
 537+				for lineEnd < int32(len(a.doc.Source)) && a.doc.Source[lineEnd] != '\n' {
 538+					lineEnd++
 539+				}
 540+				if lineEnd > lineStart {
 541+					content = string(a.doc.Source[lineStart:lineEnd])
 542+				}
 543+			}
 544+
 545+			if len(content) > 0 && content[0] == '[' {
 546+				closeIdx := strings.IndexByte(content, ']')
 547+				if closeIdx != -1 && closeIdx+1 < len(content) && content[closeIdx+1] == ':' {
 548+					label := normalizeLabel(content[1:closeIdx])
 549+					value := content[closeIdx+2:]
 550+
 551+					value = strings.ReplaceAll(value, "\r\n", "\n")
 552+					value = strings.ReplaceAll(value, "\r", "\n")
 553+					parts := strings.Split(value, "\n")
 554+					var cleanedParts []string
 555+					for _, p := range parts {
 556+						cleaned := strings.TrimSpace(p)
 557+						if len(cleaned) > 0 {
 558+							cleanedParts = append(cleanedParts, cleaned)
 559+						}
 560+					}
 561+					cleanValue := strings.Join(cleanedParts, "")
 562+					valStart := int32(len(a.doc.Extra))
 563+					a.doc.Extra = append(a.doc.Extra, []byte(cleanValue)...)
 564+					valEnd := int32(len(a.doc.Extra)) - 1
 565+
 566+					refAttrs := make([]Attribute, len(pendingAttr))
 567+					copy(refAttrs, pendingAttr)
 568+
 569+					a.references[label] = Reference{
 570+						DestStart:  ^valStart,
 571+						DestEnd:    valEnd,
 572+						Attributes: refAttrs,
 573+					}
 574+					pendingAttr = pendingAttr[:0]
 575+				}
 576+			}
 577+			i = j
 578+		case EvOpenFootnote:
 579+			j := i + 1
 580+			if j < len(events) && events[j].Type == EvStr {
 581+				content := a.doc.Source[events[j].Start : events[j].End+1]
 582+				if len(content) > 0 && content[0] == '[' && content[1] == '^' {
 583+					closeIdx := bytes.IndexByte(content, ']')
 584+					if closeIdx != -1 && closeIdx+1 < len(content) && content[closeIdx+1] == ':' {
 585+						label := normalizeLabel(string(content[2:closeIdx]))
 586+						a.footnoteContent[label] = -2
 587+					}
 588+				}
 589+			}
 590+			pendingAttr = pendingAttr[:0]
 591+		}
 592+	}
 593+}
 594+
 595+func (a *Assembler) parseAttributesTo(events []Event, startIdx int, target *[]Attribute) int {
 596+	i := startIdx + 1
 597+	for i < len(events) && events[i].Type != EvCloseAttributes {
 598+		ev := events[i]
 599+		switch ev.Type {
 600+		case EvAttrIdMarker:
 601+			*target = append(*target, Attribute{
 602+				KeyStart: -1,
 603+				ValStart: ev.Start,
 604+				ValEnd:   ev.End,
 605+			})
 606+		case EvAttrClassMarker:
 607+			*target = append(*target, Attribute{
 608+				KeyStart: -2,
 609+				ValStart: ev.Start,
 610+				ValEnd:   ev.End,
 611+			})
 612+		case EvAttrKey:
 613+			keyEv := ev
 614+			if keyEv.Start < int32(len(a.doc.Source)) && a.doc.Source[keyEv.Start] == '=' {
 615+				*target = append(*target, Attribute{
 616+					KeyStart: -3,
 617+					ValStart: keyEv.Start + 1,
 618+					ValEnd:   keyEv.End,
 619+				})
 620+			} else {
 621+				for i+1 < len(events) && events[i+1].Type != EvCloseAttributes {
 622+					i++
 623+					if events[i].Type == EvAttrValue {
 624+						*target = append(*target, Attribute{
 625+							KeyStart: keyEv.Start,
 626+							KeyEnd:   keyEv.End,
 627+							ValStart: events[i].Start,
 628+							ValEnd:   events[i].End,
 629+						})
 630+						break
 631+					}
 632+				}
 633+			}
 634+		}
 635+		i++
 636+	}
 637+	return i
 638+}
 639+
 640+func (a *Assembler) collectHeadingReferences(events []Event) {
 641+	tempAs := &Assembler{doc: a.doc, references: a.references, usedIDs: make(map[string]bool)}
 642+	i := 0
 643+	for i < len(events) {
 644+		ev := events[i]
 645+		switch ev.Type {
 646+		case EvOpenAttributes:
 647+			i = tempAs.parseAttributes(events, i)
 648+			continue
 649+		case EvOpenPara, EvOpenBlockQuote, EvOpenList, EvOpenListItem, EvOpenCodeBlock, EvOpenDiv, EvOpenTable, EvOpenCaption, EvOpenRow, EvOpenCell:
 650+			if len(tempAs.pendingAttr) > 0 {
 651+				for _, attr := range tempAs.pendingAttr {
 652+					if attr.KeyStart == -1 {
 653+						var id string
 654+						if attr.ValStart < 0 {
 655+							id = string(a.doc.Extra[^attr.ValStart : attr.ValEnd+1])
 656+						} else {
 657+							id = string(a.doc.Source[attr.ValStart : attr.ValEnd+1])
 658+						}
 659+						tempAs.usedIDs[id] = true
 660+						break
 661+					}
 662+				}
 663+				tempAs.pendingAttr = tempAs.pendingAttr[:0]
 664+			}
 665+		case EvOpenHeading:
 666+			headingStart := ev.Start
 667+			j := i + 1
 668+			var buf strings.Builder
 669+			for j < len(events) && events[j].Type != EvCloseHeading {
 670+				if events[j].Type == EvStr {
 671+					buf.Write(a.doc.Source[events[j].Start : events[j].End+1])
 672+				} else if events[j].Type == EvSoftBreak {
 673+					buf.WriteByte('\n')
 674+				}
 675+				j++
 676+			}
 677+			text := buf.String()
 678+			label := normalizeLabel(text)
 679+
 680+			var manualID string
 681+			if len(tempAs.pendingAttr) > 0 {
 682+				for _, attr := range tempAs.pendingAttr {
 683+					if attr.KeyStart == -1 {
 684+						if attr.ValStart < 0 {
 685+							manualID = string(a.doc.Extra[^attr.ValStart : attr.ValEnd+1])
 686+						} else {
 687+							manualID = string(a.doc.Source[attr.ValStart : attr.ValEnd+1])
 688+						}
 689+						tempAs.usedIDs[manualID] = true
 690+						break
 691+					}
 692+				}
 693+				tempAs.pendingAttr = tempAs.pendingAttr[:0]
 694+			}
 695+
 696+			ident := manualID
 697+			if ident == "" {
 698+				base := a.slugify(text)
 699+				ident = base
 700+				if ident == "" {
 701+					ident = "s"
 702+				}
 703+				if tempAs.usedIDs[ident] || base == "" {
 704+					k := 1
 705+					if base == "" {
 706+						ident = "s-1"
 707+					} else {
 708+						ident = base + "-1"
 709+					}
 710+					for tempAs.usedIDs[ident] {
 711+						k++
 712+						ident = base + "-" + strconv.Itoa(k)
 713+						if base == "" {
 714+							ident = "s-" + strconv.Itoa(k)
 715+						}
 716+					}
 717+				}
 718+				tempAs.usedIDs[ident] = true
 719+			}
 720+
 721+			a.headingIDs[headingStart] = ident
 722+
 723+			if _, ok := a.references[label]; !ok {
 724+				start := int32(len(a.doc.Extra))
 725+				a.doc.Extra = append(a.doc.Extra, '#')
 726+				a.doc.Extra = append(a.doc.Extra, []byte(ident)...)
 727+				a.references[label] = Reference{
 728+					DestStart: ^start,
 729+					DestEnd:   int32(len(a.doc.Extra)) - 1,
 730+				}
 731+			}
 732+			i = j
 733+		}
 734+		i++
 735+	}
 736+}
 737+
 738+func (a *Assembler) generateID(idx int32, start int32) {
 739+	node := &a.doc.Nodes[idx]
 740+	if node.Data&DataNoAutoID != 0 {
 741+		return
 742+	}
 743+
 744+	parentIdx := a.stack[len(a.stack)-1]
 745+	targetIdx := idx
 746+	if a.doc.Nodes[parentIdx].Type == NodeSection {
 747+		targetIdx = parentIdx
 748+	}
 749+
 750+	if a.doc.Nodes[targetIdx].Attr != -1 {
 751+		for j := uint16(0); j < a.doc.Nodes[targetIdx].AttrCount; j++ {
 752+			attr := a.doc.Attributes[a.doc.Nodes[targetIdx].Attr+int32(j)]
 753+			if attr.KeyStart == -1 {
 754+				return
 755+			}
 756+		}
 757+	}
 758+
 759+	ident, ok := a.headingIDs[start]
 760+	if !ok {
 761+		return
 762+	}
 763+
 764+	valStart := int32(len(a.doc.Extra))
 765+	a.doc.Extra = append(a.doc.Extra, []byte(ident)...)
 766+	valEnd := int32(len(a.doc.Extra)) - 1
 767+
 768+	attr := Attribute{
 769+		KeyStart: -1,
 770+		ValStart: ^valStart,
 771+		ValEnd:   valEnd,
 772+	}
 773+
 774+	a.pendingAttr = append(a.pendingAttr, attr)
 775+	a.applyPendingAttr(targetIdx)
 776+}
 777+
 778+func (a *Assembler) collectPlainText(idx int32, buf *strings.Builder) {
 779+	curr := a.doc.Nodes[idx].Child
 780+	for curr != -1 {
 781+		node := a.doc.Nodes[curr]
 782+		if node.Type == NodeStr {
 783+			buf.Write(a.doc.Source[node.Start : node.End+1])
 784+		} else if node.Type == NodeSoftBreak {
 785+			buf.WriteByte('\n')
 786+		} else {
 787+			a.collectPlainText(curr, buf)
 788+		}
 789+		curr = a.doc.Nodes[curr].Next
 790+	}
 791+}
 792+
 793+func (a *Assembler) slugify(text string) string {
 794+	var res []rune
 795+	lastWasSpec := false
 796+	for _, r := range text {
 797+		isSpec := strings.ContainsRune("][~!@#$%^&*(){}`,.<>\\|=+/? \t\r\n", r)
 798+		if isSpec {
 799+			if !lastWasSpec {
 800+				res = append(res, ' ')
 801+			}
 802+			lastWasSpec = true
 803+		} else {
 804+			res = append(res, r)
 805+			lastWasSpec = false
 806+		}
 807+	}
 808+	s := strings.TrimSpace(string(res))
 809+	return strings.ReplaceAll(s, " ", "-")
 810+}
 811+
 812+func (a *Assembler) handleTableSeparator(start, end int32) {
 813+	parentIdx := a.stack[len(a.stack)-1]
 814+	curr := a.doc.Nodes[parentIdx].Child
 815+	var lastRow int32 = -1
 816+	for curr != -1 {
 817+		if a.doc.Nodes[curr].Type == NodeRow {
 818+			lastRow = curr
 819+		}
 820+		curr = a.doc.Nodes[curr].Next
 821+	}
 822+
 823+	line := a.doc.Source[start : end+1]
 824+	parts := bytes.Split(line, []byte("|"))
 825+
 826+	newAlignments := make([]uint32, 0)
 827+	for _, part := range parts {
 828+		part = bytes.TrimSpace(part)
 829+		if len(part) == 0 {
 830+			continue
 831+		}
 832+
 833+		align := uint32(0)
 834+		left := part[0] == ':'
 835+		right := part[len(part)-1] == ':'
 836+
 837+		if left && right {
 838+			align = DataAlignCenter
 839+		} else if left {
 840+			align = DataAlignLeft
 841+		} else if right {
 842+			align = DataAlignRight
 843+		}
 844+		newAlignments = append(newAlignments, align)
 845+	}
 846+	a.currentTableAlignments = newAlignments
 847+
 848+	if lastRow != -1 {
 849+		cell := a.doc.Nodes[lastRow].Child
 850+		col := 0
 851+		for cell != -1 {
 852+			a.doc.Nodes[cell].Data |= DataCellHeader
 853+			if col < len(a.currentTableAlignments) {
 854+				a.doc.Nodes[cell].Data &= ^(DataAlignLeft | DataAlignCenter | DataAlignRight)
 855+				a.doc.Nodes[cell].Data |= a.currentTableAlignments[col]
 856+			}
 857+			cell = a.doc.Nodes[cell].Next
 858+			col++
 859+		}
 860+	}
 861+}
 862+
 863+func (a *Assembler) handleHeadingOpen(level int16, start int32) {
 864+	for len(a.sectionStack) > 0 && a.sectionStack[len(a.sectionStack)-1] >= level {
 865+		a.stack = a.stack[:len(a.stack)-1]
 866+		a.sectionStack = a.sectionStack[:len(a.sectionStack)-1]
 867+	}
 868+
 869+	parentIdx := a.stack[len(a.stack)-1]
 870+	parentType := a.doc.Nodes[parentIdx].Type
 871+
 872+	if parentType == NodeDoc || parentType == NodeSection {
 873+		sIdx := a.openNode(NodeSection, start)
 874+		a.sectionStack = append(a.sectionStack, level)
 875+		a.applyPendingAttr(sIdx)
 876+	}
 877+
 878+	hIdx := a.openNode(NodeHeading, start)
 879+	a.doc.Nodes[hIdx].Level = level
 880+	if len(a.pendingAttr) > 0 {
 881+		a.doc.Nodes[hIdx].Data |= DataNoAutoID
 882+		a.applyPendingAttr(hIdx)
 883+	}
 884+}
 885+
 886+func (a *Assembler) parseAttributes(events []Event, startIdx int) int {
 887+	i := startIdx + 1
 888+	for i < len(events) && events[i].Type != EvCloseAttributes {
 889+		ev := events[i]
 890+		switch ev.Type {
 891+		case EvAttrIdMarker:
 892+			a.pendingAttr = append(a.pendingAttr, Attribute{
 893+				KeyStart: -1,
 894+				ValStart: ev.Start,
 895+				ValEnd:   ev.End,
 896+			})
 897+		case EvAttrClassMarker:
 898+			a.pendingAttr = append(a.pendingAttr, Attribute{
 899+				KeyStart: -2,
 900+				ValStart: ev.Start,
 901+				ValEnd:   ev.End,
 902+			})
 903+		case EvAttrKey:
 904+			keyEv := ev
 905+			if keyEv.Start < int32(len(a.doc.Source)) && a.doc.Source[keyEv.Start] == '=' {
 906+				a.pendingAttr = append(a.pendingAttr, Attribute{
 907+					KeyStart: -3,
 908+					ValStart: keyEv.Start + 1,
 909+					ValEnd:   keyEv.End,
 910+				})
 911+			} else {
 912+				for i+1 < len(events) && events[i+1].Type != EvCloseAttributes {
 913+					i++
 914+					if events[i].Type == EvAttrValue {
 915+						rawVal := a.doc.Source[events[i].Start : events[i].End+1]
 916+						decodedVal := unescapeBytes(rawVal)
 917+						valStart := int32(len(a.doc.Extra))
 918+						a.doc.Extra = append(a.doc.Extra, decodedVal...)
 919+						valEnd := int32(len(a.doc.Extra)) - 1
 920+						a.pendingAttr = append(a.pendingAttr, Attribute{
 921+							KeyStart: keyEv.Start,
 922+							KeyEnd:   keyEv.End,
 923+							ValStart: ^valStart,
 924+							ValEnd:   valEnd,
 925+						})
 926+						break
 927+					}
 928+				}
 929+			}
 930+		}
 931+		i++
 932+	}
 933+	return i
 934+}
 935+
 936+func (a *Assembler) getAttrVal(attr Attribute) []byte {
 937+	if attr.ValStart < 0 {
 938+		return a.doc.Extra[^attr.ValStart : attr.ValEnd+1]
 939+	}
 940+	return a.doc.Source[attr.ValStart : attr.ValEnd+1]
 941+}
 942+
 943+func (a *Assembler) applyPendingAttr(idx int32) {
 944+	if len(a.pendingAttr) == 0 {
 945+		return
 946+	}
 947+
 948+	node := &a.doc.Nodes[idx]
 949+	var attrs []Attribute
 950+	if node.Attr != -1 {
 951+		for i := uint16(0); i < node.AttrCount; i++ {
 952+			attrs = append(attrs, a.doc.Attributes[node.Attr+int32(i)])
 953+		}
 954+	}
 955+
 956+	for _, newAttr := range a.pendingAttr {
 957+		if newAttr.KeyStart == -1 {
 958+			id := string(a.getAttrVal(newAttr))
 959+			a.usedIDs[id] = true
 960+		}
 961+
 962+		foundIdx := -1
 963+		for i := range attrs {
 964+			if newAttr.KeyStart == -1 && attrs[i].KeyStart == -1 {
 965+				foundIdx = i
 966+				break
 967+			} else if newAttr.KeyStart == -2 && attrs[i].KeyStart == -2 {
 968+				foundIdx = i
 969+				break
 970+			} else if newAttr.KeyStart >= 0 && attrs[i].KeyStart >= 0 {
 971+				oldKey := a.doc.Source[attrs[i].KeyStart : attrs[i].KeyEnd+1]
 972+				newKey := a.doc.Source[newAttr.KeyStart : newAttr.KeyEnd+1]
 973+				if bytes.Equal(oldKey, newKey) {
 974+					foundIdx = i
 975+					break
 976+				}
 977+			}
 978+		}
 979+
 980+		if newAttr.KeyStart == -2 && foundIdx != -1 {
 981+			oldVal := a.getAttrVal(attrs[foundIdx])
 982+			newVal := a.getAttrVal(newAttr)
 983+
 984+			start := int32(len(a.doc.Extra))
 985+			a.doc.Extra = append(a.doc.Extra, oldVal...)
 986+			a.doc.Extra = append(a.doc.Extra, ' ')
 987+			a.doc.Extra = append(a.doc.Extra, newVal...)
 988+			end := int32(len(a.doc.Extra)) - 1
 989+
 990+			attrs[foundIdx].ValStart = ^start
 991+			attrs[foundIdx].ValEnd = end
 992+		} else if foundIdx != -1 {
 993+			attrs[foundIdx] = newAttr
 994+		} else {
 995+			attrs = append(attrs, newAttr)
 996+		}
 997+	}
 998+
 999+	node.Attr = int32(len(a.doc.Attributes))
1000+	node.AttrCount = uint16(len(attrs))
1001+	a.doc.Attributes = append(a.doc.Attributes, attrs...)
1002+	a.pendingAttr = a.pendingAttr[:0]
1003+}
1004+
1005+func (a *Assembler) handleDefinitionListItem(idx int32) {
1006+	firstIdx := a.doc.Nodes[idx].Child
1007+	if firstIdx != -1 && a.doc.Nodes[firstIdx].Type == NodePara {
1008+		termIdx := firstIdx
1009+		a.doc.Nodes[termIdx].Type = NodeTerm
1010+
1011+		defIdx := a.addNode(NodeDefinition, a.doc.Nodes[termIdx].End, a.doc.Nodes[idx].End)
1012+		a.doc.Nodes[defIdx].Child = a.doc.Nodes[termIdx].Next
1013+		a.doc.Nodes[termIdx].Next = defIdx
1014+	} else {
1015+		termIdx := a.addNode(NodeTerm, a.doc.Nodes[idx].Start, a.doc.Nodes[idx].Start)
1016+		defIdx := a.addNode(NodeDefinition, a.doc.Nodes[idx].Start, a.doc.Nodes[idx].End)
1017+		a.doc.Nodes[defIdx].Child = firstIdx
1018+		a.doc.Nodes[idx].Child = termIdx
1019+		a.doc.Nodes[termIdx].Next = defIdx
1020+	}
1021+}
1022+
1023+func (a *Assembler) isInline(t NodeType) bool {
1024+	switch t {
1025+	case NodeStr, NodeEmph, NodeStrong, NodeLink, NodeImage, NodeSpan, NodeVerbatim, NodeSubscript, NodeSuperscript, NodeInsert, NodeDelete, NodeMark, NodeDoubleQuoted, NodeSingleQuoted, NodeSmartPunctuation, NodeSoftBreak, NodeHardBreak, NodeInlineMath, NodeDisplayMath, NodeSymb, NodeUrl, NodeEmail, NodeFootnoteReference:
1026+		return true
1027+	}
1028+	return false
1029+}
1030+
1031+func (a *Assembler) addNode(ntype NodeType, start, end int32) int32 {
1032+	idx := int32(len(a.doc.Nodes))
1033+	a.doc.Nodes = append(a.doc.Nodes, Node{
1034+		Type:  ntype,
1035+		Start: start,
1036+		End:   end,
1037+		Child: -1,
1038+		Next:  -1,
1039+		Attr:  -1,
1040+	})
1041+	return idx
1042+}
1043+
1044+func (a *Assembler) linkToParent(parentIdx, nodeIdx int32) {
1045+	if parentIdx == -1 || nodeIdx == -1 || parentIdx == nodeIdx {
1046+		return
1047+	}
1048+	parent := &a.doc.Nodes[parentIdx]
1049+	if parent.Child == -1 {
1050+		parent.Child = nodeIdx
1051+	} else {
1052+		curr := parent.Child
1053+		if curr == nodeIdx {
1054+			return
1055+		}
1056+		for a.doc.Nodes[curr].Next != -1 {
1057+			if a.doc.Nodes[curr].Next == nodeIdx {
1058+				return
1059+			}
1060+			curr = a.doc.Nodes[curr].Next
1061+		}
1062+		a.doc.Nodes[curr].Next = nodeIdx
1063+	}
1064+}
1065+
1066+func (a *Assembler) openNode(ntype NodeType, start int32) int32 {
1067+	var parentIdx int32 = -1
1068+	if len(a.stack) > 0 {
1069+		parentIdx = a.stack[len(a.stack)-1]
1070+	}
1071+	nodeIdx := a.addNode(ntype, start, -1)
1072+	a.linkToParent(parentIdx, nodeIdx)
1073+	a.stack = append(a.stack, nodeIdx)
1074+	a.lastClosedNode = -1
1075+
1076+	if ntype == NodeTable {
1077+		a.currentTableAlignments = nil
1078+	}
1079+	return nodeIdx
1080+}
1081+
1082+func (a *Assembler) closeNode(end int32) int32 {
1083+	if len(a.stack) <= 1 {
1084+		return -1
1085+	}
1086+	nodeIdx := a.stack[len(a.stack)-1]
1087+	node := &a.doc.Nodes[nodeIdx]
1088+	if node.Type != NodeCodeBlock && node.Type != NodeRawBlock && node.Type != NodeDiv {
1089+		node.End = end
1090+	}
1091+	a.stack = a.stack[:len(a.stack)-1]
1092+	if a.isInline(a.doc.Nodes[nodeIdx].Type) {
1093+		a.lastClosedNode = nodeIdx
1094+	}
1095+	return nodeIdx
1096+}
1097+
1098+func (a *Assembler) addLeafNode(ntype NodeType, start, end int32) {
1099+	parentIdx := a.stack[len(a.stack)-1]
1100+
1101+	if ntype == NodeStr && a.lastClosedNode != -1 {
1102+		prevNode := &a.doc.Nodes[a.lastClosedNode]
1103+		if prevNode.Type == NodeStr && prevNode.End+1 == start {
1104+			prevNode.End = end
1105+			return
1106+		}
1107+	}
1108+
1109+	nodeIdx := a.addNode(ntype, start, end)
1110+	a.linkToParent(parentIdx, nodeIdx)
1111+	a.lastClosedNode = nodeIdx
1112+}
1113+
1114+func isOrderedMarker(marker byte) bool {
1115+	switch marker {
1116+	case '1', 'a', 'A', 'i', 'I':
1117+		return true
1118+	}
1119+	return false
1120+}
1121+
1122+func (a *Assembler) setStyleFromMarker(idx int32, marker byte) {
1123+	node := &a.doc.Nodes[idx]
1124+	switch marker {
1125+	case '1':
1126+		node.Data |= DataListDecimal
1127+	case 'a':
1128+		node.Data |= DataListLowerAlpha
1129+	case 'A':
1130+		node.Data |= DataListUpperAlpha
1131+	case 'i':
1132+		node.Data |= DataListLowerRoman
1133+	case 'I':
1134+		node.Data |= DataListUpperRoman
1135+	}
1136+}
+250, -0
  1@@ -0,0 +1,250 @@
  2+package djot
  3+
  4+type NodeType uint16
  5+
  6+const (
  7+	NodeDoc NodeType = iota
  8+	NodeSection
  9+	NodePara
 10+	NodeHeading
 11+	NodeThematicBreak
 12+	NodeDiv
 13+	NodeCodeBlock
 14+	NodeRawBlock
 15+	NodeBlockQuote
 16+	NodeOrderedList
 17+	NodeBulletList
 18+	NodeTaskList
 19+	NodeDefinitionList
 20+	NodeTable
 21+	NodeCaption
 22+	NodeRow
 23+	NodeCell
 24+	NodeListItem
 25+	NodeTaskListItem
 26+	NodeDefinitionListItem
 27+	NodeTerm
 28+	NodeDefinition
 29+	NodeFootnote
 30+	NodeReference
 31+
 32+	NodeStr
 33+	NodeSoftBreak
 34+	NodeHardBreak
 35+	NodeNonBreakingSpace
 36+	NodeSymb
 37+	NodeVerbatim
 38+	NodeRawInline
 39+	NodeInlineMath
 40+	NodeDisplayMath
 41+	NodeUrl
 42+	NodeEmail
 43+	NodeFootnoteReference
 44+	NodeSmartPunctuation
 45+	NodeEmph
 46+	NodeStrong
 47+	NodeLink
 48+	NodeImage
 49+	NodeSpan
 50+	NodeMark
 51+	NodeSuperscript
 52+	NodeSubscript
 53+	NodeInsert
 54+	NodeDelete
 55+	NodeDoubleQuoted
 56+	NodeSingleQuoted
 57+)
 58+
 59+type Node struct {
 60+	Type       NodeType
 61+	Start, End int32
 62+	Child      int32
 63+	Next       int32
 64+	Attr       int32
 65+	AttrCount  uint16
 66+	Level      int16
 67+	Data       uint32
 68+}
 69+
 70+type Attribute struct {
 71+	KeyStart, KeyEnd int32
 72+	ValStart, ValEnd int32
 73+}
 74+
 75+const (
 76+	AttrKeyID     = -1
 77+	AttrKeyClass  = -2
 78+	AttrKeyFormat = -3
 79+)
 80+
 81+type EventType uint16
 82+
 83+const (
 84+	EvNone EventType = iota
 85+	EvStr
 86+	EvSoftBreak
 87+	EvHardBreak
 88+	EvNonBreakingSpace
 89+	EvEscape
 90+	EvSymb
 91+	EvFootnoteReference
 92+	EvSmartPunctuation
 93+	EvOpenVerbatim
 94+	EvCloseVerbatim
 95+	EvRawInline
 96+	EvInlineMath
 97+	EvDisplayMath
 98+	EvUrl
 99+	EvEmail
100+
101+	EvOpenAttributes
102+	EvOpenInlineAttributes
103+	EvCloseAttributes
104+	EvAttrIdMarker
105+	EvAttrClassMarker
106+	EvAttrKey
107+	EvAttrValue
108+	EvAttrEqualMarker
109+	EvAttrQuoteMarker
110+	EvAttrSpace
111+	EvComment
112+
113+	EvText
114+
115+	EvOpenPara
116+	EvClosePara
117+	EvOpenHeading
118+	EvCloseHeading
119+	EvOpenBlockQuote
120+	EvCloseBlockQuote
121+	EvOpenList
122+	EvCloseList
123+	EvOpenListItem
124+	EvCloseListItem
125+	EvOpenDiv
126+	EvCloseDiv
127+	EvOpenCodeBlock
128+	EvCloseCodeBlock
129+	EvOpenRawBlock
130+	EvCloseRawBlock
131+	EvOpenTable
132+	EvCloseTable
133+	EvOpenRow
134+	EvCloseRow
135+	EvOpenCell
136+	EvCloseCell
137+	EvOpenCaption
138+	EvCloseCaption
139+	EvOpenFootnote
140+	EvCloseFootnote
141+	EvOpenReferenceDefinition
142+	EvCloseReferenceDefinition
143+
144+	EvOpenEmph
145+	EvCloseEmph
146+	EvOpenStrong
147+	EvCloseStrong
148+	EvOpenLinkText
149+	EvCloseLinkText
150+	EvOpenImageText
151+	EvCloseImageText
152+	EvOpenDestination
153+	EvCloseDestination
154+	EvOpenReference
155+	EvCloseReference
156+	EvOpenSpan
157+	EvCloseSpan
158+	EvOpenMark
159+	EvCloseMark
160+	EvOpenInsert
161+	EvCloseInsert
162+	EvOpenDelete
163+	EvCloseDelete
164+	EvOpenSuperscript
165+	EvCloseSuperscript
166+	EvOpenSubscript
167+	EvCloseSubscript
168+	EvOpenDoubleQuoted
169+	EvCloseDoubleQuoted
170+	EvOpenSingleQuoted
171+	EvCloseSingleQuoted
172+
173+	EvBlankLine
174+	EvThematicBreak
175+)
176+
177+type Event struct {
178+	Start, End int32
179+	Type       EventType
180+}
181+
182+type Reference struct {
183+	LabelStart, LabelEnd int32
184+	DestStart, DestEnd   int32
185+	Attributes           []Attribute
186+}
187+
188+type Document struct {
189+	Source          []byte
190+	Extra           []byte
191+	Nodes           []Node
192+	Attributes      []Attribute
193+	References      map[string]Reference
194+	UsedFootnotes   []string
195+	FootnoteContent map[string]int32
196+	Events          []Event
197+}
198+
199+type ContainerType uint8
200+
201+const (
202+	ContainerNone ContainerType = iota
203+	ContainerDoc
204+	ContainerBlockQuote
205+	ContainerListItem
206+	ContainerList
207+	ContainerPara
208+	ContainerHeading
209+	ContainerTable
210+	ContainerCaption
211+	ContainerFootnote
212+	ContainerDiv
213+	ContainerCodeBlock
214+	ContainerRawBlock
215+	ContainerReferenceDef
216+	ContainerThematicBreak
217+)
218+
219+type ContainerData struct {
220+	Level            int16
221+	Marker           byte
222+	MarkerEnd        byte
223+	Ordered          bool
224+	Tight            bool
225+	BlankLines       bool
226+	Indent           int32
227+	MarkerWidth      int32
228+	FenceChar        byte
229+	FenceLen         int32
230+	IsTask           bool
231+	Checked          bool
232+	NextNumber       int
233+	MarkerAmbiguous  bool
234+	MarkerFirstChar  byte
235+	OpenListEventIdx int32
236+}
237+
238+const (
239+	DataAlignLeft uint32 = 1 << iota
240+	DataAlignCenter
241+	DataAlignRight
242+	DataListTight
243+	DataListDecimal
244+	DataListLowerAlpha
245+	DataListUpperAlpha
246+	DataListLowerRoman
247+	DataListUpperRoman
248+	DataTaskChecked
249+	DataCellHeader
250+	DataNoAutoID
251+)
+154, -0
  1@@ -0,0 +1,154 @@
  2+package djot
  3+
  4+func isKeyStartChar(b byte) bool {
  5+	return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || b == '-' || b == ':'
  6+}
  7+
  8+func isKeyChar(b byte) bool {
  9+	return isKeyStartChar(b) || (b >= '0' && b <= '9')
 10+}
 11+
 12+func isIDChar(b byte) bool {
 13+	return isKeyChar(b)
 14+}
 15+
 16+func isSpace(b byte) bool {
 17+	return b == ' ' || b == '\t' || b == '\r' || b == '\n'
 18+}
 19+
 20+func ParseAttributes(source []byte, start, end int32) ([]Event, int32, bool) {
 21+	if start >= end || source[start] != '{' {
 22+		return nil, start, false
 23+	}
 24+
 25+	pos := start + 1
 26+	var events []Event
 27+
 28+	for pos < end {
 29+		b := source[pos]
 30+		if isSpace(b) {
 31+			pos++
 32+			continue
 33+		}
 34+
 35+		if b == '}' {
 36+			return events, pos, true
 37+		}
 38+
 39+		if b == '#' {
 40+			pos++
 41+			idStart := pos
 42+			for pos < end && isIDChar(source[pos]) {
 43+				pos++
 44+			}
 45+			if pos == idStart {
 46+				return nil, start, false
 47+			}
 48+			events = append(events, Event{Start: idStart, End: pos - 1, Type: EvAttrIdMarker})
 49+			continue
 50+		}
 51+
 52+		if b == '.' {
 53+			pos++
 54+			classStart := pos
 55+			for pos < end && isKeyChar(source[pos]) {
 56+				pos++
 57+			}
 58+			if pos == classStart {
 59+				return nil, start, false
 60+			}
 61+			events = append(events, Event{Start: classStart, End: pos - 1, Type: EvAttrClassMarker})
 62+			continue
 63+		}
 64+
 65+		if b == '%' {
 66+			pos++
 67+			commentStart := pos
 68+			for pos < end && source[pos] != '%' && source[pos] != '}' {
 69+				pos++
 70+			}
 71+			if pos < end && source[pos] == '%' {
 72+				events = append(events, Event{Start: commentStart, End: pos - 1, Type: EvComment})
 73+				pos++
 74+			}
 75+			continue
 76+		}
 77+
 78+		if b == '=' {
 79+			eqPos := pos
 80+			pos++
 81+			keyStart := pos
 82+			for pos < end && source[pos] != '}' && !isSpace(source[pos]) {
 83+				pos++
 84+			}
 85+			keyEnd := pos - 1
 86+			if keyEnd >= keyStart {
 87+				events = append(events, Event{Type: EvAttrKey, Start: eqPos, End: keyEnd})
 88+			}
 89+			continue
 90+		}
 91+
 92+		if isKeyStartChar(b) {
 93+			keyStart := pos
 94+			for pos < end && isKeyChar(source[pos]) {
 95+				pos++
 96+			}
 97+			keyEnd := pos - 1
 98+
 99+			for pos < end && isSpace(source[pos]) {
100+				pos++
101+			}
102+
103+			if pos < end && source[pos] == '=' {
104+				events = append(events, Event{Start: keyStart, End: keyEnd, Type: EvAttrKey})
105+				events = append(events, Event{Start: pos, End: pos, Type: EvAttrEqualMarker})
106+				pos++
107+
108+				for pos < end && isSpace(source[pos]) {
109+					pos++
110+				}
111+
112+				if pos < end && source[pos] == '"' {
113+					events = append(events, Event{Start: pos, End: pos, Type: EvAttrQuoteMarker})
114+					pos++
115+					valStart := pos
116+					valEnd := valStart
117+					for pos < end {
118+						if source[pos] == '\\' && pos+1 < end {
119+							pos += 2
120+						} else if source[pos] == '"' {
121+							valEnd = pos
122+							break
123+						} else {
124+							pos++
125+						}
126+					}
127+
128+					if pos < end && source[pos] == '"' {
129+						events = append(events, Event{Start: valStart, End: valEnd - 1, Type: EvAttrValue})
130+						events = append(events, Event{Start: pos, End: pos, Type: EvAttrQuoteMarker})
131+						pos++
132+					} else {
133+						return nil, start, false
134+					}
135+				} else {
136+					valStart := pos
137+					for pos < end && isKeyChar(source[pos]) {
138+						pos++
139+					}
140+					if pos == valStart {
141+						return nil, start, false
142+					}
143+					events = append(events, Event{Start: valStart, End: pos - 1, Type: EvAttrValue})
144+				}
145+			} else {
146+				return nil, start, false
147+			}
148+			continue
149+		}
150+
151+		return nil, start, false
152+	}
153+
154+	return nil, start, false
155+}
+1224, -0
   1@@ -0,0 +1,1224 @@
   2+package djot
   3+
   4+import (
   5+	"bytes"
   6+)
   7+
   8+type ActiveContainer struct {
   9+	Type  ContainerType
  10+	Start int32
  11+	End   int32
  12+	Data  ContainerData
  13+}
  14+type BlockParser struct {
  15+	source []byte
  16+	pos    int32
  17+	events []Event
  18+	stack  []ActiveContainer
  19+
  20+	inPara    bool
  21+	paraLines [][2]int32
  22+
  23+	pendingAttr []Event
  24+	hasAttr     bool
  25+
  26+	inAttrBlock    bool
  27+	attrLines      [][2]int32
  28+	lineIndent     int32
  29+	afterBlankLine bool
  30+}
  31+
  32+func NewBlockParser(source []byte) *BlockParser {
  33+	p := &BlockParser{
  34+		source:         source,
  35+		events:         make([]Event, 0, len(source)/8+32),
  36+		stack:          make([]ActiveContainer, 0, 16),
  37+		paraLines:      make([][2]int32, 0, 8),
  38+		attrLines:      make([][2]int32, 0, 2),
  39+		afterBlankLine: true,
  40+	}
  41+	p.stack = append(p.stack, ActiveContainer{Type: ContainerDoc})
  42+	return p
  43+}
  44+
  45+func (p *BlockParser) addEvent(start, end int32, etype EventType) {
  46+	p.events = append(p.events, Event{Start: start, End: end, Type: etype})
  47+}
  48+
  49+func (p *BlockParser) Parse() []Event {
  50+	for p.pos < int32(len(p.source)) {
  51+		lineStart := p.pos
  52+		eol := bytes.IndexByte(p.source[p.pos:], '\n')
  53+		var nextPos int32
  54+		if eol == -1 {
  55+			nextPos = int32(len(p.source))
  56+		} else {
  57+			nextPos = p.pos + int32(eol) + 1
  58+		}
  59+
  60+		p.handleLine(lineStart, nextPos)
  61+		p.pos = nextPos
  62+	}
  63+
  64+	p.closePara()
  65+	p.flushFailedAttr()
  66+	p.closePara()
  67+	p.discardDanglingAttr()
  68+	for i := len(p.stack) - 1; i > 0; i-- {
  69+		p.closeContainer(i)
  70+	}
  71+	return p.events
  72+}
  73+
  74+func (p *BlockParser) handleLine(start, end int32) {
  75+	contentEnd := end
  76+	if contentEnd > start && p.source[contentEnd-1] == '\n' {
  77+		contentEnd--
  78+		if contentEnd > start && p.source[contentEnd-1] == '\r' {
  79+			contentEnd--
  80+		}
  81+	}
  82+
  83+	p.lineIndent = 0
  84+	for start+p.lineIndent < contentEnd && p.source[start+p.lineIndent] == ' ' {
  85+		p.lineIndent++
  86+	}
  87+
  88+	matched := 1
  89+	currentPos := start
  90+	allMatched := true
  91+	tipIdx := len(p.stack) - 1
  92+
  93+	for i := 1; i < len(p.stack); i++ {
  94+		c := &p.stack[i]
  95+		if i == tipIdx && (c.Type == ContainerCodeBlock || c.Type == ContainerRawBlock) {
  96+			if p.isClosingFence(c, currentPos, contentEnd) {
  97+				p.closePara()
  98+				p.closeContainer(i)
  99+				return
 100+			}
 101+			p.closePara()
 102+			p.handleLeafContent(c, currentPos, end)
 103+			return
 104+		}
 105+
 106+		tipIsCode := p.stack[tipIdx].Type == ContainerCodeBlock ||
 107+			p.stack[tipIdx].Type == ContainerRawBlock
 108+		if !tipIsCode && c.Type == ContainerDiv {
 109+			if p.isClosingFence(c, currentPos, contentEnd) {
 110+				p.closePara()
 111+				for j := len(p.stack) - 1; j >= i; j-- {
 112+					p.closeContainer(j)
 113+				}
 114+				return
 115+			}
 116+		}
 117+
 118+		if c.Type == ContainerCodeBlock || c.Type == ContainerRawBlock {
 119+			break
 120+		}
 121+
 122+		newPos := p.checkContinue(c, currentPos, contentEnd)
 123+		if newPos != -1 {
 124+			matched++
 125+			currentPos = newPos
 126+			if isLeaf(c.Type) {
 127+				break
 128+			}
 129+		} else {
 130+			allMatched = false
 131+			break
 132+		}
 133+	}
 134+	isBlank := isBlankLine(p.source, currentPos, contentEnd)
 135+
 136+	if isBlank {
 137+		p.closePara()
 138+		p.discardDanglingAttr()
 139+		for i := len(p.stack) - 1; i >= matched; i-- {
 140+			p.closeContainer(i)
 141+		}
 142+		if p.inAttrBlock {
 143+			p.flushFailedAttr()
 144+		}
 145+		p.addEvent(start, end, EvBlankLine)
 146+		p.markListBlankLines(matched)
 147+		p.afterBlankLine = true
 148+		return
 149+	}
 150+
 151+	defer func() { p.afterBlankLine = false }()
 152+
 153+	if p.inAttrBlock {
 154+		p.attrLines = append(p.attrLines, [2]int32{currentPos, contentEnd})
 155+		p.tryParseBufferedAttr()
 156+		return
 157+	}
 158+
 159+	if allMatched && !p.inAttrBlock && currentPos < contentEnd && p.source[currentPos] == '{' && (p.afterBlankLine || p.hasAttr) {
 160+		p.inAttrBlock = true
 161+		p.attrLines = append(p.attrLines, [2]int32{currentPos, contentEnd})
 162+		p.tryParseBufferedAttr()
 163+		return
 164+	}
 165+
 166+	newStarts := false
 167+	if !allMatched || p.inPara || !isLeaf(p.stack[len(p.stack)-1].Type) {
 168+		openPos := currentPos
 169+		if openPos < start+p.lineIndent {
 170+			openPos = start + p.lineIndent
 171+		}
 172+		for {
 173+			for i := 0; i < 3 && openPos < contentEnd && p.source[openPos] == ' '; i++ {
 174+				openPos++
 175+			}
 176+
 177+			ctype, nextOpenPos, data, ok := p.findOpen(start, openPos, contentEnd)
 178+			if !ok {
 179+				break
 180+			}
 181+
 182+			if p.inPara && !p.canInterrupt(ctype, openPos, contentEnd) {
 183+				break
 184+			}
 185+
 186+			newStarts = true
 187+			canHaveImmediateAttr := ctype == ContainerDiv || ctype == ContainerCodeBlock || ctype == ContainerRawBlock || ctype == ContainerHeading
 188+			attrPos := nextOpenPos
 189+			for attrPos < contentEnd && (p.source[attrPos] == ' ' || p.source[attrPos] == '\t') {
 190+				attrPos++
 191+			}
 192+			if attrPos < contentEnd && canHaveImmediateAttr {
 193+				if p.source[attrPos] == '{' {
 194+					attrEvents, nextPos, ok := ParseAttributes(p.source, attrPos, contentEnd)
 195+					if ok && isBlankLine(p.source, nextPos+1, contentEnd) {
 196+						p.hasAttr = true
 197+						p.pendingAttr = append(p.pendingAttr, attrEvents...)
 198+						p.closePara()
 199+						for i := len(p.stack) - 1; i >= matched; i-- {
 200+							p.closeContainer(i)
 201+						}
 202+						p.openContainer(ctype, openPos, data)
 203+						return
 204+					}
 205+				} else if ctype == ContainerDiv || ctype == ContainerCodeBlock || ctype == ContainerRawBlock {
 206+					classStart := attrPos
 207+					if ctype == ContainerDiv && p.source[classStart] == '.' {
 208+						classStart++
 209+					}
 210+					classEnd := classStart
 211+					for classEnd < contentEnd && !isSpace(p.source[classEnd]) {
 212+						classEnd++
 213+					}
 214+					if classEnd > classStart {
 215+						p.hasAttr = true
 216+						if ctype == ContainerRawBlock {
 217+							eqPos := attrPos - 1
 218+							p.pendingAttr = append(p.pendingAttr, Event{Start: eqPos, End: classEnd - 1, Type: EvAttrKey})
 219+						} else if ctype == ContainerCodeBlock && p.source[classStart] == '=' {
 220+							ctype = ContainerRawBlock
 221+							p.pendingAttr = append(p.pendingAttr, Event{Start: classStart, End: classEnd - 1, Type: EvAttrKey})
 222+						} else {
 223+							p.pendingAttr = append(p.pendingAttr, Event{Start: classStart, End: classEnd - 1, Type: EvAttrClassMarker})
 224+						}
 225+						nextAttrPos := classEnd
 226+						for nextAttrPos < contentEnd && isSpace(p.source[nextAttrPos]) {
 227+							nextAttrPos++
 228+						}
 229+						if nextAttrPos < contentEnd && p.source[nextAttrPos] == '{' {
 230+							attrEvents, _, ok := ParseAttributes(p.source, nextAttrPos, contentEnd)
 231+							if ok {
 232+								p.pendingAttr = append(p.pendingAttr, attrEvents...)
 233+							}
 234+						}
 235+						p.closePara()
 236+						for i := len(p.stack) - 1; i >= matched; i-- {
 237+							p.closeContainer(i)
 238+						}
 239+						p.openContainer(ctype, openPos, data)
 240+						return
 241+					}
 242+				}
 243+			}
 244+
 245+			p.closePara()
 246+			for i := len(p.stack) - 1; i >= matched; i-- {
 247+				c := &p.stack[i]
 248+				if c.Data.BlankLines && c.Type == ContainerListItem {
 249+					c.Data.Tight = false
 250+					if i > 0 && p.stack[i-1].Type == ContainerList {
 251+						p.stack[i-1].Data.Tight = false
 252+					}
 253+				}
 254+				p.closeContainer(i)
 255+			}
 256+			p.openContainer(ctype, openPos, data)
 257+
 258+			currentPos = nextOpenPos
 259+			openPos = nextOpenPos
 260+			matched = len(p.stack)
 261+
 262+			if currentPos < contentEnd && p.source[currentPos] == '{' && !p.inAttrBlock {
 263+				p.inAttrBlock = true
 264+				p.attrLines = append(p.attrLines, [2]int32{currentPos, contentEnd})
 265+				p.tryParseBufferedAttr()
 266+				return
 267+			}
 268+
 269+			if isLeaf(ctype) {
 270+				if ctype == ContainerReferenceDef {
 271+					currentPos = openPos
 272+				}
 273+				break
 274+			}
 275+		}
 276+	}
 277+
 278+	isLazy := !isBlank && !newStarts && matched < len(p.stack) && p.inPara
 279+
 280+	if !isLazy {
 281+		if matched < len(p.stack) {
 282+			p.closePara()
 283+			for i := len(p.stack) - 1; i >= matched; i-- {
 284+				p.closeContainer(i)
 285+			}
 286+		}
 287+	}
 288+
 289+	tipIdx = len(p.stack) - 1
 290+	tip := &p.stack[tipIdx]
 291+
 292+	if tip.Type == ContainerTable {
 293+		p.handleTableRow(currentPos, contentEnd)
 294+		return
 295+	}
 296+
 297+	if isBlankLine(p.source, currentPos, contentEnd) {
 298+		return
 299+	}
 300+
 301+	if hasInlineContent(tip.Type) || isBlock(tip.Type) || p.inPara {
 302+		for currentPos < contentEnd && (p.source[currentPos] == ' ' || p.source[currentPos] == '\t') {
 303+			currentPos++
 304+		}
 305+		p.accumulateInlineLine(currentPos, contentEnd)
 306+	} else {
 307+		p.handleLeafContent(tip, currentPos, end)
 308+	}
 309+}
 310+
 311+func isBlock(t ContainerType) bool {
 312+	switch t {
 313+	case ContainerDoc, ContainerBlockQuote, ContainerListItem, ContainerList, ContainerDiv, ContainerFootnote:
 314+		return true
 315+	}
 316+	return false
 317+}
 318+
 319+func hasInlineContent(t ContainerType) bool {
 320+	switch t {
 321+	case ContainerPara, ContainerHeading, ContainerCaption:
 322+		return true
 323+	}
 324+	return false
 325+}
 326+
 327+func (p *BlockParser) accumulateInlineLine(start, end int32) {
 328+	tipIdx := len(p.stack) - 1
 329+	tip := &p.stack[tipIdx]
 330+	if (tip.Type == ContainerPara || isBlock(tip.Type)) && !p.inPara {
 331+		p.inPara = true
 332+		if p.hasAttr {
 333+			p.addEvent(start, start, EvOpenAttributes)
 334+			p.events = append(p.events, p.pendingAttr...)
 335+			p.addEvent(start, start, EvCloseAttributes)
 336+			p.hasAttr = false
 337+			p.pendingAttr = p.pendingAttr[:0]
 338+		}
 339+		p.addEvent(start, start, EvOpenPara)
 340+	}
 341+	p.paraLines = append(p.paraLines, [2]int32{start, end})
 342+}
 343+
 344+func (p *BlockParser) tryParseBufferedAttr() {
 345+	if len(p.attrLines) == 0 {
 346+		return
 347+	}
 348+
 349+	start := p.attrLines[0][0]
 350+	end := p.attrLines[len(p.attrLines)-1][1]
 351+
 352+	attrEvents, nextPos, ok := ParseAttributes(p.source, start, end)
 353+
 354+	if ok {
 355+		if isBlankLine(p.source, nextPos+1, end) {
 356+			p.closePara()
 357+			p.hasAttr = true
 358+			p.pendingAttr = append(p.pendingAttr, attrEvents...)
 359+			p.inAttrBlock = false
 360+			p.attrLines = p.attrLines[:0]
 361+			return
 362+		} else {
 363+			p.flushFailedAttr()
 364+			return
 365+		}
 366+	} else {
 367+		hasClosing := false
 368+		for i := start; i < end; i++ {
 369+			if p.source[i] == '}' {
 370+				hasClosing = true
 371+				break
 372+			}
 373+		}
 374+		if hasClosing {
 375+			p.flushFailedAttr()
 376+		}
 377+	}
 378+}
 379+func (p *BlockParser) flushFailedAttr() {
 380+	p.inAttrBlock = false
 381+	for _, line := range p.attrLines {
 382+		start := line[0]
 383+		for start < line[1] && (p.source[start] == ' ' || p.source[start] == '\t') {
 384+			start++
 385+		}
 386+		p.accumulateInlineLine(start, line[1])
 387+	}
 388+	p.attrLines = p.attrLines[:0]
 389+}
 390+
 391+func (p *BlockParser) closePara() {
 392+	if !p.inPara {
 393+		if len(p.paraLines) > 0 {
 394+			for i, line := range p.paraLines {
 395+				start := line[0]
 396+				end := line[1]
 397+				if i == len(p.paraLines)-1 {
 398+					for end > start && (p.source[end-1] == ' ' || p.source[end-1] == '\t') {
 399+						end--
 400+					}
 401+				}
 402+				isHardBreak := false
 403+				if i < len(p.paraLines)-1 {
 404+					tempEnd := end
 405+					for tempEnd > start && (p.source[tempEnd-1] == ' ' || p.source[tempEnd-1] == '\t') {
 406+						tempEnd--
 407+					}
 408+					if tempEnd > start && p.source[tempEnd-1] == '\\' {
 409+						backslashCount := 0
 410+						for j := tempEnd - 1; j >= start && p.source[j] == '\\'; j-- {
 411+							backslashCount++
 412+						}
 413+						if backslashCount%2 != 0 {
 414+							isHardBreak = true
 415+							end = tempEnd - 1
 416+							for end > start && (p.source[end-1] == ' ' || p.source[end-1] == '\t') {
 417+								end--
 418+							}
 419+						}
 420+					}
 421+				}
 422+				if start < end {
 423+					p.addEvent(start, end-1, EvText)
 424+				}
 425+				if i < len(p.paraLines)-1 {
 426+					if isHardBreak {
 427+						p.addEvent(line[1], line[1], EvHardBreak)
 428+					} else {
 429+						p.addEvent(line[1], line[1], EvSoftBreak)
 430+					}
 431+				}
 432+			}
 433+			p.paraLines = p.paraLines[:0]
 434+		}
 435+		return
 436+	}
 437+
 438+	for i, line := range p.paraLines {
 439+		start := line[0]
 440+		end := line[1]
 441+		if i == len(p.paraLines)-1 {
 442+			for end > start && (p.source[end-1] == ' ' || p.source[end-1] == '\t') {
 443+				end--
 444+			}
 445+		}
 446+		isHardBreak := false
 447+		if i < len(p.paraLines)-1 {
 448+			tempEnd := end
 449+			for tempEnd > start && (p.source[tempEnd-1] == ' ' || p.source[tempEnd-1] == '\t') {
 450+				tempEnd--
 451+			}
 452+			if tempEnd > start && p.source[tempEnd-1] == '\\' {
 453+				backslashCount := 0
 454+				for j := tempEnd - 1; j >= start && p.source[j] == '\\'; j-- {
 455+					backslashCount++
 456+				}
 457+				if backslashCount%2 != 0 {
 458+					isHardBreak = true
 459+					end = tempEnd - 1
 460+					for end > start && (p.source[end-1] == ' ' || p.source[end-1] == '\t') {
 461+						end--
 462+					}
 463+				}
 464+			}
 465+		}
 466+		if start < end {
 467+			p.addEvent(start, end-1, EvText)
 468+		}
 469+		if i < len(p.paraLines)-1 {
 470+			if isHardBreak {
 471+				p.addEvent(line[1], line[1], EvHardBreak)
 472+			} else {
 473+				p.addEvent(line[1], line[1], EvSoftBreak)
 474+			}
 475+		}
 476+	}
 477+
 478+	if len(p.paraLines) > 0 {
 479+		lastLine := p.paraLines[len(p.paraLines)-1]
 480+		p.addEvent(lastLine[1], lastLine[1], EvClosePara)
 481+	}
 482+	p.inPara = false
 483+	p.paraLines = p.paraLines[:0]
 484+}
 485+
 486+func (p *BlockParser) discardDanglingAttr() {
 487+	p.hasAttr = false
 488+	p.pendingAttr = p.pendingAttr[:0]
 489+}
 490+
 491+func isLeaf(t ContainerType) bool {
 492+	switch t {
 493+	case ContainerPara, ContainerHeading, ContainerCodeBlock, ContainerRawBlock, ContainerThematicBreak, ContainerReferenceDef, ContainerCaption, ContainerTable:
 494+		return true
 495+	}
 496+	return false
 497+}
 498+
 499+func isBlankLine(source []byte, start, end int32) bool {
 500+	for i := start; i < end; i++ {
 501+		if source[i] != ' ' && source[i] != '\t' {
 502+			return false
 503+		}
 504+	}
 505+	return true
 506+}
 507+
 508+func (p *BlockParser) markListBlankLines(matched int) {
 509+}
 510+
 511+func (p *BlockParser) checkContinue(c *ActiveContainer, pos, end int32) int32 {
 512+	switch c.Type {
 513+	case ContainerBlockQuote:
 514+		if pos < end && p.source[pos] == '>' {
 515+			pos++
 516+			if pos == end {
 517+				return pos
 518+			}
 519+			if p.source[pos] == ' ' {
 520+				return pos + 1
 521+			}
 522+			return -1
 523+		}
 524+	case ContainerHeading:
 525+		if isBlankLine(p.source, pos, end) {
 526+			return -1
 527+		}
 528+		hLevel := int32(0)
 529+		for pos+hLevel < end && p.source[pos+hLevel] == '#' {
 530+			hLevel++
 531+		}
 532+		if hLevel > 0 && hLevel <= 6 && (pos+hLevel == end || p.source[pos+hLevel] == ' ' || p.source[pos+hLevel] == '\t') {
 533+			if hLevel == int32(c.Data.Level) {
 534+				newPos := pos + hLevel
 535+				if newPos < end && (p.source[newPos] == ' ' || p.source[newPos] == '\t') {
 536+					newPos++
 537+				}
 538+				return newPos
 539+			}
 540+			return -1
 541+		}
 542+		return pos
 543+	case ContainerCodeBlock, ContainerRawBlock:
 544+		if p.isClosingFence(c, pos, end) {
 545+			return -1
 546+		}
 547+		return pos
 548+	case ContainerDiv:
 549+		return pos
 550+	case ContainerList:
 551+		return pos
 552+	case ContainerListItem:
 553+		if isBlankLine(p.source, pos, end) {
 554+			return pos
 555+		}
 556+		if p.lineIndent > c.Data.Indent {
 557+			return pos
 558+		}
 559+		if p.inPara && !p.afterBlankLine {
 560+			markerPos := pos + p.lineIndent
 561+			_, _, ok := parseListMarker(p.source, markerPos, end)
 562+			if !ok {
 563+				return pos
 564+			}
 565+		}
 566+		return -1
 567+	case ContainerTable:
 568+		if pos < end && p.source[pos] == '|' {
 569+			return pos
 570+		}
 571+	case ContainerFootnote:
 572+		if isBlankLine(p.source, pos, end) {
 573+			return pos
 574+		}
 575+		if p.lineIndent > c.Data.Indent {
 576+			return pos
 577+		}
 578+		return -1
 579+	case ContainerReferenceDef:
 580+		if isBlankLine(p.source, pos, end) {
 581+			return pos
 582+		}
 583+		if p.lineIndent > c.Data.Indent {
 584+			return pos
 585+		}
 586+		return -1
 587+	case ContainerCaption:
 588+		if isBlankLine(p.source, pos, end) {
 589+			return -1
 590+		}
 591+		return pos
 592+	}
 593+	return -1
 594+}
 595+
 596+func (p *BlockParser) isClosingFence(c *ActiveContainer, pos, end int32) bool {
 597+	i := 0
 598+	for i < 3 && pos < end && p.source[pos] == ' ' {
 599+		pos++
 600+		i++
 601+	}
 602+
 603+	char := c.Data.FenceChar
 604+	count := int32(0)
 605+	for pos < end && p.source[pos] == char {
 606+		count++
 607+		pos++
 608+	}
 609+
 610+	if count < c.Data.FenceLen {
 611+		return false
 612+	}
 613+
 614+	for pos < end && (p.source[pos] == ' ' || p.source[pos] == '\t') {
 615+		pos++
 616+	}
 617+
 618+	return pos == end || p.source[pos] == '\r' || p.source[pos] == '\n'
 619+}
 620+
 621+func (p *BlockParser) findOpen(start, pos, end int32) (ContainerType, int32, ContainerData, bool) {
 622+	if pos >= end {
 623+		return ContainerNone, pos, ContainerData{}, false
 624+	}
 625+	b := p.source[pos]
 626+
 627+	if isThematicBreak(p.source, pos, end) {
 628+		return ContainerThematicBreak, end, ContainerData{}, true
 629+	}
 630+
 631+	if b == '`' || b == '~' || b == ':' {
 632+		count := int32(0)
 633+		for i := pos; i < end && p.source[i] == b; i++ {
 634+			count++
 635+		}
 636+		if count >= 3 {
 637+			if b == '`' || b == '~' {
 638+				rest := p.source[pos+count : end]
 639+				hasInvalid := false
 640+				for j := 0; j < len(rest); j++ {
 641+					if rest[j] == '{' {
 642+						break
 643+					}
 644+					if rest[j] == b {
 645+						c := int32(0)
 646+						for j < len(rest) && rest[j] == b {
 647+							c++
 648+							j++
 649+						}
 650+						if c >= count {
 651+							hasInvalid = true
 652+							break
 653+						}
 654+						j--
 655+						continue
 656+					}
 657+					if rest[j] != ' ' && rest[j] != '	' {
 658+						k := j
 659+						for k < len(rest) && rest[k] != ' ' && rest[k] != '	' && rest[k] != '{' {
 660+							if rest[k] == b {
 661+								c := int32(0)
 662+								for k < len(rest) && rest[k] == b {
 663+									c++
 664+									k++
 665+								}
 666+								if c >= count {
 667+									hasInvalid = true
 668+									break
 669+								}
 670+								continue
 671+							}
 672+							k++
 673+						}
 674+						if hasInvalid {
 675+							break
 676+						}
 677+						for k < len(rest) && (rest[k] == ' ' || rest[k] == '	') {
 678+							k++
 679+						}
 680+						if k < len(rest) && rest[k] != '{' {
 681+							hasInvalid = true
 682+							break
 683+						}
 684+						j = k - 1
 685+					}
 686+				}
 687+				if hasInvalid {
 688+					return ContainerNone, pos, ContainerData{}, false
 689+				}
 690+			}
 691+			if b == ':' {
 692+				return ContainerDiv, pos + count, ContainerData{FenceChar: b, FenceLen: count, Indent: pos - start}, true
 693+			}
 694+			classPos := pos + count
 695+			for classPos < end && p.source[classPos] == ' ' {
 696+				classPos++
 697+			}
 698+			if classPos < end && p.source[classPos] == '=' {
 699+				return ContainerRawBlock, classPos + 1, ContainerData{FenceChar: b, FenceLen: count, Indent: pos - start}, true
 700+			}
 701+			return ContainerCodeBlock, pos + count, ContainerData{FenceChar: b, FenceLen: count, Indent: pos - start}, true
 702+		}
 703+	}
 704+
 705+	if b == '#' {
 706+		level := int32(0)
 707+		for i := pos; i < end && p.source[i] == '#'; i++ {
 708+			level++
 709+		}
 710+		if level > 0 && level <= 6 && (pos+level == end || p.source[pos+level] == ' ') {
 711+			newPos := pos + level
 712+			if newPos < end && p.source[newPos] == ' ' {
 713+				newPos++
 714+			}
 715+			return ContainerHeading, newPos, ContainerData{Level: int16(level)}, true
 716+		}
 717+	}
 718+
 719+	if b == '>' {
 720+		newPos := pos + 1
 721+		if newPos == end {
 722+			return ContainerBlockQuote, newPos, ContainerData{}, true
 723+		}
 724+		if p.source[newPos] == ' ' {
 725+			return ContainerBlockQuote, newPos + 1, ContainerData{}, true
 726+		}
 727+		return ContainerNone, pos, ContainerData{}, false
 728+	}
 729+
 730+	if b == '|' {
 731+		pipes := 0
 732+		i := pos
 733+		for i < end {
 734+			if p.source[i] == '|' {
 735+				pipes++
 736+				i++
 737+			} else if p.source[i] == '`' {
 738+				count := int32(0)
 739+				for i < end && p.source[i] == '`' {
 740+					count++
 741+					i++
 742+				}
 743+				for i < end {
 744+					if p.source[i] == '`' {
 745+						c := int32(0)
 746+						for i < end && p.source[i] == '`' {
 747+							c++
 748+							i++
 749+						}
 750+						if c == count {
 751+							break
 752+						}
 753+					} else {
 754+						i++
 755+					}
 756+				}
 757+			} else {
 758+				i++
 759+			}
 760+		}
 761+		if pipes >= 2 {
 762+			return ContainerTable, pos, ContainerData{}, true
 763+		}
 764+	}
 765+
 766+	if b == '^' {
 767+		if pos+1 < end && p.source[pos+1] == ' ' {
 768+			return ContainerCaption, pos + 2, ContainerData{}, true
 769+		}
 770+	}
 771+
 772+	if b == '[' {
 773+		if pos+1 < end && p.source[pos+1] == '^' {
 774+			for i := pos + 2; i < end; i++ {
 775+				if p.source[i] == ']' && i+1 < end && p.source[i+1] == ':' {
 776+					newPos := i + 2
 777+					for newPos < end && (p.source[newPos] == ' ' || p.source[newPos] == '\t') {
 778+						newPos++
 779+					}
 780+					return ContainerFootnote, newPos, ContainerData{Indent: p.lineIndent, MarkerWidth: newPos - pos}, true
 781+				}
 782+			}
 783+		} else {
 784+			for i := pos + 1; i < end; i++ {
 785+				if p.source[i] == ']' && i+1 < end && p.source[i+1] == ':' {
 786+					newPos := i + 2
 787+					for newPos < end && (p.source[newPos] == ' ' || p.source[newPos] == '\t') {
 788+						newPos++
 789+					}
 790+					return ContainerReferenceDef, newPos, ContainerData{Indent: p.lineIndent}, true
 791+				}
 792+			}
 793+		}
 794+	}
 795+
 796+	newPos, data, ok := parseListMarker(p.source, pos, end)
 797+	if ok {
 798+		data.Indent = pos - start
 799+		if !p.canListMarkerInterrupt(p.lineIndent, data.Marker, p.afterBlankLine) {
 800+			return ContainerNone, pos, ContainerData{}, false
 801+		}
 802+		return ContainerListItem, newPos, data, true
 803+	}
 804+
 805+	return ContainerNone, pos, ContainerData{}, false
 806+}
 807+
 808+func (p *BlockParser) openContainer(ctype ContainerType, start int32, data ContainerData) {
 809+	if p.hasAttr {
 810+		p.addEvent(start, start, EvOpenAttributes)
 811+		p.events = append(p.events, p.pendingAttr...)
 812+		p.addEvent(start, start, EvCloseAttributes)
 813+		p.hasAttr = false
 814+		p.pendingAttr = p.pendingAttr[:0]
 815+	}
 816+
 817+	switch ctype {
 818+	case ContainerHeading:
 819+		p.addEvent(start, start+int32(data.Level)-1, EvOpenHeading)
 820+	case ContainerBlockQuote:
 821+		p.addEvent(start, start, EvOpenBlockQuote)
 822+	case ContainerListItem:
 823+		tipIdx := len(p.stack) - 1
 824+		tip := &p.stack[tipIdx]
 825+		if tip.Type == ContainerListItem {
 826+			pIdx := len(p.stack) - 2
 827+			parent := &p.stack[pIdx]
 828+			if parent.Type == ContainerList {
 829+				itemIndent := tip.Data.Indent
 830+				if data.Indent > itemIndent {
 831+					p.openList(start, data)
 832+				} else if data.Indent == parent.Data.Indent {
 833+					p.closeContainer(tipIdx)
 834+					if parent.Data.Marker == data.Marker && parent.Data.MarkerEnd == data.MarkerEnd && parent.Data.Ordered == data.Ordered {
 835+						if data.Ordered {
 836+							parent.Data.NextNumber++
 837+						}
 838+					} else {
 839+						p.closeContainer(pIdx)
 840+						p.openList(start, data)
 841+					}
 842+				} else {
 843+					p.closeContainer(tipIdx)
 844+					p.closeContainer(pIdx)
 845+					p.openList(start, data)
 846+				}
 847+			} else {
 848+				p.openList(start, data)
 849+			}
 850+		} else if tip.Type == ContainerList {
 851+			if tip.Data.Marker == data.Marker && tip.Data.MarkerEnd == data.MarkerEnd && tip.Data.Ordered == data.Ordered {
 852+				if !data.MarkerAmbiguous {
 853+					tip.Data.MarkerAmbiguous = false
 854+				}
 855+			} else if data.Indent == tip.Data.Indent {
 856+				if p.tryNarrowList(tip, data) {
 857+				} else {
 858+					p.closeContainer(tipIdx)
 859+					p.openList(start, data)
 860+				}
 861+			} else {
 862+				p.openList(start, data)
 863+			}
 864+		} else {
 865+			p.openList(start, data)
 866+		}
 867+
 868+		taskStatus := int32(0)
 869+		if data.IsTask {
 870+			if data.Checked {
 871+				taskStatus = 2
 872+			} else {
 873+				taskStatus = 1
 874+			}
 875+		}
 876+		p.addEvent(start, taskStatus, EvOpenListItem)
 877+	case ContainerCodeBlock:
 878+		p.addEvent(start, start+data.FenceLen-1, EvOpenCodeBlock)
 879+		p.addEvent(start, start, EvNone)
 880+	case ContainerRawBlock:
 881+		p.addEvent(start, start+data.FenceLen-1, EvOpenRawBlock)
 882+		p.addEvent(start, start, EvNone)
 883+	case ContainerDiv:
 884+		p.addEvent(start, start+data.FenceLen-1, EvOpenDiv)
 885+		p.addEvent(start, start, EvNone)
 886+	case ContainerThematicBreak:
 887+		p.addEvent(start, start, EvThematicBreak)
 888+	case ContainerTable:
 889+		p.addEvent(start, start, EvOpenTable)
 890+	case ContainerCaption:
 891+		p.addEvent(start, start, EvOpenCaption)
 892+	case ContainerFootnote:
 893+		p.addEvent(start, start, EvOpenFootnote)
 894+		p.addEvent(start, start+data.MarkerWidth-1, EvStr)
 895+	case ContainerReferenceDef:
 896+		p.addEvent(start, start, EvOpenReferenceDefinition)
 897+	}
 898+	p.stack = append(p.stack, ActiveContainer{Type: ctype, Start: start, Data: data})
 899+}
 900+
 901+func (p *BlockParser) closeContainer(idx int) {
 902+	c := p.stack[idx]
 903+	switch c.Type {
 904+	case ContainerHeading:
 905+		p.addEvent(c.Start, c.Start, EvCloseHeading)
 906+	case ContainerBlockQuote:
 907+		p.addEvent(c.Start, c.Start, EvCloseBlockQuote)
 908+	case ContainerListItem:
 909+		p.addEvent(c.Start, c.Start, EvCloseListItem)
 910+	case ContainerList:
 911+		tight := int32(0)
 912+		if c.Data.Tight {
 913+			tight = 1
 914+		}
 915+		p.addEvent(c.Start, tight, EvCloseList)
 916+	case ContainerCodeBlock:
 917+		p.addEvent(c.Start, c.Start, EvCloseCodeBlock)
 918+	case ContainerRawBlock:
 919+		p.addEvent(c.Start, c.Start, EvCloseRawBlock)
 920+	case ContainerDiv:
 921+		p.addEvent(c.Start, c.Start, EvCloseDiv)
 922+	case ContainerTable:
 923+		p.addEvent(c.Start, c.Start, EvCloseTable)
 924+	case ContainerCaption:
 925+		p.addEvent(c.Start, c.Start, EvCloseCaption)
 926+	case ContainerFootnote:
 927+		p.addEvent(c.Start, c.Start, EvCloseFootnote)
 928+	case ContainerReferenceDef:
 929+		p.addEvent(c.Start, c.Start, EvCloseReferenceDefinition)
 930+	}
 931+	p.stack = p.stack[:idx]
 932+}
 933+
 934+func (p *BlockParser) handleLeafContent(tip *ActiveContainer, start, end int32) {
 935+	contentEnd := end
 936+	if contentEnd > start && p.source[contentEnd-1] == '\n' {
 937+		contentEnd--
 938+		if contentEnd > start && p.source[contentEnd-1] == '\r' {
 939+			contentEnd--
 940+		}
 941+	}
 942+
 943+	if p.hasAttr {
 944+		p.addEvent(start, start, EvOpenAttributes)
 945+		p.events = append(p.events, p.pendingAttr...)
 946+		p.addEvent(start, start, EvCloseAttributes)
 947+		p.hasAttr = false
 948+		p.pendingAttr = p.pendingAttr[:0]
 949+	}
 950+	switch tip.Type {
 951+	case ContainerCodeBlock, ContainerRawBlock, ContainerDiv:
 952+		if p.isClosingFence(tip, start, end) {
 953+			p.closeContainer(len(p.stack) - 1)
 954+			return
 955+		}
 956+
 957+		indentToStrip := tip.Data.Indent
 958+		current := start
 959+		for indentToStrip > 0 && current < end && p.source[current] == ' ' {
 960+			current++
 961+			indentToStrip--
 962+		}
 963+		if end < int32(len(p.source)) && p.source[end] == '\n' {
 964+			end++
 965+		} else if end < int32(len(p.source)) && p.source[end] == '\r' {
 966+			end++
 967+			if end < int32(len(p.source)) && p.source[end] == '\n' {
 968+				end++
 969+			}
 970+		}
 971+		p.addEvent(current, end-1, EvStr)
 972+	case ContainerTable:
 973+		p.handleTableRow(start, end)
 974+	case ContainerHeading, ContainerCaption:
 975+		p.accumulateInlineLine(start, end)
 976+	case ContainerReferenceDef:
 977+		actualEnd := contentEnd - 1
 978+		if actualEnd >= int32(len(p.source)) {
 979+			actualEnd = int32(len(p.source)) - 1
 980+		}
 981+		if tip.Start <= actualEnd {
 982+			p.addEvent(tip.Start, actualEnd, EvStr)
 983+		}
 984+	case ContainerFootnote:
 985+		actualEnd := contentEnd - 1
 986+		if actualEnd >= int32(len(p.source)) {
 987+			actualEnd = int32(len(p.source)) - 1
 988+		}
 989+		if actualEnd >= start {
 990+			p.addEvent(start, actualEnd, EvStr)
 991+		}
 992+	}
 993+}
 994+
 995+func (p *BlockParser) handleTableRow(start, end int32) {
 996+	isSep := false
 997+	hasDash := false
 998+	for i := start; i < end; i++ {
 999+		b := p.source[i]
1000+		if b == '-' || b == ':' {
1001+			hasDash = true
1002+		} else if b != '|' && b != ' ' && b != '\t' {
1003+			hasDash = false
1004+			break
1005+		}
1006+	}
1007+	isSep = hasDash
1008+
1009+	if isSep {
1010+		p.addEvent(start, end, EvStr)
1011+		return
1012+	}
1013+
1014+	p.addEvent(start, start, EvOpenRow)
1015+	pos := start
1016+	for pos < end {
1017+		if p.source[pos] == '|' {
1018+			pos++
1019+			if pos >= end {
1020+				break
1021+			}
1022+			cellStart := pos
1023+			for pos < end {
1024+				if p.source[pos] == '\\' && pos+1 < end {
1025+					pos += 2
1026+				} else if p.source[pos] == '`' {
1027+					count := int32(0)
1028+					for pos < end && p.source[pos] == '`' {
1029+						count++
1030+						pos++
1031+					}
1032+					for pos < end {
1033+						if p.source[pos] == '`' {
1034+							c := int32(0)
1035+							for pos < end && p.source[pos] == '`' {
1036+								c++
1037+								pos++
1038+							}
1039+							if c == count {
1040+								break
1041+							}
1042+						} else {
1043+							pos++
1044+						}
1045+					}
1046+				} else if p.source[pos] == '|' {
1047+					break
1048+				} else {
1049+					pos++
1050+				}
1051+			}
1052+			cellEnd := pos - 1
1053+			for cellStart <= cellEnd && isSpace(p.source[cellStart]) {
1054+				cellStart++
1055+			}
1056+			for cellEnd >= cellStart && isSpace(p.source[cellEnd]) {
1057+				cellEnd--
1058+			}
1059+			p.addEvent(cellStart, cellStart, EvOpenCell)
1060+			if cellStart <= cellEnd {
1061+				p.addEvent(cellStart, cellEnd, EvText)
1062+			}
1063+			p.addEvent(pos, pos, EvCloseCell)
1064+		} else {
1065+			pos++
1066+		}
1067+	}
1068+	p.addEvent(end, end, EvCloseRow)
1069+}
1070+
1071+func (p *BlockParser) canListMarkerInterrupt(markerIndent int32, markerChar byte, afterBlankLine bool) bool {
1072+	isDefinition := markerChar == ':'
1073+
1074+	for i := len(p.stack) - 1; i >= 0; i-- {
1075+		c := p.stack[i]
1076+		if c.Type == ContainerList || c.Type == ContainerListItem {
1077+			parentMarker := c.Data.Marker
1078+			parentIsDefinition := parentMarker == ':'
1079+			parentIndent := c.Data.Indent
1080+
1081+			if isDefinition == parentIsDefinition {
1082+				if !afterBlankLine && markerIndent > parentIndent && markerIndent < parentIndent+4 {
1083+					return false
1084+				}
1085+			}
1086+			return true
1087+		}
1088+	}
1089+	return true
1090+}
1091+
1092+func (p *BlockParser) canInterrupt(ctype ContainerType, pos, end int32) bool {
1093+	switch ctype {
1094+	case ContainerFootnote:
1095+		return true
1096+	case ContainerListItem:
1097+		_, data, ok := parseListMarker(p.source, pos, end)
1098+		if !ok {
1099+			return false
1100+		}
1101+		if data.Ordered && data.NextNumber != 1 {
1102+			alreadyInOrderedList := false
1103+			canNarrowExistingList := false
1104+			canContinueAsRoman := false
1105+			hasDifferentMarkerList := false
1106+			for i := len(p.stack) - 1; i >= 0; i-- {
1107+				c := p.stack[i]
1108+				if c.Type == ContainerList && c.Data.Ordered &&
1109+					c.Data.Marker == data.Marker && c.Data.MarkerEnd == data.MarkerEnd &&
1110+					c.Data.Indent == p.lineIndent {
1111+					alreadyInOrderedList = true
1112+					break
1113+				}
1114+				if c.Type == ContainerList && c.Data.Ordered &&
1115+					c.Data.Indent == p.lineIndent {
1116+					if c.Data.Marker != data.Marker || c.Data.MarkerEnd != data.MarkerEnd {
1117+						hasDifferentMarkerList = true
1118+					}
1119+					if c.Data.Marker == 'i' && data.Marker == 'a' && c.Data.MarkerAmbiguous && !isRomanLowerStart(data.MarkerFirstChar) {
1120+						canNarrowExistingList = true
1121+						break
1122+					}
1123+					if c.Data.Marker == 'I' && data.Marker == 'A' && c.Data.MarkerAmbiguous && !isRomanUpperStart(data.MarkerFirstChar) {
1124+						canNarrowExistingList = true
1125+						break
1126+					}
1127+					if c.Data.Marker == 'i' && data.Marker == 'a' && data.MarkerAmbiguous && isRomanLowerStart(data.MarkerFirstChar) {
1128+						if parseRoman([]byte{data.MarkerFirstChar}) == c.Data.NextNumber {
1129+							canContinueAsRoman = true
1130+							break
1131+						}
1132+					}
1133+					if c.Data.Marker == 'I' && data.Marker == 'A' && data.MarkerAmbiguous && isRomanUpperStart(data.MarkerFirstChar) {
1134+						if parseRoman([]byte{data.MarkerFirstChar}) == c.Data.NextNumber {
1135+							canContinueAsRoman = true
1136+							break
1137+						}
1138+					}
1139+				}
1140+			}
1141+			if !alreadyInOrderedList && !canNarrowExistingList && !canContinueAsRoman && !hasDifferentMarkerList {
1142+				return false
1143+			}
1144+		}
1145+		return p.canListMarkerInterrupt(p.lineIndent, data.Marker, p.afterBlankLine)
1146+	}
1147+	return false
1148+}
1149+func (p *BlockParser) tryNarrowList(list *ActiveContainer, newItem ContainerData) bool {
1150+	lower := list.Data.Marker == 'i'
1151+	upper := list.Data.Marker == 'I'
1152+	if !lower && !upper {
1153+		return false
1154+	}
1155+
1156+	if lower {
1157+		if list.Data.MarkerAmbiguous && newItem.Marker == 'a' && !isRomanLowerStart(newItem.MarkerFirstChar) {
1158+			startNum := int(list.Data.MarkerFirstChar - 'a' + 1)
1159+			p.events[list.Data.OpenListEventIdx].End = int32('a')
1160+			p.events[list.Data.OpenListEventIdx+1].End = int32(startNum)
1161+			list.Data.Marker = 'a'
1162+			list.Data.MarkerAmbiguous = false
1163+			return true
1164+		}
1165+		if !list.Data.MarkerAmbiguous && newItem.Marker == 'a' && !isRomanLowerStart(newItem.MarkerFirstChar) {
1166+			return false
1167+		}
1168+		if !list.Data.MarkerAmbiguous && newItem.Marker == 'a' && isRomanLowerStart(newItem.MarkerFirstChar) {
1169+			if parseRoman([]byte{newItem.MarkerFirstChar}) == list.Data.NextNumber {
1170+				list.Data.NextNumber++
1171+				return true
1172+			}
1173+		}
1174+		if !list.Data.MarkerAmbiguous && newItem.Marker == 'i' && newItem.MarkerAmbiguous {
1175+			if newItem.NextNumber == list.Data.NextNumber {
1176+				list.Data.NextNumber++
1177+				return true
1178+			}
1179+		}
1180+		if !list.Data.MarkerAmbiguous && newItem.Marker == 'a' && newItem.MarkerAmbiguous && isRomanLowerStart(newItem.MarkerFirstChar) {
1181+			if parseRoman([]byte{newItem.MarkerFirstChar}) == list.Data.NextNumber {
1182+				list.Data.NextNumber++
1183+				return true
1184+			}
1185+		}
1186+	}
1187+
1188+	if upper {
1189+		if list.Data.MarkerAmbiguous && newItem.Marker == 'A' && !isRomanUpperStart(newItem.MarkerFirstChar) {
1190+			startNum := int(list.Data.MarkerFirstChar - 'A' + 1)
1191+			p.events[list.Data.OpenListEventIdx].End = int32('A')
1192+			p.events[list.Data.OpenListEventIdx+1].End = int32(startNum)
1193+			list.Data.Marker = 'A'
1194+			list.Data.MarkerAmbiguous = false
1195+			return true
1196+		}
1197+		if !list.Data.MarkerAmbiguous && newItem.Marker == 'A' && isRomanUpperStart(newItem.MarkerFirstChar) {
1198+			if parseRoman([]byte{newItem.MarkerFirstChar}) == list.Data.NextNumber {
1199+				list.Data.NextNumber++
1200+				return true
1201+			}
1202+		}
1203+		if !list.Data.MarkerAmbiguous && newItem.Marker == 'I' && newItem.MarkerAmbiguous {
1204+			if newItem.NextNumber == list.Data.NextNumber {
1205+				list.Data.NextNumber++
1206+				return true
1207+			}
1208+		}
1209+		if !list.Data.MarkerAmbiguous && newItem.Marker == 'A' && newItem.MarkerAmbiguous && isRomanUpperStart(newItem.MarkerFirstChar) {
1210+			if parseRoman([]byte{newItem.MarkerFirstChar}) == list.Data.NextNumber {
1211+				list.Data.NextNumber++
1212+				return true
1213+			}
1214+		}
1215+	}
1216+	return false
1217+}
1218+
1219+func (p *BlockParser) openList(start int32, data ContainerData) {
1220+	data.OpenListEventIdx = int32(len(p.events))
1221+	p.addEvent(start, int32(data.Marker), EvOpenList)
1222+	p.addEvent(start, int32(data.NextNumber), EvNone)
1223+	p.stack = append(p.stack, ActiveContainer{Type: ContainerList, Start: start, Data: data})
1224+	p.stack[len(p.stack)-1].Data.NextNumber++
1225+}
+918, -0
  1@@ -0,0 +1,918 @@
  2+package djot
  3+
  4+type Opener struct {
  5+	MatchIndex int32
  6+	Start      int32
  7+	Type       EventType
  8+	Char       byte
  9+	Explicit   bool
 10+}
 11+
 12+type InlineParser struct {
 13+	source []byte
 14+	events []Event
 15+	stack  []Opener
 16+}
 17+
 18+func NewInlineParser(source []byte) *InlineParser {
 19+	return &InlineParser{
 20+		source: source,
 21+		events: make([]Event, 0, 256),
 22+		stack:  make([]Opener, 0, 16),
 23+	}
 24+}
 25+
 26+func (p *InlineParser) Reset() {
 27+	p.stack = p.stack[:0]
 28+}
 29+
 30+func (p *InlineParser) Parse(blockEvents []Event) []Event {
 31+	var textEvents []Event
 32+	for i := 0; i < len(blockEvents); i++ {
 33+		ev := blockEvents[i]
 34+		if ev.Type == EvText || ev.Type == EvSoftBreak || ev.Type == EvHardBreak {
 35+			textEvents = append(textEvents, ev)
 36+		} else {
 37+			if len(textEvents) > 0 {
 38+				p.parseInlineBlock(textEvents)
 39+				textEvents = textEvents[:0]
 40+			}
 41+			if ev.Type == EvClosePara || ev.Type == EvCloseHeading || ev.Type == EvCloseCaption || ev.Type == EvCloseListItem || ev.Type == EvCloseCell {
 42+				p.Trim()
 43+			}
 44+			p.events = append(p.events, ev)
 45+			p.Reset()
 46+		}
 47+	}
 48+	if len(textEvents) > 0 {
 49+		p.parseInlineBlock(textEvents)
 50+	}
 51+
 52+	return p.events
 53+}
 54+
 55+func (p *InlineParser) Trim() {
 56+	if len(p.events) == 0 {
 57+		return
 58+	}
 59+	i := len(p.events) - 1
 60+	if p.events[i].Type == EvSoftBreak || p.events[i].Type == EvHardBreak {
 61+		p.events = p.events[:i]
 62+		i--
 63+		if i >= 0 && p.events[i].Type == EvStr {
 64+			end := p.events[i].End
 65+			start := p.events[i].Start
 66+			for end >= start && (p.source[end] == ' ' || p.source[end] == '\t') {
 67+				end--
 68+			}
 69+			if end < start {
 70+				p.events = p.events[:i]
 71+			} else {
 72+				p.events[i].End = end
 73+			}
 74+		}
 75+	}
 76+}
 77+
 78+func (p *InlineParser) findNextSpecialAhead(pos int32, end int32) int32 {
 79+	for i := pos; i <= end; i++ {
 80+		if isSpecial(p.source[i]) || p.source[i] == '{' {
 81+			return i
 82+		}
 83+	}
 84+	return end + 1
 85+}
 86+
 87+func (p *InlineParser) parseInlineBlock(textEvents []Event) {
 88+	if len(textEvents) == 0 {
 89+		return
 90+	}
 91+
 92+	blockEnd := textEvents[len(textEvents)-1].End
 93+
 94+	evIdx := 0
 95+	for evIdx < len(textEvents) {
 96+		ev := textEvents[evIdx]
 97+		if ev.Type == EvSoftBreak {
 98+			p.addEvent(ev.Start, ev.End, EvSoftBreak)
 99+			evIdx++
100+			continue
101+		}
102+		if ev.Type == EvHardBreak {
103+			p.addEvent(ev.Start, ev.End, EvHardBreak)
104+			evIdx++
105+			continue
106+		}
107+
108+		start := ev.Start
109+		end := ev.End
110+		pos := start
111+
112+		for pos <= end {
113+			if pos >= int32(len(p.source)) {
114+				break
115+			}
116+
117+			b := p.source[pos]
118+
119+			if !isSpecial(b) && b != '{' {
120+				next := p.findNextSpecialAhead(pos+1, end)
121+				p.addEvent(pos, next-1, EvStr)
122+				pos = next
123+				continue
124+			}
125+
126+			if b == '{' {
127+				if pos+1 <= end {
128+					next := p.source[pos+1]
129+					if next == '_' || next == '*' || next == '~' || next == '^' || next == '-' || next == '+' || next == '=' || next == '\'' || next == '"' {
130+						p.addEvent(pos, pos, EvOpenInlineAttributes)
131+						newPos := p.handleEmphasis(next, pos+1, end)
132+						pos = newPos
133+						continue
134+					}
135+				}
136+
137+				attrEvents, nextPos, ok := ParseAttributes(p.source, pos, blockEnd+1)
138+				if ok {
139+					p.handleAttributeAttachment(pos, nextPos, attrEvents)
140+					pos = nextPos + 1
141+					for evIdx < len(textEvents) && pos > textEvents[evIdx].End {
142+						evIdx++
143+					}
144+					if evIdx < len(textEvents) && pos >= textEvents[evIdx].Start {
145+						end = textEvents[evIdx].End
146+						start = pos
147+					}
148+					continue
149+				} else {
150+					p.addEvent(pos, pos, EvStr)
151+					pos++
152+					continue
153+				}
154+			}
155+
156+			switch b {
157+			case '\\':
158+				if pos+1 <= end {
159+					next := p.source[pos+1]
160+					if next == ' ' {
161+						p.addEvent(pos, pos+1, EvNonBreakingSpace)
162+						pos += 2
163+					} else if isPunctuation(next) {
164+						p.addEvent(pos, pos+1, EvEscape)
165+						pos += 2
166+					} else {
167+						p.addEvent(pos, pos, EvStr)
168+						pos++
169+					}
170+				} else {
171+					p.addEvent(pos, pos, EvStr)
172+					pos++
173+				}
174+			case '*':
175+				pos = p.handleEmphasis(b, pos, end)
176+			case '_':
177+				pos = p.handleEmphasis(b, pos, end)
178+			case '^':
179+				pos = p.handleEmphasis(b, pos, end)
180+			case '~':
181+				pos = p.handleEmphasis(b, pos, end)
182+			case '"':
183+				p.handleDoubleQuote(pos, end)
184+				pos++
185+			case '\'':
186+				pos = p.handleSingleQuote(pos, end)
187+			case '[':
188+				if pos+1 <= blockEnd && p.source[pos+1] == '^' {
189+					closeIdx := p.findClosingBracketAhead(pos, textEvents, evIdx)
190+					if closeIdx != -1 {
191+						p.addEvent(pos, closeIdx, EvFootnoteReference)
192+						pos = closeIdx + 1
193+						for evIdx < len(textEvents) && pos > textEvents[evIdx].End {
194+							evIdx++
195+						}
196+						if evIdx < len(textEvents) && pos >= textEvents[evIdx].Start {
197+							end = textEvents[evIdx].End
198+							start = pos
199+						}
200+						continue
201+					}
202+				}
203+				p.addOpener(b, pos, EvOpenLinkText, false)
204+				pos++
205+			case '!':
206+				if pos+1 <= end && p.source[pos+1] == '[' {
207+					p.addOpener('[', pos+1, EvOpenImageText, false)
208+					pos += 2
209+				} else {
210+					p.addEvent(pos, pos, EvStr)
211+					pos++
212+				}
213+			case ']':
214+				pos = p.handleCloseBracketAhead(pos, textEvents, evIdx, blockEnd)
215+				for evIdx < len(textEvents) && pos > textEvents[evIdx].End {
216+					evIdx++
217+				}
218+				if evIdx < len(textEvents) && pos >= textEvents[evIdx].Start {
219+					end = textEvents[evIdx].End
220+					start = pos
221+				}
222+			case '(':
223+				if len(p.events) > 0 && p.events[len(p.events)-1].Type == EvCloseLinkText {
224+					destEnd := p.findClosingParenAhead(pos+1, textEvents, evIdx)
225+					if destEnd != -1 {
226+						p.addEvent(pos+1, destEnd-1, EvOpenDestination)
227+						p.addEvent(pos+1, destEnd-1, EvCloseDestination)
228+						pos = destEnd + 1
229+						for evIdx < len(textEvents) && pos > textEvents[evIdx].End {
230+							evIdx++
231+						}
232+						if evIdx < len(textEvents) && pos >= textEvents[evIdx].Start {
233+							end = textEvents[evIdx].End
234+							start = pos
235+						}
236+						continue
237+					}
238+				}
239+				p.addEvent(pos, pos, EvStr)
240+				pos++
241+			case '`':
242+				pos = p.handleVerbatimAhead(pos, textEvents, evIdx)
243+				for evIdx < len(textEvents) && pos > textEvents[evIdx].End {
244+					evIdx++
245+				}
246+				if evIdx < len(textEvents) && pos >= textEvents[evIdx].Start {
247+					end = textEvents[evIdx].End
248+					start = pos
249+				}
250+
251+				if pos < blockEnd && p.source[pos] == '{' {
252+					attrEvents, nextPos, ok := ParseAttributes(p.source, pos, blockEnd+1)
253+					if ok {
254+						isRaw := false
255+						if len(attrEvents) == 1 {
256+							ae := attrEvents[0]
257+							if ae.Type == EvAttrKey && p.source[ae.Start] == '=' {
258+								isRaw = true
259+							}
260+						}
261+
262+						if isRaw {
263+							p.events[len(p.events)-2].Type = EvRawInline
264+							p.events[len(p.events)-1].Type = EvRawInline
265+							p.handleAttributeAttachment(pos, nextPos, attrEvents)
266+							pos = nextPos + 1
267+						}
268+
269+						for evIdx < len(textEvents) && pos > textEvents[evIdx].End {
270+							evIdx++
271+						}
272+						if evIdx < len(textEvents) && pos >= textEvents[evIdx].Start {
273+							end = textEvents[evIdx].End
274+							start = pos
275+						}
276+					}
277+				}
278+			case '.':
279+				if pos+2 <= end && p.source[pos+1] == '.' && p.source[pos+2] == '.' {
280+					p.addEvent(pos, pos+2, EvSmartPunctuation)
281+					pos += 3
282+				} else {
283+					p.addEvent(pos, pos, EvStr)
284+					pos++
285+				}
286+			case '-':
287+				if (pos > 0 && p.source[pos-1] == '{') || (pos+1 <= end && p.source[pos+1] == '}') {
288+					pos = p.handleEmphasis(b, pos, end)
289+					continue
290+				}
291+
292+				count := int32(0)
293+				for pos+count <= end && p.source[pos+count] == '-' {
294+					count++
295+				}
296+
297+				if pos+count <= end && p.source[pos+count] == '}' {
298+					if count > 1 {
299+						count--
300+					} else {
301+						pos = p.handleEmphasis(b, pos, end)
302+						continue
303+					}
304+				}
305+
306+				if count == 1 {
307+					p.addEvent(pos, pos, EvStr)
308+					pos++
309+				} else {
310+					emd := int32(0)
311+					end2 := int32(0)
312+					if count%3 == 0 {
313+						emd = count / 3
314+						end2 = 0
315+					} else if count%2 == 0 {
316+						emd = 0
317+						end2 = count / 2
318+					} else if count%3 == 2 {
319+						emd = count / 3
320+						end2 = 1
321+					} else {
322+						emd = (count - 4) / 3
323+						end2 = 2
324+					}
325+					p2 := pos
326+					for i := int32(0); i < emd; i++ {
327+						p.addEvent(p2, p2+2, EvSmartPunctuation)
328+						p2 += 3
329+					}
330+					for i := int32(0); i < end2; i++ {
331+						p.addEvent(p2, p2+1, EvSmartPunctuation)
332+						p2 += 2
333+					}
334+					pos = p2
335+				}
336+			case '+':
337+				if (pos > 0 && p.source[pos-1] == '{') || (pos+1 <= end && p.source[pos+1] == '}') {
338+					pos = p.handleEmphasis(b, pos, end)
339+					continue
340+				} else {
341+					p.addEvent(pos, pos, EvStr)
342+					pos++
343+				}
344+			case '=':
345+				if (pos > 0 && p.source[pos-1] == '{') || (pos+1 <= end && p.source[pos+1] == '}') {
346+					pos = p.handleEmphasis(b, pos, end)
347+					continue
348+				} else {
349+					p.addEvent(pos, pos, EvStr)
350+					pos++
351+				}
352+			case '<':
353+				pos = p.handleAutolink(pos, end)
354+			case ':':
355+				pos = p.handleEmoji(pos, end)
356+			case '$':
357+				count := int32(0)
358+				for pos+count <= end && p.source[pos+count] == '$' {
359+					count++
360+				}
361+				if pos+count <= end && p.source[pos+count] == '`' {
362+					pos = p.handleMathVerbatim(pos, pos+count, end, textEvents, evIdx)
363+					for evIdx < len(textEvents) && pos > textEvents[evIdx].End {
364+						evIdx++
365+					}
366+					if evIdx < len(textEvents) && pos >= textEvents[evIdx].Start {
367+						end = textEvents[evIdx].End
368+						start = pos
369+					}
370+				} else {
371+					pos = p.handleMath(pos, end)
372+				}
373+			default:
374+				p.addEvent(pos, pos, EvStr)
375+				pos++
376+			}
377+			start = pos
378+		}
379+		evIdx++
380+	}
381+
382+	for _, opener := range p.stack {
383+		if opener.MatchIndex != -1 && (opener.Char == '\'' || opener.Char == '"') {
384+			p.events[opener.MatchIndex].Type = EvSmartPunctuation
385+		}
386+	}
387+}
388+
389+func (p *InlineParser) findClosingParenAhead(start int32, textEvents []Event, startEvIdx int) int32 {
390+	count := 0
391+	for j := startEvIdx; j < len(textEvents); j++ {
392+		ev := textEvents[j]
393+		if ev.Type == EvSoftBreak {
394+			continue
395+		}
396+		s := ev.Start
397+		if j == startEvIdx {
398+			s = start
399+		}
400+		for i := s; i <= ev.End; i++ {
401+			if p.source[i] == '(' {
402+				count++
403+			} else if p.source[i] == ')' {
404+				count--
405+				if count == 0 {
406+					return i
407+				}
408+			}
409+		}
410+	}
411+	return -1
412+}
413+
414+func (p *InlineParser) findClosingBracketAhead(start int32, textEvents []Event, startEvIdx int) int32 {
415+	count := 0
416+	for j := startEvIdx; j < len(textEvents); j++ {
417+		ev := textEvents[j]
418+		if ev.Type == EvSoftBreak {
419+			continue
420+		}
421+		s := ev.Start
422+		if j == startEvIdx {
423+			s = start
424+		}
425+		for i := s; i <= ev.End; i++ {
426+			if p.source[i] == '[' {
427+				count++
428+			} else if p.source[i] == ']' {
429+				count--
430+				if count == 0 {
431+					return i
432+				}
433+			}
434+		}
435+	}
436+	return -1
437+}
438+
439+func (p *InlineParser) handleCloseBracketAhead(pos int32, textEvents []Event, evIdx int, blockEnd int32) int32 {
440+	for i := len(p.stack) - 1; i >= 0; i-- {
441+		opener := p.stack[i]
442+		if opener.MatchIndex != -1 && opener.Char == '[' {
443+			next := pos + 1
444+			if next <= blockEnd && p.source[next] == '(' {
445+				closeIdx := p.findClosingParenAhead(next, textEvents, evIdx)
446+				if closeIdx != -1 {
447+					p.events[opener.MatchIndex].Type = opener.Type
448+					p.addEvent(next+1, closeIdx-1, EvOpenDestination)
449+					p.addEvent(next+1, closeIdx-1, EvCloseDestination)
450+					p.addEvent(pos, pos, closeTypeFor(opener.Type))
451+					p.stack = p.stack[:i]
452+					return closeIdx + 1
453+				}
454+			}
455+
456+			if next <= blockEnd && p.source[next] == '[' {
457+				closeIdx := p.findClosingBracketAhead(next, textEvents, evIdx)
458+				if closeIdx != -1 {
459+					p.events[opener.MatchIndex].Type = opener.Type
460+					p.addEvent(next+1, closeIdx-1, EvOpenReference)
461+					p.addEvent(next+1, closeIdx-1, EvCloseReference)
462+					p.addEvent(pos, pos, closeTypeFor(opener.Type))
463+					p.stack = p.stack[:i]
464+					return closeIdx + 1
465+				}
466+			}
467+
468+			if next <= blockEnd && p.source[next] == '{' {
469+				attrEvents, attrEndPos, ok := ParseAttributes(p.source, next, blockEnd+1)
470+				if ok {
471+					p.events[opener.MatchIndex].Type = EvOpenSpan
472+					p.addEvent(pos, pos, EvCloseSpan)
473+					p.addEvent(next, next, EvOpenInlineAttributes)
474+					p.events = append(p.events, attrEvents...)
475+					p.addEvent(attrEndPos, attrEndPos, EvCloseAttributes)
476+					p.stack = p.stack[:i]
477+					return attrEndPos + 1
478+				}
479+			}
480+
481+			p.events[opener.MatchIndex].Type = EvStr
482+			p.addEvent(pos, pos, EvStr)
483+			p.stack = p.stack[:i]
484+			return pos + 1
485+		}
486+	}
487+	p.addEvent(pos, pos, EvStr)
488+	return pos + 1
489+}
490+
491+func (p *InlineParser) handleMathVerbatim(dollarPos, backtickPos, end int32, textEvents []Event, startEvIdx int) int32 {
492+	count := int32(0)
493+	pos := backtickPos
494+	ev := textEvents[startEvIdx]
495+	for pos <= ev.End && p.source[pos] == '`' {
496+		count++
497+		pos++
498+	}
499+
500+	contentStart := pos
501+
502+	for j := startEvIdx; j < len(textEvents); j++ {
503+		tev := textEvents[j]
504+		if tev.Type == EvSoftBreak {
505+			continue
506+		}
507+
508+		s := tev.Start
509+		if j == startEvIdx {
510+			s = pos
511+		}
512+
513+		for i := s; i <= tev.End; i++ {
514+			if p.source[i] == '`' {
515+				c := int32(0)
516+				for i+c <= tev.End && p.source[i+c] == '`' {
517+					c++
518+				}
519+				if c == count {
520+					contentEnd := i - 1
521+					dollarCount := backtickPos - dollarPos
522+					etype := EvInlineMath
523+					if dollarCount == 2 {
524+						etype = EvDisplayMath
525+					}
526+					p.addEvent(contentStart, contentEnd, etype)
527+					return i + count
528+				}
529+				i += c - 1
530+			}
531+		}
532+	}
533+
534+	lastEv := textEvents[len(textEvents)-1]
535+	dollarCount := backtickPos - dollarPos
536+	etype := EvInlineMath
537+	if dollarCount == 2 {
538+		etype = EvDisplayMath
539+	}
540+	p.addEvent(contentStart, lastEv.End, etype)
541+	return lastEv.End + 1
542+}
543+
544+func (p *InlineParser) handleVerbatimAhead(pos int32, textEvents []Event, startEvIdx int) int32 {
545+	count := int32(0)
546+
547+	ev := textEvents[startEvIdx]
548+	for pos <= ev.End && p.source[pos] == '`' {
549+		count++
550+		pos++
551+	}
552+
553+	contentStart := pos
554+
555+	for j := startEvIdx; j < len(textEvents); j++ {
556+		tev := textEvents[j]
557+		if tev.Type == EvSoftBreak {
558+			continue
559+		}
560+
561+		s := tev.Start
562+		if j == startEvIdx {
563+			s = pos
564+		}
565+
566+		for i := s; i <= tev.End; i++ {
567+			if p.source[i] == '`' {
568+				c := int32(0)
569+				for i+c <= tev.End && p.source[i+c] == '`' {
570+					c++
571+				}
572+				if c == count {
573+					contentEnd := i - 1
574+
575+					if count == 1 && contentEnd-contentStart+1 >= 2 && p.source[contentStart] == ' ' && p.source[contentEnd] == ' ' {
576+						allSpaces := true
577+						for k := contentStart; k <= contentEnd; k++ {
578+							if p.source[k] != ' ' {
579+								allSpaces = false
580+								break
581+							}
582+						}
583+						if !allSpaces {
584+							contentStart++
585+							contentEnd--
586+						}
587+					}
588+
589+					p.addEvent(contentStart, contentEnd, EvOpenVerbatim)
590+					p.addEvent(contentStart, contentEnd, EvCloseVerbatim)
591+					return i + count
592+				}
593+				i += c - 1
594+			}
595+		}
596+	}
597+
598+	lastEv := textEvents[len(textEvents)-1]
599+	p.addEvent(contentStart, lastEv.End, EvOpenVerbatim)
600+	p.addEvent(contentStart, lastEv.End, EvCloseVerbatim)
601+	return lastEv.End + 1
602+}
603+
604+func (p *InlineParser) handleAttributeAttachment(braceStart, braceEnd int32, attrEvents []Event) {
605+	p.addEvent(braceStart, braceStart, EvOpenInlineAttributes)
606+	p.events = append(p.events, attrEvents...)
607+	p.addEvent(braceEnd, braceEnd, EvCloseAttributes)
608+}
609+
610+func isSpecial(b byte) bool {
611+	switch b {
612+	case '\\', '`', '_', '*', '~', '^', '$', '[', ']', '!', '<', '.', '-', ':', '"', '\'', '+', '=':
613+		return true
614+	}
615+	return false
616+}
617+
618+func closeTypeFor(openType EventType) EventType {
619+	if openType == EvOpenLinkText {
620+		return EvCloseLinkText
621+	}
622+	return EvCloseImageText
623+}
624+
625+func (p *InlineParser) addEvent(start, end int32, etype EventType) int32 {
626+	p.events = append(p.events, Event{Start: start, End: end, Type: etype})
627+	return int32(len(p.events) - 1)
628+}
629+
630+func (p *InlineParser) addOpener(char byte, pos int32, etype EventType, explicit bool) {
631+	idx := p.addEvent(pos, pos, etype)
632+	p.stack = append(p.stack, Opener{
633+		MatchIndex: idx,
634+		Start:      pos,
635+		Type:       etype,
636+		Char:       char,
637+		Explicit:   explicit,
638+	})
639+}
640+
641+func (p *InlineParser) handleEmphasis(b byte, pos int32, end int32) int32 {
642+	lastMatch := Event{Type: EvNone}
643+	if len(p.events) > 0 {
644+		lastMatch = p.events[len(p.events)-1]
645+	}
646+
647+	isExplicitOpen := lastMatch.Type == EvOpenInlineAttributes && lastMatch.Start == pos-1
648+	isExplicitClose := pos+1 <= end && p.source[pos+1] == '}'
649+
650+	canOpen := pos+1 <= end && !isSpace(p.source[pos+1])
651+	canClose := pos-1 >= 0 && !isSpace(p.source[pos-1])
652+
653+	if isExplicitOpen {
654+		canOpen = true
655+		canClose = false
656+	}
657+	if isExplicitClose {
658+		canClose = true
659+		canOpen = false
660+	}
661+
662+	if canClose {
663+		var openerIdx = -1
664+		for i := len(p.stack) - 1; i >= 0; i-- {
665+			if p.stack[i].MatchIndex != -1 && p.stack[i].Char == b && p.stack[i].Explicit == isExplicitClose && p.stack[i].Type != EvOpenLinkText && p.stack[i].Type != EvOpenImageText {
666+				openerIdx = i
667+				break
668+			}
669+		}
670+
671+		if openerIdx != -1 {
672+			opener := p.stack[openerIdx]
673+			if opener.Start != pos-1 {
674+				p.handleCloseEmphasisAt(openerIdx, pos, isExplicitClose)
675+				if isExplicitClose {
676+					return pos + 2
677+				}
678+				return pos + 1
679+			}
680+		}
681+	}
682+
683+	if canOpen {
684+		startPos := pos
685+		if isExplicitOpen {
686+			startPos = pos - 1
687+			p.events = p.events[:len(p.events)-1]
688+			p.addOpener(b, startPos, EvStr, isExplicitOpen)
689+			p.addEvent(pos, pos, EvStr)
690+		} else {
691+			p.addOpener(b, startPos, EvStr, isExplicitOpen)
692+		}
693+		return pos + 1
694+	} else {
695+		p.addEvent(pos, pos, EvStr)
696+		if isExplicitClose {
697+			p.addEvent(pos+1, pos+1, EvStr)
698+			return pos + 2
699+		}
700+		return pos + 1
701+	}
702+}
703+
704+func (p *InlineParser) handleCloseEmphasisAt(openerIdx int, pos int32, explicit bool) {
705+	opener := p.stack[openerIdx]
706+	var b = opener.Char
707+	var openType, closeType EventType
708+	switch b {
709+	case '_':
710+		openType, closeType = EvOpenEmph, EvCloseEmph
711+	case '*':
712+		openType, closeType = EvOpenStrong, EvCloseStrong
713+	case '~':
714+		openType, closeType = EvOpenSubscript, EvCloseSubscript
715+	case '^':
716+		openType, closeType = EvOpenSuperscript, EvCloseSuperscript
717+	case '-':
718+		openType, closeType = EvOpenDelete, EvCloseDelete
719+	case '+':
720+		openType, closeType = EvOpenInsert, EvCloseInsert
721+	case '=':
722+		openType, closeType = EvOpenMark, EvCloseMark
723+	case '\'':
724+		openType, closeType = EvOpenSingleQuoted, EvCloseSingleQuoted
725+	case '"':
726+		openType, closeType = EvOpenDoubleQuoted, EvCloseDoubleQuoted
727+	}
728+	p.events[opener.MatchIndex].Type = openType
729+	p.events[opener.MatchIndex].Start = opener.Start
730+
731+	if opener.Explicit {
732+		if opener.MatchIndex+1 < int32(len(p.events)) && p.events[opener.MatchIndex+1].Start == opener.Start+1 && p.events[opener.MatchIndex+1].Type == EvStr {
733+			p.events[opener.MatchIndex+1].Type = EvNone
734+		}
735+	}
736+
737+	endPos := pos
738+	if explicit {
739+		endPos = pos + 1
740+	}
741+	p.addEvent(pos, endPos, closeType)
742+
743+	p.clearOpeners(opener.Start, pos)
744+	p.stack = p.stack[:openerIdx]
745+}
746+
747+func (p *InlineParser) clearOpeners(start, end int32) {
748+	for i := range p.stack {
749+		if p.stack[i].Start > start && p.stack[i].Start < end {
750+			if p.stack[i].MatchIndex != -1 {
751+				p.events[p.stack[i].MatchIndex].Type = EvStr
752+				p.stack[i].MatchIndex = -1
753+			}
754+		}
755+	}
756+}
757+
758+func (p *InlineParser) handleMath(pos, end int32) int32 {
759+	start := pos
760+	pos++
761+	count := int32(1)
762+	if pos <= end && p.source[pos] == '$' {
763+		count = 2
764+		pos++
765+	}
766+	for pos <= end {
767+		if p.source[pos] == '$' {
768+			c := int32(0)
769+			for pos <= end && p.source[pos] == '$' {
770+				c++
771+				pos++
772+			}
773+			if c == count {
774+				etype := EvInlineMath
775+				if count == 2 {
776+					etype = EvDisplayMath
777+				}
778+				p.addEvent(start+count, pos-count-1, etype)
779+				return pos
780+			}
781+		} else {
782+			pos++
783+		}
784+	}
785+	p.addEvent(start, start+count-1, EvStr)
786+	return start + count
787+}
788+
789+func (p *InlineParser) handleAutolink(pos, end int32) int32 {
790+	start := pos
791+	hasAt := false
792+	hasSlash := false
793+	for pos <= end && p.source[pos] != '>' && p.source[pos] != ' ' {
794+		if p.source[pos] == '@' {
795+			hasAt = true
796+		} else if p.source[pos] == '/' {
797+			hasSlash = true
798+		}
799+		pos++
800+	}
801+	if pos <= end && p.source[pos] == '>' {
802+		etype := EvUrl
803+		if hasAt && !hasSlash {
804+			etype = EvEmail
805+		}
806+		p.addEvent(start+1, pos-1, etype)
807+		return pos + 1
808+	}
809+	p.addEvent(start, start, EvStr)
810+	return start + 1
811+}
812+
813+func (p *InlineParser) handleEmoji(pos, end int32) int32 {
814+	start := pos
815+	pos++
816+	for pos <= end && isAlphaLower(p.source[pos]) {
817+		pos++
818+	}
819+	if pos <= end && p.source[pos] == ':' {
820+		p.addEvent(start+1, pos-1, EvSymb)
821+		return pos + 1
822+	}
823+	p.addEvent(start, start, EvStr)
824+	return start + 1
825+}
826+
827+func (p *InlineParser) handleDoubleQuote(pos, end int32) {
828+	for i := len(p.stack) - 1; i >= 0; i-- {
829+		opener := p.stack[i]
830+		if opener.MatchIndex != -1 && opener.Char == '"' {
831+			p.events[opener.MatchIndex].Type = EvOpenDoubleQuoted
832+			p.addEvent(pos, pos, EvCloseDoubleQuoted)
833+			p.stack = p.stack[:i]
834+			return
835+		}
836+	}
837+	p.addOpener('"', pos, EvStr, false)
838+}
839+
840+func (p *InlineParser) handleSingleQuote(pos, end int32) int32 {
841+	isExplicitClose := pos+1 <= end && p.source[pos+1] == '}'
842+
843+	if isExplicitClose {
844+		for i := len(p.stack) - 1; i >= 0; i-- {
845+			if p.stack[i].MatchIndex != -1 && p.stack[i].Char == '\'' && p.stack[i].Explicit {
846+				opener := p.stack[i]
847+				p.events[opener.MatchIndex].Type = EvOpenSingleQuoted
848+				p.addEvent(pos, pos, EvCloseSingleQuoted)
849+				if opener.MatchIndex+1 < int32(len(p.events)) && p.events[opener.MatchIndex+1].Start == opener.Start+1 && p.events[opener.MatchIndex+1].Type == EvStr {
850+					p.events[opener.MatchIndex+1].Type = EvNone
851+				}
852+				p.stack = p.stack[:i]
853+				return pos + 2
854+			}
855+		}
856+	}
857+
858+	prevIsCloser := pos > 0 && (isAlnum(p.source[pos-1]) || isClosingPunct(p.source[pos-1]))
859+	nextDigit := pos+1 <= end && isDigit(p.source[pos+1])
860+
861+	canClose := pos > 0 && !isSpace(p.source[pos-1])
862+	canOpen := pos+1 <= end && !isSpace(p.source[pos+1]) &&
863+		(pos == 0 || isOpeningContext(p.source[pos-1]))
864+
865+	if prevIsCloser || nextDigit {
866+		for i := len(p.stack) - 1; i >= 0; i-- {
867+			if p.stack[i].MatchIndex != -1 && p.stack[i].Char == '\'' {
868+				p.events[p.stack[i].MatchIndex].Type = EvOpenSingleQuoted
869+				p.addEvent(pos, pos, EvCloseSingleQuoted)
870+				p.stack = p.stack[:i]
871+				return pos + 1
872+			}
873+		}
874+		p.addEvent(pos, pos, EvSmartPunctuation)
875+		return pos + 1
876+	}
877+
878+	if canClose && !canOpen {
879+		for i := len(p.stack) - 1; i >= 0; i-- {
880+			opener := p.stack[i]
881+			if opener.MatchIndex != -1 && opener.Char == '\'' {
882+				p.events[opener.MatchIndex].Type = EvOpenSingleQuoted
883+				p.addEvent(pos, pos, EvCloseSingleQuoted)
884+				p.stack = p.stack[:i]
885+				return pos + 1
886+			}
887+		}
888+	}
889+
890+	if canOpen {
891+		p.addOpener('\'', pos, EvStr, false)
892+	} else {
893+		p.addEvent(pos, pos, EvSmartPunctuation)
894+	}
895+	return pos + 1
896+}
897+
898+func isAlnum(b byte) bool {
899+	return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')
900+}
901+
902+func isClosingPunct(b byte) bool {
903+	return b == ')' || b == ']' || b == '}' ||
904+		b == '.' || b == ',' || b == ';' || b == ':' ||
905+		b == '!' || b == '?'
906+}
907+
908+func isOpeningContext(b byte) bool {
909+	return b == ' ' || b == '\t' || b == '\n' || b == '\r' ||
910+		b == '"' || b == '\'' || b == '-' || b == '(' || b == '['
911+}
912+
913+func isPunctuation(b byte) bool {
914+	switch b {
915+	case '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~':
916+		return true
917+	}
918+	return false
919+}
+306, -0
  1@@ -0,0 +1,306 @@
  2+package djot
  3+
  4+func isThematicBreak(source []byte, start, end int32) bool {
  5+	count := 0
  6+	for i := start; i < end; i++ {
  7+		b := source[i]
  8+		if b == '-' || b == '*' {
  9+			count++
 10+		} else if b == ' ' || b == '\t' {
 11+			continue
 12+		} else {
 13+			return false
 14+		}
 15+	}
 16+	return count >= 3
 17+}
 18+
 19+func parseListMarker(source []byte, pos, end int32) (int32, ContainerData, bool) {
 20+	if pos >= end {
 21+		return pos, ContainerData{}, false
 22+	}
 23+
 24+	startPos := pos
 25+	b := source[pos]
 26+
 27+	if b == '-' || b == '+' || b == '*' {
 28+		return checkListItemEnd(source, startPos, pos, end, b, false, 0, 0)
 29+	}
 30+
 31+	if b == ':' {
 32+		return checkListItemEnd(source, startPos, pos, end, ':', false, 0, 0)
 33+	}
 34+
 35+	if isDigit(b) {
 36+		p := pos
 37+		for p < end && isDigit(source[p]) {
 38+			p++
 39+		}
 40+		if p < end && (source[p] == '.' || source[p] == ')') {
 41+			num := parseDecimal(source[pos:p])
 42+			return checkListItemEnd(source, startPos, p, end, '1', true, num, source[p])
 43+		}
 44+	}
 45+
 46+	if isRomanLowerStart(b) {
 47+		p := pos
 48+		for p < end && isRomanLower(source[p]) {
 49+			p++
 50+		}
 51+		if p < end && (source[p] == '.' || source[p] == ')') {
 52+			num := parseRoman(source[pos:p])
 53+			newPos, data, ok := checkListItemEnd(source, startPos, p, end, 'i', true, num, source[p])
 54+			if ok && p == pos+1 {
 55+				data.MarkerAmbiguous = true
 56+			}
 57+			if ok {
 58+				data.MarkerFirstChar = b
 59+			}
 60+			return newPos, data, ok
 61+		}
 62+	}
 63+
 64+	if isAlphaLower(b) {
 65+		if pos+1 < end && (source[pos+1] == '.' || source[pos+1] == ')') {
 66+			num := int(b - 'a' + 1)
 67+			newPos, data, ok := checkListItemEnd(source, startPos, pos+1, end, 'a', true, num, source[pos+1])
 68+			if ok {
 69+				data.MarkerFirstChar = b
 70+			}
 71+			return newPos, data, ok
 72+		}
 73+	}
 74+
 75+	if isRomanUpperStart(b) {
 76+		p := pos
 77+		for p < end && isRomanUpper(source[p]) {
 78+			p++
 79+		}
 80+		if p < end && (source[p] == '.' || source[p] == ')') {
 81+			num := parseRoman(source[pos:p])
 82+			newPos, data, ok := checkListItemEnd(source, startPos, p, end, 'I', true, num, source[p])
 83+			if ok && p == pos+1 {
 84+				data.MarkerAmbiguous = true
 85+			}
 86+			if ok {
 87+				data.MarkerFirstChar = b
 88+			}
 89+			return newPos, data, ok
 90+		}
 91+	}
 92+
 93+	if isAlphaUpper(b) {
 94+		if pos+1 < end && (source[pos+1] == '.' || source[pos+1] == ')') {
 95+			num := int(b - 'A' + 1)
 96+			newPos, data, ok := checkListItemEnd(source, startPos, pos+1, end, 'A', true, num, source[pos+1])
 97+			if ok {
 98+				data.MarkerFirstChar = b
 99+			}
100+			return newPos, data, ok
101+		}
102+	}
103+
104+	if b == '(' {
105+		p := pos + 1
106+		if p < end {
107+			b2 := source[p]
108+
109+			if isDigit(b2) {
110+				start := p
111+				for p < end && isDigit(source[p]) {
112+					p++
113+				}
114+				if p < end && source[p] == ')' {
115+					num := parseDecimal(source[start:p])
116+					newPos, data, ok := checkListItemEnd(source, startPos, p, end, '1', true, num, 'X')
117+					if ok {
118+						data.MarkerFirstChar = b2
119+					}
120+					return newPos, data, ok
121+				}
122+			}
123+
124+			if isAlphaLower(b2) && p+1 < end && source[p+1] == ')' {
125+				num := int(b2 - 'a' + 1)
126+				newPos, data, ok := checkListItemEnd(source, startPos, p+1, end, 'a', true, num, 'X')
127+				if ok {
128+					data.MarkerFirstChar = b2
129+					if isRomanLowerStart(b2) {
130+						data.MarkerAmbiguous = true
131+					}
132+				}
133+				return newPos, data, ok
134+			}
135+			if isAlphaUpper(b2) && p+1 < end && source[p+1] == ')' {
136+				num := int(b2 - 'A' + 1)
137+				newPos, data, ok := checkListItemEnd(source, startPos, p+1, end, 'A', true, num, 'X')
138+				if ok {
139+					data.MarkerFirstChar = b2
140+					if isRomanUpperStart(b2) {
141+						data.MarkerAmbiguous = true
142+					}
143+				}
144+				return newPos, data, ok
145+			}
146+
147+			if isRomanLowerStart(b2) {
148+				start := p
149+				for p < end && isRomanLower(source[p]) {
150+					p++
151+				}
152+				if p < end && source[p] == ')' {
153+					if p > start+1 {
154+						num := parseRoman(source[start:p])
155+						newPos, data, ok := checkListItemEnd(source, startPos, p, end, 'i', true, num, 'X')
156+						if ok {
157+							data.MarkerFirstChar = b2
158+						}
159+						return newPos, data, ok
160+					}
161+				}
162+			}
163+
164+			if isRomanUpperStart(b2) {
165+				start := p
166+				for p < end && isRomanUpper(source[p]) {
167+					p++
168+				}
169+				if p < end && source[p] == ')' {
170+					if p > start+1 {
171+						num := parseRoman(source[start:p])
172+						newPos, data, ok := checkListItemEnd(source, startPos, p, end, 'I', true, num, 'X')
173+						if ok {
174+							data.MarkerFirstChar = b2
175+						}
176+						return newPos, data, ok
177+					}
178+				}
179+			}
180+		}
181+	}
182+
183+	return startPos, ContainerData{}, false
184+}
185+
186+func checkListItemEnd(source []byte, start, pos, end int32, style byte, ordered bool, num int, endChar byte) (int32, ContainerData, bool) {
187+	if pos+1 == end || source[pos+1] == ' ' || source[pos+1] == '\t' {
188+		newPos := pos + 1
189+		for newPos < end && (source[newPos] == ' ' || source[newPos] == '\t') {
190+			newPos++
191+		}
192+		indent := newPos - start
193+
194+		checked := false
195+		isTask := false
196+		if !ordered && (style == '-' || style == '+' || style == '*') && newPos+3 <= end && source[newPos] == '[' {
197+			if source[newPos+1] == ' ' && source[newPos+2] == ']' {
198+				isTask = true
199+				newPos += 3
200+			} else if (source[newPos+1] == 'x' || source[newPos+1] == 'X') && source[newPos+2] == ']' {
201+				isTask = true
202+				checked = true
203+				newPos += 3
204+			}
205+
206+			if isTask {
207+				if newPos == end || source[newPos] == ' ' || source[newPos] == '\t' {
208+					for newPos < end && (source[newPos] == ' ' || source[newPos] == '\t') {
209+						newPos++
210+					}
211+					indent = newPos - start
212+				} else {
213+					isTask = false
214+					checked = false
215+					newPos = pos + 1
216+					for newPos < end && (source[newPos] == ' ' || source[newPos] == '\t') {
217+						newPos++
218+					}
219+					indent = newPos - start
220+				}
221+			}
222+		}
223+
224+		if !ordered && indent == 1 && newPos < end {
225+			return start, ContainerData{}, false
226+		}
227+
228+		return newPos, ContainerData{Marker: style, MarkerEnd: endChar, Ordered: ordered, IsTask: isTask, Checked: checked, Tight: true, NextNumber: num}, true
229+	}
230+	return start, ContainerData{}, false
231+}
232+
233+func parseDecimal(b []byte) int {
234+	res := 0
235+	for _, c := range b {
236+		res = res*10 + int(c-'0')
237+	}
238+	return res
239+}
240+
241+func parseRoman(b []byte) int {
242+	val := func(c byte) int {
243+		switch c {
244+		case 'i', 'I':
245+			return 1
246+		case 'v', 'V':
247+			return 5
248+		case 'x', 'X':
249+			return 10
250+		case 'l', 'L':
251+			return 50
252+		case 'c', 'C':
253+			return 100
254+		case 'd', 'D':
255+			return 500
256+		case 'm', 'M':
257+			return 1000
258+		}
259+		return 0
260+	}
261+	res := 0
262+	for i := 0; i < len(b); i++ {
263+		v := val(b[i])
264+		if i+1 < len(b) && val(b[i+1]) > v {
265+			res -= v
266+		} else {
267+			res += v
268+		}
269+	}
270+	return res
271+}
272+
273+func isDigit(b byte) bool {
274+	return b >= '0' && b <= '9'
275+}
276+
277+func isAlphaLower(b byte) bool {
278+	return b >= 'a' && b <= 'z'
279+}
280+
281+func isAlphaUpper(b byte) bool {
282+	return b >= 'A' && b <= 'Z'
283+}
284+
285+func isRomanLowerStart(b byte) bool {
286+	switch b {
287+	case 'i', 'v', 'x', 'l', 'c', 'd', 'm':
288+		return true
289+	}
290+	return false
291+}
292+
293+func isRomanLower(b byte) bool {
294+	return isRomanLowerStart(b)
295+}
296+
297+func isRomanUpperStart(b byte) bool {
298+	switch b {
299+	case 'I', 'V', 'X', 'L', 'C', 'D', 'M':
300+		return true
301+	}
302+	return false
303+}
304+
305+func isRomanUpper(b byte) bool {
306+	return isRomanUpperStart(b)
307+}
+17, -0
 1@@ -0,0 +1,17 @@
 2+package djot
 3+
 4+func Parse(source []byte) *Document {
 5+	doc := &Document{Source: source}
 6+	bp := NewBlockParser(source)
 7+
 8+	blockEvents := bp.Parse()
 9+
10+	ip := NewInlineParser(source)
11+	inlineEvents := ip.Parse(blockEvents)
12+
13+	as := Assembler{doc: doc}
14+	as.Assemble(inlineEvents)
15+	doc.Events = inlineEvents
16+
17+	return doc
18+}
+3, -0
1@@ -0,0 +1,3 @@
2+module aruu/mkman
3+
4+go 1.21
+600, -0
  1@@ -0,0 +1,600 @@
  2+package main
  3+
  4+import (
  5+	"bufio"
  6+	"flag"
  7+	"fmt"
  8+	"os"
  9+	"path/filepath"
 10+	"strings"
 11+	"time"
 12+
 13+	"aruu/mkman/djot"
 14+)
 15+
 16+// Parse config.mk variables and values
 17+//
 18+type Config map[string]string
 19+
 20+func parseConfigMk(path string) (Config, error) {
 21+	f, err := os.Open(path)
 22+	if err != nil {
 23+		return nil, err
 24+	}
 25+	defer f.Close()
 26+
 27+	raw := make(Config)
 28+	sc := bufio.NewScanner(f)
 29+	for sc.Scan() {
 30+		line := strings.TrimSpace(sc.Text())
 31+		if line == "" || strings.HasPrefix(line, "#") {
 32+			continue
 33+		}
 34+		eq := strings.IndexByte(line, '=')
 35+		if eq < 0 {
 36+			continue
 37+		}
 38+		k := strings.TrimSpace(line[:eq])
 39+		v := strings.TrimSpace(line[eq+1:])
 40+		raw[k] = v
 41+	}
 42+	if err := sc.Err(); err != nil {
 43+		return nil, err
 44+	}
 45+
 46+	cfg := make(Config, len(raw))
 47+	for k, v := range raw {
 48+		cfg[k] = expandVars(v, raw)
 49+	}
 50+	return cfg, nil
 51+}
 52+
 53+func expandVars(s string, env Config) string {
 54+	for {
 55+		start := strings.Index(s, "$(")
 56+		if start < 0 {
 57+			break
 58+		}
 59+		end := strings.Index(s[start:], ")")
 60+		if end < 0 {
 61+			break
 62+		}
 63+		end += start
 64+		varname := s[start+2 : end]
 65+		replacement := ""
 66+		if v, ok := env[varname]; ok {
 67+			replacement = v
 68+		}
 69+		s = s[:start] + replacement + s[end+1:]
 70+	}
 71+	return s
 72+}
 73+
 74+func (cfg Config) isEnabled(key string) bool {
 75+	return cfg[key] == "1"
 76+}
 77+
 78+// Preprocessor condition stack for nested feature blocks
 79+//
 80+type ifFrame struct {
 81+	active bool
 82+	seen   bool
 83+}
 84+
 85+type ifStack struct {
 86+	frames []ifFrame
 87+}
 88+
 89+func (s *ifStack) push(active, seen bool) {
 90+	s.frames = append(s.frames, ifFrame{active: active, seen: seen})
 91+}
 92+
 93+func (s *ifStack) pop() {
 94+	if len(s.frames) > 0 {
 95+		s.frames = s.frames[:len(s.frames)-1]
 96+	}
 97+}
 98+
 99+func (s *ifStack) parentActive() bool {
100+	for i := 0; i < len(s.frames)-1; i++ {
101+		if !s.frames[i].active {
102+			return false
103+		}
104+	}
105+	return true
106+}
107+
108+func (s *ifStack) globallyActive() bool {
109+	for _, f := range s.frames {
110+		if !f.active {
111+			return false
112+		}
113+	}
114+	return true
115+}
116+
117+func (s *ifStack) top() *ifFrame {
118+	if len(s.frames) == 0 {
119+		return nil
120+	}
121+	return &s.frames[len(s.frames)-1]
122+}
123+
124+// Parse and evaluate preprocessor feature conditions
125+//
126+func evalCondition(expr string, cfg Config) bool {
127+	expr = strings.TrimSpace(expr)
128+	if strings.HasPrefix(expr, "defined(") && strings.HasSuffix(expr, ")") {
129+		key := expr[8 : len(expr)-1]
130+		_, ok := cfg[key]
131+		return ok
132+	}
133+	if strings.HasPrefix(expr, "!defined(") && strings.HasSuffix(expr, ")") {
134+		key := expr[9 : len(expr)-1]
135+		_, ok := cfg[key]
136+		return !ok
137+	}
138+	if idx := strings.Index(expr, "=="); idx >= 0 {
139+		lhs := strings.TrimSpace(expr[:idx])
140+		rhs := strings.TrimSpace(expr[idx+2:])
141+		return cfg[lhs] == rhs
142+	}
143+	if idx := strings.Index(expr, "!="); idx >= 0 {
144+		lhs := strings.TrimSpace(expr[:idx])
145+		rhs := strings.TrimSpace(expr[idx+2:])
146+		return cfg[lhs] != rhs
147+	}
148+	if strings.HasPrefix(expr, "!") {
149+		key := strings.TrimSpace(expr[1:])
150+		return cfg[key] != "1"
151+	}
152+	return cfg[expr] == "1"
153+}
154+
155+// Scanning and parsing djot comments in c source files
156+//
157+func formatToken(tok string) string {
158+	if strings.HasPrefix(tok, "-") {
159+		return "`" + tok + "`"
160+	}
161+	if len(tok) >= 2 {
162+		first := tok[0]
163+		last := tok[len(tok)-1]
164+		if (first == '[' && last == ']') || (first == '<' && last == '>') || (first == '(' && last == ')') {
165+			inner := tok[1 : len(tok)-1]
166+			if strings.HasPrefix(inner, "-") {
167+				return string(first) + "`" + inner + "`" + string(last)
168+			}
169+			return string(first) + "_" + inner + "_" + string(last)
170+		}
171+	}
172+	return "_" + tok + "_"
173+}
174+
175+func parseNameSummary(line string) (string, string, bool) {
176+	line = strings.TrimSpace(line)
177+	if idx := strings.Index(line, ":"); idx >= 0 {
178+		return strings.TrimSpace(line[:idx]), strings.TrimSpace(line[idx+1:]), true
179+	}
180+	if idx := strings.Index(line, " \\- "); idx >= 0 {
181+		return strings.TrimSpace(line[:idx]), strings.TrimSpace(line[idx+4:]), true
182+	}
183+	if idx := strings.Index(line, " - "); idx >= 0 {
184+		return strings.TrimSpace(line[:idx]), strings.TrimSpace(line[idx+3:]), true
185+	}
186+	return "", "", false
187+}
188+
189+func isOptionPattern(s string) (string, string, bool) {
190+	s = strings.TrimSpace(s)
191+	if !strings.HasPrefix(s, "-") {
192+		return "", "", false
193+	}
194+	idx := strings.Index(s, ":")
195+	if idx < 0 {
196+		return "", "", false
197+	}
198+	opt := strings.TrimSpace(s[:idx])
199+	desc := strings.TrimSpace(s[idx+1:])
200+	fields := strings.Fields(opt)
201+	if len(fields) == 0 || !strings.HasPrefix(fields[0], "-") {
202+		return "", "", false
203+	}
204+	return opt, desc, true
205+}
206+
207+func scanC(path string, cfg Config) (string, error) {
208+	contentBytes, err := os.ReadFile(path)
209+	if err != nil {
210+		return "", err
211+	}
212+	contentStr := string(contentBytes)
213+
214+	if !strings.Contains(contentStr, "?man") {
215+		// Original parser mode using explicit man markers
216+		var djotBuf strings.Builder
217+		stack := &ifStack{}
218+		sc := bufio.NewScanner(strings.NewReader(contentStr))
219+		sc.Buffer(make([]byte, 1<<20), 1<<20)
220+		inBlockComment := false
221+
222+		for sc.Scan() {
223+			line := sc.Text()
224+			trimmed := strings.TrimSpace(line)
225+
226+			if inBlockComment {
227+				if trimmed == "*/" || trimmed == "* /" {
228+					inBlockComment = false
229+					djotBuf.WriteByte('\n')
230+				} else {
231+					stripped := trimmed
232+					if strings.HasPrefix(stripped, "* ") {
233+						stripped = stripped[2:]
234+					} else if stripped == "*" {
235+						stripped = ""
236+					}
237+					djotBuf.WriteString(stripped)
238+					djotBuf.WriteByte('\n')
239+				}
240+				continue
241+			}
242+
243+			if strings.HasPrefix(trimmed, "#") {
244+				directive := trimmed[1:]
245+				if ci := strings.Index(directive, "//"); ci >= 0 {
246+					directive = directive[:ci]
247+				}
248+				directive = strings.TrimSpace(directive)
249+
250+				switch {
251+				case strings.HasPrefix(directive, "ifdef "):
252+					key := strings.TrimSpace(directive[6:])
253+					_, defined := cfg[key]
254+					pa := stack.parentActive()
255+					stack.push(pa && defined, defined)
256+				case strings.HasPrefix(directive, "ifndef "):
257+					key := strings.TrimSpace(directive[7:])
258+					_, defined := cfg[key]
259+					pa := stack.parentActive()
260+					stack.push(pa && !defined, !defined)
261+				case strings.HasPrefix(directive, "if "):
262+					expr := strings.TrimSpace(directive[3:])
263+					result := evalCondition(expr, cfg)
264+					pa := stack.parentActive()
265+					stack.push(pa && result, result)
266+				case directive == "else":
267+					if top := stack.top(); top != nil {
268+						pa := stack.parentActive()
269+						newActive := pa && !top.seen
270+						top.active = newActive
271+						top.seen = true
272+					}
273+				case strings.HasPrefix(directive, "elif "):
274+					if top := stack.top(); top != nil {
275+						expr := strings.TrimSpace(directive[5:])
276+						result := evalCondition(expr, cfg)
277+						pa := stack.parentActive()
278+						newActive := pa && !top.seen && result
279+						top.active = newActive
280+						if result {
281+							top.seen = true
282+						}
283+					}
284+				case directive == "endif":
285+					stack.pop()
286+				}
287+				continue
288+			}
289+
290+			if !stack.globallyActive() {
291+				continue
292+			}
293+
294+			if strings.Contains(trimmed, "/* !man") {
295+				startIdx := strings.Index(trimmed, "/* !man")
296+				rest := strings.TrimSpace(trimmed[startIdx+7:])
297+				if strings.HasSuffix(rest, "*/") {
298+					content := strings.TrimSpace(rest[:len(rest)-2])
299+					if content != "" {
300+						djotBuf.WriteString(content)
301+						djotBuf.WriteByte('\n')
302+					}
303+				} else {
304+					if rest != "" {
305+						djotBuf.WriteString(rest)
306+						djotBuf.WriteByte('\n')
307+					}
308+					inBlockComment = true
309+				}
310+				continue
311+			}
312+
313+			if idx := strings.Index(trimmed, "// !man"); idx >= 0 {
314+				content := strings.TrimSpace(trimmed[idx+7:])
315+				djotBuf.WriteString(content)
316+				djotBuf.WriteByte('\n')
317+				continue
318+			}
319+		}
320+
321+		if err := sc.Err(); err != nil {
322+			return "", err
323+		}
324+		return djotBuf.String(), nil
325+	}
326+
327+	// Heuristic parser mode using question mark man markers
328+	type optionDoc struct {
329+		opt  string
330+		desc string
331+	}
332+	type sectionDoc struct {
333+		title string
334+		lines []string
335+	}
336+
337+	var name, summary, synopsis string
338+	var descriptionBody []string
339+	var options []optionDoc
340+	var otherSections []sectionDoc
341+	var currentSection *sectionDoc
342+	var lastOption *optionDoc
343+
344+	stack := &ifStack{}
345+	sc := bufio.NewScanner(strings.NewReader(contentStr))
346+	sc.Buffer(make([]byte, 1<<20), 1<<20)
347+	inBlockComment := false
348+	firstLineOfFirstBlock := true
349+
350+	for sc.Scan() {
351+		line := sc.Text()
352+		trimmed := strings.TrimSpace(line)
353+
354+		if inBlockComment {
355+			if trimmed == "*/" || trimmed == "* /" {
356+				inBlockComment = false
357+			} else {
358+				stripped := trimmed
359+				if strings.HasPrefix(stripped, "* ") {
360+					stripped = stripped[2:]
361+				} else if stripped == "*" {
362+					stripped = ""
363+				}
364+				if strings.HasPrefix(stripped, "// ?man ") {
365+					stripped = stripped[8:]
366+				} else if strings.HasPrefix(stripped, "// ?man") {
367+					stripped = stripped[7:]
368+				}
369+				if firstLineOfFirstBlock {
370+					firstLineOfFirstBlock = false
371+					if n, s, ok := parseNameSummary(stripped); ok {
372+						name = n
373+						summary = s
374+						continue
375+					}
376+				}
377+				if synopsis == "" && strings.HasPrefix(strings.ToLower(stripped), "usage:") {
378+					synopsis = strings.TrimSpace(stripped[6:])
379+					continue
380+				}
381+				if strings.HasPrefix(stripped, "## ") {
382+					title := strings.TrimSpace(stripped[3:])
383+					otherSections = append(otherSections, sectionDoc{title: title})
384+					currentSection = &otherSections[len(otherSections)-1]
385+					lastOption = nil
386+				} else {
387+					if opt, desc, ok := isOptionPattern(stripped); ok {
388+						options = append(options, optionDoc{opt: opt, desc: desc})
389+						lastOption = &options[len(options)-1]
390+						currentSection = nil
391+					} else {
392+						if currentSection != nil {
393+							currentSection.lines = append(currentSection.lines, stripped)
394+						} else {
395+							descriptionBody = append(descriptionBody, stripped)
396+						}
397+					}
398+				}
399+			}
400+			continue
401+		}
402+
403+		if strings.HasPrefix(trimmed, "#") {
404+			directive := trimmed[1:]
405+			if ci := strings.Index(directive, "//"); ci >= 0 {
406+				directive = directive[:ci]
407+			}
408+			directive = strings.TrimSpace(directive)
409+
410+			switch {
411+			case strings.HasPrefix(directive, "ifdef "):
412+				key := strings.TrimSpace(directive[6:])
413+				_, defined := cfg[key]
414+				pa := stack.parentActive()
415+				stack.push(pa && defined, defined)
416+			case strings.HasPrefix(directive, "ifndef "):
417+				key := strings.TrimSpace(directive[7:])
418+				_, defined := cfg[key]
419+				pa := stack.parentActive()
420+				stack.push(pa && !defined, !defined)
421+			case strings.HasPrefix(directive, "if "):
422+				expr := strings.TrimSpace(directive[3:])
423+				result := evalCondition(expr, cfg)
424+				pa := stack.parentActive()
425+				stack.push(pa && result, result)
426+			case directive == "else":
427+				if top := stack.top(); top != nil {
428+					pa := stack.parentActive()
429+					newActive := pa && !top.seen
430+					top.active = newActive
431+					top.seen = true
432+				}
433+			case strings.HasPrefix(directive, "elif "):
434+				if top := stack.top(); top != nil {
435+					expr := strings.TrimSpace(directive[5:])
436+					result := evalCondition(expr, cfg)
437+					pa := stack.parentActive()
438+					newActive := pa && !top.seen && result
439+					top.active = newActive
440+					if result {
441+						top.seen = true
442+					}
443+				}
444+			case directive == "endif":
445+				stack.pop()
446+			}
447+			continue
448+		}
449+
450+		if !stack.globallyActive() {
451+			continue
452+		}
453+
454+		if strings.Contains(trimmed, "/* ?man") {
455+			startIdx := strings.Index(trimmed, "/* ?man")
456+			rest := strings.TrimSpace(trimmed[startIdx+7:])
457+			if strings.HasSuffix(rest, "*/") {
458+				content := strings.TrimSpace(rest[:len(rest)-2])
459+				if n, s, ok := parseNameSummary(content); ok {
460+					name = n
461+					summary = s
462+				}
463+			} else {
464+				if rest != "" {
465+					if n, s, ok := parseNameSummary(rest); ok {
466+						name = n
467+						summary = s
468+					} else {
469+						descriptionBody = append(descriptionBody, rest)
470+					}
471+				}
472+				inBlockComment = true
473+			}
474+			continue
475+		}
476+
477+		if idx := strings.Index(trimmed, "// ?man"); idx >= 0 {
478+			content := strings.TrimSpace(trimmed[idx+7:])
479+			if opt, desc, ok := isOptionPattern(content); ok {
480+				options = append(options, optionDoc{opt: opt, desc: desc})
481+				lastOption = &options[len(options)-1]
482+				currentSection = nil
483+			} else {
484+				if lastOption != nil {
485+					lastOption.desc += " " + content
486+				} else if currentSection != nil {
487+					currentSection.lines = append(currentSection.lines, content)
488+				} else {
489+					descriptionBody = append(descriptionBody, content)
490+				}
491+			}
492+			continue
493+		}
494+	}
495+
496+	if err := sc.Err(); err != nil {
497+		return "", err
498+	}
499+
500+	var djotBuf strings.Builder
501+	djotBuf.WriteString("# " + name + "\n\n")
502+	djotBuf.WriteString("## NAME\n\n")
503+	djotBuf.WriteString(name + " \\- " + summary + "\n\n")
504+
505+	if synopsis != "" {
506+		djotBuf.WriteString("## SYNOPSIS\n\n")
507+		djotBuf.WriteString("```\n" + synopsis + "\n```\n\n")
508+	}
509+
510+	if len(descriptionBody) > 0 {
511+		djotBuf.WriteString("## DESCRIPTION\n\n")
512+		for _, l := range descriptionBody {
513+			djotBuf.WriteString(l + "\n")
514+		}
515+		djotBuf.WriteString("\n")
516+	}
517+
518+	if len(options) > 0 {
519+		djotBuf.WriteString("## OPTIONS\n\n")
520+		for _, opt := range options {
521+			words := strings.Fields(opt.opt)
522+			var formatted []string
523+			for _, w := range words {
524+				formatted = append(formatted, formatToken(w))
525+			}
526+			djotBuf.WriteString(strings.Join(formatted, " ") + "\n")
527+			djotBuf.WriteString(": " + opt.desc + "\n\n")
528+		}
529+	}
530+
531+	for _, sec := range otherSections {
532+		djotBuf.WriteString("## " + sec.title + "\n\n")
533+		for _, l := range sec.lines {
534+			djotBuf.WriteString(l + "\n")
535+		}
536+		djotBuf.WriteString("\n")
537+	}
538+
539+	return djotBuf.String(), nil
540+}
541+
542+// Section inference from file path
543+//
544+func inferSection(path string) int {
545+	clean := filepath.ToSlash(path)
546+	switch {
547+	case strings.Contains(clean, "/linux/"),
548+		strings.Contains(clean, "/net/"),
549+		strings.Contains(clean, "/xsi/"):
550+		return 8
551+	default:
552+		return 1
553+	}
554+}
555+
556+// Main entry point and flags definition
557+//
558+func main() {
559+	configPath := flag.String("config", "config.mk", "path to config.mk")
560+	sectionFlag := flag.Int("section", 0, "man section override (0 = infer from path)")
561+	dateFlag := flag.String("date", "", "date string for TH line (default: current month/year)")
562+	flag.Parse()
563+
564+	if flag.NArg() < 1 {
565+		fmt.Fprintf(os.Stderr, "usage: mkman [-config config.mk] [-section N] file.c\n")
566+		os.Exit(1)
567+	}
568+	cfile := flag.Arg(0)
569+
570+	cfg, err := parseConfigMk(*configPath)
571+	if err != nil {
572+		fmt.Fprintf(os.Stderr, "mkman: config: %v\n", err)
573+		os.Exit(1)
574+	}
575+
576+	section := *sectionFlag
577+	if section == 0 {
578+		section = inferSection(cfile)
579+	}
580+
581+	date := *dateFlag
582+	if date == "" {
583+		date = time.Now().Format("January 2006")
584+	}
585+
586+	djotText, err := scanC(cfile, cfg)
587+	if err != nil {
588+		fmt.Fprintf(os.Stderr, "mkman: scan: %v\n", err)
589+		os.Exit(1)
590+	}
591+
592+	if strings.TrimSpace(djotText) == "" {
593+		fmt.Fprintf(os.Stderr, "mkman: %s: no !man comments found\n", cfile)
594+		os.Exit(0)
595+	}
596+
597+	doc := djot.Parse([]byte(djotText))
598+
599+	r := NewTroffRenderer(doc, os.Stdout, section, date)
600+	r.Render()
601+}
+248, -0
  1@@ -0,0 +1,248 @@
  2+package main
  3+
  4+import (
  5+	"fmt"
  6+	"io"
  7+	"strings"
  8+
  9+	"aruu/mkman/djot"
 10+)
 11+
 12+type TroffRenderer struct {
 13+	doc       *djot.Document
 14+	w         io.Writer
 15+	section   int
 16+	date      string
 17+	thDone    bool
 18+	listDepth int
 19+}
 20+
 21+func NewTroffRenderer(doc *djot.Document, w io.Writer, section int, date string) *TroffRenderer {
 22+	return &TroffRenderer{doc: doc, w: w, section: section, date: date}
 23+}
 24+
 25+func (r *TroffRenderer) getBytes(start, end int32) []byte {
 26+	if start == -1 && end == -1 {
 27+		return nil
 28+	}
 29+	if start < 0 {
 30+		idx := ^start
 31+		if idx < 0 || idx >= int32(len(r.doc.Extra)) {
 32+			return nil
 33+		}
 34+		return r.doc.Extra[idx : end+1]
 35+	}
 36+	if start >= int32(len(r.doc.Source)) || end >= int32(len(r.doc.Source)) || start > end {
 37+		return nil
 38+	}
 39+	return r.doc.Source[start : end+1]
 40+}
 41+
 42+func troffEscape(s string) string {
 43+	s = strings.ReplaceAll(s, "\\", "\\\\")
 44+	s = strings.ReplaceAll(s, "-", "\\-")
 45+	// Leading dot is escaped to prevent troff from treating it as a macro
 46+	if len(s) > 0 && s[0] == '.' {
 47+		s = "\\&" + s
 48+	}
 49+	return s
 50+}
 51+
 52+func (r *TroffRenderer) collectInlineText(idx int32) string {
 53+	var b strings.Builder
 54+	curr := r.doc.Nodes[idx].Child
 55+	for curr != -1 {
 56+		node := r.doc.Nodes[curr]
 57+		switch node.Type {
 58+		case djot.NodeStr:
 59+			b.Write(r.getBytes(node.Start, node.End))
 60+		case djot.NodeSoftBreak:
 61+			b.WriteRune(' ')
 62+		case djot.NodeVerbatim:
 63+			b.Write(r.getBytes(node.Start, node.End))
 64+		default:
 65+			b.WriteString(r.collectInlineText(curr))
 66+		}
 67+		curr = node.Next
 68+	}
 69+	return b.String()
 70+}
 71+
 72+func (r *TroffRenderer) renderInlines(idx int32) {
 73+	curr := r.doc.Nodes[idx].Child
 74+	for curr != -1 {
 75+		r.renderInlineNode(curr)
 76+		curr = r.doc.Nodes[curr].Next
 77+	}
 78+}
 79+
 80+func (r *TroffRenderer) renderInlineNode(idx int32) {
 81+	node := r.doc.Nodes[idx]
 82+	switch node.Type {
 83+	case djot.NodeStr:
 84+		raw := string(r.getBytes(node.Start, node.End))
 85+		fmt.Fprint(r.w, troffEscape(raw))
 86+	case djot.NodeSoftBreak:
 87+		fmt.Fprint(r.w, " ")
 88+	case djot.NodeHardBreak:
 89+		fmt.Fprint(r.w, "\n.br\n")
 90+	case djot.NodeNonBreakingSpace:
 91+		fmt.Fprint(r.w, "\\ ")
 92+	case djot.NodeEmph:
 93+		fmt.Fprint(r.w, `\fI`)
 94+		r.renderInlines(idx)
 95+		fmt.Fprint(r.w, `\fP`)
 96+	case djot.NodeStrong:
 97+		fmt.Fprint(r.w, `\fB`)
 98+		r.renderInlines(idx)
 99+		fmt.Fprint(r.w, `\fP`)
100+	case djot.NodeVerbatim:
101+		fmt.Fprint(r.w, `\fB`)
102+		raw := string(r.getBytes(node.Start, node.End))
103+		fmt.Fprint(r.w, troffEscape(raw))
104+		fmt.Fprint(r.w, `\fP`)
105+	case djot.NodeSmartPunctuation:
106+		data := node.Data
107+		switch data {
108+		case 1:
109+			fmt.Fprint(r.w, `\(em`)
110+		case 2:
111+			fmt.Fprint(r.w, `\(en`)
112+		default:
113+			fmt.Fprint(r.w, `\-\-`)
114+		}
115+	case djot.NodeLink:
116+		// Render link text only because raw urls are typically omitted in man pages
117+		r.renderInlines(idx)
118+	case djot.NodeDoubleQuoted:
119+		fmt.Fprint(r.w, `\(lq`)
120+		r.renderInlines(idx)
121+		fmt.Fprint(r.w, `\(rq`)
122+	case djot.NodeSingleQuoted:
123+		fmt.Fprint(r.w, `\(oq`)
124+		r.renderInlines(idx)
125+		fmt.Fprint(r.w, `\(cq`)
126+	default:
127+		r.renderInlines(idx)
128+	}
129+}
130+
131+func (r *TroffRenderer) renderChildren(idx int32) {
132+	curr := r.doc.Nodes[idx].Child
133+	for curr != -1 {
134+		r.renderNode(curr)
135+		curr = r.doc.Nodes[curr].Next
136+	}
137+}
138+
139+func (r *TroffRenderer) renderNode(idx int32) {
140+	node := r.doc.Nodes[idx]
141+	switch node.Type {
142+	case djot.NodeDoc:
143+		r.renderChildren(idx)
144+
145+	case djot.NodeSection:
146+		r.renderChildren(idx)
147+
148+	case djot.NodeHeading:
149+		level := int(node.Level)
150+		if level == 0 {
151+			level = int(node.Data & 0xFFFF)
152+		}
153+		text := r.collectInlineText(idx)
154+		switch level {
155+		case 1:
156+			if !r.thDone {
157+				fmt.Fprintf(r.w, ".TH %s %d \"%s\"\n",
158+					strings.ToUpper(text), r.section, r.date)
159+				r.thDone = true
160+			} else {
161+				fmt.Fprintf(r.w, ".SH %s\n", strings.ToUpper(text))
162+			}
163+		case 2:
164+			fmt.Fprintf(r.w, ".SH %s\n", strings.ToUpper(text))
165+		case 3:
166+			fmt.Fprintf(r.w, ".SS %s\n", text)
167+		default:
168+			fmt.Fprintf(r.w, ".PP\n\\fB%s\\fP\n", troffEscape(text))
169+		}
170+
171+	case djot.NodePara:
172+		if r.listDepth > 0 {
173+			r.renderInlines(idx)
174+			fmt.Fprint(r.w, "\n")
175+		} else {
176+			fmt.Fprint(r.w, ".PP\n")
177+			r.renderInlines(idx)
178+			fmt.Fprint(r.w, "\n")
179+		}
180+
181+	case djot.NodeCodeBlock:
182+		fmt.Fprint(r.w, ".nf\n")
183+		if node.Child != -1 {
184+			r.renderChildren(idx)
185+		} else {
186+			raw := r.getBytes(node.Start, node.End)
187+			r.w.Write(raw) //nolint
188+		}
189+		fmt.Fprint(r.w, ".fi\n")
190+
191+	case djot.NodeStr:
192+		raw := string(r.getBytes(node.Start, node.End))
193+		fmt.Fprint(r.w, raw)
194+
195+	case djot.NodeBlockQuote:
196+		fmt.Fprint(r.w, ".RS\n")
197+		r.renderChildren(idx)
198+		fmt.Fprint(r.w, ".RE\n")
199+
200+	case djot.NodeBulletList, djot.NodeOrderedList, djot.NodeTaskList:
201+		r.listDepth++
202+		r.renderChildren(idx)
203+		r.listDepth--
204+
205+	case djot.NodeListItem, djot.NodeTaskListItem:
206+		if r.doc.Nodes[idx].Type == djot.NodeTaskListItem {
207+			checked := (node.Data & djot.DataTaskChecked) != 0
208+			if checked {
209+				fmt.Fprint(r.w, ".IP \"[x]\" 4\n")
210+			} else {
211+				fmt.Fprint(r.w, ".IP \"[ ]\" 4\n")
212+			}
213+		} else {
214+			fmt.Fprint(r.w, ".IP \\(bu 4\n")
215+		}
216+		r.renderChildren(idx)
217+
218+	case djot.NodeDefinitionList:
219+		r.renderChildren(idx)
220+
221+	case djot.NodeDefinitionListItem:
222+		r.renderChildren(idx)
223+
224+	case djot.NodeTerm:
225+		fmt.Fprint(r.w, ".TP\n")
226+		r.renderInlines(idx)
227+		fmt.Fprint(r.w, "\n")
228+
229+	case djot.NodeDefinition:
230+		r.listDepth++
231+		r.renderChildren(idx)
232+		r.listDepth--
233+
234+	case djot.NodeThematicBreak:
235+		// Omit thematic breaks because troff lacks a clean representation
236+
237+	case djot.NodeDiv:
238+		r.renderChildren(idx)
239+
240+	default:
241+	}
242+}
243+
244+func (r *TroffRenderer) Render() {
245+	if len(r.doc.Nodes) == 0 {
246+		return
247+	}
248+	r.renderNode(0)
249+}
+1412, -0
   1@@ -0,0 +1,1412 @@
   2+/* see license file for copyright and license details */
   3+
   4+#include "redline.h"
   5+
   6+#include <ctype.h>
   7+#include <dirent.h>
   8+#include <errno.h>
   9+#include <fcntl.h>
  10+#include <signal.h>
  11+#include <stdint.h>
  12+#include <stdio.h>
  13+#include <stdlib.h>
  14+#include <string.h>
  15+#include <sys/ioctl.h>
  16+#include <sys/stat.h>
  17+#include <sys/types.h>
  18+#include <termios.h>
  19+#include <unistd.h>
  20+
  21+#define REDLINE_DEFAULT_HISTORY_MAX_LEN 100
  22+#define REDLINE_MAX_LINE (1024*1024)
  23+#define REDLINE_INITIAL_BUFLEN 4096
  24+
  25+#define ENTER 13
  26+#define CTRL_A 1
  27+#define CTRL_B 2
  28+#define CTRL_C 3
  29+#define CTRL_D 4
  30+#define CTRL_E 5
  31+#define CTRL_F 6
  32+#define CTRL_H 8
  33+#define CTRL_K 11
  34+#define CTRL_L 12
  35+#define CTRL_N 14
  36+#define CTRL_P 16
  37+#define CTRL_T 20
  38+#define CTRL_U 21
  39+#define CTRL_W 23
  40+#define CTRL_Y 25
  41+#define BACKSPACE 127
  42+#define ESC 27
  43+
  44+struct redlineState {
  45+	int in_completion;
  46+	int ifd;
  47+	int ofd;
  48+	char *buf;
  49+	size_t buflen;
  50+	const char *prompt;
  51+	size_t plen;
  52+	size_t pos;
  53+	size_t oldpos;
  54+	size_t len;
  55+	size_t cols;
  56+	size_t oldrows;
  57+	int oldrpos;
  58+	int history_index;
  59+};
  60+
  61+
  62+struct abuf {
  63+	char *b;
  64+	int len;
  65+};
  66+
  67+static struct termios orig_termios;
  68+static int rawmode = 0;
  69+static int mlmode = 0;
  70+static int history_max_len = REDLINE_DEFAULT_HISTORY_MAX_LEN;
  71+static int history_len = 0;
  72+static char **history = NULL;
  73+static char *kill_buffer = NULL;
  74+static volatile sig_atomic_t winch_received = 0;
  75+static struct sigaction orig_sigwinch;
  76+
  77+static void (*completionCallback)(const char *, struct redlineCompletions *) = NULL;
  78+
  79+/* return the number of bytes that compose the utf-8 character starting at c */
  80+static int
  81+utf8ByteLen(char c)
  82+{
  83+	unsigned char uc = (unsigned char)c;
  84+	if ((uc & 0x80) == 0)    return 1;
  85+	if ((uc & 0xE0) == 0xC0) return 2;
  86+	if ((uc & 0xF0) == 0xE0) return 3;
  87+	if ((uc & 0xF8) == 0xF0) return 4;
  88+	return 1;
  89+}
  90+
  91+/* decode character starting at s */
  92+static uint32_t
  93+utf8DecodeChar(const char *s, size_t *len)
  94+{
  95+	uint32_t cp = 0;
  96+	int l = utf8ByteLen(*s);
  97+	*len = l;
  98+	if (l == 1) {
  99+		cp = ((unsigned char)*s);
 100+	} else if (l == 2) {
 101+		cp = ((unsigned char)*s & 0x1F) << 6;
 102+		cp |= ((unsigned char)*(s+1) & 0x3F);
 103+	} else if (l == 3) {
 104+		cp = ((unsigned char)*s & 0x0F) << 12;
 105+		cp |= ((unsigned char)*(s+1) & 0x3F) << 6;
 106+		cp |= ((unsigned char)*(s+2) & 0x3F);
 107+	} else if (l == 4) {
 108+		cp = ((unsigned char)*s & 0x07) << 18;
 109+		cp |= ((unsigned char)*(s+1) & 0x3F) << 12;
 110+		cp |= ((unsigned char)*(s+2) & 0x3F) << 6;
 111+		cp |= ((unsigned char)*(s+3) & 0x3F);
 112+	}
 113+	return cp;
 114+}
 115+
 116+static int
 117+isZWJ(uint32_t cp)
 118+{
 119+	return cp == 0x200D;
 120+}
 121+
 122+static int
 123+isCombiningMark(uint32_t cp)
 124+{
 125+	return (cp >= 0x0300 && cp <= 0x036F) || (cp >= 0x1DC0 && cp <= 0x1DFF) ||
 126+	       (cp >= 0x20D0 && cp <= 0x20FF) || (cp >= 0xFE20 && cp <= 0xFE2F);
 127+}
 128+
 129+static int
 130+isVariationSelector(uint32_t cp)
 131+{
 132+	return (cp >= 0xFE00 && cp <= 0xFE0F) || (cp >= 0xE0100 && cp <= 0xE01EF);
 133+}
 134+
 135+static int
 136+isSkinToneModifier(uint32_t cp)
 137+{
 138+	return cp >= 0x1F3FB && cp <= 0x1F3FF;
 139+}
 140+
 141+
 142+static int
 143+isGraphemeExtend(uint32_t cp)
 144+{
 145+	return isCombiningMark(cp) || isVariationSelector(cp) || isSkinToneModifier(cp);
 146+}
 147+
 148+/* decode character going backward from pos */
 149+static uint32_t
 150+utf8DecodePrev(const char *buf, size_t pos, size_t *cplen)
 151+{
 152+	size_t i = 1;
 153+	while (pos >= i && i <= 4) {
 154+		unsigned char uc = (unsigned char)buf[pos-i];
 155+		if ((uc & 0x80) == 0) {
 156+			if (i == 1) {
 157+				*cplen = 1;
 158+				return uc;
 159+			}
 160+			break;
 161+		}
 162+		if ((uc & 0xC0) == 0xC0) {
 163+			int l = utf8ByteLen(buf[pos-i]);
 164+			if ((size_t)l == i) {
 165+				*cplen = i;
 166+				return utf8DecodeChar(buf + pos - i, cplen);
 167+			}
 168+			break;
 169+		}
 170+		i++;
 171+	}
 172+	*cplen = 1;
 173+	return (unsigned char)buf[pos-1];
 174+}
 175+
 176+/* calculate width of utf-8 char pos */
 177+static size_t
 178+utf8PrevCharLen(const char *buf, size_t pos)
 179+{
 180+	size_t len = 0;
 181+	size_t next_len = 0;
 182+	uint32_t cp;
 183+	if (pos == 0) return 0;
 184+	cp = utf8DecodePrev(buf, pos, &len);
 185+	pos -= len;
 186+	while (pos > 0 && isGraphemeExtend(cp)) {
 187+		cp = utf8DecodePrev(buf, pos, &next_len);
 188+		len += next_len;
 189+		pos -= next_len;
 190+	}
 191+	if (pos > 0 && isZWJ(cp)) {
 192+		size_t j = utf8PrevCharLen(buf, pos);
 193+		if (j > 0) len += j;
 194+	}
 195+	return len;
 196+}
 197+
 198+/* calculate width of next utf-8 char */
 199+static size_t
 200+utf8NextCharLen(const char *buf, size_t pos, size_t len)
 201+{
 202+	size_t clen = 0;
 203+	size_t offset = 0;
 204+	uint32_t cp;
 205+	if (pos >= len) return 0;
 206+	cp = utf8DecodeChar(buf + pos, &clen);
 207+	offset = clen;
 208+	while (pos + offset < len) {
 209+		size_t next_len = 0;
 210+		uint32_t next_cp = utf8DecodeChar(buf + pos + offset, &next_len);
 211+		if (isGraphemeExtend(next_cp)) {
 212+			offset += next_len;
 213+		} else if (isZWJ(cp)) {
 214+			offset += next_len;
 215+			cp = next_cp;
 216+		} else {
 217+			break;
 218+		}
 219+	}
 220+	return offset;
 221+}
 222+
 223+/* get columns needed to display char */
 224+static int
 225+utf8CharWidth(uint32_t cp)
 226+{
 227+	if (cp == 0) return 0;
 228+	if (cp < 0x20 || (cp >= 0x7f && cp < 0xa0)) return 0;
 229+	if ((cp >= 0x1100 && cp <= 0x115f) ||
 230+	    (cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f) ||
 231+	    (cp >= 0xac00 && cp <= 0xd7a3) ||
 232+	    (cp >= 0xf900 && cp <= 0xfaff) ||
 233+	    (cp >= 0xfe10 && cp <= 0xfe19) ||
 234+	    (cp >= 0xfe30 && cp <= 0xfe6f) ||
 235+	    (cp >= 0xff00 && cp <= 0xff60) ||
 236+	    (cp >= 0xffe0 && cp <= 0xffe6) ||
 237+	    (cp >= 0x20000 && cp <= 0x2fffd) ||
 238+	    (cp >= 0x30000 && cp <= 0x3fffd)) {
 239+		return 2;
 240+	}
 241+	return 1;
 242+}
 243+
 244+/* get ansi escape sequence length */
 245+static size_t
 246+ansiEscapeLen(const char *s, size_t len)
 247+{
 248+	size_t i = 0;
 249+	if (len < 2 || s[0] != '\x1b' || s[1] != '[') return 0;
 250+	i = 2;
 251+	while (i < len) {
 252+		char c = s[i];
 253+		if ((c >= '0' && c <= '9') || c == ';' || c == '?' || c == '"') {
 254+			i++;
 255+		} else if (c >= 'A' && c <= 'Z') {
 256+			return i + 1;
 257+		} else if (c >= 'a' && c <= 'z') {
 258+			return i + 1;
 259+		} else {
 260+			break;
 261+		}
 262+	}
 263+	return 0;
 264+}
 265+
 266+/* calculate width of string */
 267+static size_t
 268+utf8StrWidth(const char *s, size_t len)
 269+{
 270+	size_t width = 0;
 271+	size_t i = 0;
 272+	while (i < len) {
 273+		size_t elen = ansiEscapeLen(s + i, len - i);
 274+		if (elen > 0) {
 275+			i += elen;
 276+			continue;
 277+		}
 278+		size_t clen = 0;
 279+		uint32_t cp = utf8DecodeChar(s + i, &clen);
 280+		width += utf8CharWidth(cp);
 281+		i += clen;
 282+	}
 283+	return width;
 284+}
 285+
 286+/* get single character width */
 287+static int
 288+utf8SingleCharWidth(const char *s, size_t len)
 289+{
 290+	size_t clen = 0;
 291+	uint32_t cp = utf8DecodeChar(s, &clen);
 292+	(void)len;
 293+	return utf8CharWidth(cp);
 294+}
 295+
 296+static int
 297+isUnsupportedTerm(void)
 298+{
 299+	char *term = getenv("TERM");
 300+	int i;
 301+	static char *unsupported[] = {"dumb", "cons25", "emacs", NULL};
 302+	if (term == NULL) return 0;
 303+	for (i = 0; unsupported[i]; i++) {
 304+		if (strcasecmp(term, unsupported[i]) == 0)
 305+			return 1;
 306+	}
 307+	return 0;
 308+}
 309+
 310+static void
 311+sigwinchHandler(int sig)
 312+{
 313+	(void)sig;
 314+	winch_received = 1;
 315+}
 316+
 317+static int
 318+enableRawMode(int fd)
 319+{
 320+	struct termios raw;
 321+	struct sigaction sa;
 322+
 323+	if (!isatty(STDIN_FILENO))
 324+		return -1;
 325+	if (tcgetattr(fd, &orig_termios) == -1)
 326+		return -1;
 327+
 328+	raw = orig_termios;
 329+	raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
 330+	raw.c_oflag &= ~(OPOST);
 331+	raw.c_cflag |= (CS8);
 332+	raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
 333+	raw.c_cc[VMIN] = 1;
 334+	raw.c_cc[VTIME] = 0;
 335+
 336+	if (tcsetattr(fd, TCSAFLUSH, &raw) < 0)
 337+		return -1;
 338+
 339+	rawmode = 1;
 340+
 341+	/* register sigwinch handler */
 342+	sa.sa_handler = sigwinchHandler;
 343+	sigemptyset(&sa.sa_mask);
 344+	sa.sa_flags = 0;
 345+	sigaction(SIGWINCH, &sa, &orig_sigwinch);
 346+
 347+	return 0;
 348+}
 349+
 350+static void
 351+disableRawMode(int fd)
 352+{
 353+	if (rawmode) {
 354+		tcsetattr(fd, TCSAFLUSH, &orig_termios);
 355+		sigaction(SIGWINCH, &orig_sigwinch, NULL);
 356+		rawmode = 0;
 357+	}
 358+}
 359+
 360+static int
 361+getCursorPosition(int ifd, int ofd)
 362+{
 363+	char buf[32];
 364+	int cols, rows;
 365+	unsigned int i = 0;
 366+
 367+	if (write(ofd, "\x1b[6n", 4) != 4) return -1;
 368+
 369+	while (i < sizeof(buf)-1) {
 370+		if (read(ifd,buf+i,1) != 1) break;
 371+		if (buf[i] == 'R') break;
 372+		i++;
 373+	}
 374+	buf[i] = '\0';
 375+
 376+	if (buf[0] != 27 || buf[1] != '[') return -1;
 377+	if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1;
 378+	return cols;
 379+}
 380+
 381+static int
 382+getColumns(int ifd, int ofd)
 383+{
 384+	struct winsize ws;
 385+	char *cols_env;
 386+	int tty_fd;
 387+	int cols = 0;
 388+
 389+	if (ioctl(ofd, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20)
 390+		return ws.ws_col;
 391+	if (ioctl(ifd, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20)
 392+		return ws.ws_col;
 393+	if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20)
 394+		return ws.ws_col;
 395+	if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20)
 396+		return ws.ws_col;
 397+	if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20)
 398+		return ws.ws_col;
 399+
 400+	tty_fd = open("/dev/tty", O_RDWR | O_NOCTTY);
 401+	if (tty_fd >= 0) {
 402+		if (ioctl(tty_fd, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20) {
 403+			cols = ws.ws_col;
 404+		}
 405+		close(tty_fd);
 406+		if (cols >= 20) return cols;
 407+	}
 408+
 409+	cols_env = getenv("COLUMNS");
 410+	if (cols_env) {
 411+		cols = atoi(cols_env);
 412+		if (cols >= 20) return cols;
 413+	}
 414+
 415+	/* fallback to cursor position query */
 416+	int start;
 417+
 418+	if (!isatty(ifd) || !isatty(ofd)) return 80;
 419+
 420+	start = getCursorPosition(ifd,ofd);
 421+	if (start == -1) return 80;
 422+
 423+	if (write(ofd,"\x1b[999C",6) != 6) return 80;
 424+	cols = getCursorPosition(ifd,ofd);
 425+	if (cols == -1) return 80;
 426+
 427+	if (cols > start) {
 428+		char seq[32];
 429+		snprintf(seq,sizeof(seq),"\x1b[%dD",cols-start);
 430+		if (write(ofd,seq,strlen(seq)) == -1) {}
 431+	}
 432+	if (cols < 20) return 80;
 433+	return cols;
 434+}
 435+
 436+static void
 437+redlineBeep(void)
 438+{
 439+	fprintf(stderr, "\x7");
 440+	fflush(stderr);
 441+}
 442+
 443+static void
 444+freeCompletions(struct redlineCompletions *lc)
 445+{
 446+	size_t i;
 447+	if (lc->cvec) {
 448+		for (i = 0; i < lc->len; i++) {
 449+			free(lc->cvec[i]);
 450+		}
 451+		free(lc->cvec);
 452+	}
 453+}
 454+
 455+static size_t
 456+longestCommonPrefix(struct redlineCompletions *lc)
 457+{
 458+	size_t i, j;
 459+	if (lc->len == 0) return 0;
 460+	for (i = 0; ; i++) {
 461+		char c = lc->cvec[0][i];
 462+		if (c == '\0') return i;
 463+		for (j = 1; j < lc->len; j++) {
 464+			if (lc->cvec[j][i] != c) {
 465+				return i;
 466+			}
 467+		}
 468+	}
 469+}
 470+
 471+static void
 472+printCompletions(struct redlineState *ls, struct redlineCompletions *lc)
 473+{
 474+	size_t max_len = 0;
 475+	size_t i, j, k;
 476+	size_t col_width, num_cols, num_rows;
 477+	size_t sp, len, idx;
 478+	const char *comp;
 479+	const char *name;
 480+
 481+	for (i = 0; i < lc->len; i++) {
 482+		comp = lc->cvec[i];
 483+		sp = strlen(comp);
 484+		if (sp > 0 && comp[sp-1] == ' ') {
 485+			sp--;
 486+		}
 487+		while (sp > 0 && comp[sp-1] != ' ') {
 488+			sp--;
 489+		}
 490+		len = strlen(comp + sp);
 491+		if (len > 0 && (comp + sp)[len-1] == ' ') {
 492+			len--;
 493+		}
 494+		if (len > max_len) {
 495+			max_len = len;
 496+		}
 497+	}
 498+
 499+	col_width = max_len + 2;
 500+	num_cols = ls->cols / col_width;
 501+	if (num_cols == 0) num_cols = 1;
 502+	num_rows = (lc->len + num_cols - 1) / num_cols;
 503+
 504+	if (write(ls->ofd, "\r\n", 2) == -1) {}
 505+	for (i = 0; i < num_rows; i++) {
 506+		for (j = 0; j < num_cols; j++) {
 507+			idx = j * num_rows + i;
 508+			if (idx < lc->len) {
 509+				comp = lc->cvec[idx];
 510+				sp = strlen(comp);
 511+				if (sp > 0 && comp[sp-1] == ' ') {
 512+					sp--;
 513+				}
 514+				while (sp > 0 && comp[sp-1] != ' ') {
 515+					sp--;
 516+				}
 517+				name = comp + sp;
 518+				len = strlen(name);
 519+				if (len > 0 && name[len-1] == ' ') {
 520+					len--;
 521+				}
 522+				if (write(ls->ofd, name, len) == -1) {}
 523+				if (j < num_cols - 1) {
 524+					for (k = len; k < col_width; k++) {
 525+						if (write(ls->ofd, " ", 1) == -1) {}
 526+					}
 527+				}
 528+			}
 529+		}
 530+		if (write(ls->ofd, "\r\n", 2) == -1) {}
 531+	}
 532+}
 533+
 534+static void
 535+abInit(struct abuf *ab)
 536+{
 537+	ab->b = NULL;
 538+	ab->len = 0;
 539+}
 540+
 541+static void
 542+abAppend(struct abuf *ab, const char *s, int len)
 543+{
 544+	char *new = realloc(ab->b,ab->len+len);
 545+	if (new == NULL) return;
 546+	memcpy(new+ab->len,s,len);
 547+	ab->b = new;
 548+	ab->len += len;
 549+}
 550+
 551+static void
 552+abFree(struct abuf *ab)
 553+{
 554+	free(ab->b);
 555+}
 556+
 557+static void
 558+refreshSingleLine(struct redlineState *l, int flags)
 559+{
 560+	char seq[64];
 561+	size_t pwidth = utf8StrWidth(l->prompt, l->plen);
 562+	int fd = l->ofd;
 563+	char *buf = l->buf;
 564+	size_t len = l->len;
 565+	size_t pos = l->pos;
 566+	size_t poscol;
 567+	size_t lencol;
 568+	struct abuf ab;
 569+
 570+	poscol = utf8StrWidth(buf, pos);
 571+	lencol = utf8StrWidth(buf, len);
 572+
 573+	while (pwidth + poscol >= l->cols) {
 574+		size_t clen = utf8NextCharLen(buf, 0, len);
 575+		int cwidth = utf8SingleCharWidth(buf, clen);
 576+		buf += clen;
 577+		len -= clen;
 578+		pos -= clen;
 579+		poscol -= cwidth;
 580+		lencol -= cwidth;
 581+	}
 582+
 583+	while (pwidth + lencol > l->cols) {
 584+		size_t clen = utf8PrevCharLen(buf, len);
 585+		int cwidth = utf8SingleCharWidth(buf + len - clen, clen);
 586+		len -= clen;
 587+		lencol -= cwidth;
 588+	}
 589+
 590+	abInit(&ab);
 591+	snprintf(seq,sizeof(seq),"\r");
 592+	abAppend(&ab,seq,strlen(seq));
 593+
 594+	if (flags & 1) {
 595+		abAppend(&ab,l->prompt,l->plen);
 596+		abAppend(&ab,buf,len);
 597+	}
 598+
 599+	snprintf(seq,sizeof(seq),"\x1b[0K");
 600+	abAppend(&ab,seq,strlen(seq));
 601+
 602+	if (flags & 1) {
 603+		snprintf(seq,sizeof(seq),"\r\x1b[%dC", (int)(poscol+pwidth));
 604+		abAppend(&ab,seq,strlen(seq));
 605+	}
 606+
 607+	if (write(fd,ab.b,ab.len) == -1) {}
 608+	abFree(&ab);
 609+}
 610+
 611+static void
 612+refreshMultiLine(struct redlineState *l, int flags)
 613+{
 614+	char seq[64];
 615+	size_t pwidth = utf8StrWidth(l->prompt, l->plen);
 616+	size_t bufwidth;
 617+	size_t poswidth;
 618+	int rows;
 619+	int rpos2;
 620+	int col;
 621+	int old_rows = l->oldrows;
 622+	int rpos = l->oldrpos;
 623+	int fd = l->ofd, j;
 624+	struct abuf ab;
 625+
 626+	(void)flags;
 627+
 628+	bufwidth = utf8StrWidth(l->buf, l->len);
 629+	poswidth = utf8StrWidth(l->buf, l->pos);
 630+	rows = (pwidth+bufwidth+l->cols-1)/l->cols;
 631+	l->oldrows = rows;
 632+
 633+	abInit(&ab);
 634+
 635+	/* move cursor up to the first row, column 0 of the input area */
 636+	if (rpos > 1) {
 637+		snprintf(seq, 64, "\r\x1b[%dA", rpos - 1);
 638+		abAppend(&ab, seq, strlen(seq));
 639+	} else {
 640+		abAppend(&ab, "\r", 1);
 641+	}
 642+
 643+	/* clear all old rows */
 644+	for (j = 0; j < old_rows; j++) {
 645+		abAppend(&ab, "\x1b[0K", 4);
 646+		if (j < old_rows - 1) {
 647+			abAppend(&ab, "\n\r", 2);
 648+		}
 649+	}
 650+
 651+	/* move cursor back to the first row, column 0 */
 652+	if (old_rows > 1) {
 653+		snprintf(seq, 64, "\r\x1b[%dA", old_rows - 1);
 654+		abAppend(&ab, seq, strlen(seq));
 655+	} else {
 656+		abAppend(&ab, "\r", 1);
 657+	}
 658+
 659+	/* print prompt and new buffer */
 660+	abAppend(&ab, l->prompt, l->plen);
 661+	abAppend(&ab, l->buf, l->len);
 662+
 663+	/* if cursor is at the end of the line and wraps, print a newline */
 664+	if (l->pos && l->pos == l->len && (poswidth+pwidth) % l->cols == 0) {
 665+		abAppend(&ab, "\n\r", 2);
 666+		rows++;
 667+		if (rows > (int)l->oldrows) l->oldrows = rows;
 668+	}
 669+
 670+	/* calculate cursor row and column */
 671+	rpos2 = (pwidth+poswidth+l->cols)/l->cols;
 672+	col = (pwidth+poswidth) % l->cols;
 673+
 674+	/* move cursor to the correct row and column */
 675+	if (rows - rpos2 > 0) {
 676+		snprintf(seq, 64, "\x1b[%dA", rows - rpos2);
 677+		abAppend(&ab, seq, strlen(seq));
 678+	}
 679+	if (col) {
 680+		snprintf(seq, 64, "\r\x1b[%dC", col);
 681+		abAppend(&ab, seq, strlen(seq));
 682+	} else {
 683+		abAppend(&ab, "\r", 1);
 684+	}
 685+
 686+	l->oldpos = l->pos;
 687+	l->oldrpos = rpos2;
 688+
 689+	if (write(fd, ab.b, ab.len) == -1) {}
 690+	abFree(&ab);
 691+}
 692+
 693+static void
 694+refreshLineWithFlags(struct redlineState *l, int flags)
 695+{
 696+	if (mlmode)
 697+		refreshMultiLine(l,flags);
 698+	else
 699+		refreshSingleLine(l,flags);
 700+}
 701+
 702+static void
 703+refreshLine(struct redlineState *l)
 704+{
 705+	refreshLineWithFlags(l, 1);
 706+}
 707+
 708+static int
 709+completeLine(struct redlineState *ls, int keypressed)
 710+{
 711+	struct redlineCompletions lc = { 0, NULL };
 712+	size_t lcp_len;
 713+	int c = keypressed;
 714+	int proceed = 1;
 715+	char query[128];
 716+	char answer = 0;
 717+
 718+	if (c != 9) {
 719+		ls->in_completion = 0;
 720+		return c;
 721+	}
 722+
 723+	completionCallback(ls->buf, &lc);
 724+	if (lc.len == 0) {
 725+		redlineBeep();
 726+		ls->in_completion = 0;
 727+		c = 0;
 728+	} else if (lc.len == 1) {
 729+		size_t nwritten = snprintf(ls->buf, ls->buflen, "%s", lc.cvec[0]);
 730+		ls->len = ls->pos = nwritten;
 731+		refreshLine(ls);
 732+		ls->in_completion = 0;
 733+		c = 0;
 734+	} else {
 735+		lcp_len = longestCommonPrefix(&lc);
 736+		if (lcp_len > ls->len) {
 737+			size_t nwritten = snprintf(ls->buf, ls->buflen, "%.*s", (int)lcp_len, lc.cvec[0]);
 738+			ls->len = ls->pos = nwritten;
 739+			refreshLine(ls);
 740+			ls->in_completion = 1;
 741+			c = 0;
 742+		} else {
 743+			/* prefix cannot be expanded further */
 744+			if (ls->in_completion == 0) {
 745+				/* first tab: beep and wait for the second tab */
 746+				redlineBeep();
 747+				ls->in_completion = 1;
 748+				c = 0;
 749+			} else {
 750+				/* second tab: display possibilities */
 751+				if (lc.len > 100) {
 752+					snprintf(query, sizeof(query), "\r\nDisplay all %d possibilities? (y or n) ", (int)lc.len);
 753+					if (write(ls->ofd, query, strlen(query)) == -1) {}
 754+					while (1) {
 755+						if (read(ls->ifd, &answer, 1) != 1) {
 756+							proceed = 0;
 757+							break;
 758+						}
 759+						if (answer == 'y' || answer == 'Y' || answer == ' ' || answer == '\t') {
 760+							proceed = 1;
 761+							break;
 762+						}
 763+						if (answer == 'n' || answer == 'N' || answer == 27 || answer == 3 || answer == 4) {
 764+							proceed = 0;
 765+							break;
 766+						}
 767+						redlineBeep();
 768+					}
 769+				}
 770+				if (proceed) {
 771+					printCompletions(ls, &lc);
 772+				} else {
 773+					if (write(ls->ofd, "\r\n", 2) == -1) {}
 774+				}
 775+				ls->oldrows = 0;
 776+				refreshLine(ls);
 777+				ls->in_completion = 0;
 778+				c = 0;
 779+			}
 780+		}
 781+	}
 782+
 783+	freeCompletions(&lc);
 784+	return c;
 785+}
 786+
 787+static int
 788+redlineEditInsert(struct redlineState *l, const char *c, int clen)
 789+{
 790+	if (l->len + clen >= l->buflen) {
 791+		return 0;
 792+	}
 793+	if (l->len == l->pos) {
 794+		memcpy(l->buf + l->pos, c, clen);
 795+		l->pos += clen;
 796+		l->len += clen;
 797+		l->buf[l->len] = '\0';
 798+		refreshLine(l);
 799+	} else {
 800+		memmove(l->buf + l->pos + clen, l->buf + l->pos, l->len - l->pos);
 801+		memcpy(l->buf + l->pos, c, clen);
 802+		l->pos += clen;
 803+		l->len += clen;
 804+		l->buf[l->len] = '\0';
 805+		refreshLine(l);
 806+	}
 807+	return 1;
 808+}
 809+
 810+static void
 811+redlineEditBackspace(struct redlineState *l)
 812+{
 813+	if (l->pos > 0 && l->len > 0) {
 814+		size_t clen = utf8PrevCharLen(l->buf, l->pos);
 815+		memmove(l->buf+l->pos-clen, l->buf+l->pos, l->len-l->pos);
 816+		l->pos -= clen;
 817+		l->len -= clen;
 818+		l->buf[l->len] = '\0';
 819+		refreshLine(l);
 820+	}
 821+}
 822+
 823+static void
 824+redlineEditDelete(struct redlineState *l)
 825+{
 826+	if (l->len > 0 && l->pos < l->len) {
 827+		size_t clen = utf8NextCharLen(l->buf, l->pos, l->len);
 828+		memmove(l->buf+l->pos, l->buf+l->pos+clen, l->len-l->pos-clen);
 829+		l->len -= clen;
 830+		l->buf[l->len] = '\0';
 831+		refreshLine(l);
 832+	}
 833+}
 834+
 835+static void
 836+redlineEditMoveLeft(struct redlineState *l)
 837+{
 838+	if (l->pos > 0) {
 839+		l->pos -= utf8PrevCharLen(l->buf, l->pos);
 840+		refreshLine(l);
 841+	}
 842+}
 843+
 844+static void
 845+redlineEditMoveRight(struct redlineState *l)
 846+{
 847+	if (l->pos != l->len) {
 848+		l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
 849+		refreshLine(l);
 850+	}
 851+}
 852+
 853+static void
 854+redlineEditMoveHome(struct redlineState *l)
 855+{
 856+	if (l->pos != 0) {
 857+		l->pos = 0;
 858+		refreshLine(l);
 859+	}
 860+}
 861+
 862+static void
 863+redlineEditMoveEnd(struct redlineState *l)
 864+{
 865+	if (l->pos != l->len) {
 866+		l->pos = l->len;
 867+		refreshLine(l);
 868+	}
 869+}
 870+
 871+static void
 872+redlineEditMoveWordLeft(struct redlineState *l)
 873+{
 874+	if (l->pos > 0) {
 875+		while (l->pos > 0 && l->buf[l->pos-1] == ' ')
 876+			l->pos -= utf8PrevCharLen(l->buf, l->pos);
 877+		while (l->pos > 0 && l->buf[l->pos-1] != ' ')
 878+			l->pos -= utf8PrevCharLen(l->buf, l->pos);
 879+		refreshLine(l);
 880+	}
 881+}
 882+
 883+static void
 884+redlineEditMoveWordRight(struct redlineState *l)
 885+{
 886+	if (l->pos < l->len) {
 887+		while (l->pos < l->len && l->buf[l->pos] == ' ')
 888+			l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
 889+		while (l->pos < l->len && l->buf[l->pos] != ' ')
 890+			l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
 891+		refreshLine(l);
 892+	}
 893+}
 894+
 895+static void
 896+killBufferSave(const char *text, size_t len)
 897+{
 898+	free(kill_buffer);
 899+	kill_buffer = malloc(len + 1);
 900+	if (kill_buffer) {
 901+		memcpy(kill_buffer, text, len);
 902+		kill_buffer[len] = '\0';
 903+	}
 904+}
 905+
 906+static void
 907+redlineEditDeleteWordRight(struct redlineState *l)
 908+{
 909+	size_t old_pos = l->pos;
 910+	size_t diff;
 911+	if (l->pos < l->len) {
 912+		while (l->pos < l->len && l->buf[l->pos] == ' ')
 913+			l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
 914+		while (l->pos < l->len && l->buf[l->pos] != ' ')
 915+			l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
 916+		diff = l->pos - old_pos;
 917+		l->pos = old_pos;
 918+		killBufferSave(l->buf + l->pos, diff);
 919+		memmove(l->buf + l->pos, l->buf + l->pos + diff, l->len - l->pos - diff + 1);
 920+		l->len -= diff;
 921+		refreshLine(l);
 922+	}
 923+}
 924+
 925+static void
 926+redlineEditDeletePrevWord(struct redlineState *l)
 927+{
 928+	size_t old_pos = l->pos;
 929+	size_t diff;
 930+	if (l->pos > 0) {
 931+		while (l->pos > 0 && l->buf[l->pos-1] == ' ')
 932+			l->pos -= utf8PrevCharLen(l->buf, l->pos);
 933+		while (l->pos > 0 && l->buf[l->pos-1] != ' ')
 934+			l->pos -= utf8PrevCharLen(l->buf, l->pos);
 935+		diff = old_pos - l->pos;
 936+		killBufferSave(l->buf + l->pos, diff);
 937+		memmove(l->buf + l->pos, l->buf + old_pos, l->len - old_pos + 1);
 938+		l->len -= diff;
 939+		refreshLine(l);
 940+	}
 941+}
 942+
 943+void
 944+redlineHistoryAdd(const char *line)
 945+{
 946+	char *linecopy;
 947+	if (history_max_len == 0) return;
 948+	if (history == NULL) {
 949+		history = malloc(sizeof(char*) * history_max_len);
 950+		if (history == NULL) return;
 951+		memset(history,0,sizeof(char*)*history_max_len);
 952+	}
 953+	if (history_len && strcmp(history[history_len-1], line) == 0) return;
 954+	linecopy = strdup(line);
 955+	if (!linecopy) return;
 956+	if (history_len == history_max_len) {
 957+		free(history[0]);
 958+		memmove(history,history+1,sizeof(char*)*(history_max_len-1));
 959+		history_len--;
 960+	}
 961+	history[history_len] = linecopy;
 962+	history_len++;
 963+}
 964+
 965+void
 966+redlineHistorySetMaxLen(int len)
 967+{
 968+	char **new;
 969+	if (len < 1) return;
 970+	if (history) {
 971+		int tocopy = history_len;
 972+		new = malloc(sizeof(char*)*len);
 973+		if (new == NULL) return;
 974+		if (len < tocopy) {
 975+			int j;
 976+			for (j = 0; j < tocopy-len; j++) free(history[j]);
 977+			tocopy = len;
 978+		}
 979+		memset(new,0,sizeof(char*)*len);
 980+		memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy);
 981+		free(history);
 982+		history = new;
 983+	}
 984+	history_max_len = len;
 985+	if (history_len > history_max_len)
 986+		history_len = history_max_len;
 987+}
 988+
 989+int
 990+redlineHistorySave(const char *filename)
 991+{
 992+	mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
 993+	FILE *fp;
 994+	int j;
 995+
 996+	fp = fopen(filename,"w");
 997+	umask(old_umask);
 998+	if (fp == NULL) return -1;
 999+	chmod(filename,S_IRUSR|S_IWUSR);
1000+	for (j = 0; j < history_len; j++) {
1001+		fprintf(fp,"%s\n",history[j]);
1002+	}
1003+	fclose(fp);
1004+	return 0;
1005+}
1006+
1007+int
1008+redlineHistoryLoad(const char *filename)
1009+{
1010+	FILE *fp = fopen(filename,"r");
1011+	char buf[REDLINE_INITIAL_BUFLEN];
1012+	if (fp == NULL) return -1;
1013+	while (fgets(buf,sizeof(buf),fp) != NULL) {
1014+		char *p = strchr(buf,'\r');
1015+		if (!p) p = strchr(buf,'\n');
1016+		if (p) *p = '\0';
1017+		redlineHistoryAdd(buf);
1018+	}
1019+	fclose(fp);
1020+	return 0;
1021+}
1022+
1023+char *
1024+redlineHistoryGet(int idx)
1025+{
1026+	if (idx >= 0 && idx < history_len)
1027+		return history[idx];
1028+	return NULL;
1029+}
1030+
1031+int
1032+redlineHistoryLen(void)
1033+{
1034+	return history_len;
1035+}
1036+
1037+static void
1038+redlineEditHistoryNext(struct redlineState *l, int dir)
1039+{
1040+	if (history_len > 1) {
1041+		const char *src;
1042+		size_t len;
1043+		free(history[history_len - 1 - l->history_index]);
1044+		history[history_len - 1 - l->history_index] = strdup(l->buf);
1045+		l->history_index += (dir == 1) ? 1 : -1;
1046+		if (l->history_index < 0) {
1047+			l->history_index = 0;
1048+			return;
1049+		} else if (l->history_index >= history_len) {
1050+			l->history_index = history_len - 1;
1051+			return;
1052+		}
1053+		src = history[history_len - 1 - l->history_index];
1054+		len = strlen(src);
1055+		if (len >= l->buflen) len = l->buflen - 1;
1056+		memcpy(l->buf, src, len);
1057+		l->buf[len] = '\0';
1058+		l->len = l->pos = len;
1059+		refreshLine(l);
1060+	}
1061+}
1062+
1063+static char *
1064+redlineReadLine(FILE *fp)
1065+{
1066+	char *line = NULL;
1067+	size_t len = 0, cap = 0;
1068+	while (1) {
1069+		if (len+1 >= cap) {
1070+			size_t newcap = cap ? cap*2 : 16;
1071+			char *new = realloc(line, newcap);
1072+			if (!new) {
1073+				free(line);
1074+				return NULL;
1075+			}
1076+			line = new;
1077+			cap = newcap;
1078+		}
1079+		int c = fgetc(fp);
1080+		if (c == EOF || c == '\n') {
1081+			if (c == EOF && len == 0) {
1082+				free(line);
1083+				return NULL;
1084+			}
1085+			line[len] = '\0';
1086+			return line;
1087+		}
1088+		line[len++] = c;
1089+	}
1090+}
1091+
1092+static char *
1093+redlineNoTTY(void)
1094+{
1095+	return redlineReadLine(stdin);
1096+}
1097+
1098+void
1099+redlineClearScreen(void)
1100+{
1101+	if (write(STDOUT_FILENO, "\x1b[H\x1b[2J", 7) == -1) {}
1102+}
1103+
1104+static char *
1105+redlineEditFeed(struct redlineState *l)
1106+{
1107+	char c;
1108+	int nread;
1109+	char seq[3];
1110+	char param[8];
1111+	size_t plen;
1112+	char final;
1113+	char p;
1114+	int is_word_jump;
1115+	char tmp[32];
1116+	size_t prevlen;
1117+	size_t currlen;
1118+	size_t prevstart;
1119+	char utf8[4];
1120+	int utf8len;
1121+	int i;
1122+
1123+	if (!isatty(l->ifd) && !getenv("REDLINE_ASSUME_TTY")) return redlineNoTTY();
1124+
1125+	while (1) {
1126+		nread = read(l->ifd, &c, 1);
1127+		if (nread < 0) {
1128+			if (errno == EINTR) {
1129+				if (winch_received) {
1130+					winch_received = 0;
1131+					l->cols = getColumns(l->ifd, l->ofd);
1132+					refreshLine(l);
1133+				}
1134+				continue;
1135+			}
1136+			return (errno == EAGAIN || errno == EWOULDBLOCK) ? "more" : NULL;
1137+		} else if (nread == 0) {
1138+			return NULL;
1139+		}
1140+		break;
1141+	}
1142+
1143+	if ((l->in_completion || c == 9) && completionCallback != NULL) {
1144+		int retval = completeLine(l, c);
1145+		if (retval == 0) return "more";
1146+		c = retval;
1147+	}
1148+
1149+	switch (c) {
1150+	case ENTER:
1151+		if (mlmode) redlineEditMoveEnd(l);
1152+		return strdup(l->buf);
1153+	case CTRL_C:
1154+		errno = EAGAIN;
1155+		return NULL;
1156+	case BACKSPACE:
1157+	case 8:
1158+		redlineEditBackspace(l);
1159+		break;
1160+	case CTRL_D:
1161+		if (l->len > 0) {
1162+			redlineEditDelete(l);
1163+		} else {
1164+			errno = ENOENT;
1165+			return NULL;
1166+		}
1167+		break;
1168+	case CTRL_T:
1169+		if (l->pos > 0 && l->pos < l->len) {
1170+			prevlen = utf8PrevCharLen(l->buf, l->pos);
1171+			currlen = utf8NextCharLen(l->buf, l->pos, l->len);
1172+			prevstart = l->pos - prevlen;
1173+			if (prevlen > sizeof(tmp) || currlen > sizeof(tmp)) break;
1174+			memcpy(tmp, l->buf + l->pos, currlen);
1175+			memmove(l->buf + prevstart + currlen, l->buf + prevstart, prevlen);
1176+			memcpy(l->buf + prevstart, tmp, currlen);
1177+			if (l->pos + currlen <= l->len) l->pos += currlen;
1178+			refreshLine(l);
1179+		}
1180+		break;
1181+	case CTRL_B:
1182+		redlineEditMoveLeft(l);
1183+		break;
1184+	case CTRL_F:
1185+		redlineEditMoveRight(l);
1186+		break;
1187+	case CTRL_P:
1188+		redlineEditHistoryNext(l, 1);
1189+		break;
1190+	case CTRL_N:
1191+		redlineEditHistoryNext(l, 0);
1192+		break;
1193+	case ESC:
1194+		if (read(l->ifd, seq, 1) == -1) break;
1195+		if (seq[0] == '[' || seq[0] == 'O') {
1196+			if (read(l->ifd, seq+1, 1) == -1) break;
1197+			if (seq[0] == '[') {
1198+				if (seq[1] >= '0' && seq[1] <= '9') {
1199+					plen = 1;
1200+					final = 0;
1201+					param[0] = seq[1];
1202+					while (plen < sizeof(param)) {
1203+						if (read(l->ifd, &p, 1) != 1) break;
1204+						if ((p >= '0' && p <= '9') || p == ';') {
1205+							param[plen++] = p;
1206+						} else {
1207+							final = p;
1208+							break;
1209+						}
1210+					}
1211+					if (final == '~') {
1212+						if (plen == 1 && param[0] == '3') {
1213+							redlineEditDelete(l);
1214+						}
1215+					} else if (final == 'D' || final == 'C') {
1216+						is_word_jump = 0;
1217+						if (plen == 3 && param[0] == '1' && param[1] == ';' && (param[2] == '5' || param[2] == '3')) {
1218+							is_word_jump = 1;
1219+						} else if (plen == 1 && (param[0] == '5' || param[0] == '3')) {
1220+							is_word_jump = 1;
1221+						}
1222+						if (is_word_jump) {
1223+							if (final == 'D') {
1224+								redlineEditMoveWordLeft(l);
1225+							} else {
1226+								redlineEditMoveWordRight(l);
1227+							}
1228+						}
1229+					}
1230+				} else {
1231+					switch (seq[1]) {
1232+					case 'A':
1233+						redlineEditHistoryNext(l, 1);
1234+						break;
1235+					case 'B':
1236+						redlineEditHistoryNext(l, 0);
1237+						break;
1238+					case 'C':
1239+						redlineEditMoveRight(l);
1240+						break;
1241+					case 'D':
1242+						redlineEditMoveLeft(l);
1243+						break;
1244+					case 'H':
1245+						redlineEditMoveHome(l);
1246+						break;
1247+					case 'F':
1248+						redlineEditMoveEnd(l);
1249+						break;
1250+					}
1251+				}
1252+			} else if (seq[0] == 'O') {
1253+				switch (seq[1]) {
1254+				case 'H':
1255+					redlineEditMoveHome(l);
1256+					break;
1257+				case 'F':
1258+					redlineEditMoveEnd(l);
1259+					break;
1260+				}
1261+			}
1262+		} else {
1263+			if (seq[0] == 'b' || seq[0] == 'B') {
1264+				redlineEditMoveWordLeft(l);
1265+			} else if (seq[0] == 'f' || seq[0] == 'F') {
1266+				redlineEditMoveWordRight(l);
1267+			} else if (seq[0] == 'd' || seq[0] == 'D') {
1268+				redlineEditDeleteWordRight(l);
1269+			} else if (seq[0] == 127 || seq[0] == 8) {
1270+				redlineEditDeletePrevWord(l);
1271+			}
1272+		}
1273+		break;
1274+	default:
1275+		utf8len = utf8ByteLen(c);
1276+		utf8[0] = c;
1277+		if (utf8len > 1) {
1278+			for (i = 1; i < utf8len; i++) {
1279+				if (read(l->ifd, utf8+i, 1) != 1) break;
1280+			}
1281+		}
1282+		if (redlineEditInsert(l, utf8, utf8len) == 0) return NULL;
1283+		break;
1284+	case CTRL_U:
1285+		killBufferSave(l->buf, l->pos);
1286+		memmove(l->buf, l->buf + l->pos, l->len - l->pos + 1);
1287+		l->len -= l->pos;
1288+		l->pos = 0;
1289+		refreshLine(l);
1290+		break;
1291+	case CTRL_K:
1292+		killBufferSave(l->buf + l->pos, l->len - l->pos);
1293+		l->buf[l->pos] = '\0';
1294+		l->len = l->pos;
1295+		refreshLine(l);
1296+		break;
1297+	case CTRL_A:
1298+		redlineEditMoveHome(l);
1299+		break;
1300+	case CTRL_E:
1301+		redlineEditMoveEnd(l);
1302+		break;
1303+	case CTRL_L:
1304+		redlineClearScreen();
1305+		refreshLine(l);
1306+		break;
1307+	case CTRL_W:
1308+		redlineEditDeletePrevWord(l);
1309+		break;
1310+	case CTRL_Y:
1311+		if (kill_buffer) {
1312+			redlineEditInsert(l, kill_buffer, strlen(kill_buffer));
1313+		}
1314+		break;
1315+	}
1316+	return "more";
1317+}
1318+
1319+static int
1320+redlineEditStart(struct redlineState *l, int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt)
1321+{
1322+	l->in_completion = 0;
1323+	l->ifd = stdin_fd;
1324+	l->ofd = stdout_fd;
1325+	l->buf = buf;
1326+	l->buflen = buflen;
1327+	l->prompt = prompt;
1328+	l->plen = strlen(prompt);
1329+	l->pos = 0;
1330+	l->oldpos = 0;
1331+	l->len = 0;
1332+	l->cols = getColumns(stdin_fd, stdout_fd);
1333+	l->oldrows = 0;
1334+	l->oldrpos = 0;
1335+	l->history_index = 0;
1336+	l->buf[0] = '\0';
1337+
1338+	if (enableRawMode(l->ifd) == -1) return -1;
1339+	refreshLine(l);
1340+	return 0;
1341+}
1342+
1343+static void
1344+redlineEditStop(struct redlineState *l)
1345+{
1346+	if (!isatty(l->ifd) && !getenv("REDLINE_ASSUME_TTY")) return;
1347+	disableRawMode(l->ifd);
1348+	printf("\n");
1349+}
1350+
1351+char *
1352+redline(const char *prompt)
1353+{
1354+	struct redlineState l;
1355+	char *buf;
1356+	char *res;
1357+
1358+	if (!isatty(STDIN_FILENO) || isUnsupportedTerm()) {
1359+		if (write(STDOUT_FILENO, prompt, strlen(prompt)) == -1) {}
1360+		return redlineNoTTY();
1361+	}
1362+
1363+	buf = malloc(REDLINE_INITIAL_BUFLEN);
1364+	if (buf == NULL) return NULL;
1365+	if (redlineEditStart(&l, STDIN_FILENO, STDOUT_FILENO, buf, REDLINE_INITIAL_BUFLEN, prompt) == -1) {
1366+		free(buf);
1367+		return NULL;
1368+	}
1369+	redlineHistoryAdd("");
1370+	while (1) {
1371+		res = redlineEditFeed(&l);
1372+		if (res == NULL || strcmp(res, "more") != 0) {
1373+			break;
1374+		}
1375+	}
1376+	redlineEditStop(&l);
1377+	if (history_len > 0) {
1378+		history_len--;
1379+		free(history[history_len]);
1380+	}
1381+	free(l.buf);
1382+	return res;
1383+}
1384+
1385+void
1386+redlineSetCompletionCallback(void (*cb)(const char *, struct redlineCompletions *))
1387+{
1388+	completionCallback = cb;
1389+}
1390+
1391+void
1392+redlineAddCompletion(struct redlineCompletions *lc, const char *str)
1393+{
1394+	size_t len = strlen(str);
1395+	char *copy, **cvec;
1396+
1397+	copy = malloc(len+1);
1398+	if (copy == NULL) return;
1399+	memcpy(copy,str,len+1);
1400+	cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1));
1401+	if (cvec == NULL) {
1402+		free(copy);
1403+		return;
1404+	}
1405+	lc->cvec = cvec;
1406+	lc->cvec[lc->len++] = copy;
1407+}
1408+
1409+void
1410+redlineSetMultiLine(int ml)
1411+{
1412+	mlmode = ml;
1413+}
+403, -0
  1@@ -0,0 +1,403 @@
  2+#include "../util.h"
  3+
  4+#include <arpa/inet.h>
  5+#include <ctype.h>
  6+#include <errno.h>
  7+#include <ifaddrs.h>
  8+#include <net/if.h>
  9+#include <stdio.h>
 10+#include <stdlib.h>
 11+#include <string.h>
 12+#include <sys/ioctl.h>
 13+#include <sys/socket.h>
 14+#include <unistd.h>
 15+
 16+#ifdef __linux__
 17+#include <netpacket/packet.h>
 18+#else
 19+#include <net/if_dl.h>
 20+#include <sys/sysctl.h>
 21+#endif
 22+
 23+static void
 24+get_mtu_metric(const char *name, int *mtu, int *metric)
 25+{
 26+	struct ifreq ifr;
 27+	int sock;
 28+
 29+	*mtu = 0;
 30+	*metric = 0;
 31+	sock = socket(AF_INET, SOCK_DGRAM, 0);
 32+	if (sock < 0)
 33+		return;
 34+	memset(&ifr, 0, sizeof(ifr));
 35+	strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
 36+	if (ioctl(sock, SIOCGIFMTU, &ifr) >= 0)
 37+		*mtu = ifr.ifr_mtu;
 38+	if (ioctl(sock, SIOCGIFMETRIC, &ifr) >= 0)
 39+		*metric = ifr.ifr_metric;
 40+	close(sock);
 41+}
 42+
 43+int
 44+net_get_interfaces(struct NetInterface **ifaces, int *count)
 45+{
 46+	struct ifaddrs *ifaddr, *ifa;
 47+	struct NetInterface *list = NULL;
 48+	struct NetInterface *iface;
 49+	int found_count = 0;
 50+	int i;
 51+
 52+	if (getifaddrs(&ifaddr) < 0)
 53+		return -1;
 54+
 55+	for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
 56+		iface = NULL;
 57+		for (i = 0; i < found_count; i++) {
 58+			if (strcmp(list[i].name, ifa->ifa_name) == 0) {
 59+				iface = &list[i];
 60+				break;
 61+			}
 62+		}
 63+		if (!iface) {
 64+			list = reallocarray(list, found_count + 1, sizeof(*list));
 65+			if (!list) {
 66+				freeifaddrs(ifaddr);
 67+				return -1;
 68+			}
 69+			iface = &list[found_count];
 70+			memset(iface, 0, sizeof(*iface));
 71+			strlcpy(iface->name, ifa->ifa_name, sizeof(iface->name));
 72+			iface->flags = ifa->ifa_flags;
 73+			get_mtu_metric(iface->name, &iface->mtu, &iface->metric);
 74+			found_count++;
 75+		}
 76+
 77+		if (!ifa->ifa_addr)
 78+			continue;
 79+
 80+		if (ifa->ifa_addr->sa_family == AF_INET) {
 81+			memcpy(&iface->ipv4_addr, ifa->ifa_addr, sizeof(struct sockaddr_in));
 82+			iface->has_ipv4 = 1;
 83+			if (ifa->ifa_netmask)
 84+				memcpy(&iface->ipv4_mask, ifa->ifa_netmask, sizeof(struct sockaddr_in));
 85+			if (ifa->ifa_dstaddr)
 86+				memcpy(&iface->ipv4_brd, ifa->ifa_dstaddr, sizeof(struct sockaddr_in));
 87+		} else if (ifa->ifa_addr->sa_family == AF_INET6) {
 88+			memcpy(&iface->ipv6_addr, ifa->ifa_addr, sizeof(struct sockaddr_in6));
 89+			iface->has_ipv6 = 1;
 90+			/* scope id could be set here from sin6_scope_id */
 91+		}
 92+#ifdef __linux__
 93+		else if (ifa->ifa_addr->sa_family == AF_PACKET) {
 94+			struct sockaddr_ll *sll = (struct sockaddr_ll *)ifa->ifa_addr;
 95+			if (sll->sll_halen == 6) {
 96+				memcpy(iface->mac, sll->sll_addr, 6);
 97+				iface->has_mac = 1;
 98+			}
 99+		}
100+#else
101+		else if (ifa->ifa_addr->sa_family == AF_LINK) {
102+			struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifa->ifa_addr;
103+			if (sdl->sdl_alen == 6) {
104+				memcpy(iface->mac, LLADDR(sdl), 6);
105+				iface->has_mac = 1;
106+			}
107+		}
108+#endif
109+	}
110+
111+	freeifaddrs(ifaddr);
112+	*ifaces = list;
113+	*count = found_count;
114+	return 0;
115+}
116+
117+#ifdef __linux__
118+int
119+net_get_stats(const char *ifname, struct NetStats *stats)
120+{
121+	FILE *fp;
122+	char line[256];
123+	char *p, *name;
124+	int found = 0;
125+
126+	memset(stats, 0, sizeof(*stats));
127+	fp = fopen("/proc/net/dev", "r");
128+	if (!fp)
129+		return -1;
130+
131+	while (fgets(line, sizeof(line), fp)) {
132+		p = strchr(line, ':');
133+		if (p) {
134+			*p = '\0';
135+			name = line;
136+			while (isspace(*name))
137+				name++;
138+			if (strcmp(name, ifname) == 0) {
139+				if (sscanf(p + 1, "%llu %llu %llu %llu %*u %*u %*u %*u %llu %llu %llu %llu",
140+				           &stats->rx_bytes, &stats->rx_packets, &stats->rx_errs, &stats->rx_drop,
141+				           &stats->tx_bytes, &stats->tx_packets, &stats->tx_errs, &stats->tx_drop) == 8) {
142+					found = 1;
143+				}
144+				break;
145+			}
146+		}
147+	}
148+	fclose(fp);
149+	return found ? 0 : -1;
150+}
151+#else
152+int
153+net_get_stats(const char *ifname, struct NetStats *stats)
154+{
155+	struct ifaddrs *ifaddr, *ifa;
156+	int found = 0;
157+
158+	memset(stats, 0, sizeof(*stats));
159+	if (getifaddrs(&ifaddr) < 0)
160+		return -1;
161+
162+	for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
163+		if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_LINK &&
164+		    strcmp(ifa->ifa_name, ifname) == 0) {
165+			struct if_data *ifd = (struct if_data *)ifa->ifa_data;
166+			if (ifd) {
167+				stats->rx_bytes = ifd->ifi_ibytes;
168+				stats->rx_packets = ifd->ifi_ipackets;
169+				stats->rx_errs = ifd->ifi_ierrors;
170+				stats->rx_drop = ifd->ifi_iqdrops;
171+				stats->tx_bytes = ifd->ifi_obytes;
172+				stats->tx_packets = ifd->ifi_opackets;
173+				stats->tx_errs = ifd->ifi_oerrors;
174+				stats->tx_drop = 0;
175+				found = 1;
176+				break;
177+			}
178+		}
179+	}
180+	freeifaddrs(ifaddr);
181+	return found ? 0 : -1;
182+}
183+#endif
184+
185+#ifdef __linux__
186+int
187+net_set_txqueuelen(const char *name, int qlen)
188+{
189+	struct ifreq ifr;
190+	int sock;
191+
192+	sock = socket(AF_INET, SOCK_DGRAM, 0);
193+	if (sock < 0)
194+		return -1;
195+	memset(&ifr, 0, sizeof(ifr));
196+	strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
197+	ifr.ifr_qlen = qlen;
198+	if (ioctl(sock, SIOCSIFTXQLEN, &ifr) < 0) {
199+		close(sock);
200+		return -1;
201+	}
202+	close(sock);
203+	return 0;
204+}
205+#else
206+int
207+net_set_txqueuelen(const char *name, int qlen)
208+{
209+	(void)name;
210+	(void)qlen;
211+	return -1;
212+}
213+#endif
214+
215+int
216+net_set_flags(const char *name, unsigned int flags, int set)
217+{
218+	struct ifreq ifr;
219+	int sock;
220+
221+	sock = socket(AF_INET, SOCK_DGRAM, 0);
222+	if (sock < 0)
223+		return -1;
224+	memset(&ifr, 0, sizeof(ifr));
225+	strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
226+	if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {
227+		close(sock);
228+		return -1;
229+	}
230+	if (set)
231+		ifr.ifr_flags |= flags;
232+	else
233+		ifr.ifr_flags &= ~flags;
234+	if (ioctl(sock, SIOCSIFFLAGS, &ifr) < 0) {
235+		close(sock);
236+		return -1;
237+	}
238+	close(sock);
239+	return 0;
240+}
241+
242+int
243+net_set_mtu(const char *name, int mtu)
244+{
245+	struct ifreq ifr;
246+	int sock;
247+
248+	sock = socket(AF_INET, SOCK_DGRAM, 0);
249+	if (sock < 0)
250+		return -1;
251+	memset(&ifr, 0, sizeof(ifr));
252+	strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
253+	ifr.ifr_mtu = mtu;
254+	if (ioctl(sock, SIOCSIFMTU, &ifr) < 0) {
255+		close(sock);
256+		return -1;
257+	}
258+	close(sock);
259+	return 0;
260+}
261+
262+int
263+net_set_mac(const char *name, const unsigned char mac[6])
264+{
265+#ifdef __linux__
266+	struct ifreq ifr;
267+	int sock;
268+
269+	sock = socket(AF_INET, SOCK_DGRAM, 0);
270+	if (sock < 0)
271+		return -1;
272+	memset(&ifr, 0, sizeof(ifr));
273+	strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
274+	ifr.ifr_hwaddr.sa_family = 1;
275+	memcpy(ifr.ifr_hwaddr.sa_data, mac, 6);
276+	if (ioctl(sock, SIOCSIFHWADDR, &ifr) < 0) {
277+		close(sock);
278+		return -1;
279+	}
280+	close(sock);
281+	return 0;
282+#else
283+	(void)name;
284+	(void)mac;
285+	return -1;
286+#endif
287+}
288+
289+int
290+net_add_addr(const char *name, const char *addr, int prefix)
291+{
292+	struct ifreq ifr;
293+	struct sockaddr_in *sin;
294+	int sock;
295+	unsigned int mask;
296+
297+	sock = socket(AF_INET, SOCK_DGRAM, 0);
298+	if (sock < 0)
299+		return -1;
300+
301+	memset(&ifr, 0, sizeof(ifr));
302+	strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
303+
304+	sin = (struct sockaddr_in *)&ifr.ifr_addr;
305+	sin->sin_family = AF_INET;
306+	if (inet_pton(AF_INET, addr, &sin->sin_addr) <= 0) {
307+		close(sock);
308+		return -1;
309+	}
310+
311+	if (ioctl(sock, SIOCSIFADDR, &ifr) < 0) {
312+		close(sock);
313+		return -1;
314+	}
315+
316+	if (prefix >= 0) {
317+		mask = 0;
318+		if (prefix > 0)
319+			mask = htonl(~0U << (32 - prefix));
320+		sin = (struct sockaddr_in *)&ifr.ifr_netmask;
321+		sin->sin_family = AF_INET;
322+		sin->sin_addr.s_addr = mask;
323+		if (ioctl(sock, SIOCSIFNETMASK, &ifr) < 0) {
324+			close(sock);
325+			return -1;
326+		}
327+	}
328+
329+	memset(&ifr, 0, sizeof(ifr));
330+	strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
331+	if (ioctl(sock, SIOCGIFFLAGS, &ifr) >= 0) {
332+		ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
333+		ioctl(sock, SIOCSIFFLAGS, &ifr);
334+	}
335+
336+	close(sock);
337+	return 0;
338+}
339+
340+int
341+net_del_addr(const char *name, const char *addr, int prefix)
342+{
343+	struct ifreq ifr;
344+	struct sockaddr_in *sin;
345+	int sock;
346+
347+	(void)prefix;
348+	sock = socket(AF_INET, SOCK_DGRAM, 0);
349+	if (sock < 0)
350+		return -1;
351+
352+	memset(&ifr, 0, sizeof(ifr));
353+	strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
354+
355+	sin = (struct sockaddr_in *)&ifr.ifr_addr;
356+	sin->sin_family = AF_INET;
357+	if (inet_pton(AF_INET, addr, &sin->sin_addr) <= 0) {
358+		close(sock);
359+		return -1;
360+	}
361+
362+	if (ioctl(sock, SIOCDIFADDR, &ifr) < 0) {
363+		close(sock);
364+		return -1;
365+	}
366+	close(sock);
367+	return 0;
368+}
369+
370+int
371+net_show_routes(void)
372+{
373+#ifdef __linux__
374+	FILE *fp;
375+	char line[256];
376+	char iface[16], dest[16], gateway[16], mask[16];
377+	unsigned int d, g, m, flags, metric;
378+
379+	fp = fopen("/proc/net/route", "r");
380+	if (!fp)
381+		return -1;
382+
383+	printf("%-10s %-15s %-15s %-15s %-6s %-6s\n", "Iface", "Destination", "Gateway", "Genmask", "Flags", "Metric");
384+	if (fgets(line, sizeof(line), fp)) {
385+		while (fgets(line, sizeof(line), fp)) {
386+			if (sscanf(line, "%15s %x %x %x %*u %*u %u %x", iface, &d, &g, &m, &metric, &flags) == 6) {
387+				struct in_addr dest_addr, gw_addr, mask_addr;
388+				dest_addr.s_addr = d;
389+				gw_addr.s_addr = g;
390+				mask_addr.s_addr = m;
391+				strlcpy(dest, inet_ntoa(dest_addr), sizeof(dest));
392+				strlcpy(gateway, inet_ntoa(gw_addr), sizeof(gateway));
393+				strlcpy(mask, inet_ntoa(mask_addr), sizeof(mask));
394+				printf("%-10s %-15s %-15s %-15s 0x%04X %-6u\n", iface, dest, gateway, mask, flags, metric);
395+			}
396+		}
397+	}
398+	fclose(fp);
399+	return 0;
400+#else
401+	printf("route listing not implemented on this OS\n");
402+	return 0;
403+#endif
404+}
+147, -0
  1@@ -0,0 +1,147 @@
  2+#include "../util.h"
  3+
  4+#include <stdio.h>
  5+#include <stdlib.h>
  6+#include <string.h>
  7+#include <time.h>
  8+#include <unistd.h>
  9+
 10+#ifdef __linux__
 11+#include <sys/sysinfo.h>
 12+#else
 13+#include <sys/sysctl.h>
 14+#include <sys/time.h>
 15+#endif
 16+
 17+int
 18+get_uptime(long *uptime_secs)
 19+{
 20+#ifdef __linux__
 21+	struct sysinfo info;
 22+
 23+	if (sysinfo(&info) < 0)
 24+		return -1;
 25+	*uptime_secs = info.uptime;
 26+	return 0;
 27+#else
 28+	struct timeval boottime;
 29+	size_t len = sizeof(boottime);
 30+	int mib[2] = { CTL_KERN, KERN_BOOTTIME };
 31+	time_t now;
 32+
 33+	if (sysctl(mib, 2, &boottime, &len, NULL, 0) < 0)
 34+		return -1;
 35+	time(&now);
 36+	*uptime_secs = (long)(now - boottime.tv_sec);
 37+	return 0;
 38+#endif
 39+}
 40+
 41+int
 42+get_loads(double loads[3])
 43+{
 44+	if (getloadavg(loads, 3) != 3)
 45+		return -1;
 46+	return 0;
 47+}
 48+
 49+int
 50+get_meminfo(struct MemInfo *mi)
 51+{
 52+	memset(mi, 0, sizeof(*mi));
 53+#ifdef __linux__
 54+	struct sysinfo info;
 55+	unsigned long long unit;
 56+
 57+	if (sysinfo(&info) < 0)
 58+		return -1;
 59+	unit = info.mem_unit ? info.mem_unit : 1;
 60+	mi->total = info.totalram * unit;
 61+	mi->free = info.freeram * unit;
 62+	mi->shared = info.sharedram * unit;
 63+	mi->buffers = info.bufferram * unit;
 64+	mi->cached = 0;
 65+	mi->totalswap = info.totalswap * unit;
 66+	mi->freeswap = info.freeswap * unit;
 67+	return 0;
 68+#else
 69+	int mib[2];
 70+	size_t len;
 71+	unsigned long physmem = 0;
 72+	unsigned int page_size = 4096;
 73+	unsigned int free_pages = 0;
 74+
 75+	mib[0] = CTL_HW;
 76+	mib[1] = HW_PHYSMEM;
 77+	len = sizeof(physmem);
 78+	if (sysctl(mib, 2, &physmem, &len, NULL, 0) < 0)
 79+		return -1;
 80+	mi->total = physmem;
 81+
 82+	mib[0] = CTL_HW;
 83+	mib[1] = HW_PAGESIZE;
 84+	len = sizeof(page_size);
 85+	sysctl(mib, 2, &page_size, &len, NULL, 0);
 86+
 87+	len = sizeof(free_pages);
 88+	if (sysctlbyname("vm.stats.vm.v_free_count", &free_pages, &len, NULL, 0) >= 0) {
 89+		mi->free = (unsigned long long)free_pages * page_size;
 90+	} else {
 91+		mi->free = mi->total / 4;
 92+	}
 93+	return 0;
 94+#endif
 95+}
 96+
 97+#ifdef __linux__
 98+#include <sys/klog.h>
 99+ssize_t
100+get_dmesg(char *buf, size_t len)
101+{
102+	if (!buf && len == 0)
103+		return klogctl(10, NULL, 0);
104+	return klogctl(3, buf, len);
105+}
106+
107+int
108+clear_dmesg(void)
109+{
110+	if (klogctl(5, NULL, 0) < 0)
111+		return -1;
112+	return 0;
113+}
114+
115+int
116+set_console_level(int level)
117+{
118+	if (klogctl(8, NULL, level) < 0)
119+		return -1;
120+	return 0;
121+}
122+#else
123+ssize_t
124+get_dmesg(char *buf, size_t len)
125+{
126+	int mib[2];
127+	size_t needed = len;
128+
129+	mib[0] = CTL_KERN;
130+	mib[1] = KERN_MSGBUF;
131+	if (sysctl(mib, 2, buf, &needed, NULL, 0) < 0)
132+		return -1;
133+	return (ssize_t)needed;
134+}
135+
136+int
137+clear_dmesg(void)
138+{
139+	return -1;
140+}
141+
142+int
143+set_console_level(int level)
144+{
145+	(void)level;
146+	return -1;
147+}
148+#endif
+437, -0
  1@@ -0,0 +1,437 @@
  2+/* see license file for copyright and license details */
  3+#include "../tls.h"
  4+#include "../util.h"
  5+
  6+#include <errno.h>
  7+#include <signal.h>
  8+#include <stdio.h>
  9+#include <stdlib.h>
 10+#include <string.h>
 11+#include <unistd.h>
 12+
 13+#if FEATURE_USE_LIBRESSL
 14+#include <tls.h>
 15+
 16+struct TlsSocket {
 17+	int fd;
 18+	int is_tls;
 19+	struct tls *ctx;
 20+};
 21+
 22+#elif FEATURE_USE_BEARSSL
 23+#include <bearssl.h>
 24+
 25+struct my_x509_context {
 26+	const br_x509_class *vtable;
 27+	br_x509_minimal_context minimal;
 28+};
 29+
 30+struct TlsSocket {
 31+	int fd;
 32+	int is_tls;
 33+	br_ssl_client_context sc;
 34+	struct my_x509_context my_x509;
 35+	unsigned char iobuf[BR_SSL_BUFSIZE_BIDI];
 36+	br_sslio_context ioc;
 37+};
 38+
 39+struct dn_accum {
 40+	unsigned char *data;
 41+	size_t len;
 42+	size_t cap;
 43+};
 44+
 45+static void
 46+append_dn_callback(void *dn_ctx, const void *src, size_t len)
 47+{
 48+	struct dn_accum *accum = dn_ctx;
 49+	if (accum->len + len > accum->cap) {
 50+		accum->cap = accum->len + len + 256;
 51+		accum->data = erealloc(accum->data, accum->cap);
 52+	}
 53+	memcpy(accum->data + accum->len, src, len);
 54+	accum->len += len;
 55+}
 56+
 57+static void
 58+my_start_chain(const br_x509_class **ctx, const char *server_name)
 59+{
 60+	struct my_x509_context *t = (struct my_x509_context *)ctx;
 61+	br_x509_minimal_vtable.start_chain((const br_x509_class **)&t->minimal, server_name);
 62+}
 63+
 64+static void
 65+my_start_cert(const br_x509_class **ctx, uint32_t length)
 66+{
 67+	struct my_x509_context *t = (struct my_x509_context *)ctx;
 68+	br_x509_minimal_vtable.start_cert((const br_x509_class **)&t->minimal, length);
 69+}
 70+
 71+static void
 72+my_append(const br_x509_class **ctx, const unsigned char *buf, size_t len)
 73+{
 74+	struct my_x509_context *t = (struct my_x509_context *)ctx;
 75+	br_x509_minimal_vtable.append((const br_x509_class **)&t->minimal, buf, len);
 76+}
 77+
 78+static void
 79+my_end_cert(const br_x509_class **ctx)
 80+{
 81+	struct my_x509_context *t = (struct my_x509_context *)ctx;
 82+	br_x509_minimal_vtable.end_cert((const br_x509_class **)&t->minimal);
 83+}
 84+
 85+static unsigned
 86+my_end_chain(const br_x509_class **ctx)
 87+{
 88+	struct my_x509_context *t = (struct my_x509_context *)ctx;
 89+	(void)br_x509_minimal_vtable.end_chain((const br_x509_class **)&t->minimal);
 90+	/* always succeed and accept certificate when check_cert is 0 */
 91+	return 0;
 92+}
 93+
 94+static const br_x509_pkey *
 95+my_get_pkey(const br_x509_class *const *ctx, unsigned *usages)
 96+{
 97+	const struct my_x509_context *t = (const struct my_x509_context *)ctx;
 98+	return br_x509_minimal_vtable.get_pkey((const br_x509_class *const *)&t->minimal, usages);
 99+}
100+
101+static const br_x509_class my_x509_vtable = {
102+	sizeof(struct my_x509_context),
103+	my_start_chain,
104+	my_start_cert,
105+	my_append,
106+	my_end_cert,
107+	my_end_chain,
108+	my_get_pkey
109+};
110+
111+static int
112+sock_read(void *ctx, unsigned char *buf, size_t len)
113+{
114+	int fd = *(int *)ctx;
115+	for (;;) {
116+		ssize_t rlen = read(fd, buf, len);
117+		if (rlen <= 0) {
118+			if (rlen < 0 && errno == EINTR)
119+				continue;
120+			return -1;
121+		}
122+		return (int)rlen;
123+	}
124+}
125+
126+static int
127+sock_write(void *ctx, const unsigned char *buf, size_t len)
128+{
129+	int fd = *(int *)ctx;
130+	for (;;) {
131+		ssize_t wlen = write(fd, buf, len);
132+		if (wlen <= 0) {
133+			if (wlen < 0 && errno == EINTR)
134+				continue;
135+			return -1;
136+		}
137+		return (int)wlen;
138+	}
139+}
140+
141+static int
142+b64_decode_char(char c)
143+{
144+	if (c >= 'A' && c <= 'Z') return c - 'A';
145+	if (c >= 'a' && c <= 'z') return c - 'a' + 26;
146+	if (c >= '0' && c <= '9') return c - '0' + 52;
147+	if (c == '+') return 62;
148+	if (c == '/') return 63;
149+	return -1;
150+}
151+
152+static size_t
153+b64_decode(const char *src, size_t src_len, unsigned char *dst)
154+{
155+	size_t i, j = 0;
156+	int val = 0, valb = -8;
157+	for (i = 0; i < src_len; i++) {
158+		int c = b64_decode_char(src[i]);
159+		if (c >= 0) {
160+			val = (val << 6) | c;
161+			valb += 6;
162+			if (valb >= 0) {
163+				dst[j++] = (val >> valb) & 0xFF;
164+				valb -= 8;
165+			}
166+		}
167+	}
168+	return j;
169+}
170+
171+static int
172+decode_cert_der(const unsigned char *der, size_t der_len, br_x509_trust_anchor *ta)
173+{
174+	br_x509_decoder_context dc;
175+	struct dn_accum accum = { NULL, 0, 0 };
176+	const br_x509_pkey *pk;
177+
178+	br_x509_decoder_init(&dc, append_dn_callback, &accum);
179+	br_x509_decoder_push(&dc, der, der_len);
180+	if (br_x509_decoder_last_error(&dc) != 0) {
181+		free(accum.data);
182+		return 0;
183+	}
184+	pk = br_x509_decoder_get_pkey(&dc);
185+	if (!pk) {
186+		free(accum.data);
187+		return 0;
188+	}
189+
190+	ta->dn.data = accum.data;
191+	ta->dn.len = accum.len;
192+	ta->flags = br_x509_decoder_isCA(&dc) ? BR_X509_TA_CA : 0;
193+	ta->pkey.key_type = pk->key_type;
194+
195+	if (pk->key_type == BR_KEYTYPE_RSA) {
196+		ta->pkey.key.rsa.nlen = pk->key.rsa.nlen;
197+		ta->pkey.key.rsa.n = emalloc(pk->key.rsa.nlen);
198+		memcpy(ta->pkey.key.rsa.n, pk->key.rsa.n, pk->key.rsa.nlen);
199+		ta->pkey.key.rsa.elen = pk->key.rsa.elen;
200+		ta->pkey.key.rsa.e = emalloc(pk->key.rsa.elen);
201+		memcpy(ta->pkey.key.rsa.e, pk->key.rsa.e, pk->key.rsa.elen);
202+	} else if (pk->key_type == BR_KEYTYPE_EC) {
203+		ta->pkey.key.ec.curve = pk->key.ec.curve;
204+		ta->pkey.key.ec.qlen = pk->key.ec.qlen;
205+		ta->pkey.key.ec.q = emalloc(pk->key.ec.qlen);
206+		memcpy(ta->pkey.key.ec.q, pk->key.ec.q, pk->key.ec.qlen);
207+	} else {
208+		free(accum.data);
209+		return 0;
210+	}
211+	return 1;
212+}
213+
214+static br_x509_trust_anchor *tas = NULL;
215+static size_t tas_num = 0;
216+
217+static void
218+load_ca_certs(void)
219+{
220+	static const char *ca_paths[] = {
221+		"/etc/ssl/certs/ca-certificates.crt",
222+		"/etc/ssl/cert.pem",
223+		"/etc/pki/tls/certs/ca-bundle.crt",
224+	};
225+	FILE *fp = NULL;
226+	size_t i;
227+	char line[512];
228+	struct {
229+		char *data;
230+		size_t len;
231+		size_t cap;
232+	} pem = { NULL, 0, 0 };
233+	int in_cert = 0;
234+
235+	for (i = 0; i < LEN(ca_paths); i++) {
236+		if ((fp = fopen(ca_paths[i], "r")))
237+			break;
238+	}
239+	if (!fp) {
240+		weprintf("no CA certificates found, TLS verification will fail\n");
241+		return;
242+	}
243+
244+	while (fgets(line, sizeof(line), fp)) {
245+		if (strncmp(line, "-----BEGIN CERTIFICATE-----", 27) == 0) {
246+			in_cert = 1;
247+			pem.len = 0;
248+		} else if (strncmp(line, "-----END CERTIFICATE-----", 25) == 0) {
249+			if (in_cert) {
250+				unsigned char *der = emalloc(pem.len);
251+				size_t der_len = b64_decode(pem.data, pem.len, der);
252+				br_x509_trust_anchor ta;
253+				if (decode_cert_der(der, der_len, &ta)) {
254+					tas = ereallocarray(tas, tas_num + 1, sizeof(*tas));
255+					tas[tas_num++] = ta;
256+				}
257+				free(der);
258+				in_cert = 0;
259+			}
260+		} else if (in_cert) {
261+			size_t llen = strlen(line);
262+			while (llen > 0 && (line[llen-1] == '\r' || line[llen-1] == '\n'))
263+				llen--;
264+			if (pem.len + llen > pem.cap) {
265+				pem.cap = pem.len + llen + 1024;
266+				pem.data = erealloc(pem.data, pem.cap);
267+			}
268+			memcpy(pem.data + pem.len, line, llen);
269+			pem.len += llen;
270+		}
271+	}
272+	free(pem.data);
273+	fclose(fp);
274+}
275+#else
276+struct TlsSocket {
277+	int fd;
278+	int is_tls;
279+};
280+#endif
281+
282+struct TlsSocket *
283+tls_connect(int fd, const char *host, int check_cert, int is_tls)
284+{
285+	struct TlsSocket *s;
286+
287+	s = emalloc(sizeof(*s));
288+	s->fd = fd;
289+	s->is_tls = is_tls;
290+
291+	if (!is_tls)
292+		return s;
293+
294+#if FEATURE_USE_LIBRESSL
295+	{
296+		struct tls_config *cfg;
297+
298+		s->ctx = tls_client();
299+		if (!s->ctx) {
300+			weprintf("tls_client failed\n");
301+			free(s);
302+			return NULL;
303+		}
304+		cfg = tls_config_new();
305+		if (!cfg) {
306+			weprintf("tls_config_new failed\n");
307+			tls_free(s->ctx);
308+			free(s);
309+			return NULL;
310+		}
311+		if (!check_cert) {
312+			tls_config_insecure_noverifycert(cfg);
313+			tls_config_insecure_noverifyname(cfg);
314+		}
315+		if (tls_configure(s->ctx, cfg) < 0) {
316+			weprintf("tls_configure: %s\n", tls_error(s->ctx));
317+			tls_config_free(cfg);
318+			tls_free(s->ctx);
319+			free(s);
320+			return NULL;
321+		}
322+		tls_config_free(cfg);
323+
324+		if (tls_connect_socket(s->ctx, fd, host) < 0) {
325+			weprintf("tls_connect_socket: %s\n", tls_error(s->ctx));
326+			tls_free(s->ctx);
327+			free(s);
328+			return NULL;
329+		}
330+	}
331+#elif FEATURE_USE_BEARSSL
332+	{
333+		signal(SIGPIPE, SIG_IGN);
334+
335+		if (check_cert) {
336+			static int ca_loaded = 0;
337+			if (!ca_loaded) {
338+				load_ca_certs();
339+				ca_loaded = 1;
340+			}
341+			br_ssl_client_init_full(&s->sc, &s->my_x509.minimal, tas, tas_num);
342+		} else {
343+			br_ssl_client_init_full(&s->sc, &s->my_x509.minimal, NULL, 0);
344+			s->my_x509.vtable = &my_x509_vtable;
345+			br_ssl_engine_set_x509(&s->sc.eng, &s->my_x509.vtable);
346+		}
347+
348+		br_ssl_engine_set_buffer(&s->sc.eng, s->iobuf, sizeof(s->iobuf), 1);
349+		br_ssl_client_reset(&s->sc, host, 0);
350+		br_sslio_init(&s->ioc, &s->sc.eng, sock_read, &s->fd, sock_write, &s->fd);
351+
352+		if (br_sslio_flush(&s->ioc) < 0) {
353+			weprintf("TLS handshake failed with %s\n", host);
354+			free(s);
355+			return NULL;
356+		}
357+	}
358+#else
359+	weprintf("TLS not supported, compile with FEATURE_USE_LIBRESSL or FEATURE_USE_BEARSSL\n");
360+	free(s);
361+	return NULL;
362+#endif
363+
364+	return s;
365+}
366+
367+ssize_t
368+tls_read(struct TlsSocket *s, void *buf, size_t len)
369+{
370+	if (!s->is_tls) {
371+		for (;;) {
372+			ssize_t r = read(s->fd, buf, len);
373+			if (r < 0 && errno == EINTR)
374+				continue;
375+			return r;
376+		}
377+	}
378+#if FEATURE_USE_LIBRESSL
379+	for (;;) {
380+		ssize_t r = tls_read(s->ctx, buf, len);
381+		if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT)
382+			continue;
383+		return r;
384+	}
385+#elif FEATURE_USE_BEARSSL
386+	return br_sslio_read(&s->ioc, buf, len);
387+#else
388+	return -1;
389+#endif
390+}
391+
392+ssize_t
393+tls_write(struct TlsSocket *s, const void *buf, size_t len)
394+{
395+	if (!s->is_tls) {
396+		for (;;) {
397+			ssize_t r = write(s->fd, buf, len);
398+			if (r < 0 && errno == EINTR)
399+				continue;
400+			return r;
401+		}
402+	}
403+#if FEATURE_USE_LIBRESSL
404+	for (;;) {
405+		ssize_t r = tls_write(s->ctx, buf, len);
406+		if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT)
407+			continue;
408+		return r;
409+	}
410+#elif FEATURE_USE_BEARSSL
411+	{
412+		int r = br_sslio_write_all(&s->ioc, buf, len);
413+		if (r < 0)
414+			return -1;
415+		if (br_sslio_flush(&s->ioc) < 0)
416+			return -1;
417+		return len;
418+	}
419+#else
420+	return -1;
421+#endif
422+}
423+
424+void
425+tls_close(struct TlsSocket *s, int close_fd)
426+{
427+	if (s->is_tls) {
428+#if FEATURE_USE_LIBRESSL
429+		tls_close(s->ctx);
430+		tls_free(s->ctx);
431+#elif FEATURE_USE_BEARSSL
432+		br_sslio_close(&s->ioc);
433+#endif
434+	}
435+	if (close_fd)
436+		close(s->fd);
437+	free(s);
438+}
+24, -0
 1@@ -0,0 +1,24 @@
 2+/* see license file for copyright and license details */
 3+#ifndef REDLINE_H_
 4+#define REDLINE_H_
 5+
 6+#include <stddef.h>
 7+
 8+struct redlineCompletions {
 9+	size_t len;
10+	char **cvec;
11+};
12+
13+char *redline(const char *prompt);
14+void redlineSetCompletionCallback(void (*cb)(const char *, struct redlineCompletions *));
15+void redlineAddCompletion(struct redlineCompletions *lc, const char *str);
16+void redlineHistoryAdd(const char *line);
17+void redlineHistorySetMaxLen(int len);
18+int redlineHistorySave(const char *filename);
19+int redlineHistoryLoad(const char *filename);
20+char *redlineHistoryGet(int idx);
21+int redlineHistoryLen(void);
22+void redlineSetMultiLine(int ml);
23+void redlineClearScreen(void);
24+
25+#endif /* REDLINE_H_ */
+21, -0
 1@@ -0,0 +1,21 @@
 2+/* see license file for copyright and license details */
 3+#ifndef TLS_H_
 4+#define TLS_H_
 5+
 6+#include <sys/types.h>
 7+
 8+struct TlsSocket;
 9+
10+/* tls_connect takes an already connected socket fd and upgrades it to tls
11+   if check_cert is 0 certificate validation is bypassed
12+   returns a new tls socket or null on error */
13+struct TlsSocket *tls_connect(int fd, const char *host, int check_cert, int is_tls);
14+
15+/* standard read and write wrapping either libressl bearssl or plain fallback */
16+ssize_t tls_read(struct TlsSocket *s, void *buf, size_t len);
17+ssize_t tls_write(struct TlsSocket *s, const void *buf, size_t len);
18+
19+/* closes the tls connection and optionally closes the socket fd */
20+void tls_close(struct TlsSocket *s, int close_fd);
21+
22+#endif /* tls_h_ */
+49, -0
 1@@ -106,3 +106,52 @@ void explicit_bzero(void *, size_t);
 2 void recurse_dir(const char *, void (*)(const char *));
 3 void devtotty(int, int *, int *);
 4 int ttytostr(int, int, char *, size_t);
 5+
 6+#include <sys/socket.h>
 7+#include <netinet/in.h>
 8+
 9+#ifndef IFNAMSIZ
10+#define IFNAMSIZ 16
11+#endif
12+
13+struct NetStats {
14+	unsigned long long rx_bytes;
15+	unsigned long long rx_packets;
16+	unsigned long long rx_errs;
17+	unsigned long long rx_drop;
18+	unsigned long long tx_bytes;
19+	unsigned long long tx_packets;
20+	unsigned long long tx_errs;
21+	unsigned long long tx_drop;
22+};
23+
24+struct NetInterface {
25+	char name[IFNAMSIZ];
26+	unsigned int flags;
27+	int mtu;
28+	int metric;
29+	unsigned char mac[6];
30+	int has_mac;
31+
32+	/* ipv4 addresses */
33+	struct sockaddr_in ipv4_addr;
34+	int has_ipv4;
35+	struct sockaddr_in ipv4_mask;
36+	struct sockaddr_in ipv4_brd;
37+
38+	/* ipv6 addresses */
39+	struct sockaddr_in6 ipv6_addr;
40+	int has_ipv6;
41+	unsigned int ipv6_scope;
42+	unsigned int ipv6_prefix;
43+};
44+
45+struct MemInfo {
46+	unsigned long long total;
47+	unsigned long long free;
48+	unsigned long long shared;
49+	unsigned long long buffers;
50+	unsigned long long cached;
51+	unsigned long long totalswap;
52+	unsigned long long freeswap;
53+};