commit ea91416
Merge pull request #2 from shrub900/master use mdoc for manpages and add some manpages
117 files changed,
+2030,
-5190
M
Makefile
M
Makefile
+1,
-2
1@@ -685,7 +685,7 @@ box: $(LIB)
2
3 .PHONY: man clean
4
5-scripts/mkman/mkman: scripts/mkman/main.go scripts/mkman/troff.go
6+scripts/mkman/mkman: scripts/mkman/main.go scripts/mkman/page.go scripts/mkman/parse.go scripts/mkman/mdoc.go
7 cd scripts/mkman && go build -o mkman .
8
9 man: scripts/mkman/mkman
10@@ -902,4 +902,3 @@ cmd/dev/cc/cpp: cmd/dev/cc/cpp.o $(CPP_OBJ) $(LIB)
11
12
13
14-
+8,
-5
1@@ -15,20 +15,23 @@ usage(void)
2 eprintf("usage: %s -h | -s\n", argv0);
3 }
4
5-// ?man ctrlaltdel: set ctrl-alt-del function
6-// ?man arguments: -h | -s
7-// ?man set the behavior of the ctrl-alt-del key combination
8+// ?man ctrlaltdel: toggle Ctrl-Alt-Del behaviour
9+// ?man synopsis: -h
10+// ?man synopsis: -s
11+// ?man ctrlaltdel toggles the function of Ctrl-Alt-Del based on the two choices given in linux/kernel/sys.c.
12+// ?man Hard reset reboots the computer immediately without calling sync.
13+// ?man Soft reset sends SIGINT to init.
14 int
15 main(int argc, char *argv[])
16 {
17 int hflag = 0, sflag = 0, cmd;
18
19 ARGBEGIN {
20- // ?man -h: suppress headers or print help
21+ // ?man -h: Set to hard reset.
22 case 'h':
23 hflag = 1;
24 break;
25- // ?man -s: silent mode or print summary
26+ // ?man -s: Set to soft reset.
27 case 's':
28 sflag = 1;
29 break;
+2,
-4
1@@ -263,8 +263,6 @@ usage(void)
2 // ?man depmod: generate modules.dep and map files
3 // ?man arguments: version
4 // ?man depmod generates modules.dep containing dependency information for modprobe
5-// ?man // ?man -n: dry run print results to stdout instead of writing files
6-// ?man // ?man -b basedir: use basedir as prefix for module directories
7 int
8 main(int argc, char *argv[])
9 {
10@@ -281,11 +279,11 @@ main(int argc, char *argv[])
11 FILE *f_dep, *f_alias, *f_sym;
12
13 ARGBEGIN {
14- // ?man -n: specify n option
15+ // ?man -n: Dry run; print results to stdout instead of writing files.
16 case 'n':
17 nflag = 1;
18 break;
19- // ?man -b:dir: specify b option
20+ // ?man -b:dir: Use dir as the prefix for module directories.
21 case 'b':
22 basedir = EARGF(usage());
23 break;
+4,
-3
1@@ -48,14 +48,15 @@ usage(void)
2 eprintf("usage: %s [-t] [device ...]\n", argv0);
3 }
4
5-// ?man eject: eject removable media
6+// ?man eject: control device trays
7 // ?man arguments: device ...
8-// ?man eject optical discs or other removable storage media
9+// ?man eject opens the tray of each device.
10+// ?man If no device is given eject opens the tray of /dev/sr0.
11 int
12 main(int argc, char *argv[])
13 {
14 ARGBEGIN {
15- // ?man -t: sort or specify timestamp
16+ // ?man -t: Close instead of open the tray.
17 case 't':
18 tflag = 1;
19 break;
+7,
-4
1@@ -22,8 +22,11 @@ usage(void)
2 }
3
4 // ?man fsfreeze: suspend access to a filesystem
5-// ?man arguments: (-f | -u) mountpoint
6-// ?man freeze or unfreeze a filesystem to allow safe backups
7+// ?man arguments: mountpoint
8+// ?man fsfreeze suspends and resumes access to a filesystem.
9+// ?man fsfreeze is intended to be used with hardware RAID devices that support the creation of snapshots.
10+// ?man The mountpoint argument is the pathname of the directory where the filesystem is mounted.
11+// ?man The filesystem must be mounted to be frozen.
12 int
13 main(int argc, char *argv[])
14 {
15@@ -33,11 +36,11 @@ main(int argc, char *argv[])
16 int fd;
17
18 ARGBEGIN {
19- // ?man -f: force the operation
20+ // ?man -f: Freeze the filesystem mounted at mountpoint.
21 case 'f':
22 fflag = 1;
23 break;
24- // ?man -u: unbuffered output
25+ // ?man -u: Unfreeze the filesystem mounted at mountpoint.
26 case 'u':
27 uflag = 1;
28 break;
+10,
-6
1@@ -116,7 +116,11 @@ usage(void)
2
3 // ?man hwclock: query or set the hardware clock
4 // ?man arguments: dev
5-// ?man view or adjust the hardware real time clock
6+// ?man hwclock is a tool for accessing the hardware clock.
7+// ?man You can display the current time, set the hardware clock from the System Time, or set the System Time from the hardware clock.
8+// ?man It currently only works with UTC.
9+// ?man You can use dev to specify the RTC device node absolute path.
10+// ?man By default it will use /dev/rtc.
11 int
12 main(int argc, char *argv[])
13 {
14@@ -126,19 +130,19 @@ main(int argc, char *argv[])
15 int wflag = 0;
16
17 ARGBEGIN {
18- // ?man -r: operate recursively
19+ // ?man -r: Read the hardware clock and print the time on stdout.
20 case 'r':
21 rflag = 1;
22 break;
23- // ?man -s: silent mode or print summary
24+ // ?man -s: Set the system time from the hardware clock.
25 case 's':
26 sflag = 1;
27 break;
28- // ?man -w: wait for completion
29+ // ?man -w: Set the hardware clock to the system time.
30 case 'w':
31 wflag = 1;
32 break;
33- // ?man -u: unbuffered output
34+ // ?man -u: Use UTC. This is the default option.
35 case 'u':
36 break;
37 default:
38@@ -165,4 +169,4 @@ main(int argc, char *argv[])
39 systohc(dev);
40
41 return 0;
42-}
43+}
+2,
-2
1@@ -17,8 +17,8 @@ usage(void)
2 }
3
4 // ?man insmod: insert a module into the Linux kernel
5-// ?man arguments: filename [args...
6-// ?man insmod inserts a kernel module from filename into the running kernel
7+// ?man arguments: filename [args ...]
8+// ?man insmod inserts the module specified by filename into the kernel. It does not handle module dependencies.
9 int
10 main(int argc, char *argv[])
11 {
+10,
-19
1@@ -535,7 +535,7 @@ usage(void)
2 }
3
4 // ?man modprobe: add or remove modules from the Linux kernel
5-// ?man arguments: module [symbol=value ...
6+// ?man arguments: module [symbol=value ...]
7 // ?man modprobe loads or removes kernel modules from the running system.
8 // ?man It reads modules.dep, modules.alias, and modules.symbols from the appropriate
9 // ?man /lib/modules/release directory to resolve module names and dependencies,
10@@ -551,48 +551,39 @@ main(int argc, char *argv[])
11 int i, ret = 0;
12
13 ARGBEGIN {
14- // ?man -a: specify a option
15+ // ?man -a: Load all modules named on the command line rather than stopping after the first.
16 case 'a':
17- // ?man -a: Load all modules named on the command line (rather than stopping after the first).
18 aflag = 1; break;
19- // ?man -r: specify r option
20+ // ?man -r: Remove the named modules from the kernel. Dependencies are not automatically removed.
21 case 'r':
22- // ?man -r: Remove the named modules from the kernel. Dependencies are not automatically removed.
23 rflag = 1; break;
24- // ?man -q: specify q option
25+ // ?man -q: Quiet mode. Suppress error messages.
26 case 'q':
27- // ?man -q: Quiet mode. Suppress error messages.
28 qflag = 1; break;
29- // ?man -v: specify v option
30+ // ?man -v: Verbose mode. Print each action taken.
31 case 'v':
32- // ?man -v: Verbose mode. Print each action taken.
33 vflag = 1; break;
34- // ?man -l: specify l option
35+ // ?man -l: List available modules matching the optional pattern, a fnmatch(3) glob.
36 case 'l':
37- // ?man -l: List available modules matching the optional _pattern_ (a fnmatch(3) glob).
38 lflag = 1; break;
39 #if FEATURE_MODPROBE_SHOW_DEPENDS
40- // ?man -D: specify D option
41+ // ?man -D: Print the sequence of insmod commands that would be used to load the module, without actually loading anything.
42 case 'D':
43- // ?man -D: Print the sequence of insmod commands that would be used to load the module, without actually loading anything.
44 Dflag = 1; break;
45 #endif
46 #if FEATURE_MODPROBE_BLACKLIST
47- // ?man -b: specify b option
48+ // ?man -b: Skip modules listed as blacklist in /etc/modprobe.d/.
49 case 'b':
50- // ?man -b: Skip modules listed as blacklist in /etc/modprobe.d/.
51 bflag = 1; break;
52 #endif
53 #if FEATURE_MODPROBE_SYSLOG
54- // ?man -s: specify s option
55+ // ?man -s: Log messages to syslog(3) facility LOG_DAEMON instead of standard error.
56 case 's':
57- // ?man -s: Log messages to syslog(3) (facility LOG_DAEMON) instead of standard error.
58 sflag = 1; break;
59 #endif
60 #if FEATURE_MODPROBE_DIR_OVERRIDE
61- // ?man -d:file: specify d option
62+ // ?man -d:dir: Use dir as the base directory for module files instead of /lib/modules/release.
63 case 'd':
64- // ?man -d dir: Use dir as the base directory for module files instead of /lib/modules/release.
65 strlist_append(&moddirs, EARGF(usage())); break;
66 #endif
67 default:
+12,
-9
1@@ -191,8 +191,8 @@ usage(void)
2 }
3
4 // ?man mount: mount a filesystem
5-// ?man arguments: source] [target
6-// ?man mount a filesystem to the directory tree
7+// ?man arguments: [source] [target]
8+// ?man mount attaches the filesystem specified to the filesystem hierarchy. The umount command will detach it again.
9 int
10 main(int argc, char *argv[])
11 {
12@@ -205,32 +205,35 @@ main(int argc, char *argv[])
13 FILE *fp;
14
15 ARGBEGIN {
16- // ?man -B: specify option flag
17+ // ?man -B: Remount a subtree somewhere else (so that its contents are visible in both places).
18 case 'B':
19 argflags |= MS_BIND;
20 break;
21- // ?man -M: specify option flag
22+ // ?man -M: Move a subtree to some other place.
23 case 'M':
24 argflags |= MS_MOVE;
25 break;
26- // ?man -R: operate recursively on directories
27+ // ?man -R: Remount a subtree and all possible submounts somewhere else (so that its contents are available in both places).
28 case 'R':
29 argflags |= MS_REC;
30 break;
31- // ?man -a: print or show all entries
32+ // ?man -a: Mount all filesystems mentioned in /etc/fstab.
33 case 'a':
34 aflag = 1;
35 break;
36- // ?man -o:str: specify output file
37+ // ?man -o:options: Specify a comma separated string of filesystem specific options.
38 case 'o':
39 estrlcat(fsopts, EARGF(usage()), sizeof(fsopts));
40 parseopts(fsopts, &flags, data, sizeof(data));
41 break;
42- // ?man -t:str: sort or specify timestamp
43+ // ?man -t:fstype: Set the filesystem type. More than one type may be specified in a comma separated list.
44+ // ?man The list of file system types can be prefixed with "no" to specify the file system types for which action should not be taken.
45+ // ?man For example, mount -a -t nonfs,ext4 mounts all file systems except those of type NFS and EXT4.
46+ // ?man mount will attempt to execute a program in your PATH mount.XXX where XXX is replaced by the type name. For example, NFS file systems are mounted by the program mount.nfs.
47 case 't':
48 types = EARGF(usage());
49 break;
50- // ?man -n: print line numbers or counts
51+ // ?man -n: Mount without writing in /etc/mtab. This is the default action.
52 case 'n':
53 break;
54 default:
+4,
-4
1@@ -21,7 +21,7 @@ usage(void)
2
3 // ?man mountpoint: check if a directory is a mountpoint
4 // ?man arguments: target
5-// ?man determine if a directory is a mountpoint
6+// ?man mountpoint checks if the directory is mentioned in the /proc/mounts file.
7 int
8 main(int argc, char *argv[])
9 {
10@@ -32,15 +32,15 @@ main(int argc, char *argv[])
11 struct stat st1, st2;
12
13 ARGBEGIN {
14- // ?man -d: specify directory
15+ // ?man -d: Print the major/minor device number of the filesystem on stdout.
16 case 'd':
17 dflag = 1;
18 break;
19- // ?man -q: quiet mode; suppress output
20+ // ?man -q: Be quiet, don't print anything.
21 case 'q':
22 qflag = 1;
23 break;
24- // ?man -x: hex format or match whole lines
25+ // ?man -x: Print the major/minor device number of the device on stdout.
26 case 'x':
27 xflag = 1;
28 break;
+3,
-5
1@@ -18,9 +18,7 @@ usage(void)
2
3 // ?man rmmod: remove a module from the Linux kernel
4 // ?man arguments: module...
5-// ?man rmmod removes a kernel module from the running kernel
6-// ?man // ?man -f: force removal of a module even if it is busy or in use
7-// ?man // ?man -w: wait for the module to become unused before removing
8+// ?man rmmod removes one or more modules from the kernel.
9 int
10 main(int argc, char *argv[])
11 {
12@@ -29,11 +27,11 @@ main(int argc, char *argv[])
13 int flags = O_NONBLOCK;
14
15 ARGBEGIN {
16- // ?man -f: specify f option
17+ // ?man -f: This option can be extremely dangerous: it has no effect unless CONFIG_MODULE_FORCE_UNLOAD was set when the kernel was compiled. With this option, you can remove modules which are being used, or which are not designed to be removed, or have been marked as unsafe.
18 case 'f':
19 flags |= O_TRUNC;
20 break;
21- // ?man -w: specify w option
22+ // ?man -w: Normally, rmmod will refuse to unload modules which are in use. With this option, rmmod will isolate the module, and wait until the module is no longer used. Noone new will be able to use the module, but it's up to you to make sure the current users eventually finish with it.
23 case 'w':
24 flags &= ~O_NONBLOCK;
25 break;
+3,
-3
1@@ -24,9 +24,9 @@ usage(void)
2 eprintf("usage: %s [-L label] device\n", argv0);
3 }
4
5-// ?man swaplabel: print or change swap label
6+// ?man swaplabel: set the label of a swap filesystem
7 // ?man arguments: device
8-// ?man display or modify the label and uuid of a swap device
9+// ?man swaplabel is used to change the label of a swap device or file.
10 int
11 main(int argc, char *argv[])
12 {
13@@ -38,7 +38,7 @@ main(int argc, char *argv[])
14 int i;
15
16 ARGBEGIN {
17- // ?man -L:str: specify option flag
18+ // ?man -L:label: Change the label.
19 case 'L':
20 setlabel = 1;
21 label = EARGF(usage());
+5,
-4
1@@ -16,9 +16,10 @@ usage(void)
2 eprintf("usage: %s -a | device\n", argv0);
3 }
4
5-// ?man swapoff: disable swap devices
6-// ?man arguments: -a | device
7-// ?man disable paging and swapping on specified devices
8+// ?man swapoff: disable devices and files for paging and swapping
9+// ?man synopsis: -a
10+// ?man synopsis: device
11+// ?man swapoff disables swapping on the specified devices and files.
12 int
13 main(int argc, char *argv[])
14 {
15@@ -29,7 +30,7 @@ main(int argc, char *argv[])
16 FILE *fp;
17
18 ARGBEGIN {
19- // ?man -a: print or show all entries
20+ // ?man -a: Disable swapping on all known swap devices and files as found in /etc/fstab.
21 case 'a':
22 all = 1;
23 break;
+7,
-6
1@@ -16,9 +16,10 @@ usage(void)
2 eprintf("usage: %s [-dp] -a | device\n", argv0);
3 }
4
5-// ?man swapon: enable swap devices
6-// ?man arguments: -a | device
7-// ?man enable paging and swapping on specified devices
8+// ?man swapon: enable devices and files for paging and swapping
9+// ?man synopsis: [-d] [-p] -a
10+// ?man synopsis: [-d] [-p] device
11+// ?man swapon is used to specify devices on which paging and swapping are to take place.
12 int
13 main(int argc, char *argv[])
14 {
15@@ -30,15 +31,15 @@ main(int argc, char *argv[])
16 FILE *fp;
17
18 ARGBEGIN {
19- // ?man -a: print or show all entries
20+ // ?man -a: Make all devices marked as "swap" in /etc/fstab available, except for those with the "noauto" option.
21 case 'a':
22 all = 1;
23 break;
24- // ?man -d: specify directory
25+ // ?man -d: Discard freed swap pages before they are reused.
26 case 'd':
27 flags |= SWAP_FLAG_DISCARD;
28 break;
29- // ?man -p: preserve file attributes
30+ // ?man -p: Set higher priority than the default for the new swap area.
31 case 'p':
32 flags |= SWAP_FLAG_PREFER;
33 break;
+6,
-3
1@@ -67,8 +67,11 @@ usage(void)
2 }
3
4 // ?man switch_root: switch to another root filesystem
5-// ?man arguments: newroot] [init] (PID 1)
6-// ?man switch to another filesystem as the root of the mount tree
7+// ?man arguments: [newroot] [init]
8+// ?man switch_root removes all files and directories on the current root filesystem and overmounts it with newroot.
9+// ?man If a console is specified, redirect stdio and stderr to it.
10+// ?man After the switch, execute init.
11+// ?man switch_root can only be run as PID 1 in an initramfs or tmpfs with a regular and executable /sbin/init.
12 int
13 main(int argc, char *argv[])
14 {
15@@ -78,7 +81,7 @@ main(int argc, char *argv[])
16 struct statfs stfs;
17
18 ARGBEGIN {
19- // ?man -c:str: print count or perform stdout action
20+ // ?man -c:console: Redirect stdio and stderr to console after switching to newroot.
21 case 'c':
22 console = EARGF(usage());
23 break;
+6,
-3
1@@ -160,9 +160,12 @@ usage(void)
2 eprintf("usage: %s [-p file] variable[=value]...\n", argv0);
3 }
4
5-// ?man sysctl: configure kernel parameters
6+// ?man sysctl: configure kernel parameters at runtime
7 // ?man arguments: variable[=value]...
8-// ?man view and modify kernel parameters at runtime
9+// ?man sysctl modifies kernel parameters at runtime.
10+// ?man The parameters available are those listed under /proc/sys.
11+// ?man Procfs is required for sysctl support in Linux.
12+// ?man You can use sysctl to both read and write sysctl data.
13 int
14 main(int argc, char *argv[])
15 {
16@@ -174,7 +177,7 @@ main(int argc, char *argv[])
17 int r = 0;
18
19 ARGBEGIN {
20- // ?man -p:file: preserve file attributes
21+ // ?man -p:file: Load the sysctl key=value pairs from file.
22 case 'p':
23 file = EARGF(usage());
24 break;
+4,
-4
1@@ -43,21 +43,21 @@ main(int argc, char *argv[])
2
3 ARGBEGIN
4 {
5- // ?man -d: specify directory
6+ // ?man -d: Clear persistence on the interface.
7 case 'd':
8 dflag = 1;
9 tflag = 0;
10 break;
11- // ?man -t: sort or specify timestamp
12+ // ?man -t: Make the interface persistent.
13 case 't':
14 tflag = 1;
15 dflag = 0;
16 break;
17- // ?man -T: specify option flag
18+ // ?man -T: Create a TAP device instead of a TUN device.
19 case 'T':
20 Tflag = 1;
21 break;
22- // ?man -u:str: unbuffered output
23+ // ?man -u:owner: Set the owner of the persistent interface.
24 case 'u':
25 owner = EARGF(usage());
26 break;
+8,
-6
1@@ -127,7 +127,9 @@ usage(void)
2
3 // ?man umount: unmount filesystems
4 // ?man arguments: target...
5-// ?man unmount a filesystem from the directory tree
6+// ?man umount detaches the target filesystem or filesystems.
7+// ?man A file system is specified by giving the directory where it has been mounted.
8+// ?man Giving the special device on which the file system lives may also work, but is obsolete, mainly because it will fail in case this device was mounted on more than one directory.
9 int
10 main(int argc, char *argv[])
11 {
12@@ -140,23 +142,23 @@ main(int argc, char *argv[])
13 #endif
14
15 ARGBEGIN {
16- // ?man -a: print or show all entries
17+ // ?man -a: All of the file systems described in /proc/mounts are unmounted. The proc filesystem is not unmounted.
18 case 'a':
19 aflag = 1;
20 break;
21- // ?man -f: force the operation
22+ // ?man -f: Force unmount (in case of an unreachable NFS server).
23 case 'f':
24 flags |= MNT_FORCE;
25 break;
26- // ?man -l: list in long format
27+ // ?man -l: Lazy unmount. Detach the filesystem from the fs hierarchy now, and cleanup all references to the filesystem as soon as it is not busy anymore.
28 case 'l':
29 flags |= MNT_DETACH;
30 break;
31- // ?man -n: print line numbers or counts
32+ // ?man -n: Unmount without writing in /etc/mtab. This is the default action.
33 case 'n':
34 break;
35 #if FEATURE_UMOUNT_OPTIONS
36- // ?man -O:str: specify option flag
37+ // ?man -O:opts: Only act on mounts matching opts.
38 case 'O':
39 oflag = EARGF(usage());
40 break;
1@@ -15,36 +15,37 @@ usage(void)
2 eprintf("usage: %s [-muinpU] cmd [args...]\n", argv0);
3 }
4
5-// ?man unshare: run program in new namespaces
6-// ?man arguments: cmd [args...
7-// ?man run a program with some namespaces unshared from the parent
8+// ?man unshare: run program with some namespaces unshared from parent
9+// ?man arguments: cmd [args ...]
10+// ?man unshare unshares the indicated namespaces from the parent process and then executes the specified program.
11+// ?man The namespaces to be unshared are indicated via options.
12 int
13 main(int argc, char *argv[])
14 {
15 int flags = 0;
16
17 ARGBEGIN {
18- // ?man -m: specify mode or limit
19+ // ?man -m: Unshare the mount namespace, so that the calling process has a private copy of its namespace which is not shared with any other process. This flag has the same effect as the clone CLONE_NEWNS flag.
20 case 'm':
21 flags |= CLONE_NEWNS;
22 break;
23- // ?man -u: unbuffered output
24+ // ?man -u: Unshare the UTS IPC namespace, so that the calling process has a private copy of the UTS namespace which is not shared with any other process. This flag has the same effect as the clone CLONE_NEWUTS flag.
25 case 'u':
26 flags |= CLONE_NEWUTS;
27 break;
28- // ?man -i: interactive mode or prompt for confirmation
29+ // ?man -i: Unshare the System V IPC namespace, so that the calling process has a private copy of the System V IPC namespace which is not shared with any other process. This flag has the same effect as the clone CLONE_NEWIPC flag.
30 case 'i':
31 flags |= CLONE_NEWIPC;
32 break;
33- // ?man -n: print line numbers or counts
34+ // ?man -n: Unshare the network namespace, so that the calling process is moved into a new network namespace which is not shared with any previously existing process. This flag has the same effect as the clone CLONE_NEWNET flag.
35 case 'n':
36 flags |= CLONE_NEWNET;
37 break;
38- // ?man -p: preserve file attributes
39+ // ?man -p: Create the process in a new PID namespace. This flag has the same effect as the clone CLONE_NEWPID flag.
40 case 'p':
41 flags |= CLONE_NEWPID;
42 break;
43- // ?man -U: specify option flag
44+ // ?man -U: The process will have a distinct set of UIDs, GIDs and capabilities.
45 case 'U':
46 flags |= CLONE_NEWUSER;
47 break;
+2,
-2
1@@ -258,7 +258,7 @@ parse_response(const unsigned char *resp, int resp_len, const char *query_name)
2 }
3
4 // ?man host: dns lookup utility
5-// ?man arguments: name [server
6+// ?man arguments: name [server]
7 // ?man look up hostnames and IP addresses using dns
8 int
9 main(int argc, char *argv[])
10@@ -274,7 +274,7 @@ main(int argc, char *argv[])
11 int i, r;
12
13 ARGBEGIN {
14- // ?man -t:str: sort or specify timestamp
15+ // ?man -t:type: Query DNS records of the given type.
16 case 't':
17 tflag = EARGF(usage());
18 break;
+3,
-3
1@@ -262,15 +262,15 @@ main(int argc, char *argv[])
2 char *enc;
3
4 ARGBEGIN {
5- // ?man -e:str: specify expression or pattern
6+ // ?man -e:str: Print the URL-encoded form of str and exit.
7 case 'e':
8 eflag = EARGF(usage());
9 break;
10- // ?man -d:str: specify directory
11+ // ?man -d:str: Print the URL-decoded form of str and exit.
12 case 'd':
13 dflag = EARGF(usage());
14 break;
15- // ?man -v: verbose mode; show progress
16+ // ?man -v: Accepted for compatibility; no effect.
17 case 'v':
18 break;
19 default:
+2,
-2
1@@ -98,7 +98,7 @@ list_interfaces(int all)
2 }
3
4 // ?man ifconfig: configure network interfaces
5-// ?man arguments: interface [action ...
6+// ?man arguments: interface [action ...]
7 // ?man configure network interface parameters and view stats
8 int
9 main(int argc, char *argv[])
10@@ -118,7 +118,7 @@ main(int argc, char *argv[])
11 unsigned int mask;
12
13 ARGBEGIN {
14- // ?man -a: print or show all entries
15+ // ?man -a: Show all interfaces, including those that are down.
16 case 'a':
17 aflag = 1;
18 break;
+3,
-1
1@@ -346,7 +346,9 @@ do_route(int argc, char *argv[])
2 }
3
4 // ?man ip: show or configure routing and devices
5-// ?man arguments: addr | link | route] [args...
6+// ?man synopsis: addr [args ...]
7+// ?man synopsis: link [args ...]
8+// ?man synopsis: route [args ...]
9 // ?man configure and view network devices, routing, and tunnels
10 // ?man ## COMMANDS
11 // ?man ### addr [show | list [dev <device>]]
+4,
-4
1@@ -49,7 +49,7 @@ resolve(const char *host, const char *port, int family, int socktype,
2 }
3
4 // ?man netcat: read and write data across network connections
5-// ?man arguments: host] [port
6+// ?man arguments: [host] [port]
7 // ?man arbitrary data transmission over tcp or udp
8 int
9 main(int argc, char *argv[])
10@@ -70,15 +70,15 @@ main(int argc, char *argv[])
11
12 ARGBEGIN
13 {
14- // ?man -l: list in long format
15+ // ?man -l: Listen for an incoming connection.
16 case 'l':
17 lflag = 1;
18 break;
19- // ?man -p:str: preserve file attributes
20+ // ?man -p:port: Use port as the local port.
21 case 'p':
22 local_port = EARGF(usage());
23 break;
24- // ?man -u: unbuffered output
25+ // ?man -u: Use UDP instead of TCP.
26 case 'u':
27 uflag = 1;
28 break;
+5,
-5
1@@ -141,23 +141,23 @@ main(int argc, char *argv[])
2 socklen_t fromlen;
3
4 ARGBEGIN {
5- // ?man -c:str: print count or perform stdout action
6+ // ?man -c:count: Stop after sending count requests.
7 case 'c':
8 cflag = EARGF(usage());
9 break;
10- // ?man -i:str: interactive mode or prompt for confirmation
11+ // ?man -i:interval: Wait interval seconds between requests.
12 case 'i':
13 iflag = EARGF(usage());
14 break;
15- // ?man -s:str: silent mode or print summary
16+ // ?man -s:size: Send size bytes of payload data.
17 case 's':
18 sflag = EARGF(usage());
19 break;
20- // ?man -t:str: sort or specify timestamp
21+ // ?man -t:ttl: Set the IP time-to-live.
22 case 't':
23 tflag = EARGF(usage());
24 break;
25- // ?man -w:str: wait for completion
26+ // ?man -w:deadline: Stop after deadline seconds.
27 case 'w':
28 wflag = EARGF(usage());
29 break;
+4,
-4
1@@ -261,19 +261,19 @@ main(int argc, char *argv[])
2 int ret;
3
4 ARGBEGIN {
5- // ?man -h:str: suppress headers or print help
6+ // ?man -h:host: Connect to host.
7 case 'h':
8 host = EARGF(usage());
9 break;
10- // ?man -p:str: preserve file attributes
11+ // ?man -p:port: Use port instead of the default tftp service.
12 case 'p':
13 port = EARGF(usage());
14 break;
15- // ?man -x: hex format or match whole lines
16+ // ?man -x: Download file from the server.
17 case 'x':
18 fn = getfile;
19 break;
20- // ?man -c: print count or perform stdout action
21+ // ?man -c: Upload file to the server.
22 case 'c':
23 fn = putfile;
24 break;
+8,
-8
1@@ -327,35 +327,35 @@ main(int argc, char *argv[])
2 size_t i;
3
4 ARGBEGIN {
5- // ?man -O:str: specify output file path
6+ // ?man -O:str: Write output to str instead of the default file name.
7 case 'O':
8 Oflag = EARGF(usage());
9 break;
10- // ?man -P:str: specify output directory prefix
11+ // ?man -P:str: Write output files under directory str.
12 case 'P':
13 Pflag = EARGF(usage());
14 break;
15- // ?man -T:num: set network read and connect timeout
16+ // ?man -T:num: Set the network read and connect timeout to num seconds.
17 case 'T':
18 timeout_sec = estrtonum(EARGF(usage()), 0, 100000);
19 break;
20- // ?man -U:str: set User-Agent header
21+ // ?man -U:str: Set the User-Agent header to str.
22 case 'U':
23 user_agent = EARGF(usage());
24 break;
25- // ?man -c: continue retrieval of aborted transfer
26+ // ?man -c: Continue retrieval of an aborted transfer.
27 case 'c':
28 cflag = 1;
29 break;
30- // ?man -q: quiet mode to suppress stderr output
31+ // ?man -q: Quiet mode. Suppress stderr output.
32 case 'q':
33 qflag = 1;
34 break;
35- // ?man -S: print server response headers to stderr
36+ // ?man -S: Print server response headers to stderr.
37 case 'S':
38 Sflag = 1;
39 break;
40- // ?man --: specify - option
41+ /* long options handle TLS verification, extra headers, POST data, and spider mode */
42 case '-':
43 if (strcmp(argv[0], "-no-check-certificate") == 0) {
44 no_check_certificate = 1;
+1,
-1
1@@ -14,7 +14,7 @@ usage(void)
2 }
3
4 // ?man basename: strip directory and suffix from filenames
5-// ?man arguments: path [suffix
6+// ?man arguments: path [suffix]
7 // ?man print filename with leading directories and optional suffix removed
8 int
9 main(int argc, char *argv[])
+8,
-8
1@@ -179,11 +179,11 @@ main(int argc, char *argv[])
2
3 ARGBEGIN {
4 #ifdef FEATURE_CAL_EXT
5- // ?man -1: specify option flag
6+ // ?man -1: print the current month
7 case '1':
8 nmons = 1;
9 break;
10- // ?man -3: specify option flag
11+ // ?man -3: print the previous, current, and next month
12 case '3':
13 nmons = 3;
14 if (--month == 0) {
15@@ -191,28 +191,28 @@ main(int argc, char *argv[])
16 year--;
17 }
18 break;
19- // ?man -c:num: print count or perform stdout action
20+ // ?man -c:num: print num calendars in a row
21 case 'c':
22 ncols = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
23 break;
24- // ?man -f:num: force the operation
25+ // ?man -f:num: use num as the first day of the week, where 0 is Sunday
26 case 'f':
27 fday = estrtonum(EARGF(usage()), 0, 6);
28 break;
29- // ?man -m: specify mode or limit
30+ // ?man -m: use Monday as the first day of the week
31 case 'm': /* Monday */
32 fday = 1;
33 break;
34- // ?man -n:num: print line numbers or counts
35+ // ?man -n:num: output num months starting with the current month
36 case 'n':
37 nmons = estrtonum(EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
38 break;
39- // ?man -s: silent mode or print summary
40+ // ?man -s: use Sunday as the first day of the week
41 case 's': /* Sunday */
42 fday = 0;
43 break;
44 #endif
45- // ?man -y: specify option flag
46+ // ?man -y: print the entire year
47 case 'y':
48 month = 1;
49 nmons = 12;
+3,
-4
1@@ -15,8 +15,8 @@ usage(void)
2 }
3
4 // ?man cat: concatenate files and print to standard output
5-// ?man arguments: file ...
6-// ?man cat reads each file in sequence and writes it to standard output
7+// ?man arguments: [file ...]
8+// ?man cat reads each file in sequence and writes its contents to standard output
9 // ?man if no file is given, or a file is -, standard input is read
10 int
11 main(int argc, char *argv[])
12@@ -24,9 +24,8 @@ main(int argc, char *argv[])
13 int fd, ret = 0;
14
15 ARGBEGIN {
16- // ?man -u: specify u option
17+ // ?man -u: accepted for posix compatibility; output is always unbuffered
18 case 'u':
19- // ?man -u: ignored; accepted for posix compatibility; output is always unbuffered
20 break;
21 default:
22 usage();
+4,
-4
1@@ -48,7 +48,7 @@ main(int argc, char *argv[])
2 struct recursor r = { .fn = chgrp, .maxdepth = 1, .follow = 'P' };
3
4 ARGBEGIN {
5- // ?man -h: affect symbolic links instead of referenced files
6+ // ?man -h: operate on symbolic links themselves instead of their targets
7 case 'h':
8 hflag = 1;
9 break;
10@@ -56,11 +56,11 @@ main(int argc, char *argv[])
11 case 'R':
12 r.maxdepth = 0;
13 break;
14- // ?man -H: specify option flag
15+ // ?man -H: dereference command-line symbolic links during recursive traversal
16 case 'H':
17- // ?man -L: specify option flag
18+ // ?man -L: dereference all symbolic links during recursive traversal
19 case 'L':
20- // ?man -P: specify option flag
21+ // ?man -P: preserve symbolic links during recursive traversal
22 case 'P':
23 r.follow = ARGC();
24 break;
+6,
-5
1@@ -45,7 +45,8 @@ usage(void)
2 }
3
4 // ?man chown: change ownership
5-// ?man arguments: owner[:[group]] file ...
6+// ?man synopsis: owner[:[group]] file ...
7+// ?man synopsis: :group file ...
8 // ?man change the user and group ownership of files and directories
9 int
10 main(int argc, char *argv[])
11@@ -56,7 +57,7 @@ main(int argc, char *argv[])
12 char *owner, *group;
13
14 ARGBEGIN {
15- // ?man -h: affect symbolic links instead of referenced files
16+ // ?man -h: operate on symbolic links themselves instead of their targets
17 case 'h':
18 hflag = 1;
19 break;
20@@ -66,11 +67,11 @@ main(int argc, char *argv[])
21 case 'R':
22 r.maxdepth = 0;
23 break;
24- // ?man -H: specify option flag
25+ // ?man -H: dereference command-line symbolic links during recursive traversal
26 case 'H':
27- // ?man -L: specify option flag
28+ // ?man -L: dereference all symbolic links during recursive traversal
29 case 'L':
30- // ?man -P: specify option flag
31+ // ?man -P: preserve symbolic links during recursive traversal
32 case 'P':
33 r.follow = ARGC();
34 break;
+2,
-2
1@@ -24,11 +24,11 @@ main(int argc, char *argv[])
2 int ret = 0, lflag = 0, sflag = 0, same = 1, b[2];
3
4 ARGBEGIN {
5- // ?man -l: list in long format
6+ // ?man -l: print the byte number and differing bytes for every difference
7 case 'l':
8 lflag = 1;
9 break;
10- // ?man -s: silent mode or print summary
11+ // ?man -s: print nothing and return status only
12 case 's':
13 sflag = 1;
14 break;
+3,
-3
1@@ -44,11 +44,11 @@ main(int argc, char *argv[])
2 int ret = 0, i, diff = 0, seenline = 0;
3
4 ARGBEGIN {
5- // ?man -1: specify option flag
6+ // ?man -1: suppress lines unique to file1
7 case '1':
8- // ?man -2: specify option flag
9+ // ?man -2: suppress lines unique to file2
10 case '2':
11- // ?man -3: specify option flag
12+ // ?man -3: suppress lines common to both files
13 case '3':
14 show &= 0x07 ^ (1 << (ARGC() - '1'));
15 break;
+6,
-6
1@@ -25,7 +25,7 @@ main(int argc, char *argv[])
2 case 'i':
3 cp_iflag = 1;
4 break;
5- // ?man -a: archive mode; equivalent to -dpR
6+ // ?man -a: preserve devices, sockets, and fifos; implies -pPR
7 case 'a':
8 cp_follow = 'P';
9 cp_aflag = cp_pflag = cp_rflag = 1;
10@@ -34,7 +34,7 @@ main(int argc, char *argv[])
11 case 'f':
12 cp_fflag = 1;
13 break;
14- // ?man -p: preserve file attributes
15+ // ?man -p: preserve mode, timestamps, and ownership
16 case 'p':
17 cp_pflag = 1;
18 break;
19@@ -44,15 +44,15 @@ main(int argc, char *argv[])
20 case 'R':
21 cp_rflag = 1;
22 break;
23- // ?man -v: verbose mode; show progress
24+ // ?man -v: write each copied source and destination path
25 case 'v':
26 cp_vflag = 1;
27 break;
28- // ?man -H: specify option flag
29+ // ?man -H: dereference source arguments that are symbolic links
30 case 'H':
31- // ?man -L: specify option flag
32+ // ?man -L: dereference all symbolic links
33 case 'L':
34- // ?man -P: specify option flag
35+ // ?man -P: preserve symbolic links
36 case 'P':
37 cp_follow = ARGC();
38 break;
+23,
-9
1@@ -161,9 +161,20 @@ usage(void)
2 argv0, argv0, argv0);
3 }
4
5-// ?man cut: cut out fields from lines
6-// ?man arguments: -b list [file ...
7-// ?man print selected parts of lines from files
8+// ?man cut: extract columns of data
9+// ?man synopsis: -b list [-n] [file ...]
10+// ?man synopsis: -c list [file ...]
11+// ?man synopsis: -f list [-d delim] [-s] [file ...]
12+// ?man cut out bytes, characters or delimited fields from each line of file and
13+// ?man write to stdout.
14+// ?man If no file is given or file is '-', cut reads from stdin.
15+// ?man list is a comma or space separated list of numbers and ranges starting
16+// ?man from 1.
17+// ?man Ranges have the form 'N-M'. If N or M is missing, beginning or end
18+// ?man of line is assumed.
19+// ?man Numbers and ranges may be repeated, overlapping and in any order.
20+// ?man Selected input is written in the same order it is read
21+// ?man and is written exactly once.
22 int
23 main(int argc, char *argv[])
24 {
25@@ -171,27 +182,30 @@ main(int argc, char *argv[])
26 int ret = 0;
27
28 ARGBEGIN {
29- // ?man -b: specify block size or base directory
30+ // ?man -b:list: list specifies byte | character positions.
31 case 'b':
32- // ?man -c: print count or perform stdout action
33+ // ?man -c:list: list specifies byte | character positions.
34 case 'c':
35- // ?man -f:mode: force the operation
36+ // ?man -f:list: list specifies field numbers.
37+ // ?man Lines not containing field delimiters are passed through, unless -s is specified.
38 case 'f':
39 mode = ARGC();
40 parselist(EARGF(usage()));
41 break;
42- // ?man -d:str: specify directory
43+ // ?man -d:delim: Use delim as field delimiter, which can be an arbitrary string.
44+ // ?man Default is '\\t'.
45 case 'd':
46 delim = EARGF(usage());
47 if (!*delim)
48 eprintf("empty delimiter\n");
49 delimlen = unescape(delim);
50 break;
51- // ?man -n: print line numbers or counts
52+ // ?man -n: Do not split multibyte characters.
53+ // ?man A character is written when its last byte is selected.
54 case 'n':
55 nflag = 1;
56 break;
57- // ?man -s: silent mode or print summary
58+ // ?man -s: Suppress lines not containing field delimiters.
59 case 's':
60 sflag = 1;
61 break;
+4,
-3
1@@ -64,7 +64,8 @@ setdate(const char *s, struct tm *now)
2 }
3
4 // ?man date: print or set system date and time
5-// ?man arguments: +format | mmddHHMM[[CC]yy
6+// ?man synopsis: [-u] [-d time] [+format]
7+// ?man synopsis: [-u] [-d time] [mmddHHMM[[CC]yy]]
8 // ?man display or configure the system date and time
9 int
10 main(int argc, char *argv[])
11@@ -78,11 +79,11 @@ main(int argc, char *argv[])
12 eprintf("time:");
13
14 ARGBEGIN {
15- // ?man -d:num: specify directory
16+ // ?man -d:time: print time as seconds since the Unix epoch
17 case 'd':
18 t = estrtonum(EARGF(usage()), 0, LLONG_MAX);
19 break;
20- // ?man -u: unbuffered output
21+ // ?man -u: print or set UTC time instead of local time
22 case 'u':
23 if (setenv("TZ", "UTC0", 1) < 0)
24 eprintf("setenv:");
+7,
-7
1@@ -91,8 +91,8 @@ usage(void)
2 eprintf("usage: %s [-a]\n", argv0);
3 }
4
5-// ?man df: report disk space usage
6-// ?man display free and used disk space on filesystems
7+// ?man df: show file system usage
8+// ?man df displays the amount of disk space available on a file system. If no arguments are given, df shows all the file systems using 512-byte blocks.
9 int
10 main(int argc, char *argv[])
11 {
12@@ -101,24 +101,24 @@ main(int argc, char *argv[])
13 int ret = 0;
14
15 ARGBEGIN {
16- // ?man -a: print or show all entries
17+ // ?man -a: Show all file systems including dummy ones. This is the default option.
18 case 'a':
19 aflag = 1;
20 break;
21- // ?man -h: suppress headers or print help
22+ // ?man -h: Not implemented.
23 case 'h':
24 hflag = 1;
25 kflag = 0;
26 break;
27- // ?man -k: specify option flag
28+ // ?man -k: Print sizes in 1024-byte blocks.
29 case 'k':
30 kflag = 1;
31 hflag = 0;
32 blksize = 1024;
33 break;
34- // ?man -s: silent mode or print summary
35+ // ?man -s: Accepted for compatibility; not implemented.
36 case 's':
37- // ?man -i: interactive mode or prompt for confirmation
38+ // ?man -i: Not implemented.
39 case 'i':
40 eprintf("not implemented\n");
41 break;
+9,
-9
1@@ -122,36 +122,36 @@ main(int argc, char *argv[])
2 char *bsize;
3
4 ARGBEGIN {
5- // ?man -a: print or show all entries
6+ // ?man -a: display an entry for each file in the hierarchy
7 case 'a':
8 aflag = 1;
9 break;
10- // ?man -d:num: specify directory
11+ // ?man -d:depth: limit output to depth levels of subdirectories
12 case 'd':
13 dflag = 1;
14 maxdepth = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
15 break;
16- // ?man -h: suppress headers or print help
17+ // ?man -h: print sizes in human-readable units
18 case 'h':
19 hflag = 1;
20 break;
21- // ?man -k: specify option flag
22+ // ?man -k: report sizes in kilobytes instead of 512-byte blocks
23 case 'k':
24 kflag = 1;
25 break;
26- // ?man -s: silent mode or print summary
27+ // ?man -s: display only the total for each specified file
28 case 's':
29 sflag = 1;
30 break;
31- // ?man -x: hex format or match whole lines
32+ // ?man -x: stay on the current file system
33 case 'x':
34 r.flags |= SAMEDEV;
35 break;
36- // ?man -H: specify option flag
37+ // ?man -H: follow symbolic links given on the command line
38 case 'H':
39- // ?man -L: specify option flag
40+ // ?man -L: follow all symbolic links
41 case 'L':
42- // ?man -P: specify option flag
43+ // ?man -P: do not follow symbolic links
44 case 'P':
45 r.follow = ARGC();
46 break;
+3,
-3
1@@ -18,7 +18,7 @@ usage(void)
2 }
3
4 // ?man env: run command in modified environment
5-// ?man arguments: ... [var=value] ... [cmd [arg ...
6+// ?man arguments: [name=value ...] [cmd [arg ...]]
7 // ?man set environment variables and run a command
8 int
9 main(int argc, char *argv[])
10@@ -26,11 +26,11 @@ main(int argc, char *argv[])
11 int savederrno;
12
13 ARGBEGIN {
14- // ?man -i: interactive mode or prompt for confirmation
15+ // ?man -i: ignore the inherited environment and start from an empty one
16 case 'i':
17 *environ = NULL;
18 break;
19- // ?man -u:str: unbuffered output
20+ // ?man -u:name: remove name from the environment
21 case 'u':
22 if (unsetenv(EARGF(usage())) < 0)
23 eprintf("unsetenv:");
+2,
-2
1@@ -98,11 +98,11 @@ main(int argc, char *argv[])
2 char *tl = "8";
3
4 ARGBEGIN {
5- // ?man -i: interactive mode or prompt for confirmation
6+ // ?man -i: only expand tabs at the start of lines
7 case 'i':
8 iflag = 1;
9 break;
10- // ?man -t:str: sort or specify timestamp
11+ // ?man -t:list: use list as the tab size or tab stops
12 case 't':
13 tl = EARGF(usage());
14 if (!*tl)
+3,
-3
1@@ -1421,7 +1421,7 @@ usage(void)
2 }
3
4 // ?man find: search for files
5-// ?man arguments: path ... [expression ...
6+// ?man arguments: path ... [expression ...]
7 // ?man search for files in a directory hierarchy
8 int
9 main(int argc, char **argv)
10@@ -1434,12 +1434,12 @@ main(int argc, char **argv)
11 gflags.mindepth = -1;
12
13 ARGBEGIN {
14- // ?man -H: specify option flag
15+ // ?man -H: dereference symbolic links given on the command line
16 case 'H':
17 gflags.h = 1;
18 gflags.l = 0;
19 break;
20- // ?man -L: specify option flag
21+ // ?man -L: dereference all symbolic links encountered
22 case 'L':
23 gflags.l = 1;
24 gflags.h = 0;
+5,
-5
1@@ -85,7 +85,7 @@ usage(void)
2 }
3
4 // ?man fold: wrap lines to fit width
5-// ?man arguments: FILE ...
6+// ?man arguments: file ...
7 // ?man wrap input lines to fit a specified width
8 int
9 main(int argc, char *argv[])
10@@ -94,19 +94,19 @@ main(int argc, char *argv[])
11 int ret = 0;
12
13 ARGBEGIN {
14- // ?man -b: specify block size or base directory
15+ // ?man -b: count bytes rather than characters
16 case 'b':
17 bflag = 1;
18 break;
19- // ?man -s: silent mode or print summary
20+ // ?man -s: break at the last space within the width when possible
21 case 's':
22 sflag = 1;
23 break;
24- // ?man -w:num: wait for completion
25+ // ?man -w:num: wrap lines at num columns instead of 80
26 case 'w':
27 width = estrtonum(EARGF(usage()), 1, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
28 break;
29- // ?man ARGNUM: specify RGNUM option
30+ /* -num is accepted as shorthand for -w num */
31 ARGNUM:
32 if (!(width = ARGNUMF()))
33 eprintf("illegal width value, too small\n");
+2,
-2
1@@ -23,7 +23,7 @@ usage(void)
2 }
3
4 // ?man getconf: query configuration variables
5-// ?man arguments: var [path
6+// ?man arguments: var [path]
7 // ?man query system configuration variables
8 int
9 main(int argc, char *argv[])
10@@ -34,7 +34,7 @@ main(int argc, char *argv[])
11 char *str;
12
13 ARGBEGIN {
14- // ?man -v:str: verbose mode; show progress
15+ // ?man -v:spec: accepted for compatibility; ignored
16 case 'v':
17 /* ignore */
18 EARGF(usage());
+21,
-21
1@@ -244,7 +244,7 @@ usage(void)
2 }
3
4 // ?man grep: search files for a pattern
5-// ?man arguments: pattern] [file ...
6+// ?man arguments: pattern [file ...]
7 // ?man grep searches the named input files for lines matching the given pattern
8 // ?man if no files are named, or a file is -, standard input is searched
9 // ?man by default, matching lines are written to standard output
10@@ -260,54 +260,54 @@ main(int argc, char *argv[])
11
12 ARGBEGIN {
13 #if FEATURE_GREP_CONTEXT
14- // ?man -A:num: specify A option
15+ // ?man -A:num: print num lines of trailing context after each match
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- // ?man -B:num: specify B option
21+ // ?man -B:num: print num lines of leading context before each match
22 case 'B':
23 // ?man -B num: print num lines of leading context before each match
24 Bflag = estrtonum(EARGF(usage()), 0, LONG_MAX);
25 break;
26- // ?man -C:num: specify C option
27+ // ?man -C:num: print num lines of context before and after each match
28 case 'C':
29 // ?man -C num: print num lines of context before and after each match; equivalent to -A num -B num
30 Aflag = Bflag = estrtonum(EARGF(usage()), 0, LONG_MAX);
31 break;
32- // ?man ARGNUM: specify RGNUM option
33+ /* -num is accepted as shorthand for -C num */
34 ARGNUM:
35 Aflag = Bflag = ARGNUMF();
36 break;
37 #endif
38 #if FEATURE_GREP_MAX_COUNT
39- // ?man -m:num: specify m option
40+ // ?man -m:num: stop reading a file after num matching lines
41 case 'm':
42 // ?man -m num: stop reading a file after num matching lines
43 mval = estrtonum(EARGF(usage()), 0, LONG_MAX);
44 break;
45 #endif
46- // ?man -E: specify E option
47+ // ?man -E: interpret pattern as an extended regular expression
48 case 'E':
49 // ?man -E: interpret pattern as an extended regular expression
50 Eflag = 1;
51 Fflag = 0;
52 flags |= REG_EXTENDED;
53 break;
54- // ?man -F: specify F option
55+ // ?man -F: interpret patterns as fixed strings
56 case 'F':
57 // ?man -F: interpret pattern as a list of fixed strings separated by newlines
58 Fflag = 1;
59 Eflag = 0;
60 flags &= ~REG_EXTENDED;
61 break;
62- // ?man -H: specify H option
63+ // ?man -H: always print file names with matching lines
64 case 'H':
65 // ?man -H: always print the file name with matching lines
66 Hflag = 1;
67 hflag = 0;
68 break;
69- // ?man -e:file: specify e option
70+ // ?man -e:pattern: specify a pattern to match; may be given multiple times
71 case 'e':
72 // ?man -e pattern: specify a pattern to match; may be given multiple times
73 arg = EARGF(usage());
74@@ -317,7 +317,7 @@ main(int argc, char *argv[])
75 efshut(fp, arg);
76 eflag = 1;
77 break;
78- // ?man -f:file: specify f option
79+ // ?man -f:file: read patterns from file, one per line
80 case 'f':
81 // ?man -f file: read patterns from file, one per line
82 arg = EARGF(usage());
83@@ -328,51 +328,51 @@ main(int argc, char *argv[])
84 efshut(fp, arg);
85 fflag = 1;
86 break;
87- // ?man -h: specify h option
88+ // ?man -h: never print file names with matching lines
89 case 'h':
90 // ?man -h: never print file names with matching lines
91 hflag = 1;
92 Hflag = 0;
93 break;
94- // ?man -c: specify c option
95+ // ?man -c: print only a count of matching lines per file
96 case 'c':
97 // ?man -c: print only a count of matching lines per file
98 /* FALLTHROUGH */
99- // ?man -l: specify l option
100+ // ?man -l: print only the names of files with at least one match
101 case 'l':
102 // ?man -l: print only the names of files with at least one matching line
103 /* FALLTHROUGH */
104- // ?man -n: specify n option
105+ // ?man -n: prefix each matching line with its line number
106 case 'n':
107 // ?man -n: prefix each matching line with its line number within its file
108 /* FALLTHROUGH */
109- // ?man -q: specify q option
110+ // ?man -q: print nothing and return success on the first match
111 case 'q':
112 // ?man -q: quiet mode; exit immediately with status 0 on first match and write nothing
113 mode = ARGC();
114 break;
115- // ?man -i: specify i option
116+ // ?man -i: perform case-insensitive matching
117 case 'i':
118 // ?man -i: perform case-insensitive matching
119 flags |= REG_ICASE;
120 iflag = 1;
121 break;
122- // ?man -s: specify s option
123+ // ?man -s: suppress errors for nonexistent or unreadable files
124 case 's':
125 // ?man -s: suppress error messages about nonexistent or unreadable files
126 sflag = 1;
127 break;
128- // ?man -v: specify v option
129+ // ?man -v: select lines that do not match
130 case 'v':
131 // ?man -v: invert the sense of matching to select non-matching lines
132 vflag = 1;
133 break;
134- // ?man -w: specify w option
135+ // ?man -w: match only whole words
136 case 'w':
137 // ?man -w: match only whole words
138 wflag = 1;
139 break;
140- // ?man -x: specify x option
141+ // ?man -x: match only whole lines
142 case 'x':
143 // ?man -x: match only whole lines
144 xflag = 1;
+2,
-2
1@@ -41,11 +41,11 @@ main(int argc, char *argv[])
2 int ret = 0, newline = 0, many = 0;
3
4 ARGBEGIN {
5- // ?man -n:num: print line numbers or counts
6+ // ?man -n:num: display the first num lines of each file
7 case 'n':
8 n = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
9 break;
10- // ?man ARGNUM: specify RGNUM option
11+ /* -num is accepted as shorthand for -n num */
12 ARGNUM:
13 n = ARGNUMF();
14 break;
+7,
-7
1@@ -135,26 +135,26 @@ usage(void)
2 eprintf("usage: %s [-n] [-g | -u | -G] [user | uid]\n", argv0);
3 }
4
5-// ?man id: print user and group ids
6-// ?man arguments: user | uid
7-// ?man display real and effective user and group identities
8+// ?man id: print real and effective user and group ids
9+// ?man synopsis: [-n] [-g | -u | -G] [user | uid]
10+// ?man id prints user and group information of the calling process to standard output. If a login name or uid is specified, the user and group information of that user is displayed.
11 int
12 main(int argc, char *argv[])
13 {
14 ARGBEGIN {
15- // ?man -g: specify option flag
16+ // ?man -g: Print only the effective group ID.
17 case 'g':
18 gflag = 1;
19 break;
20- // ?man -u: unbuffered output
21+ // ?man -u: Print only the effective user ID.
22 case 'u':
23 uflag = 1;
24 break;
25- // ?man -G: specify option flag
26+ // ?man -G: Display group information as whitespace separated numbers, in no particular order.
27 case 'G':
28 Gflag = 1;
29 break;
30- // ?man -n: print line numbers or counts
31+ // ?man -n: Print names instead of ID numbers, for -g, -u, and -G.
32 case 'n':
33 nflag = 1;
34 break;
+26,
-9
1@@ -451,8 +451,17 @@ join(FILE *fa, FILE *fb, size_t jfa, size_t jfb)
2 }
3
4
5-// ?man join: join lines on common field
6-// ?man join lines of two sorted files on a common field
7+// ?man join: relational database operator
8+// ?man join lines from file1 and file2 on a matching field.
9+// ?man If one of the input files is '-', standard input is read for that file.
10+// ?man Files are read sequentially and are assumed to be sorted on the join
11+// ?man field.
12+// ?man join does not check the order of input, and joining two unsorted files will
13+// ?man produce unexpected output.
14+// ?man By default, input lines are matched on the first blank-separated
15+// ?man field; output lines are space-separated and consist of the join field
16+// ?man followed by the remaining fields from file1,
17+// ?man then the remaining fields from file2.
18 int
19 main(int argc, char *argv[])
20 {
21@@ -462,15 +471,15 @@ main(int argc, char *argv[])
22 char *fno;
23
24 ARGBEGIN {
25- // ?man -1:num: specify option flag
26+ // ?man -1:field: Join on the fieldth field of file 1.
27 case '1':
28 jf[0] = estrtonum(EARGF(usage()), 1, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
29 break;
30- // ?man -2:num: specify option flag
31+ // ?man -2:field: Join on the fieldth field of file 2.
32 case '2':
33 jf[1] = estrtonum(EARGF(usage()), 1, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
34 break;
35- // ?man -a:str: print or show all entries
36+ // ?man -a:fileno: Print unpairable lines from file fileno in addition to normal output.
37 case 'a':
38 fno = EARGF(usage());
39 if (strcmp(fno, "1") == 0)
40@@ -480,21 +489,29 @@ main(int argc, char *argv[])
41 else
42 usage();
43 break;
44- // ?man -e:str: specify expression or pattern
45+ // ?man -e:string: When used with -o, replace empty fields in the output list with string.
46 case 'e':
47 replace = EARGF(usage());
48 break;
49- // ?man -o:str: specify output file
50+ // ?man -o:list: Format output according to the string list.
51+ // ?man Each element of list may be either fileno.field or 0 representing the join field.
52+ // ?man Elements in list may be separated by blanks or commas.
53+ // ?man For example,
54+ // ?man join -o "0 2.1 1.3"
55+ // ?man would print the join field, the first field of file2,
56+ // ?man then the third field of file1.
57+ // ?man Only paired lines are formatted with the -o option.
58+ // ?man Unpairable lines selected with -a or -v are printed raw.
59 case 'o':
60 oflag = 1;
61 initolist(&output);
62 makeolist(&output, EARGF(usage()));
63 break;
64- // ?man -t:str: sort or specify timestamp
65+ // ?man -t:delim: Use the arbitrary string delim as field delimiter for both input and output.
66 case 't':
67 sep = EARGF(usage());
68 break;
69- // ?man -v:str: verbose mode; show progress
70+ // ?man -v:fileno: Print unpairable lines from file fileno instead of normal output.
71 case 'v':
72 pairs = 0;
73 fno = EARGF(usage());
+3,
-3
1@@ -19,7 +19,7 @@ usage(void)
2 }
3
4 // ?man ln: make links between files
5-// ?man arguments: target [name
6+// ?man arguments: target [name]
7 // ?man create hard or symbolic links between files
8 int
9 main(int argc, char *argv[])
10@@ -34,11 +34,11 @@ main(int argc, char *argv[])
11 case 'f':
12 fflag = 1;
13 break;
14- // ?man -L: specify option flag
15+ // ?man -L: hard-link the target of a symbolic link rather than the link itself
16 case 'L':
17 flags |= AT_SYMLINK_FOLLOW;
18 break;
19- // ?man -P: specify option flag
20+ // ?man -P: hard-link a symbolic link itself rather than its target
21 case 'P':
22 flags &= ~AT_SYMLINK_FOLLOW;
23 break;
+4,
-4
1@@ -56,19 +56,19 @@ main(int argc, char *argv[])
2 char *buf = NULL, *tag = NULL;
3
4 ARGBEGIN {
5- // ?man -i: interactive mode or prompt for confirmation
6+ // ?man -i: include the logger process ID with each message
7 case 'i':
8 logflags |= LOG_PID;
9 break;
10- // ?man -p:str: preserve file attributes
11+ // ?man -p:priority: log with the given facility.level priority
12 case 'p':
13 priority = decodepri(EARGF(usage()));
14 break;
15- // ?man -s: silent mode or print summary
16+ // ?man -s: also write each message to standard error
17 case 's':
18 logflags |= LOG_PERROR;
19 break;
20- // ?man -t:str: sort or specify timestamp
21+ // ?man -t:tag: add tag to each logged message
22 case 't':
23 tag = EARGF(usage());
24 break;
+4,
-4
1@@ -406,11 +406,11 @@ entcmp(const void *va, const void *vb)
2 const struct entry *a = va, *b = vb;
3
4 switch (sort) {
5- // ?man -S: sort by file size
6+ // ?man -S: sort files by size in decreasing order
7 case 'S':
8 cmp = b->size - a->size;
9 break;
10- // ?man -t: sort by modification time
11+ // ?man -t: sort files by modification time
12 case 't':
13 if (!(cmp = b->t.tv_sec - a->t.tv_sec))
14 cmp = b->t.tv_nsec - a->t.tv_nsec;
15@@ -552,7 +552,7 @@ usage(void)
16 }
17
18 // ?man ls: list directory contents
19-// ?man arguments: [file ...
20+// ?man arguments: [file ...]
21 // ?man list information about files and directories
22 int
23 main(int argc, char *argv[])
24@@ -706,7 +706,7 @@ main(int argc, char *argv[])
25 uflag = 1;
26 cflag = 0;
27 break;
28- // ?man --: specify - option
29+ /* --color[=when] is accepted as a long-option extension */
30 case '-':
31 #if FEATURE_LS_COLOR
32 // ?man --color [when]: control coloring
+1,
-1
1@@ -17,7 +17,7 @@ usage(void)
2 }
3
4 // ?man mesg: control write access
5-// ?man arguments: n|y
6+// ?man synopsis: [n | y]
7 // ?man allow or disallow other users to write to the terminal
8 int
9 main(int argc, char *argv[])
+1,
-1
1@@ -23,7 +23,7 @@ main(int argc, char *argv[])
2 int ret = 0;
3
4 ARGBEGIN {
5- // ?man -m:mode: specify mode or limit
6+ // ?man -m:mode: set the file mode of newly created named pipes
7 case 'm':
8 mode = parsemode(EARGF(usage()), mode, umask(0));
9 break;
+2,
-2
1@@ -24,7 +24,7 @@ usage(void)
2 }
3
4 // ?man nice: run command with modified priority
5-// ?man arguments: cmd [arg ...
6+// ?man arguments: cmd [arg ...]
7 // ?man run a command with modified scheduling priority
8 int
9 main(int argc, char *argv[])
10@@ -32,7 +32,7 @@ main(int argc, char *argv[])
11 int val = 10, r, savederrno;
12
13 ARGBEGIN {
14- // ?man -n:num: print line numbers or counts
15+ // ?man -n:num: change niceness by num
16 case 'n':
17 val = estrtonum(EARGF(usage()), PRIO_MIN, PRIO_MAX);
18 break;
+51,
-14
1@@ -73,7 +73,7 @@ nl(const char *fname, FILE *fp)
2 if (line.data[0] != '\n')
3 donumber = 1;
4 break;
5- // ?man -p: preserve file attributes
6+ /* pexpr line-matching mode */
7 case 'p':
8 if (!regexec(preg + section, line.data, 0, NULL, 0))
9 donumber = 1;
10@@ -118,8 +118,20 @@ getlinetype(char *type, regex_t *preg)
11 return type[0];
12 }
13
14-// ?man nl: number lines
15-// ?man number the lines of files
16+// ?man nl: line numbering filter
17+// ?man nl reads lines from file and writes them to stdout, numbering non-empty lines.
18+// ?man If no file is given nl reads from stdin.
19+// ?man nl treats the input text as a collection of logical pages divided into
20+// ?man logical page sections.
21+// ?man Each logical page consists of a header section, a body
22+// ?man section and a footer section.
23+// ?man Sections may be empty.
24+// ?man The start of each section is indicated by a single delimiting line, one of:
25+// ?man ::: header
26+// ?man :: body
27+// ?man : footer
28+// ?man If the input text contains no delimiting line then all of the input text
29+// ?man belongs to a single logical page body section.
30 int
31 main(int argc, char *argv[])
32 {
33@@ -129,7 +141,9 @@ main(int argc, char *argv[])
34 char *d, *formattype, *formatblit;
35
36 ARGBEGIN {
37- // ?man -d:str: specify directory
38+ // ?man -d:delim: Set delim as the delimiter for logical pages.
39+ // ?man If delim is only one character, nl appends \":\" to it.
40+ // ?man The default is \"\\:\".
41 case 'd':
42 switch (utflen((d = EARGF(usage())))) {
43 case 0:
44@@ -148,27 +162,47 @@ main(int argc, char *argv[])
45 break;
46 }
47 break;
48- // ?man -f:str: force the operation
49+ // ?man -f:type: Define which lines to number in the head | body | footer section:
50+ // ?man a All lines.
51+ // ?man n No lines.
52+ // ?man t Only non-empty lines.
53+ // ?man This is the default.
54+ // ?man pexpr Only lines matching expr according to regex 7 or re_format 7 .
55 case 'f':
56 type[0] = getlinetype(EARGF(usage()), preg);
57 break;
58- // ?man -b:str: specify block size or base directory
59+ // ?man -b:type: Define which lines to number in the head | body | footer section:
60+ // ?man a All lines.
61+ // ?man n No lines.
62+ // ?man t Only non-empty lines.
63+ // ?man This is the default.
64+ // ?man pexpr Only lines matching expr according to regex 7 or re_format 7 .
65 case 'b':
66 type[1] = getlinetype(EARGF(usage()), preg + 1);
67 break;
68- // ?man -h:str: suppress headers or print help
69+ // ?man -h:type: Define which lines to number in the head | body | footer section:
70+ // ?man a All lines.
71+ // ?man n No lines.
72+ // ?man t Only non-empty lines.
73+ // ?man This is the default.
74+ // ?man pexpr Only lines matching expr according to regex 7 or re_format 7 .
75 case 'h':
76 type[2] = getlinetype(EARGF(usage()), preg + 2);
77 break;
78- // ?man -i:num: interactive mode or prompt for confirmation
79+ // ?man -i:num: Set the increment between numbered lines to num .
80 case 'i':
81 incr = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
82 break;
83- // ?man -l:num: list in long format
84+ // ?man -l:num: Set the number of adjacent blank lines to be considered as one to num .
85+ // ?man The default is 1.
86 case 'l':
87 blines = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
88 break;
89- // ?man -n:str: print line numbers or counts
90+ // ?man -n:format: Set the line number output format to one of:
91+ // ?man ln Left justified.
92+ // ?man rn Right justified.
93+ // ?man This is the default.
94+ // ?man rz Right justified with leading zeroes.
95 case 'n':
96 formattype = EARGF(usage());
97 estrlcpy(format, "%", sizeof(format));
98@@ -186,20 +220,23 @@ main(int argc, char *argv[])
99 estrlcat(format, formatblit, sizeof(format));
100 estrlcat(format, "*ld", sizeof(format));
101 break;
102- // ?man -p: preserve file attributes
103+ // ?man -p: Do not reset line number for logical pages.
104 case 'p':
105 pflag = 1;
106 break;
107- // ?man -s:str: silent mode or print summary
108+ // ?man -s:sep: Use sep to separate line numbers and lines.
109+ // ?man The default is \"\\t\".
110 case 's':
111 sep = EARGF(usage());
112 seplen = unescape(sep);
113 break;
114- // ?man -v:num: verbose mode; show progress
115+ // ?man -v:num: Start counting lines from num .
116+ // ?man The default is 1.
117 case 'v':
118 startnum = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
119 break;
120- // ?man -w:num: wait for completion
121+ // ?man -w:num: Set the width of the line number to num .
122+ // ?man The default is 6.
123 case 'w':
124 width = estrtonum(EARGF(usage()), 1, INT_MAX);
125 break;
+1,
-1
1@@ -17,7 +17,7 @@ usage(void)
2 }
3
4 // ?man nohup: run command immune to hangups
5-// ?man arguments: cmd [arg ...
6+// ?man arguments: cmd [arg ...]
7 // ?man run a command that persists after logging out
8 int
9 main(int argc, char *argv[])
+35,
-26
1@@ -61,7 +61,7 @@ printchunk(const unsigned char *s, unsigned char format, size_t len)
2 };
3
4 switch (format) {
5- // ?man -a: print or show all entries
6+ // equivalent to -t a
7 case 'a':
8 c = *s & ~128; /* clear high bit as required by standard */
9 if (c < LEN(namedict) || c == 127) {
10@@ -70,7 +70,7 @@ printchunk(const unsigned char *s, unsigned char format, size_t len)
11 printf(" %3c", c);
12 }
13 break;
14- // ?man -c: print count or perform stdout action
15+ // equivalent to -t c
16 case 'c':
17 if (strchr("\a\b\t\n\v\f\r\0", *s)) {
18 printf(" %3s", escdict[*s]);
19@@ -214,8 +214,9 @@ usage(void)
20 "[-j skip] [-t outputformat] [file ...]\n", argv0);
21 }
22
23-// ?man od: dump files in formats
24-// ?man display file contents in octal, hex, or other formats
25+// ?man od: octal dump
26+// ?man od writes an octal dump of each file to stdout.
27+// ?man If no file is given od reads from stdin.
28 int
29 main(int argc, char *argv[])
30 {
31@@ -226,65 +227,72 @@ main(int argc, char *argv[])
32 big_endian = (*(uint16_t *)"\0\xff" == 0xff);
33
34 ARGBEGIN {
35- // ?man -A:str: specify option flag
36+ // ?man -A:addressformat: addressformat is one of d|o|x|n and sets the address to be
37+ // ?man either in decimal, octal, hexadecimal or not printed at all.
38+ // ?man The default is octal.
39 case 'A':
40 s = EARGF(usage());
41 if (strlen(s) != 1 || !strchr("doxn", s[0]))
42 usage();
43 addr_format = s[0];
44 break;
45- // ?man -b: specify block size or base directory
46+ // ?man -b: Equivalent to -t o1 .
47 case 'b':
48 addtype('o', 1);
49 break;
50- // ?man -d: specify directory
51+ // ?man -d: Equivalent to -t u2 .
52 case 'd':
53 addtype('u', 2);
54 break;
55 #if FEATURE_OD_ENDIAN
56- // ?man -E: specify option flag
57+ // ?man -E: Force Little Endian ( e ) or Big Endian ( E ) system-independently.
58 case 'E':
59- // ?man -e: specify expression or pattern
60+ // ?man -e: Force Little Endian ( e ) or Big Endian ( E ) system-independently.
61 case 'e':
62 big_endian = (ARGC() == 'E');
63 break;
64 #endif
65- // ?man -j:str: specify option flag
66+ // ?man -j:skip: Ignore the first skip bytes of input.
67 case 'j':
68 if ((skip = parseoffset(EARGF(usage()))) < 0)
69 usage();
70 break;
71- // ?man -N:str: specify option flag
72+ // ?man -N:num: read at most num bytes of input
73 case 'N':
74 if ((max = parseoffset(EARGF(usage()))) < 0)
75 usage();
76 break;
77- // ?man -o: specify output file
78+ // ?man -o: Equivalent to -t o2 .
79 case 'o':
80 addtype('o', 2);
81 break;
82- // ?man -s: silent mode or print summary
83+ // ?man -s: Equivalent to -t d2 .
84 case 's':
85 addtype('d', 2);
86 break;
87- // ?man -t:str: sort or specify timestamp
88+ // ?man -t:outputformat: outputformat is a list of a|c|d|o|u|x followed by a digit or C|S|I|L and sets
89+ // ?man the content to be in named character, character, signed
90+ // ?man decimal, octal, unsigned decimal, or
91+ // ?man hexadecimal format, processing the given amount of bytes or the length
92+ // ?man of Char, Short, Integer or Long.
93+ // ?man The default is octal with 4 bytes.
94 case 't':
95 s = EARGF(usage());
96 for (; *s; s++) {
97 switch (*s) {
98- // ?man -a: print or show all entries
99+ /* outputformat a is equivalent to -t a */
100 case 'a':
101- // ?man -c: print count or perform stdout action
102+ /* outputformat c is equivalent to -t c */
103 case 'c':
104 addtype(*s, 1);
105 break;
106- // ?man -d: specify directory
107+ /* outputformat d is equivalent to -t d */
108 case 'd':
109- // ?man -o: specify output file
110+ /* outputformat o is equivalent to -t o */
111 case 'o':
112- // ?man -u: unbuffered output
113+ /* outputformat u is equivalent to -t u */
114 case 'u':
115- // ?man -x: hex format or match whole lines
116+ /* outputformat x is equivalent to -t x */
117 case 'x':
118 fmt_char = *s;
119 if (isdigit((unsigned char)*(s + 1))) {
120@@ -292,22 +300,22 @@ main(int argc, char *argv[])
121 s = end - 1;
122 } else {
123 switch (*(s + 1)) {
124- // ?man -C: specify option flag
125+ /* outputformat C uses sizeof(char) bytes */
126 case 'C':
127 len = sizeof(char);
128 s++;
129 break;
130- // ?man -S: specify option flag
131+ /* outputformat S uses sizeof(short) bytes */
132 case 'S':
133 len = sizeof(short);
134 s++;
135 break;
136- // ?man -I: specify option flag
137+ /* outputformat I uses sizeof(int) bytes */
138 case 'I':
139 len = sizeof(int);
140 s++;
141 break;
142- // ?man -L: specify option flag
143+ /* outputformat L uses sizeof(long) bytes */
144 case 'L':
145 len = sizeof(long);
146 s++;
147@@ -323,11 +331,12 @@ main(int argc, char *argv[])
148 }
149 }
150 break;
151- // ?man -v: verbose mode; show progress
152+ // ?man -v: Always set.
153+ // ?man Write all input data, including duplicate lines.
154 case 'v':
155 /* always set, use uniq(1) to handle duplicate lines */
156 break;
157- // ?man -x: hex format or match whole lines
158+ // ?man -x: Equivalent to -t x2 .
159 case 'x':
160 addtype('x', 2);
161 break;
+2,
-2
1@@ -99,11 +99,11 @@ main(int argc, char *argv[])
2 char *delim = "\t";
3
4 ARGBEGIN {
5- // ?man -s: silent mode or print summary
6+ // ?man -s: read each file sequentially instead of in parallel
7 case 's':
8 seq = 1;
9 break;
10- // ?man -d:str: specify directory
11+ // ?man -d:list: replace newlines using escaped characters from list
12 case 'd':
13 delim = EARGF(usage());
14 delim_bytelen = unescape(delim);
+2,
-2
1@@ -89,11 +89,11 @@ main(int argc, char *argv[])
2 int ret = 0;
3
4 ARGBEGIN {
5- // ?man -p: preserve file attributes
6+ // ?man -p: check pathnames for portability across POSIX systems
7 case 'p':
8 most = 1;
9 break;
10- // ?man -P: specify option flag
11+ // ?man -P: also reject empty pathnames and leading hyphens
12 case 'P':
13 extra = 1;
14 break;
+28,
-33
1@@ -846,11 +846,9 @@ readpax(struct bufio *f, struct header *h)
2 case 'g':
3 readexthdr(f, &globexthdr, h->size);
4 break;
5- // ?man -x: hex format or match whole lines
6 case 'x':
7 readexthdr(f, &exthdr, h->size);
8 break;
9- // ?man -L: specify option flag
10 case 'L':
11 if ((exthdr.delete | opt.delete) & PATH)
12 break;
13@@ -2084,9 +2082,7 @@ parsereplstr(char *str)
14 for (;;) {
15 switch (*++str) {
16 case 'g': r->global = 1; break;
17- // ?man -p: preserve file attributes
18 case 'p': r->print = 1; break;
19- // ?man -s: silent mode or print summary
20 case 's': r->symlink = 0; break;
21 case 'S': r->symlink = 1; break;
22 case 0: goto done;
23@@ -2333,6 +2329,10 @@ handle_append(const char *filename, const char *algo, const char *format)
24 }
25
26 // ?man pax: portable archive interchange
27+// ?man synopsis: [-cdiknuvX] [-f archive] [-o options] [-p string] [-s replstr] [pattern ...]
28+// ?man synopsis: -r [-cdiknuvX] [-f archive] [-o options] [-p string] [-s replstr] [pattern ...]
29+// ?man synopsis: -w [-adituvX] [-b blocksize] [-f archive] [-o options] [-x format] [-j | -J | -z] [file ...]
30+// ?man synopsis: -rw [-diklntuvX] [-H | -L] [-p string] [-s replstr] [file ...] directory
31 // ?man read, write, and list member files of archive files
32 int
33 main(int argc, char *argv[])
34@@ -2349,113 +2349,108 @@ main(int argc, char *argv[])
35 size_t l;
36
37 ARGBEGIN {
38- // ?man -a: print or show all entries
39+ // ?man -a: append files to the end of an existing archive
40 case 'a':
41 aflag = 1;
42 break;
43- // ?man -b:str: specify block size or base directory
44+ // ?man -b:blocksize: accepted for compatibility; block size is currently ignored
45 case 'b':
46 EARGF(usage());
47 break;
48- // ?man -c: print count or perform stdout action
49+ // ?man -c: invert the sense of path matching
50 case 'c':
51 cflag = 1;
52 break;
53- // ?man -d: specify directory
54+ // ?man -d: do not match pathnames of directory entries against patterns
55 case 'd':
56 dflag = 1;
57 break;
58- // ?man -f:str: force the operation
59+ // ?man -f:archive: read from or write to archive instead of standard input or output
60 case 'f':
61 name = EARGF(usage());
62 break;
63- // ?man -H: specify option flag
64+ // ?man -H: follow symbolic links named on the command line
65 case 'H':
66 follow = 'H';
67 break;
68- // ?man -i: interactive mode or prompt for confirmation
69+ // ?man -i: rename or skip files interactively
70 case 'i':
71 iflag = 1;
72 break;
73- // ?man -j: specify option flag
74+ // ?man -j: compress or decompress the archive with bzip2
75 case 'j':
76 algo = "bzip2";
77 break;
78- // ?man -J: specify option flag
79+ // ?man -J: compress or decompress the archive with xz
80 case 'J':
81 algo = "xz";
82 break;
83- // ?man -k: specify option flag
84+ // ?man -k: do not overwrite existing files
85 case 'k':
86 kflag = 1;
87 break;
88- // ?man -l: list in long format
89+ // ?man -l: link files instead of copying them during pass mode when possible
90 case 'l':
91 lflag = 1;
92 break;
93- // ?man -L: specify option flag
94+ // ?man -L: follow all symbolic links
95 case 'L':
96 follow = 'L';
97 break;
98- // ?man -n: print line numbers or counts
99+ // ?man -n: use only the first archive member that matches each pattern
100 case 'n':
101 nflag = 1;
102 break;
103- // ?man -o:str: specify output file
104+ // ?man -o:options: set format-specific archive options
105 case 'o':
106 parseopts(EARGF(usage()));
107 break;
108- // ?man -p:str: preserve file attributes
109+ // ?man -p:string: control which file attributes are preserved in pass mode
110 case 'p':
111 for (arg = EARGF(usage()); *arg; ++arg) {
112 switch (*arg) {
113- // ?man -a: print or show all entries
114 case 'a': preserve &= ~ATIME; break;
115- // ?man -e: specify expression or pattern
116 case 'e': preserve = ~0; break;
117- // ?man -m: specify mode or limit
118 case 'm': preserve &= ~MTIME; break;
119- // ?man -o: specify output file
120 case 'o': preserve |= UID | GID; break;
121- // ?man -p: preserve file attributes
122 case 'p': preserve |= MODE; break;
123 default: fatal("unknown -p option");
124 }
125 }
126 break;
127- // ?man -r: operate recursively
128+ // ?man -r: read an archive and extract matching members
129 case 'r':
130 mode |= READ;
131 break;
132- // ?man -s:str: silent mode or print summary
133+ // ?man -s:replstr: rename archive members or files using a substitution expression
134 case 's':
135 parsereplstr(EARGF(usage()));
136 break;
137- // ?man -t: sort or specify timestamp
138+ // ?man -t: preserve access times of files read by pax
139 case 't':
140 tflag = 1;
141 break;
142- // ?man -u: unbuffered output
143+ // ?man -u: copy or extract only files newer than existing destination files
144 case 'u':
145 uflag = 1;
146 break;
147- // ?man -v: verbose mode; show progress
148+ // ?man -v: write verbose filenames while processing files
149 case 'v':
150 vflag = 1;
151 break;
152- // ?man -w: wait for completion
153+ // ?man -w: write an archive or enter pass mode
154 case 'w':
155 mode |= WRITE;
156 break;
157- // ?man -x:str: hex format or match whole lines
158+ // ?man -x:format: use the archive format format
159 case 'x':
160 format = EARGF(usage());
161 break;
162- // ?man -X: specify option flag
163+ // ?man -X: stay on the current file system when descending directories
164 case 'X':
165 Xflag = 1;
166 break;
167- // ?man -z: specify option flag
168+ // ?man -z: compress or decompress the archive with gzip
169 case 'z':
170 algo = "gzip";
171 break;
+1,
-1
1@@ -75,7 +75,7 @@ usage(void)
2 }
3
4 // ?man printf: format and print data
5-// ?man arguments: format [arg ...
6+// ?man arguments: format [arg ...]
7 // ?man format and print arguments to standard output
8 int
9 main(int argc, char *argv[])
+7,
-7
1@@ -150,29 +150,29 @@ usage(void)
2 eprintf("usage: %s [-aAdef]\n", argv0);
3 }
4
5-// ?man ps: report process status
6-// ?man display information about active system processes
7+// ?man ps: display process status
8+// ?man ps displays information about active processes. When given no options, ps prints information about processes of the current user that has a controlling terminal.
9 int
10 main(int argc, char *argv[])
11 {
12 ARGBEGIN {
13- // ?man -a: print or show all entries
14+ // ?man -a: Select all processes except both session leaders and processes not associated with a terminal.
15 case 'a':
16 flags |= PS_aflag;
17 break;
18- // ?man -A: specify option flag
19+ // ?man -A: Select all processes. Identical to -e.
20 case 'A':
21 flags |= PS_Aflag;
22 break;
23- // ?man -d: specify directory
24+ // ?man -d: Select all processes except session leaders.
25 case 'd':
26 flags |= PS_dflag;
27 break;
28- // ?man -e: specify expression or pattern
29+ // ?man -e: Select all processes. Identical to -A.
30 case 'e':
31 flags |= PS_Aflag;
32 break;
33- // ?man -f: force the operation
34+ // ?man -f: Do full-format listing.
35 case 'f':
36 flags |= PS_fflag;
37 break;
+2,
-2
1@@ -38,9 +38,9 @@ main(int argc, char *argv[])
2 char mode = 'L';
3
4 ARGBEGIN {
5- // ?man -L: specify option flag
6+ // ?man -L: print the logical path using PWD when possible
7 case 'L':
8- // ?man -P: specify option flag
9+ // ?man -P: print the physical path with all symbolic links resolved
10 case 'P':
11 mode = ARGC();
12 break;
+2,
-2
1@@ -35,12 +35,12 @@ main(int argc, char *argv[])
2 ARGBEGIN
3 {
4 #if FEATURE_READLINK_REALPATH
5- // ?man -f: force the operation
6+ // ?man -f: canonicalize path by following every symbolic link in its components
7 case 'f':
8 fflag = 1;
9 break;
10 #endif
11- // ?man -n: print line numbers or counts
12+ // ?man -n: do not print the trailing newline
13 case 'n':
14 nflag = 1;
15 break;
+4,
-4
1@@ -55,19 +55,19 @@ main(int argc, char *argv[])
2 int who;
3
4 ARGBEGIN {
5- // ?man -n:str: print line numbers or counts
6+ // ?man -n:num: change niceness by num
7 case 'n':
8 adj = EARGF(usage());
9 break;
10- // ?man -g: specify option flag
11+ // ?man -g: treat each id as a process group ID
12 case 'g':
13 which = PRIO_PGRP;
14 break;
15- // ?man -p: preserve file attributes
16+ // ?man -p: treat each id as a process ID
17 case 'p':
18 which = PRIO_PROCESS;
19 break;
20- // ?man -u: unbuffered output
21+ // ?man -u: treat each id as a user name or user ID
22 case 'u':
23 which = PRIO_USER;
24 break;
+81,
-8
1@@ -1750,8 +1750,10 @@ old_next(void)
2 }
3
4 // ?man sed: stream editor
5-// ?man arguments: script [file ...
6-// ?man stream editor for filtering and transforming text
7+// ?man synopsis: [-nrE] script [file ...]
8+// ?man synopsis: [-nrE] -e script [-e script] ... [-f scriptfile] ... [file ...]
9+// ?man synopsis: [-nrE] [-e script] ... -f scriptfile [-f scriptfile] ... [file ...]
10+// ?man sed reads line oriented output from file or stdin, applies the editing commands supplied by script or scriptfile and writes the edited stream to stdout.
11 int
12 main(int argc, char *argv[])
13 {
14@@ -1759,30 +1761,30 @@ main(int argc, char *argv[])
15 int script = 0;
16
17 ARGBEGIN {
18- // ?man -n: print line numbers or counts
19+ // ?man -n: Suppress default printing at the end of each cycle.
20 case 'n':
21 gflags.n = 1;
22 break;
23- // ?man -r: operate recursively
24+ // ?man -r: Use extended regular expressions
25 case 'r':
26- // ?man -E: specify option flag
27+ // ?man -E: Use extended regular expressions
28 case 'E':
29 gflags.E = 1;
30 break;
31- // ?man -e:str: specify expression or pattern
32+ // ?man -e:script: Append script to the list of editing commands.
33 case 'e':
34 arg = EARGF(usage());
35 compile(arg, 0);
36 script = 1;
37 break;
38- // ?man -f:str: force the operation
39+ // ?man -f:scriptfile: Append the commands from scriptfile to the list of editing commands.
40 case 'f':
41 arg = EARGF(usage());
42 compile(arg, 1);
43 script = 1;
44 break;
45 #if FEATURE_SED_INPLACE
46- // ?man -i: interactive mode or prompt for confirmation
47+ // ?man -i: edit files in place
48 case 'i':
49 iflag = 1;
50 if (argv[0][1] != '\0') {
51@@ -1796,6 +1798,77 @@ main(int argc, char *argv[])
52 default : usage();
53 } ARGEND
54
55+ // ?man ## Extended description
56+ // ?man Editing commands take the form
57+ // ?man
58+ // ?man `[address[,address]]function`
59+ // ?man
60+ // ?man ### Addresses
61+ // ?man Addresses are either blank, a positive decimal integer denoting a line
62+ // ?man number, the character '$' denoting the last line of input, or a regular
63+ // ?man expression.
64+ // ?man A command with no addresses matches every line, one address matches
65+ // ?man individual lines, and two addresses matches a range of lines from the
66+ // ?man first to the second address inclusive.
67+ // ?man ### Functions
68+ // ?man `a [text]`
69+ // ?man : Append text to output after end of current cycle.
70+ // ?man `b [label]`
71+ // ?man : Branch to label. If no label is provided branch to end of script.
72+ // ?man `c [text]`
73+ // ?man : Change. Delete addressed range and output text after end of current cycle.
74+ // ?man `d`
75+ // ?man : Delete pattern space and begin next cycle.
76+ // ?man `D`
77+ // ?man : Delete pattern space up to and including first newline and begin new cycle without reading input. If there is no newline, behave like d.
78+ // ?man `g`
79+ // ?man : Get. Replace the pattern space with the hold space.
80+ // ?man `G`
81+ // ?man : Get. Append a newline and the hold space to the pattern space.
82+ // ?man `h`
83+ // ?man : Hold. Replace the hold space with the pattern space.
84+ // ?man `H`
85+ // ?man : Hold. Append a newline and the pattern space to the hold space.
86+ // ?man `i [text]`
87+ // ?man : Insert text in output.
88+ // ?man `l`
89+ // ?man : List? Write the pattern space replacing known non printing characters with backslash escaped versions (`\\\\`, `\\a`, `\\b`, `\\f`, `\\r`, `\\t`, `\\v`). Print bad UTF-8 sequences as `\\ooo` where ooo is a three digit octal number. Mark end of lines with '$'.
90+ // ?man `n`
91+ // ?man : Next. Write pattern space (unless `-n`), read next line into pattern space, and continue current cycle. If there is no next line, quit.
92+ // ?man `N`
93+ // ?man : Next. Read next line, append newline and next line to pattern space, and continue cycle. If there is no next line, quit without printing current pattern space.
94+ // ?man `p`
95+ // ?man : Print current pattern space.
96+ // ?man `P`
97+ // ?man : Print current pattern space up to first newline.
98+ // ?man `q`
99+ // ?man : Quit.
100+ // ?man `r file`
101+ // ?man : Read file and write contents to output.
102+ // ?man `s/re/text/flags`
103+ // ?man : Find occurences of regular expression re in the pattern space and replace with text. A '&' in text is replaced with the entire match. A `\\d` where d is a decimal digit 1-9 is replaced with the corresponding match group from the regular expression. `\\n` represents a newline in both the regular expression and replacement text. A literal newline in the replacement text must be preceded by a `\\`.
104+ // ?man ### Flags
105+ // ?man `n`
106+ // ?man : A positive decimal number denoting which match in the pattern space to replace.
107+ // ?man `g`
108+ // ?man : Global. Replace all matches in the pattern space.
109+ // ?man `p`
110+ // ?man : Print the pattern if a replacement was made.
111+ // ?man `w file`
112+ // ?man : Write the pattern space to file if a replacement was made.
113+ // ?man `t [label]`
114+ // ?man : Test. Branch to corresponding label if a substitution has been made since the last line was read or last t command was executed. If no label is provided branch to end of script.
115+ // ?man `w file`
116+ // ?man : Write pattern space to file.
117+ // ?man `x`
118+ // ?man : Exchange hold space and pattern space.
119+ // ?man `y/set1/set2/`
120+ // ?man : Replace each occurrence of a character from set 1 with the corresponding character from set 2.
121+ // ?man `:label`
122+ // ?man : Create a label for b and t commands.
123+ // ?man `=`
124+ // ?man : Write current input line number to output.
125+
126 /* no script to run */
127 if (!script && !argc)
128 usage();
+33,
-23
1@@ -305,27 +305,27 @@ parse_flags(char **s, int *flags, int bflag)
2 {
3 while (isalpha((int)**s)) {
4 switch (*((*s)++)) {
5- // ?man -b: specify block size or base directory
6+ /* parse key-specific b modifier */
7 case 'b':
8 *flags |= bflag;
9 break;
10- // ?man -d: specify directory
11+ /* parse key-specific d modifier */
12 case 'd':
13 *flags |= MOD_D;
14 break;
15- // ?man -f: force the operation
16+ /* parse key-specific f modifier */
17 case 'f':
18 *flags |= MOD_F;
19 break;
20- // ?man -i: interactive mode or prompt for confirmation
21+ /* parse key-specific i modifier */
22 case 'i':
23 *flags |= MOD_I;
24 break;
25- // ?man -n: print line numbers or counts
26+ /* parse key-specific n modifier */
27 case 'n':
28 *flags |= MOD_N;
29 break;
30- // ?man -r: operate recursively
31+ /* parse key-specific r modifier */
32 case 'r':
33 *flags |= MOD_R;
34 break;
35@@ -396,8 +396,9 @@ usage(void)
36 }
37
38 // ?man sort: sort lines
39-// ?man arguments: -Cbcdfimnru
40-// ?man sort or merge lines of text files
41+// ?man synopsis: [-Cbcdfimnru] [-o outfile] [-t delim] [-k key ...] [file ...]
42+// ?man sort writes the sorted concatenation of each file to stdout.
43+// ?man If no file is given sort reads from stdin.
44 int
45 main(int argc, char *argv[])
46 {
47@@ -408,35 +409,44 @@ main(int argc, char *argv[])
48 char *outfile = NULL;
49
50 ARGBEGIN {
51- // ?man -C: specify option flag
52+ // ?man -C: Check that the concatenation of the given files is sorted rather than sorting them.
53+ // ?man In this mode, no output is printed to stdout, and the exit status indicates the result of the check.
54 case 'C':
55 Cflag = 1;
56 break;
57- // ?man -b: specify block size or base directory
58+ // ?man -b: Skip leading whitespace of columns when sorting.
59 case 'b':
60 global_flags |= MOD_STARTB | MOD_ENDB;
61 break;
62- // ?man -c: print count or perform stdout action
63+ // ?man -c: The same as -C except that when disorder is detected, a message is written to stderr indicating the location of the disorder.
64 case 'c':
65 cflag = 1;
66 break;
67- // ?man -d: specify directory
68+ // ?man -d: Skip non-whitespace and non-alphanumeric characters.
69 case 'd':
70 global_flags |= MOD_D;
71 break;
72- // ?man -f: force the operation
73+ // ?man -f: Ignore letter case when sorting.
74 case 'f':
75 global_flags |= MOD_F;
76 break;
77- // ?man -i: interactive mode or prompt for confirmation
78+ // ?man -i: Skip non-printable characters.
79 case 'i':
80 global_flags |= MOD_I;
81 break;
82- // ?man -k:str: specify option flag
83+ // ?man -k:key: Specify a key definition of the form S[.s][f][,E[.e][f]] where S,
84+ // ?man s, E and e are the starting column, starting character in that column,
85+ // ?man ending column and the ending character of that column respectively.
86+ // ?man If they are not specified, s refers to the first character of the
87+ // ?man specified starting column, E refers to the last column of every line,
88+ // ?man and e refers to the last character of the ending column.
89+ // ?man f can be used to specify options (n, b) that only apply to this key
90+ // ?man definition.
91+ // ?man b is special in that it only applies to the column that it was specified after.
92 case 'k':
93 addkeydef(EARGF(usage()), global_flags);
94 break;
95- // ?man -m: specify mode or limit
96+ // ?man -m: Assume sorted input, merge only.
97 case 'm':
98 /* more or less for free, but for performance-reasons,
99 * we should keep this flag in mind and maybe some later
100@@ -444,37 +454,37 @@ main(int argc, char *argv[])
101 * while merging large sorted files.
102 */
103 break;
104- // ?man -n: print line numbers or counts
105+ // ?man -n: Perform a numeric sort.
106 case 'n':
107 global_flags |= MOD_N;
108 break;
109- // ?man -o:file: specify output file
110+ // ?man -o:outfile: Write output to outfile rather than stdout.
111 case 'o':
112 outfile = EARGF(usage());
113 break;
114- // ?man -r: operate recursively
115+ // ?man -r: Reverses the sort.
116 case 'r':
117 global_flags |= MOD_R;
118 break;
119 #if FEATURE_SORT_STABLE
120- // ?man -s: silent mode or print summary
121+ // ?man -s: stabilize the sort by preserving the input order of equal lines
122 case 's':
123 sflag = 1;
124 break;
125 #endif
126- // ?man -t:str: sort or specify timestamp
127+ // ?man -t:delim: Set delim as the field delimiter.
128 case 't':
129 fieldsep = EARGF(usage());
130 if (!*fieldsep)
131 eprintf("empty delimiter\n");
132 fieldseplen = unescape(fieldsep);
133 break;
134- // ?man -u: unbuffered output
135+ // ?man -u: Print equal lines only once.
136 case 'u':
137 uflag = 1;
138 break;
139 #if FEATURE_SORT_BIG
140- // ?man -z: specify option flag
141+ // ?man -z: treat input as NUL-separated records
142 case 'z':
143 zflag = 1;
144 break;
+5,
-5
1@@ -47,7 +47,7 @@ usage(void)
2 }
3
4 // ?man split: split file into pieces
5-// ?man arguments: | -l num
6+// ?man arguments: [file] [prefix]
7 // ?man split a file into fixed-size pieces
8 int
9 main(int argc, char *argv[])
10@@ -58,11 +58,11 @@ main(int argc, char *argv[])
11 char name[NAME_MAX + 1], *prefix = "x", *file = NULL;
12
13 ARGBEGIN {
14- // ?man -a:num: print or show all entries
15+ // ?man -a:num: use num characters for generated filename suffixes
16 case 'a':
17 slen = estrtonum(EARGF(usage()), 0, INT_MAX);
18 break;
19- // ?man -b:str: specify block size or base directory
20+ // ?man -b:num: start a new file every num bytes
21 case 'b':
22 always = 1;
23 if ((size = parseoffset(EARGF(usage()))) < 0)
24@@ -70,12 +70,12 @@ main(int argc, char *argv[])
25 if (!size)
26 eprintf("size needs to be positive\n");
27 break;
28- // ?man -d: specify directory
29+ // ?man -d: use decimal suffixes instead of alphabetic ones
30 case 'd':
31 base = 10;
32 start = '0';
33 break;
34- // ?man -l:num: list in long format
35+ // ?man -l:num: start a new file every num lines
36 case 'l':
37 always = 0;
38 size = estrtonum(EARGF(usage()), 1, MIN(LLONG_MAX, SSIZE_MAX));
+5,
-5
1@@ -168,15 +168,15 @@ main(int argc, char *argv[])
2 int (*tail)(int, const char *, size_t) = taketail;
3
4 ARGBEGIN {
5- // ?man -f: force the operation
6+ // ?man -f: continue writing appended data as the file grows
7 case 'f':
8 fflag = 1;
9 break;
10- // ?man -c: print count or perform stdout action
11+ // ?man -c:num: display the last num bytes of each file
12 case 'c':
13- // ?man -m: specify mode or limit
14+ // ?man -m:num: display the last num characters of each file
15 case 'm':
16- // ?man -n:num: print line numbers or counts
17+ // ?man -n:num: display the last num lines of each file
18 case 'n':
19 mode = ARGC();
20 numstr = EARGF(usage());
21@@ -185,7 +185,7 @@ main(int argc, char *argv[])
22 if (strchr(numstr, '+'))
23 tail = dropinit;
24 break;
25- // ?man ARGNUM: specify RGNUM option
26+ // ?man -num: display the last num lines of each file
27 ARGNUM:
28 n = ARGNUMF();
29 break;
+2,
-2
1@@ -26,11 +26,11 @@ main(int argc, char *argv[])
2 char buf[BUFSIZ];
3
4 ARGBEGIN {
5- // ?man -a: print or show all entries
6+ // ?man -a: append to each file instead of overwriting it
7 case 'a':
8 aflag = O_APPEND;
9 break;
10- // ?man -i: interactive mode or prompt for confirmation
11+ // ?man -i: ignore SIGINT
12 case 'i':
13 iflag = 1;
14 break;
+2,
-2
1@@ -17,7 +17,7 @@ usage(void)
2 }
3
4 // ?man time: time command execution
5-// ?man arguments: cmd [arg ...
6+// ?man arguments: cmd [arg ...]
7 // ?man run a command and report its execution duration
8 int
9 main(int argc, char *argv[])
10@@ -29,7 +29,7 @@ main(int argc, char *argv[])
11 int status, savederrno, ret = 0;
12
13 ARGBEGIN {
14- // ?man -p: preserve file attributes
15+ // ?man -p: use the portable output format "real %f\\nuser %f\\nsys %f\\n"
16 case 'p':
17 break;
18 default:
+7,
-7
1@@ -116,26 +116,26 @@ main(int argc, char *argv[])
2 char *ref = NULL;
3
4 ARGBEGIN {
5- // ?man -a: print or show all entries
6+ // ?man -a: change only the access time
7 case 'a':
8 aflag = 1;
9 break;
10- // ?man -c: print count or perform stdout action
11+ // ?man -c: do not create files that do not exist
12 case 'c':
13 cflag = 1;
14 break;
15- // ?man -d: specify directory
16+ // ?man -d: set the timestamp from a parsed date string
17 case 'd':
18- // ?man -t:str: sort or specify timestamp
19+ // ?man -t:time: set the timestamp from [[CC]YY]MMDDhhmm[.SS]
20 case 't':
21 times[0].tv_sec = parsetime(EARGF(usage()));
22 times[0].tv_nsec = 0;
23 break;
24- // ?man -m: specify mode or limit
25+ // ?man -m: change only the modification time
26 case 'm':
27 mflag = 1;
28 break;
29- // ?man -r:str: operate recursively
30+ // ?man -r:ref_file: use the timestamps from ref_file
31 case 'r':
32 ref = EARGF(usage());
33 if (stat(ref, &st) < 0)
34@@ -143,7 +143,7 @@ main(int argc, char *argv[])
35 times[0] = st.st_atim;
36 times[1] = st.st_mtim;
37 break;
38- // ?man -T:num: specify option flag
39+ // ?man -T:time: set the timestamp from seconds since the Unix epoch
40 case 'T':
41 times[0].tv_sec = estrtonum(EARGF(usage()), 0, LLONG_MAX);
42 times[0].tv_nsec = 0;
+5,
-5
1@@ -190,7 +190,7 @@ usage(void)
2 }
3
4 // ?man tr: translate characters
5-// ?man arguments: set1 [set2
6+// ?man arguments: set1 [set2]
7 // ?man translate, squeeze, or delete characters from standard input
8 int
9 main(int argc, char *argv[])
10@@ -200,17 +200,17 @@ main(int argc, char *argv[])
11 int ret = 0;
12
13 ARGBEGIN {
14- // ?man -c: print count or perform stdout action
15+ // ?man -c: use the complement of set1
16 case 'c':
17- // ?man -C: specify option flag
18+ // ?man -C: use the complement of set1
19 case 'C':
20 cflag = 1;
21 break;
22- // ?man -d: specify directory
23+ // ?man -d: delete input characters that match set1
24 case 'd':
25 dflag = 1;
26 break;
27- // ?man -s: silent mode or print summary
28+ // ?man -s: squeeze repeated output characters that match set2, or set1 if -d is set
29 case 's':
30 sflag = 1;
31 break;
+6,
-6
1@@ -22,27 +22,27 @@ main(int argc, char *argv[])
2 int mflag = 0, nflag = 0, rflag = 0, sflag = 0, vflag = 0;
3
4 ARGBEGIN {
5- // ?man -a: print or show all entries
6+ // ?man -a: print all available system information
7 case 'a':
8 mflag = nflag = rflag = sflag = vflag = 1;
9 break;
10- // ?man -m: specify mode or limit
11+ // ?man -m: print the machine hardware name
12 case 'm':
13 mflag = 1;
14 break;
15- // ?man -n: print line numbers or counts
16+ // ?man -n: print the network node hostname
17 case 'n':
18 nflag = 1;
19 break;
20- // ?man -r: operate recursively
21+ // ?man -r: print the operating system release
22 case 'r':
23 rflag = 1;
24 break;
25- // ?man -s: silent mode or print summary
26+ // ?man -s: print the operating system name
27 case 's':
28 sflag = 1;
29 break;
30- // ?man -v: verbose mode; show progress
31+ // ?man -v: print the operating system version
32 case 'v':
33 vflag = 1;
34 break;
+2,
-2
1@@ -141,13 +141,13 @@ main(int argc, char *argv[])
2 char *tl = "8";
3
4 ARGBEGIN {
5- // ?man -t:str: sort or specify timestamp
6+ // ?man -t:list: use list as the tab size or tab stops
7 case 't':
8 tl = EARGF(usage());
9 if (!*tl)
10 eprintf("tablist cannot be empty\n");
11 /* fallthrough */
12- // ?man -a: print or show all entries
13+ // ?man -a: convert spaces to tabs throughout each line, not just leading blanks
14 case 'a':
15 aflag = 1;
16 break;
+5,
-5
1@@ -109,23 +109,23 @@ main(int argc, char *argv[])
2 char *fname[2] = { "<stdin>", "<stdout>" };
3
4 ARGBEGIN {
5- // ?man -c: print count or perform stdout action
6+ // ?man -c: prefix each output line with its number of consecutive occurrences
7 case 'c':
8 countfmt = "%7ld ";
9 break;
10- // ?man -d: specify directory
11+ // ?man -d: print only duplicate lines
12 case 'd':
13 dflag = 1;
14 break;
15- // ?man -u: unbuffered output
16+ // ?man -u: print only unique lines
17 case 'u':
18 uflag = 1;
19 break;
20- // ?man -f:num: force the operation
21+ // ?man -f:num: ignore the first num fields when comparing lines
22 case 'f':
23 fskip = estrtonum(EARGF(usage()), 0, INT_MAX);
24 break;
25- // ?man -s:num: silent mode or print summary
26+ // ?man -s:num: ignore the first num characters when comparing lines
27 case 's':
28 sskip = estrtonum(EARGF(usage()), 0, INT_MAX);
29 break;
+1,
-1
1@@ -99,7 +99,7 @@ usage(void)
2 }
3
4 // ?man uuencode: encode binary file
5-// ?man arguments: file] name
6+// ?man arguments: [file] name
7 // ?man encode a binary file into ascii text
8 int
9 main(int argc, char *argv[])
+4,
-4
1@@ -79,19 +79,19 @@ main(int argc, char *argv[])
2 int ret = 0;
3
4 ARGBEGIN {
5- // ?man -c: print count or perform stdout action
6+ // ?man -c: print the number of bytes
7 case 'c':
8 cmode = 'c';
9 break;
10- // ?man -m: specify mode or limit
11+ // ?man -m: print the number of characters
12 case 'm':
13 cmode = 'm';
14 break;
15- // ?man -l: list in long format
16+ // ?man -l: print the number of lines
17 case 'l':
18 lflag = 1;
19 break;
20- // ?man -w: wait for completion
21+ // ?man -w: print the number of words
22 case 'w':
23 wflag = 1;
24 break;
+4,
-4
1@@ -17,8 +17,8 @@ usage(void)
2 eprintf("usage: %s [-ml]\n", argv0);
3 }
4
5-// ?man who: show logged in users
6-// ?man display a list of users currently logged into the system
7+// ?man who: print who has logged on
8+// ?man who prints a list of who has logged on, their controlling tty, and the time at which they logged on.
9 int
10 main(int argc, char *argv[])
11 {
12@@ -32,7 +32,7 @@ main(int argc, char *argv[])
13 time_t t;
14
15 ARGBEGIN {
16- // ?man -m: specify mode or limit
17+ // ?man -m: Only show users on current tty.
18 case 'm':
19 mflag = 1;
20 tty = ttyname(0);
21@@ -41,7 +41,7 @@ main(int argc, char *argv[])
22 if ((ttmp = strrchr(tty, '/')))
23 tty = ttmp+1;
24 break;
25- // ?man -l: list in long format
26+ // ?man -l: Print LOGIN processes as well.
27 case 'l':
28 lflag = 1;
29 break;
+29,
-14
1@@ -263,9 +263,21 @@ usage(void)
2 argv0);
3 }
4
5-// ?man xargs: build and run command lines
6-// ?man arguments: -n
7-// ?man execute commands built from standard input arguments
8+// ?man xargs: construct argument lists and execute command
9+// ?man synopsis: [-0prtx] [-E eofstr] [-I replstr] [-L maxlines] [-n num] [-P maxprocs] [-s num] [cmd [arg ...]]
10+// ?man xargs reads space, tab, newline and EOF delimited strings from stdin
11+// ?man and executes the specified cmd with the strings as arguments.
12+// ?man Any arguments specified on the command line are given to the command upon
13+// ?man each invocation, followed by some number of the arguments read from
14+// ?man stdin.
15+// ?man The command is repeatedly executed one or more times until stdin is exhausted.
16+// ?man Spaces, tabs and newlines may be embedded in arguments using single (`'')
17+// ?man or double (`"') quotes or backslashes ('\\').
18+// ?man Single quotes escape all non-single quote characters, excluding newlines, up
19+// ?man to the matching single quote.
20+// ?man Double quotes escape all non-double quote characters, excluding newlines, up
21+// ?man to the matching double quote.
22+// ?man Any single character, including newlines, may be escaped by a backslash.
23 int
24 main(int argc, char *argv[])
25 {
26@@ -283,42 +295,45 @@ main(int argc, char *argv[])
27
28 ARGBEGIN
29 {
30- // ?man -0: specify option flag
31+ // ?man -0: use NUL characters instead of blanks and newlines as argument separators
32 case '0':
33 nulflag = 1;
34 break;
35- // ?man -n:num: print line numbers or counts
36+ // ?man -n:num: Use at most num arguments per command line.
37 case 'n':
38 nflag = 1;
39 maxargs =
40 estrtonum(EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
41 break;
42- // ?man -p: preserve file attributes
43+ // ?man -p: prompt before running each constructed command line
44 case 'p':
45 pflag = 1;
46 break;
47- // ?man -r: operate recursively
48+ // ?man -r: Do not run the command if there are no arguments.
49+ // ?man Normally the command is executed at least once even if there are no arguments.
50 case 'r':
51 rflag = 1;
52 break;
53- // ?man -s:num: silent mode or print summary
54+ // ?man -s:num: Use at most num bytes per command line.
55 case 's':
56 argmaxsz =
57 estrtonum(EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
58 break;
59- // ?man -t: sort or specify timestamp
60+ // ?man -t: Write the command line to stderr before executing it.
61 case 't':
62 tflag = 1;
63 break;
64- // ?man -x: hex format or match whole lines
65+ // ?man -x: Terminate if the command line exceeds the system limit or the number of bytes given with the -s flag.
66 case 'x':
67 xflag = 1;
68 break;
69- // ?man -E:str: specify option flag
70+ // ?man -E:eofstr: Use eofstr as a logical EOF marker.
71 case 'E':
72 eofstr = EARGF(usage());
73 break;
74- // ?man -I:str: specify option flag
75+ // ?man -I:replstr: Use replstr as the placeholder for the argument.
76+ // ?man Sets the arguments count to 1 per command line.
77+ // ?man It also implies the option x.
78 case 'I':
79 Iflag = 1;
80 xflag = 1;
81@@ -326,13 +341,13 @@ main(int argc, char *argv[])
82 maxargs = 1;
83 replstr = EARGF(usage());
84 break;
85- // ?man -L:num: specify option flag
86+ // ?man -L:maxlines: use at most maxlines input lines per command line
87 case 'L':
88 Lflag = 1;
89 maxlines =
90 estrtonum(EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
91 break;
92- // ?man -P:num: specify option flag
93+ // ?man -P:maxprocs: run up to maxprocs commands at the same time
94 case 'P':
95 maxprocs =
96 estrtonum(EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
+1,
-1
1@@ -14,7 +14,7 @@ usage(void)
2 }
3
4 // ?man chroot: run command in new root
5-// ?man arguments: dir [cmd [arg ...
6+// ?man arguments: dir [cmd [arg ...]]
7 // ?man run a command or shell with a substitute root directory
8 int
9 main(int argc, char *argv[])
+9,
-7
1@@ -31,8 +31,9 @@ usage(void)
2 eprintf("usage: %s [-Ccr] [-n level]\n", argv0);
3 }
4
5-// ?man dmesg: print kernel ring buffer
6-// ?man display or control the kernel ring buffer messages
7+// ?man dmesg: print or control the kernel ring buffer
8+// ?man dmesg examines or controls the kernel ring buffer.
9+// ?man By default it reads all the messages from the kernel ring buffer and prints them to stdout.
10 int
11 main(int argc, char *argv[])
12 {
13@@ -42,19 +43,20 @@ main(int argc, char *argv[])
14 long level;
15
16 ARGBEGIN {
17- // ?man -C: specify option flag
18+ // ?man -C: Clear the ring buffer.
19 case 'C':
20 if (clear_dmesg() < 0)
21 eprintf("clear_dmesg:");
22 return 0;
23- // ?man -c: print count or perform stdout action
24+ // ?man -c: Clear the ring buffer after printing its contents.
25 case 'c':
26 cflag = 1;
27 break;
28- // ?man -r: operate recursively
29+ // ?man -r: Print the raw message buffer.
30 case 'r':
31 break;
32- // ?man -n:num: print line numbers or counts
33+ // ?man -n:level: Set the console level.
34+ // ?man The log levels are defined in the file include/linux/kern_levels.h.
35 case 'n':
36 level = estrtol(EARGF(usage()), 10);
37 if (set_console_level(level) < 0)
38@@ -88,4 +90,4 @@ main(int argc, char *argv[])
39 return 1;
40
41 return 0;
42-}
43+}
+1,
-1
1@@ -18,7 +18,7 @@ usage(void)
2 }
3
4 // ?man flock: manage locks
5-// ?man arguments: file cmd [arg ...
6+// ?man arguments: file cmd [arg ...]
7 // ?man acquire or release locks from shell scripts
8 int
9 main(int argc, char *argv[])
+6,
-6
1@@ -23,27 +23,27 @@ usage(void)
2 eprintf("usage: %s [-bkmg]\n", argv0);
3 }
4
5-// ?man free: display memory usage
6-// ?man display the amount of free and used memory in the system
7+// ?man free: display amount of free and used memory in the system
8+// ?man free displays the total amount of free and used physical and swap memory in the system, as well as the buffers used by the kernel.
9 int
10 main(int argc, char *argv[])
11 {
12 struct MemInfo mi;
13
14 ARGBEGIN {
15- // ?man -b: specify block size or base directory
16+ // ?man -b: Display the amount of memory in bytes. This is the default.
17 case 'b':
18 unit_shift = 0;
19 break;
20- // ?man -k: specify option flag
21+ // ?man -k: Display the amount of memory in kilobytes.
22 case 'k':
23 unit_shift = 10;
24 break;
25- // ?man -m: specify mode or limit
26+ // ?man -m: Display the amount of memory in megabytes.
27 case 'm':
28 unit_shift = 20;
29 break;
30- // ?man -g: specify option flag
31+ // ?man -g: Display the amount of memory in gigabytes.
32 case 'g':
33 unit_shift = 30;
34 break;
+1,
-1
1@@ -27,7 +27,7 @@ usage(void)
2 }
3
4 // ?man getty: set terminal mode
5-// ?man arguments: tty] [term] [cmd] [args...
6+// ?man arguments: [tty] [term] [cmd] [args ...]
7 // ?man set terminal line discipline, speed, and mode
8 int
9 main(int argc, char *argv[])
+4,
-3
1@@ -69,9 +69,10 @@ usage(void)
2 eprintf("usage: %s [-p] username\n", argv0);
3 }
4
5-// ?man login: begin terminal session
6+// ?man login: log into the system
7 // ?man arguments: username
8-// ?man authenticate and start a session on the system
9+// ?man login logs the username into the system.
10+// ?man It sets HOME, SHELL, USER, LOGNAME and the PATH environment variables and invokes the login shell as specified in /etc/passwd.
11 int
12 main(int argc, char *argv[])
13 {
14@@ -83,7 +84,7 @@ main(int argc, char *argv[])
15 int pflag = 0;
16
17 ARGBEGIN {
18- // ?man -p: preserve file attributes
19+ // ?man -p: Preserve the environment.
20 case 'p':
21 pflag = 1;
22 break;
+3,
-3
1@@ -33,13 +33,13 @@ main(int argc, char *argv[])
2 uint8_t md[MD5_DIGEST_LENGTH];
3
4 ARGBEGIN {
5- // ?man -b: specify block size or base directory
6+ // ?man -b: accepted for compatibility; ignored
7 case 'b':
8- // ?man -t: sort or specify timestamp
9+ // ?man -t: accepted for compatibility; ignored
10 case 't':
11 /* ignore */
12 break;
13- // ?man -c: print count or perform stdout action
14+ // ?man -c: read checksums from files and verify them
15 case 'c':
16 cryptfunc = cryptcheck;
17 break;
+4,
-4
1@@ -27,11 +27,11 @@ main(int argc, char *argv[])
2 size_t len;
3
4 ARGBEGIN {
5- // ?man -d: specify directory
6+ // ?man -d: create a temporary directory instead of a file
7 case 'd':
8 dflag = 1;
9 break;
10- // ?man -p:dir: preserve file attributes
11+ // ?man -p:directory: use directory as the path prefix and imply -t
12 case 'p':
13 pflag = 1;
14 pdir = EARGF(usage());
15@@ -40,11 +40,11 @@ main(int argc, char *argv[])
16 case 'q':
17 qflag = 1;
18 break;
19- // ?man -t: sort or specify timestamp
20+ // ?man -t: generate a path rooted in a temporary directory
21 case 't':
22 tflag = 1;
23 break;
24- // ?man -u: unbuffered output
25+ // ?man -u: unlink the file before exiting
26 case 'u':
27 uflag = 1;
28 break;
+5,
-5
1@@ -28,9 +28,9 @@ usage(void)
2 eprintf("usage: %s [-o pid1,pid2,...pidN] [-s] [program...]\n", argv0);
3 }
4
5-// ?man pidof: find process ids
6-// ?man arguments: -o pid1
7-// ?man find the process identity numbers of running programs
8+// ?man pidof: find the process id of a running program
9+// ?man arguments: [program ...]
10+// ?man pidof finds the process id's of the named programs and prints them to stdout.
11 int
12 main(int argc, char *argv[])
13 {
14@@ -44,11 +44,11 @@ main(int argc, char *argv[])
15 struct pidentry *pe;
16
17 ARGBEGIN {
18- // ?man -s: silent mode or print summary
19+ // ?man -s: Single shot - this instructs the program to only return one process id.
20 case 's':
21 sflag = 1;
22 break;
23- // ?man -o:str: specify output file
24+ // ?man -o:pid: Tell pidof to omit processes with that process id. The special pid %PPID can be used to name the parent process of the pidof program.
25 case 'o':
26 oflag = 1;
27 arg = EARGF(usage());
+1,
-1
1@@ -32,7 +32,7 @@ usage(void)
2 }
3
4 // ?man respawn: restart command on exit
5-// ?man arguments: cmd [args...
6+// ?man arguments: cmd [args ...]
7 // ?man run a command and restart it automatically when it exits
8 int
9 main(int argc, char *argv[])
+3,
-3
1@@ -90,17 +90,17 @@ main(int argc, char *argv[])
2 const char *starts = "1", *steps = "1", *ends = "1", *sep = "\n";
3
4 ARGBEGIN {
5- // ?man -f:str: force the operation
6+ // ?man -f:fmt: use fmt as the output format
7 case 'f':
8 if (!validfmt(tmp=EARGF(usage())))
9 eprintf("%s: invalid format\n", tmp);
10 fmt = tmp;
11 break;
12- // ?man -s:str: silent mode or print summary
13+ // ?man -s:sep: print sep between output numbers
14 case 's':
15 sep = EARGF(usage());
16 break;
17- // ?man -w: wait for completion
18+ // ?man -w: pad all numbers to the same width
19 case 'w':
20 wflag = 1;
21 break;
+1,
-1
1@@ -15,7 +15,7 @@ usage(void)
2 }
3
4 // ?man setsid: run in new session
5-// ?man arguments: cmd [arg ...
6+// ?man arguments: cmd [arg ...]
7 // ?man run a program in a new session
8 int
9 main(int argc, char *argv[])
+3,
-3
1@@ -32,13 +32,13 @@ main(int argc, char *argv[])
2 uint8_t md[SHA1_DIGEST_LENGTH];
3
4 ARGBEGIN {
5- // ?man -b: specify block size or base directory
6+ // ?man -b: accepted for compatibility; ignored
7 case 'b':
8- // ?man -t: sort or specify timestamp
9+ // ?man -t: accepted for compatibility; ignored
10 case 't':
11 /* ignore */
12 break;
13- // ?man -c: print count or perform stdout action
14+ // ?man -c: read checksums from files and verify them
15 case 'c':
16 cryptfunc = cryptcheck;
17 break;
+3,
-3
1@@ -32,13 +32,13 @@ main(int argc, char *argv[])
2 uint8_t md[SHA224_DIGEST_LENGTH];
3
4 ARGBEGIN {
5- // ?man -b: specify block size or base directory
6+ // ?man -b: accepted for compatibility; ignored
7 case 'b':
8- // ?man -t: sort or specify timestamp
9+ // ?man -t: accepted for compatibility; ignored
10 case 't':
11 /* ignore */
12 break;
13- // ?man -c: print count or perform stdout action
14+ // ?man -c: read checksums from files and verify them
15 case 'c':
16 cryptfunc = cryptcheck;
17 break;
+3,
-3
1@@ -32,13 +32,13 @@ main(int argc, char *argv[])
2 uint8_t md[SHA256_DIGEST_LENGTH];
3
4 ARGBEGIN {
5- // ?man -b: specify block size or base directory
6+ // ?man -b: accepted for compatibility; ignored
7 case 'b':
8- // ?man -t: sort or specify timestamp
9+ // ?man -t: accepted for compatibility; ignored
10 case 't':
11 /* ignore */
12 break;
13- // ?man -c: print count or perform stdout action
14+ // ?man -c: read checksums from files and verify them
15 case 'c':
16 cryptfunc = cryptcheck;
17 break;
+3,
-3
1@@ -32,13 +32,13 @@ main(int argc, char *argv[])
2 uint8_t md[SHA384_DIGEST_LENGTH];
3
4 ARGBEGIN {
5- // ?man -b: specify block size or base directory
6+ // ?man -b: accepted for compatibility; ignored
7 case 'b':
8- // ?man -t: sort or specify timestamp
9+ // ?man -t: accepted for compatibility; ignored
10 case 't':
11 /* ignore */
12 break;
13- // ?man -c: print count or perform stdout action
14+ // ?man -c: read checksums from files and verify them
15 case 'c':
16 cryptfunc = cryptcheck;
17 break;
+3,
-3
1@@ -32,13 +32,13 @@ main(int argc, char *argv[])
2 uint8_t md[SHA512_224_DIGEST_LENGTH];
3
4 ARGBEGIN {
5- // ?man -b: specify block size or base directory
6+ // ?man -b: accepted for compatibility; ignored
7 case 'b':
8- // ?man -t: sort or specify timestamp
9+ // ?man -t: accepted for compatibility; ignored
10 case 't':
11 /* ignore */
12 break;
13- // ?man -c: print count or perform stdout action
14+ // ?man -c: read checksums from files and verify them
15 case 'c':
16 cryptfunc = cryptcheck;
17 break;
+3,
-3
1@@ -32,13 +32,13 @@ main(int argc, char *argv[])
2 uint8_t md[SHA512_256_DIGEST_LENGTH];
3
4 ARGBEGIN {
5- // ?man -b: specify block size or base directory
6+ // ?man -b: accepted for compatibility; ignored
7 case 'b':
8- // ?man -t: sort or specify timestamp
9+ // ?man -t: accepted for compatibility; ignored
10 case 't':
11 /* ignore */
12 break;
13- // ?man -c: print count or perform stdout action
14+ // ?man -c: read checksums from files and verify them
15 case 'c':
16 cryptfunc = cryptcheck;
17 break;
+3,
-3
1@@ -32,13 +32,13 @@ main(int argc, char *argv[])
2 uint8_t md[SHA512_DIGEST_LENGTH];
3
4 ARGBEGIN {
5- // ?man -b: specify block size or base directory
6+ // ?man -b: accepted for compatibility; ignored
7 case 'b':
8- // ?man -t: sort or specify timestamp
9+ // ?man -t: accepted for compatibility; ignored
10 case 't':
11 /* ignore */
12 break;
13- // ?man -c: print count or perform stdout action
14+ // ?man -c: read checksums from files and verify them
15 case 'c':
16 cryptfunc = cryptcheck;
17 break;
+12,
-12
1@@ -801,8 +801,8 @@ usage(void)
2 }
3
4 // ?man tar: tape archiver
5-// ?man arguments: x | t | -x | -t] [file ...
6-// ?man tar [c | -c] [-C dir] [-J | -Z | -a | -j | -z] [-h] [-T file] [-X file] path ... [-f file]
7+// ?man synopsis: [x | t | -x | -t] [-C dir] [-J | -Z | -a | -j | -z] [-m] [-p] [-f file] [file ...]
8+// ?man synopsis: [c | -c] [-C dir] [-J | -Z | -a | -j | -z] [-h] path ... [-f file]
9 // ?man manipulate tape archive files
10 int
11 main(int argc, char *argv[])
12@@ -834,32 +834,32 @@ main(int argc, char *argv[])
13 case 't':
14 mode = ARGC();
15 break;
16- // ?man -C:dir: specify option flag
17+ // ?man -C:dir: change to dir before processing files
18 case 'C':
19 dir = EARGF(usage());
20 break;
21- // ?man -f:file: specify archive file
22+ // ?man -f:file: use file as the archive instead of standard input or output
23 case 'f':
24 file = EARGF(usage());
25 break;
26- // ?man -m: specify mode or limit
27+ // ?man -m: do not preserve modification times when extracting
28 case 'm':
29 mflag = 1;
30 break;
31- // ?man -J: specify option flag
32+ // ?man -J: use xz compression or decompression
33 case 'J':
34- // ?man -Z: specify option flag
35+ // ?man -Z: use compress compression or decompression
36 case 'Z':
37- // ?man -a: print or show all entries
38+ // ?man -a: use lzma compression or decompression
39 case 'a':
40- // ?man -j: specify option flag
41+ // ?man -j: use bzip2 compression or decompression
42 case 'j':
43- // ?man -z: specify option flag
44+ // ?man -z: use gzip compression or decompression
45 case 'z':
46 filtermode = ARGC();
47 filtertool = filtertools[filtermode];
48 break;
49- // ?man -h: suppress headers or print help
50+ // ?man -h: follow symbolic links when archiving
51 case 'h':
52 #if FEATURE_TAR_CREATE
53 r.follow = 'L';
54@@ -869,7 +869,7 @@ main(int argc, char *argv[])
55 case 'v':
56 vflag = 1;
57 break;
58- // ?man -p: preserve file attributes
59+ // ?man -p: preserve permissions; this is the default behavior
60 case 'p':
61 break; /* do nothing as already default behaviour */
62 #if FEATURE_TAR_TO_STDOUT
+5,
-5
1@@ -16,9 +16,9 @@ usage(void)
2 eprintf("usage: %s [-c] -s size file...\n", argv0);
3 }
4
5-// ?man truncate: set file size
6-// ?man arguments: -s size file...
7-// ?man shrink or extend a file to a specified size
8+// ?man truncate: shrink or extend the size of a file to the specified size
9+// ?man synopsis: [-c] -s size file ...
10+// ?man truncate shrinks or extends the size of each file specified size. A file argument that does not exist is created. If a file is larger than the specified size, the extra data is lost. If a file is shorter, it is extended and the extended part reads as zero bytes.
11 int
12 main(int argc, char *argv[])
13 {
14@@ -27,12 +27,12 @@ main(int argc, char *argv[])
15 long size = 0;
16
17 ARGBEGIN {
18- // ?man -s:num: silent mode or print summary
19+ // ?man -s:size: Set or adjust the file size by size bytes.
20 case 's':
21 sflag = 1;
22 size = estrtol(EARGF(usage()), 10);
23 break;
24- // ?man -c: print count or perform stdout action
25+ // ?man -c: Do not create any files.
26 case 'c':
27 cflag = 1;
28 break;
+1,
-1
1@@ -80,7 +80,7 @@ main(int argc, char *argv[])
2 int found = 0, foundall = 1;
3
4 ARGBEGIN {
5- // ?man -a: print or show all entries
6+ // ?man -a: search all PATH directories instead of stopping at the first match
7 case 'a':
8 aflag = 1;
9 break;
+11,
-9
1@@ -85,7 +85,9 @@ usage(void)
2 }
3
4 // ?man xinstall: copy files and set attributes
5-// ?man arguments: (-d dir ... | (-t dest source ... | source ... dest))
6+// ?man synopsis: [-g group] [-o owner] [-m mode] -d dir ...
7+// ?man synopsis: [-g group] [-o owner] [-m mode] [-D] -t dest source ...
8+// ?man synopsis: [-g group] [-o owner] [-m mode] [-D] source ... dest
9 // ?man copy files and set their permissions and ownership
10 int
11 main(int argc, char *argv[])
12@@ -101,35 +103,35 @@ main(int argc, char *argv[])
13 char *p;
14
15 ARGBEGIN {
16- // ?man -c: print count or perform stdout action
17+ // ?man -c: accepted for compatibility; has no effect
18 case 'c':
19 /* no-op for compatibility */
20 break;
21- // ?man -d: specify directory
22+ // ?man -d: create directories instead of installing files
23 case 'd':
24 dflag = 1;
25 break;
26- // ?man -D: specify option flag
27+ // ?man -D: create leading directories for the destination before installing
28 case 'D':
29 Dflag = 1;
30 break;
31- // ?man -s: silent mode or print summary
32+ // ?man -s: accepted for compatibility; has no effect
33 case 's':
34 /* no-op for compatibility */
35 break;
36- // ?man -g:str: specify option flag
37+ // ?man -g:group: set the installed file group to group
38 case 'g':
39 gflag = EARGF(usage());
40 break;
41- // ?man -o:str: specify output file
42+ // ?man -o:owner: set the installed file owner to owner
43 case 'o':
44 oflag = EARGF(usage());
45 break;
46- // ?man -m:str: specify mode or limit
47+ // ?man -m:mode: set the installed file mode to mode
48 case 'm':
49 mflag = EARGF(usage());
50 break;
51- // ?man -t:str: sort or specify timestamp
52+ // ?man -t:dest: install source files into the destination directory dest
53 case 't':
54 tflag = EARGF(usage());
55 break;
+1,
-1
1@@ -24,7 +24,7 @@ usage(void)
2 }
3
4 // ?man mknod: create special files
5-// ?man arguments: name b|c|u major minor
6+// ?man arguments: name <type> major minor
7 // ?man create block or character special files
8 int
9 main(int argc, char *argv[])
+6,
-4
1@@ -25,9 +25,11 @@ usage(void)
2 eprintf("usage: %s [-lp] [username]\n", argv0);
3 }
4
5-// ?man su: run command with substitute user id
6+// ?man su: run a command with a substitute user and group ID
7 // ?man arguments: username
8-// ?man run a shell or command with another user id
9+// ?man su allows to run commands with a substitute user and group ID.
10+// ?man When called without arguments, su defaults to running an interactive shell as root.
11+// ?man For backward compatibility su defaults to not change the current directory and to only set the environment variables HOME and SHELL plus USER and LOGNAME if the target username is not root.
12 int
13 main(int argc, char *argv[])
14 {
15@@ -38,11 +40,11 @@ main(int argc, char *argv[])
16 uid_t uid;
17
18 ARGBEGIN {
19- // ?man -l: list in long format
20+ // ?man -l: Starts the shell as login shell with an environment similar to a real login.
21 case 'l':
22 lflag = 1;
23 break;
24- // ?man -p: preserve file attributes
25+ // ?man -p: Preserves the whole environment. This option is ignored if the -l option is specified.
26 case 'p':
27 pflag = 1;
28 break;
+0,
-1135
1@@ -1,1135 +0,0 @@
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-}
+0,
-250
1@@ -1,250 +0,0 @@
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-)
+0,
-154
1@@ -1,154 +0,0 @@
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-}
+0,
-1224
1@@ -1,1224 +0,0 @@
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-}
+0,
-918
1@@ -1,918 +0,0 @@
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-}
+0,
-306
1@@ -1,306 +0,0 @@
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-}
+0,
-17
1@@ -1,17 +0,0 @@
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-}
+9,
-402
1@@ -8,12 +8,9 @@ import (
2 "path/filepath"
3 "strings"
4 "time"
5-
6- "aruu/mkman/djot"
7 )
8
9 // Parse config.mk variables and values
10-//
11 type Config map[string]string
12
13 func parseConfigMk(path string) (Config, error) {
14@@ -75,7 +72,6 @@ func (cfg Config) isEnabled(key string) bool {
15 }
16
17 // Preprocessor condition stack for nested feature blocks
18-//
19 type ifFrame struct {
20 active bool
21 seen bool
22@@ -121,7 +117,6 @@ func (s *ifStack) top() *ifFrame {
23 }
24
25 // Parse and evaluate preprocessor feature conditions
26-//
27 func evalCondition(expr string, cfg Config) bool {
28 expr = strings.TrimSpace(expr)
29 if strings.HasPrefix(expr, "defined(") && strings.HasSuffix(expr, ")") {
30@@ -151,395 +146,7 @@ func evalCondition(expr string, cfg Config) bool {
31 return cfg[expr] == "1"
32 }
33
34-// Scanning and parsing djot comments in c source files
35-//
36-func formatToken(tok string) string {
37- if strings.HasPrefix(tok, "-") {
38- return "`" + tok + "`"
39- }
40- if len(tok) >= 2 {
41- first := tok[0]
42- last := tok[len(tok)-1]
43- if (first == '[' && last == ']') || (first == '<' && last == '>') || (first == '(' && last == ')') {
44- inner := tok[1 : len(tok)-1]
45- if strings.HasPrefix(inner, "-") {
46- return string(first) + "`" + inner + "`" + string(last)
47- }
48- return string(first) + "_" + inner + "_" + string(last)
49- }
50- }
51- return "_" + tok + "_"
52-}
53-
54-func parseNameSummary(line string) (string, string, bool) {
55- line = strings.TrimSpace(line)
56- if idx := strings.Index(line, ":"); idx >= 0 {
57- return strings.TrimSpace(line[:idx]), strings.TrimSpace(line[idx+1:]), true
58- }
59- if idx := strings.Index(line, " \\- "); idx >= 0 {
60- return strings.TrimSpace(line[:idx]), strings.TrimSpace(line[idx+4:]), true
61- }
62- if idx := strings.Index(line, " - "); idx >= 0 {
63- return strings.TrimSpace(line[:idx]), strings.TrimSpace(line[idx+3:]), true
64- }
65- return "", "", false
66-}
67-
68-func isOptionPattern(s string) (string, string, bool) {
69- s = strings.TrimSpace(s)
70- if !strings.HasPrefix(s, "-") {
71- return "", "", false
72- }
73- idx := strings.Index(s, ":")
74- if idx < 0 {
75- return "", "", false
76- }
77- opt := strings.TrimSpace(s[:idx])
78- desc := strings.TrimSpace(s[idx+1:])
79- fields := strings.Fields(opt)
80- if len(fields) == 0 || !strings.HasPrefix(fields[0], "-") {
81- return "", "", false
82- }
83- return opt, desc, true
84-}
85-
86-func scanC(path string, cfg Config) (string, error) {
87- contentBytes, err := os.ReadFile(path)
88- if err != nil {
89- return "", err
90- }
91- contentStr := string(contentBytes)
92-
93- if !strings.Contains(contentStr, "?man") {
94- // Original parser mode using explicit man markers
95- var djotBuf strings.Builder
96- stack := &ifStack{}
97- sc := bufio.NewScanner(strings.NewReader(contentStr))
98- sc.Buffer(make([]byte, 1<<20), 1<<20)
99- inBlockComment := false
100-
101- for sc.Scan() {
102- line := sc.Text()
103- trimmed := strings.TrimSpace(line)
104-
105- if inBlockComment {
106- if trimmed == "*/" || trimmed == "* /" {
107- inBlockComment = false
108- djotBuf.WriteByte('\n')
109- } else {
110- stripped := trimmed
111- if strings.HasPrefix(stripped, "* ") {
112- stripped = stripped[2:]
113- } else if stripped == "*" {
114- stripped = ""
115- }
116- djotBuf.WriteString(stripped)
117- djotBuf.WriteByte('\n')
118- }
119- continue
120- }
121-
122- if strings.HasPrefix(trimmed, "#") {
123- directive := trimmed[1:]
124- if ci := strings.Index(directive, "//"); ci >= 0 {
125- directive = directive[:ci]
126- }
127- directive = strings.TrimSpace(directive)
128-
129- switch {
130- case strings.HasPrefix(directive, "ifdef "):
131- key := strings.TrimSpace(directive[6:])
132- _, defined := cfg[key]
133- pa := stack.parentActive()
134- stack.push(pa && defined, defined)
135- case strings.HasPrefix(directive, "ifndef "):
136- key := strings.TrimSpace(directive[7:])
137- _, defined := cfg[key]
138- pa := stack.parentActive()
139- stack.push(pa && !defined, !defined)
140- case strings.HasPrefix(directive, "if "):
141- expr := strings.TrimSpace(directive[3:])
142- result := evalCondition(expr, cfg)
143- pa := stack.parentActive()
144- stack.push(pa && result, result)
145- case directive == "else":
146- if top := stack.top(); top != nil {
147- pa := stack.parentActive()
148- newActive := pa && !top.seen
149- top.active = newActive
150- top.seen = true
151- }
152- case strings.HasPrefix(directive, "elif "):
153- if top := stack.top(); top != nil {
154- expr := strings.TrimSpace(directive[5:])
155- result := evalCondition(expr, cfg)
156- pa := stack.parentActive()
157- newActive := pa && !top.seen && result
158- top.active = newActive
159- if result {
160- top.seen = true
161- }
162- }
163- case directive == "endif":
164- stack.pop()
165- }
166- continue
167- }
168-
169- if !stack.globallyActive() {
170- continue
171- }
172-
173- if strings.Contains(trimmed, "/* !man") {
174- startIdx := strings.Index(trimmed, "/* !man")
175- rest := strings.TrimSpace(trimmed[startIdx+7:])
176- if strings.HasSuffix(rest, "*/") {
177- content := strings.TrimSpace(rest[:len(rest)-2])
178- if content != "" {
179- djotBuf.WriteString(content)
180- djotBuf.WriteByte('\n')
181- }
182- } else {
183- if rest != "" {
184- djotBuf.WriteString(rest)
185- djotBuf.WriteByte('\n')
186- }
187- inBlockComment = true
188- }
189- continue
190- }
191-
192- if idx := strings.Index(trimmed, "// !man"); idx >= 0 {
193- content := strings.TrimSpace(trimmed[idx+7:])
194- djotBuf.WriteString(content)
195- djotBuf.WriteByte('\n')
196- continue
197- }
198- }
199-
200- if err := sc.Err(); err != nil {
201- return "", err
202- }
203- return djotBuf.String(), nil
204- }
205-
206- // Heuristic parser mode using question mark man markers
207- type optionDoc struct {
208- opt string
209- desc string
210- }
211- type sectionDoc struct {
212- title string
213- lines []string
214- }
215-
216- var name, summary, synopsis string
217- var descriptionBody []string
218- var options []optionDoc
219- var otherSections []sectionDoc
220- var currentSection *sectionDoc
221- var lastOption *optionDoc
222-
223- stack := &ifStack{}
224- sc := bufio.NewScanner(strings.NewReader(contentStr))
225- sc.Buffer(make([]byte, 1<<20), 1<<20)
226- inBlockComment := false
227- firstLineOfFirstBlock := true
228-
229- for sc.Scan() {
230- line := sc.Text()
231- trimmed := strings.TrimSpace(line)
232-
233- if inBlockComment {
234- if trimmed == "*/" || trimmed == "* /" {
235- inBlockComment = false
236- } else {
237- stripped := trimmed
238- if strings.HasPrefix(stripped, "* ") {
239- stripped = stripped[2:]
240- } else if stripped == "*" {
241- stripped = ""
242- }
243- if strings.HasPrefix(stripped, "// ?man ") {
244- stripped = stripped[8:]
245- } else if strings.HasPrefix(stripped, "// ?man") {
246- stripped = stripped[7:]
247- }
248- if firstLineOfFirstBlock {
249- firstLineOfFirstBlock = false
250- if n, s, ok := parseNameSummary(stripped); ok {
251- name = n
252- summary = s
253- continue
254- }
255- }
256- if synopsis == "" && strings.HasPrefix(strings.ToLower(stripped), "usage:") {
257- synopsis = strings.TrimSpace(stripped[6:])
258- continue
259- }
260- if strings.HasPrefix(stripped, "## ") {
261- title := strings.TrimSpace(stripped[3:])
262- otherSections = append(otherSections, sectionDoc{title: title})
263- currentSection = &otherSections[len(otherSections)-1]
264- lastOption = nil
265- } else {
266- if opt, desc, ok := isOptionPattern(stripped); ok {
267- options = append(options, optionDoc{opt: opt, desc: desc})
268- lastOption = &options[len(options)-1]
269- currentSection = nil
270- } else {
271- if currentSection != nil {
272- currentSection.lines = append(currentSection.lines, stripped)
273- } else {
274- descriptionBody = append(descriptionBody, stripped)
275- }
276- }
277- }
278- }
279- continue
280- }
281-
282- if strings.HasPrefix(trimmed, "#") {
283- directive := trimmed[1:]
284- if ci := strings.Index(directive, "//"); ci >= 0 {
285- directive = directive[:ci]
286- }
287- directive = strings.TrimSpace(directive)
288-
289- switch {
290- case strings.HasPrefix(directive, "ifdef "):
291- key := strings.TrimSpace(directive[6:])
292- _, defined := cfg[key]
293- pa := stack.parentActive()
294- stack.push(pa && defined, defined)
295- case strings.HasPrefix(directive, "ifndef "):
296- key := strings.TrimSpace(directive[7:])
297- _, defined := cfg[key]
298- pa := stack.parentActive()
299- stack.push(pa && !defined, !defined)
300- case strings.HasPrefix(directive, "if "):
301- expr := strings.TrimSpace(directive[3:])
302- result := evalCondition(expr, cfg)
303- pa := stack.parentActive()
304- stack.push(pa && result, result)
305- case directive == "else":
306- if top := stack.top(); top != nil {
307- pa := stack.parentActive()
308- newActive := pa && !top.seen
309- top.active = newActive
310- top.seen = true
311- }
312- case strings.HasPrefix(directive, "elif "):
313- if top := stack.top(); top != nil {
314- expr := strings.TrimSpace(directive[5:])
315- result := evalCondition(expr, cfg)
316- pa := stack.parentActive()
317- newActive := pa && !top.seen && result
318- top.active = newActive
319- if result {
320- top.seen = true
321- }
322- }
323- case directive == "endif":
324- stack.pop()
325- }
326- continue
327- }
328-
329- if !stack.globallyActive() {
330- continue
331- }
332-
333- if strings.Contains(trimmed, "/* ?man") {
334- startIdx := strings.Index(trimmed, "/* ?man")
335- rest := strings.TrimSpace(trimmed[startIdx+7:])
336- if strings.HasSuffix(rest, "*/") {
337- content := strings.TrimSpace(rest[:len(rest)-2])
338- if n, s, ok := parseNameSummary(content); ok {
339- name = n
340- summary = s
341- }
342- } else {
343- if rest != "" {
344- if n, s, ok := parseNameSummary(rest); ok {
345- name = n
346- summary = s
347- } else {
348- descriptionBody = append(descriptionBody, rest)
349- }
350- }
351- inBlockComment = true
352- }
353- continue
354- }
355-
356- if idx := strings.Index(trimmed, "// ?man"); idx >= 0 {
357- content := strings.TrimSpace(trimmed[idx+7:])
358- if opt, desc, ok := isOptionPattern(content); ok {
359- options = append(options, optionDoc{opt: opt, desc: desc})
360- lastOption = &options[len(options)-1]
361- currentSection = nil
362- } else {
363- if lastOption != nil {
364- lastOption.desc += " " + content
365- } else if currentSection != nil {
366- currentSection.lines = append(currentSection.lines, content)
367- } else {
368- descriptionBody = append(descriptionBody, content)
369- }
370- }
371- continue
372- }
373- }
374-
375- if err := sc.Err(); err != nil {
376- return "", err
377- }
378-
379- var djotBuf strings.Builder
380- djotBuf.WriteString("# " + name + "\n\n")
381- djotBuf.WriteString("## NAME\n\n")
382- djotBuf.WriteString(name + " \\- " + summary + "\n\n")
383-
384- if synopsis != "" {
385- djotBuf.WriteString("## SYNOPSIS\n\n")
386- djotBuf.WriteString("```\n" + synopsis + "\n```\n\n")
387- }
388-
389- if len(descriptionBody) > 0 {
390- djotBuf.WriteString("## DESCRIPTION\n\n")
391- for _, l := range descriptionBody {
392- djotBuf.WriteString(l + "\n")
393- }
394- djotBuf.WriteString("\n")
395- }
396-
397- if len(options) > 0 {
398- djotBuf.WriteString("## OPTIONS\n\n")
399- for _, opt := range options {
400- words := strings.Fields(opt.opt)
401- var formatted []string
402- for _, w := range words {
403- formatted = append(formatted, formatToken(w))
404- }
405- djotBuf.WriteString(strings.Join(formatted, " ") + "\n")
406- djotBuf.WriteString(": " + opt.desc + "\n\n")
407- }
408- }
409-
410- for _, sec := range otherSections {
411- djotBuf.WriteString("## " + sec.title + "\n\n")
412- for _, l := range sec.lines {
413- djotBuf.WriteString(l + "\n")
414- }
415- djotBuf.WriteString("\n")
416- }
417-
418- return djotBuf.String(), nil
419-}
420-
421 // Section inference from file path
422-//
423 func inferSection(path string) int {
424 clean := filepath.ToSlash(path)
425 switch {
426@@ -553,7 +160,6 @@ func inferSection(path string) int {
427 }
428
429 // Main entry point and flags definition
430-//
431 func main() {
432 configPath := flag.String("config", "config.mk", "path to config.mk")
433 sectionFlag := flag.Int("section", 0, "man section override (0 = infer from path)")
434@@ -579,22 +185,23 @@ func main() {
435
436 date := *dateFlag
437 if date == "" {
438- date = time.Now().Format("January 2006")
439+ date = time.Now().Format("January 2, 2006")
440 }
441
442- djotText, err := scanC(cfile, cfg)
443+ page, err := ParsePage(cfile, cfg, section, date)
444 if err != nil {
445 fmt.Fprintf(os.Stderr, "mkman: scan: %v\n", err)
446 os.Exit(1)
447 }
448
449- if strings.TrimSpace(djotText) == "" {
450- fmt.Fprintf(os.Stderr, "mkman: %s: no !man comments found\n", cfile)
451+ if page == nil {
452+ fmt.Fprintf(os.Stderr, "mkman: %s: no ?man comments found\n", cfile)
453 os.Exit(0)
454 }
455
456- doc := djot.Parse([]byte(djotText))
457-
458- r := NewTroffRenderer(doc, os.Stdout, section, date)
459- r.Render()
460+ r := NewMdocRenderer(page, os.Stdout)
461+ if err := r.Render(); err != nil {
462+ fmt.Fprintf(os.Stderr, "mkman: render: %v\n", err)
463+ os.Exit(1)
464+ }
465 }
+251,
-0
1@@ -0,0 +1,251 @@
2+package main
3+
4+import (
5+ "fmt"
6+ "io"
7+ "strings"
8+)
9+
10+type MdocRenderer struct {
11+ page *Page
12+ w io.Writer
13+}
14+
15+func NewMdocRenderer(page *Page, w io.Writer) *MdocRenderer {
16+ return &MdocRenderer{page: page, w: w}
17+}
18+
19+func (r *MdocRenderer) Render() error {
20+ if err := r.page.validate(); err != nil {
21+ return err
22+ }
23+
24+ fmt.Fprintf(r.w, ".Dd %s\n", troffEscape(r.page.Date))
25+ fmt.Fprintf(r.w, ".Dt %s %d\n", strings.ToUpper(r.page.Name), r.page.Section)
26+ fmt.Fprintf(r.w, ".Os %s\n", "aruu")
27+
28+ r.renderName()
29+ if len(r.page.Synopsis) != 0 {
30+ r.renderSynopsis()
31+ }
32+ if len(r.page.Description) != 0 {
33+ r.renderDescription()
34+ }
35+ if len(r.page.Options) != 0 {
36+ r.renderOptions()
37+ }
38+ for _, section := range r.page.Sections {
39+ r.renderSection(section.Title, section.Blocks)
40+ }
41+ return nil
42+}
43+
44+func (r *MdocRenderer) renderName() {
45+ fmt.Fprintln(r.w, ".Sh NAME")
46+ fmt.Fprintf(r.w, ".Nm %s\n", troffEscape(r.page.Name))
47+ fmt.Fprintf(r.w, ".Nd %s\n", renderInlineSeq(parseInlines(r.page.Summary)))
48+}
49+
50+func (r *MdocRenderer) renderDescription() {
51+ fmt.Fprintln(r.w, ".Sh DESCRIPTION")
52+ for _, block := range r.page.Description {
53+ r.renderBlock(block)
54+ }
55+}
56+
57+func (r *MdocRenderer) renderSynopsis() {
58+ fmt.Fprintln(r.w, ".Sh SYNOPSIS")
59+ for _, form := range r.page.Synopsis {
60+ // synopsis macros are emitted one phrase per line
61+ fmt.Fprintf(r.w, ".Nm %s\n", troffEscape(r.page.Name))
62+ for i := 0; i < len(form.Items); {
63+ item := form.Items[i]
64+ if item.Kind == SynOptional {
65+ fmt.Fprintf(r.w, ".Op %s\n", renderSynopsisPhrase(item.Children, false))
66+ i++
67+ continue
68+ }
69+
70+ phrase, next := consumeSynopsisPhrase(form.Items, i)
71+ fmt.Fprintln(r.w, renderSynopsisPhrase(phrase, true))
72+ i = next
73+ }
74+ }
75+}
76+
77+func consumeSynopsisPhrase(items []SynopsisItem, start int) ([]SynopsisItem, int) {
78+ phrase := []SynopsisItem{items[start]}
79+ i := start + 1
80+
81+ if items[start].Kind == SynFlag && i < len(items) && items[i].Kind == SynArg {
82+ phrase = append(phrase, items[i])
83+ i++
84+ }
85+
86+ for i+1 < len(items) && items[i].Kind == SynPipe {
87+ phrase = append(phrase, items[i], items[i+1])
88+ i += 2
89+ }
90+
91+ return phrase, i
92+}
93+
94+func renderSynopsisPhrase(items []SynopsisItem, topLevel bool) string {
95+ var parts []string
96+ for _, item := range items {
97+ switch item.Kind {
98+ case SynFlag:
99+ parts = append(parts, renderMacro("Fl", troffEscape(strings.TrimPrefix(item.Text, "-")), topLevel && len(parts) == 0))
100+ case SynArg:
101+ parts = append(parts, renderMacro("Ar", troffEscape(strings.Trim(item.Text, "<>")), topLevel && len(parts) == 0))
102+ case SynCommand:
103+ parts = append(parts, renderMacro("Cm", troffEscape(item.Text), topLevel && len(parts) == 0))
104+ case SynPipe:
105+ parts = append(parts, "|")
106+ case SynLiteral:
107+ parts = append(parts, troffEscape(item.Text))
108+ case SynOptional:
109+ parts = append(parts, renderMacro("Op", renderSynopsisPhrase(item.Children, false), topLevel && len(parts) == 0))
110+ }
111+ }
112+ return strings.Join(parts, " ")
113+}
114+
115+func renderMacro(name, body string, topLevel bool) string {
116+ if body == "" {
117+ if topLevel {
118+ return "." + name
119+ }
120+ return name
121+ }
122+ if topLevel {
123+ return "." + name + " " + body
124+ }
125+ return name + " " + body
126+}
127+
128+func (r *MdocRenderer) renderOptions() {
129+ fmt.Fprintln(r.w, ".Sh OPTIONS")
130+ fmt.Fprintln(r.w, ".Bl -tag -width Ds")
131+ for _, opt := range r.page.Options {
132+ fmt.Fprintf(r.w, ".It %s\n", renderSynopsisPhrase(opt.Spec, false))
133+ for _, block := range opt.Body {
134+ r.renderBlock(block)
135+ }
136+ }
137+ fmt.Fprintln(r.w, ".El")
138+}
139+
140+func (r *MdocRenderer) renderSection(title string, blocks []Block) {
141+ fmt.Fprintf(r.w, ".Sh %s\n", troffEscape(title))
142+ for _, block := range blocks {
143+ r.renderBlock(block)
144+ }
145+}
146+
147+func (r *MdocRenderer) renderBlock(block Block) {
148+ switch block.Kind {
149+ case BlockParagraph:
150+ fmt.Fprintln(r.w, ".Pp")
151+ fmt.Fprintf(r.w, ".No %s\n", renderInlineSeq(block.Inlines))
152+ case BlockTaggedList:
153+ fmt.Fprintln(r.w, ".Bl -tag -width Ds")
154+ for _, item := range block.Items {
155+ fmt.Fprintf(r.w, ".It %s\n", renderInlineSeq(item.Label))
156+ if len(item.Body) != 0 {
157+ fmt.Fprintln(r.w, ".Pp")
158+ fmt.Fprintf(r.w, ".No %s\n", renderInlineSeq(item.Body))
159+ }
160+ }
161+ fmt.Fprintln(r.w, ".El")
162+ case BlockSeeAlso:
163+ for i, ref := range block.Refs {
164+ line := fmt.Sprintf(".Xr %s %s", troffEscape(ref.Name), troffEscape(ref.Section))
165+ if i != len(block.Refs)-1 {
166+ line += " ,"
167+ }
168+ fmt.Fprintln(r.w, line)
169+ }
170+ case BlockSubsection:
171+ fmt.Fprintf(r.w, ".Ss %s\n", troffEscape(block.Title))
172+ for _, child := range block.Blocks {
173+ r.renderBlock(child)
174+ }
175+ }
176+}
177+
178+func troffEscape(s string) string {
179+ s = strings.ReplaceAll(s, "\\", "\\\\")
180+ s = strings.ReplaceAll(s, "-", "\\-")
181+ if len(s) > 0 && s[0] == '.' {
182+ s = "\\&" + s
183+ }
184+ return s
185+}
186+
187+func renderInlineSeq(inlines []Inline) string {
188+ var b strings.Builder
189+ // adjacent semantic nodes sometimes need to join without an intervening
190+ // space, like some mixed path/emphasis/path forms in FILES
191+ for i, inline := range inlines {
192+ if i > 0 && needsNoSpace(inlines[i-1], inline) {
193+ b.WriteString(" Ns ")
194+ }
195+ b.WriteString(renderInline(inline))
196+ }
197+ return b.String()
198+}
199+
200+func renderInline(inline Inline) string {
201+ switch inline.Kind {
202+ case InlineText:
203+ return troffEscape(inline.Text)
204+ case InlineNm:
205+ return renderMacro("Nm", "", false)
206+ case InlineEmph:
207+ return renderMacro("Em", renderInlineSeq(inline.Children), false)
208+ case InlineLiteral:
209+ return renderMacro("Ql", troffEscape(inline.Text), false)
210+ case InlinePath:
211+ return renderMacro("Pa", troffEscape(inline.Text), false)
212+ case InlineXRef:
213+ return renderMacro("Xr", troffEscape(inline.Text)+" "+troffEscape(inline.Section), false)
214+ case InlineFlag:
215+ return renderMacro("Fl", troffEscape(inline.Text), false)
216+ default:
217+ return ""
218+ }
219+}
220+
221+func needsNoSpace(prev, curr Inline) bool {
222+ if trailingSpace(prev) || leadingSpace(curr) {
223+ return false
224+ }
225+ return true
226+}
227+
228+func leadingSpace(inline Inline) bool {
229+ switch inline.Kind {
230+ case InlineText:
231+ return len(inline.Text) != 0 && isSpaceByte(inline.Text[0])
232+ case InlineEmph:
233+ return len(inline.Children) != 0 && leadingSpace(inline.Children[0])
234+ default:
235+ return false
236+ }
237+}
238+
239+func trailingSpace(inline Inline) bool {
240+ switch inline.Kind {
241+ case InlineText:
242+ return len(inline.Text) != 0 && isSpaceByte(inline.Text[len(inline.Text)-1])
243+ case InlineEmph:
244+ return len(inline.Children) != 0 && trailingSpace(inline.Children[len(inline.Children)-1])
245+ default:
246+ return false
247+ }
248+}
249+
250+func isSpaceByte(b byte) bool {
251+ return b == ' ' || b == '\t' || b == '\n'
252+}
+92,
-0
1@@ -0,0 +1,92 @@
2+package main
3+
4+type Page struct {
5+ Name string
6+ Summary string
7+ Section int
8+ Date string
9+ Synopsis []SynopsisForm
10+ Description []Block
11+ Options []Option
12+ Sections []Section
13+}
14+
15+type Section struct {
16+ Title string
17+ Blocks []Block
18+}
19+
20+type BlockKind int
21+
22+const (
23+ BlockParagraph BlockKind = iota
24+ BlockTaggedList
25+ BlockSeeAlso
26+ BlockSubsection
27+)
28+
29+type Block struct {
30+ Kind BlockKind
31+ Inlines []Inline
32+ Items []Item
33+ Refs []XRef
34+ Title string
35+ Blocks []Block
36+}
37+
38+type Item struct {
39+ Label []Inline
40+ Body []Inline
41+}
42+
43+type XRef struct {
44+ Name string
45+ Section string
46+}
47+
48+type Option struct {
49+ Spec []SynopsisItem
50+ Desc string
51+ Body []Block
52+ Key string
53+}
54+
55+type SynopsisForm struct {
56+ Items []SynopsisItem
57+}
58+
59+type SynopsisItemKind int
60+
61+const (
62+ SynFlag SynopsisItemKind = iota
63+ SynArg
64+ SynCommand
65+ SynLiteral
66+ SynOptional
67+ SynPipe
68+)
69+
70+type SynopsisItem struct {
71+ Kind SynopsisItemKind
72+ Text string
73+ Children []SynopsisItem
74+}
75+
76+type InlineKind int
77+
78+const (
79+ InlineText InlineKind = iota
80+ InlineNm
81+ InlineEmph
82+ InlineLiteral
83+ InlinePath
84+ InlineXRef
85+ InlineFlag
86+)
87+
88+type Inline struct {
89+ Kind InlineKind
90+ Text string
91+ Section string
92+ Children []Inline
93+}
+951,
-0
1@@ -0,0 +1,951 @@
2+package main
3+
4+import (
5+ "bufio"
6+ "fmt"
7+ "os"
8+ "regexp"
9+ "slices"
10+ "strings"
11+)
12+
13+var xrefPattern = regexp.MustCompile(`^\s*([A-Za-z0-9][A-Za-z0-9+_.-]*)\((\d+[A-Za-z]*)\)\s*$`)
14+
15+func parseNameSummary(line string) (string, string, bool) {
16+ line = strings.TrimSpace(line)
17+ if idx := strings.Index(line, ":"); idx >= 0 {
18+ return strings.TrimSpace(line[:idx]), strings.TrimSpace(line[idx+1:]), true
19+ }
20+ if idx := strings.Index(line, " \\- "); idx >= 0 {
21+ return strings.TrimSpace(line[:idx]), strings.TrimSpace(line[idx+4:]), true
22+ }
23+ if idx := strings.Index(line, " - "); idx >= 0 {
24+ return strings.TrimSpace(line[:idx]), strings.TrimSpace(line[idx+3:]), true
25+ }
26+ return "", "", false
27+}
28+
29+func extractManLines(path string, cfg Config) ([]string, error) {
30+ contentBytes, err := os.ReadFile(path)
31+ if err != nil {
32+ return nil, err
33+ }
34+
35+ var lines []string
36+ stack := &ifStack{}
37+ sc := bufio.NewScanner(strings.NewReader(string(contentBytes)))
38+ sc.Buffer(make([]byte, 1<<20), 1<<20)
39+ inBlockComment := false
40+
41+ for sc.Scan() {
42+ line := sc.Text()
43+ trimmed := strings.TrimSpace(line)
44+
45+ if inBlockComment {
46+ if trimmed == "*/" || trimmed == "* /" {
47+ inBlockComment = false
48+ continue
49+ }
50+
51+ stripped := trimmed
52+ if strings.HasPrefix(stripped, "* ") {
53+ stripped = stripped[2:]
54+ } else if stripped == "*" {
55+ stripped = ""
56+ }
57+ lines = append(lines, stripManPrefix(stripped))
58+ continue
59+ }
60+
61+ if strings.HasPrefix(trimmed, "#") {
62+ directive := trimmed[1:]
63+ if ci := strings.Index(directive, "//"); ci >= 0 {
64+ directive = directive[:ci]
65+ }
66+ directive = strings.TrimSpace(directive)
67+
68+ switch {
69+ case strings.HasPrefix(directive, "ifdef "):
70+ key := strings.TrimSpace(directive[6:])
71+ _, defined := cfg[key]
72+ pa := stack.parentActive()
73+ stack.push(pa && defined, defined)
74+ case strings.HasPrefix(directive, "ifndef "):
75+ key := strings.TrimSpace(directive[7:])
76+ _, defined := cfg[key]
77+ pa := stack.parentActive()
78+ stack.push(pa && !defined, !defined)
79+ case strings.HasPrefix(directive, "if "):
80+ expr := strings.TrimSpace(directive[3:])
81+ result := evalCondition(expr, cfg)
82+ pa := stack.parentActive()
83+ stack.push(pa && result, result)
84+ case directive == "else":
85+ if top := stack.top(); top != nil {
86+ pa := stack.parentActive()
87+ top.active = pa && !top.seen
88+ top.seen = true
89+ }
90+ case strings.HasPrefix(directive, "elif "):
91+ if top := stack.top(); top != nil {
92+ expr := strings.TrimSpace(directive[5:])
93+ result := evalCondition(expr, cfg)
94+ pa := stack.parentActive()
95+ top.active = pa && !top.seen && result
96+ if result {
97+ top.seen = true
98+ }
99+ }
100+ case directive == "endif":
101+ stack.pop()
102+ }
103+ continue
104+ }
105+
106+ if !stack.globallyActive() {
107+ continue
108+ }
109+
110+ if strings.Contains(trimmed, "/* ?man") {
111+ startIdx := strings.Index(trimmed, "/* ?man")
112+ rest := strings.TrimSpace(trimmed[startIdx+7:])
113+ if strings.HasSuffix(rest, "*/") {
114+ lines = append(lines, strings.TrimSpace(rest[:len(rest)-2]))
115+ } else {
116+ lines = append(lines, rest)
117+ inBlockComment = true
118+ }
119+ continue
120+ }
121+
122+ if idx := strings.Index(trimmed, "// ?man"); idx >= 0 {
123+ lines = append(lines, strings.TrimSpace(trimmed[idx+7:]))
124+ }
125+ }
126+
127+ if err := sc.Err(); err != nil {
128+ return nil, err
129+ }
130+ return lines, nil
131+}
132+
133+func stripManPrefix(line string) string {
134+ if strings.HasPrefix(line, "// ?man ") {
135+ return line[8:]
136+ }
137+ if strings.HasPrefix(line, "// ?man") {
138+ return line[7:]
139+ }
140+ return line
141+}
142+
143+func ParsePage(path string, cfg Config, section int, date string) (*Page, error) {
144+ lines, err := extractManLines(path, cfg)
145+ if err != nil {
146+ return nil, err
147+ }
148+
149+ page := &Page{Section: section, Date: date}
150+
151+ var descriptionLines []string
152+ var sections []rawSection
153+ var currentSection *rawSection
154+ var sawContent bool
155+ var fallbackArgs string
156+ var rawOptions []rawOption
157+ var currentOption *rawOption
158+ optionIndex := make(map[string]int)
159+
160+ for _, raw := range lines {
161+ line := strings.TrimSpace(raw)
162+ if line == "" {
163+ if currentSection != nil {
164+ currentSection.Lines = append(currentSection.Lines, "")
165+ } else if currentOption != nil {
166+ currentOption.Lines = append(currentOption.Lines, "")
167+ } else if sawContent {
168+ descriptionLines = append(descriptionLines, "")
169+ }
170+ continue
171+ }
172+
173+ if page.Name == "" && !strings.HasPrefix(line, "-") && !strings.HasPrefix(line, "#") {
174+ if name, summary, ok := parseNameSummary(line); ok {
175+ page.Name = name
176+ page.Summary = summary
177+ sawContent = true
178+ continue
179+ }
180+ }
181+
182+ lower := strings.ToLower(line)
183+ switch {
184+ case strings.HasPrefix(lower, "synopsis:"):
185+ currentOption = nil
186+ form := strings.TrimSpace(line[len("synopsis:"):])
187+ if form != "" {
188+ page.Synopsis = append(page.Synopsis, parseSynopsis(form, true))
189+ }
190+ sawContent = true
191+ continue
192+ case strings.HasPrefix(lower, "arguments:"):
193+ currentOption = nil
194+ fallbackArgs = strings.TrimSpace(line[len("arguments:"):])
195+ sawContent = true
196+ continue
197+ case strings.HasPrefix(line, "## "):
198+ currentOption = nil
199+ title := strings.TrimSpace(line[3:])
200+ sections = append(sections, rawSection{Title: title})
201+ currentSection = §ions[len(sections)-1]
202+ sawContent = true
203+ continue
204+ }
205+
206+ if currentSection == nil {
207+ if opt, ok := parseOption(line); ok {
208+ rawOptions = append(rawOptions, rawOption{
209+ Option: opt,
210+ Lines: []string{opt.Desc},
211+ })
212+ currentOption = &rawOptions[len(rawOptions)-1]
213+ } else if isCodeLabel(line) || isCodeLabelPrefix(line) {
214+ continue
215+ } else if currentOption != nil {
216+ currentOption.Lines = append(currentOption.Lines, line)
217+ } else {
218+ descriptionLines = append(descriptionLines, line)
219+ }
220+ sawContent = true
221+ continue
222+ }
223+
224+ currentSection.Lines = append(currentSection.Lines, line)
225+ sawContent = true
226+ }
227+
228+ if !sawContent || page.Name == "" {
229+ return nil, nil
230+ }
231+
232+ // explicit 'synopsis' forms are authoritative. for simple pages we can produce
233+ // a synopsis from the parsed option specs plus the 'arguments'
234+ for _, raw := range rawOptions {
235+ raw.Option.Body = parseBlocks(raw.Lines, "")
236+ addOption(page, optionIndex, raw.Option)
237+ }
238+
239+ if len(page.Synopsis) == 0 && fallbackArgs != "" {
240+ page.Synopsis = append(page.Synopsis, synthesizeSynopsis(page.Options, fallbackArgs))
241+ }
242+
243+ page.Description = parseBlocks(descriptionLines, "")
244+ for _, section := range sections {
245+ page.Sections = append(page.Sections, Section{
246+ Title: section.Title,
247+ Blocks: parseBlocks(section.Lines, section.Title),
248+ })
249+ }
250+ normalizePageInlines(page)
251+
252+ return page, nil
253+}
254+
255+type rawSection struct {
256+ Title string
257+ Lines []string
258+}
259+
260+type rawOption struct {
261+ Option Option
262+ Lines []string
263+}
264+
265+func parseOption(line string) (Option, bool) {
266+ if !strings.HasPrefix(line, "-") {
267+ return Option{}, false
268+ }
269+
270+ parts := strings.Split(line, ":")
271+ if len(parts) < 2 {
272+ return Option{}, false
273+ }
274+
275+ specParts := []string{strings.TrimSpace(parts[0])}
276+ i := 1
277+ for i < len(parts)-1 {
278+ part := strings.TrimSpace(parts[i])
279+ if !looksLikeOptionArg(part) {
280+ break
281+ }
282+ specParts = append(specParts, part)
283+ i++
284+ }
285+
286+ desc := strings.TrimSpace(strings.Join(parts[i:], ":"))
287+ spec := normalizeOptionSpec(strings.Join(specParts, ":"))
288+ desc = stripRepeatedSpec(spec, desc)
289+ if spec == "" || desc == "" {
290+ return Option{}, false
291+ }
292+
293+ return Option{
294+ Spec: parseSynopsis(spec, false).Items,
295+ Desc: desc,
296+ Key: synopsisKey(spec),
297+ }, true
298+}
299+
300+func looksLikeOptionArg(s string) bool {
301+ if s == "" {
302+ return false
303+ }
304+ return !strings.ContainsAny(s, " \t")
305+}
306+
307+func normalizeOptionSpec(spec string) string {
308+ spec = strings.TrimSpace(spec)
309+ if spec == "" {
310+ return ""
311+ }
312+
313+ parts := strings.Split(spec, ":")
314+ if len(parts) == 1 {
315+ return strings.Join(strings.Fields(spec), " ")
316+ }
317+
318+ normalized := []string{strings.TrimSpace(parts[0])}
319+ for _, part := range parts[1:] {
320+ part = strings.TrimSpace(part)
321+ if part == "" {
322+ continue
323+ }
324+ normalized = append(normalized, part)
325+ }
326+ return strings.Join(normalized, " ")
327+}
328+
329+func stripRepeatedSpec(spec, desc string) string {
330+ spec = strings.TrimSpace(spec)
331+ desc = strings.TrimSpace(desc)
332+ candidates := []string{
333+ spec + ":",
334+ strings.ReplaceAll(spec, " ", ":") + ":",
335+ }
336+ for _, candidate := range candidates {
337+ if strings.HasPrefix(desc, candidate) {
338+ return strings.TrimSpace(desc[len(candidate):])
339+ }
340+ }
341+ return desc
342+}
343+
344+func addOption(page *Page, index map[string]int, opt Option) {
345+ if idx, ok := index[opt.Key]; ok {
346+ if betterOptionDesc(opt.Desc, page.Options[idx].Desc) {
347+ page.Options[idx].Desc = opt.Desc
348+ page.Options[idx].Spec = opt.Spec
349+ page.Options[idx].Body = opt.Body
350+ }
351+ return
352+ }
353+
354+ primary := optionPrimaryFlag(opt)
355+ if primary != "" {
356+ for idx, existing := range page.Options {
357+ if optionPrimaryFlag(existing) != primary {
358+ continue
359+ }
360+ if betterOptionDesc(opt.Desc, existing.Desc) {
361+ page.Options[idx].Desc = opt.Desc
362+ page.Options[idx].Spec = opt.Spec
363+ page.Options[idx].Body = opt.Body
364+ delete(index, existing.Key)
365+ index[opt.Key] = idx
366+ }
367+ return
368+ }
369+ }
370+
371+ index[opt.Key] = len(page.Options)
372+ page.Options = append(page.Options, opt)
373+}
374+
375+func betterOptionDesc(newDesc, oldDesc string) bool {
376+ return optionDescScore(newDesc) > optionDescScore(oldDesc)
377+}
378+
379+func optionDescScore(desc string) int {
380+ desc = strings.TrimSpace(desc)
381+ score := len(desc)
382+ lower := strings.ToLower(desc)
383+ if strings.HasPrefix(lower, "specify ") && strings.HasSuffix(lower, " option") {
384+ score -= 1000
385+ }
386+ return score
387+}
388+
389+func optionPrimaryFlag(opt Option) string {
390+ for _, item := range opt.Spec {
391+ if item.Kind == SynFlag {
392+ return item.Text
393+ }
394+ }
395+ return ""
396+}
397+
398+func synopsisKey(spec string) string {
399+ return strings.Join(strings.Fields(spec), " ")
400+}
401+
402+func isCodeLabel(line string) bool {
403+ if !strings.HasSuffix(line, ":") {
404+ return false
405+ }
406+ for _, r := range line[:len(line)-1] {
407+ if (r < 'A' || r > 'Z') && (r < '0' || r > '9') && r != '_' {
408+ return false
409+ }
410+ }
411+ return len(line) > 1
412+}
413+
414+func isCodeLabelPrefix(line string) bool {
415+ idx := strings.IndexByte(line, ':')
416+ if idx <= 0 {
417+ return false
418+ }
419+ return isCodeLabel(line[:idx+1])
420+}
421+
422+func parseBlocks(lines []string, title string) []Block {
423+ lines = trimBlankLines(lines)
424+ if len(lines) == 0 {
425+ return nil
426+ }
427+
428+ if blocks, ok := parseSubsections(lines, title); ok {
429+ return blocks
430+ }
431+
432+ if items, ok := parseHeadingItems(lines); ok {
433+ return []Block{{Kind: BlockTaggedList, Items: items}}
434+ }
435+
436+ if items, ok := parseSequentialTaggedItems(lines); ok {
437+ return []Block{{Kind: BlockTaggedList, Items: items}}
438+ }
439+
440+ paragraphs := splitParagraphs(lines)
441+ var blocks []Block
442+
443+ for _, paragraph := range paragraphs {
444+ if len(paragraph) == 0 {
445+ continue
446+ }
447+
448+ if isSeeAlsoTitle(title) {
449+ if refs, ok := parseXRefs(strings.Join(paragraph, " ")); ok {
450+ slices.SortFunc(refs, func(a, b XRef) int {
451+ if a.Section != b.Section {
452+ return strings.Compare(a.Section, b.Section)
453+ }
454+ return strings.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name))
455+ })
456+ blocks = append(blocks, Block{Kind: BlockSeeAlso, Refs: refs})
457+ continue
458+ }
459+ }
460+
461+ if item, ok := parseTaggedItem(paragraph); ok {
462+ if len(blocks) != 0 && blocks[len(blocks)-1].Kind == BlockTaggedList {
463+ blocks[len(blocks)-1].Items = append(blocks[len(blocks)-1].Items, item)
464+ } else {
465+ blocks = append(blocks, Block{Kind: BlockTaggedList, Items: []Item{item}})
466+ }
467+ continue
468+ }
469+
470+ blocks = append(blocks, Block{
471+ Kind: BlockParagraph,
472+ Inlines: parseInlines(strings.Join(paragraph, " ")),
473+ })
474+ }
475+
476+ return blocks
477+}
478+
479+func parseSubsections(lines []string, title string) ([]Block, bool) {
480+ hasHeading := false
481+ for _, line := range lines {
482+ if strings.HasPrefix(strings.TrimSpace(line), "### ") {
483+ hasHeading = true
484+ break
485+ }
486+ }
487+ if !hasHeading {
488+ return nil, false
489+ }
490+
491+ var blocks []Block
492+ var preamble []string
493+ for len(lines) != 0 && !strings.HasPrefix(strings.TrimSpace(lines[0]), "### ") {
494+ preamble = append(preamble, lines[0])
495+ lines = lines[1:]
496+ }
497+ if parsed := parseBlocks(preamble, title); len(parsed) != 0 {
498+ blocks = append(blocks, parsed...)
499+ }
500+
501+ for len(lines) != 0 {
502+ head := strings.TrimSpace(lines[0])
503+ if !strings.HasPrefix(head, "### ") {
504+ return nil, false
505+ }
506+ subtitle := strings.TrimSpace(head[4:])
507+ lines = lines[1:]
508+ var body []string
509+ for len(lines) != 0 && !strings.HasPrefix(strings.TrimSpace(lines[0]), "### ") {
510+ body = append(body, lines[0])
511+ lines = lines[1:]
512+ }
513+ blocks = append(blocks, Block{
514+ Kind: BlockSubsection,
515+ Title: subtitle,
516+ Blocks: parseBlocks(body, subtitle),
517+ })
518+ }
519+
520+ return blocks, true
521+}
522+
523+func trimBlankLines(lines []string) []string {
524+ start := 0
525+ for start < len(lines) && strings.TrimSpace(lines[start]) == "" {
526+ start++
527+ }
528+ end := len(lines)
529+ for end > start && strings.TrimSpace(lines[end-1]) == "" {
530+ end--
531+ }
532+ return lines[start:end]
533+}
534+
535+func splitParagraphs(lines []string) [][]string {
536+ var paragraphs [][]string
537+ var current []string
538+ for _, line := range lines {
539+ if strings.TrimSpace(line) == "" {
540+ if len(current) != 0 {
541+ paragraphs = append(paragraphs, current)
542+ current = nil
543+ }
544+ continue
545+ }
546+ current = append(current, line)
547+ }
548+ if len(current) != 0 {
549+ paragraphs = append(paragraphs, current)
550+ }
551+ return paragraphs
552+}
553+
554+func parseHeadingItems(lines []string) ([]Item, bool) {
555+ var items []Item
556+ var current *Item
557+ for _, line := range lines {
558+ if strings.TrimSpace(line) == "" {
559+ continue
560+ }
561+ if strings.HasPrefix(line, "### ") {
562+ items = append(items, Item{Label: parseInlines(strings.TrimSpace(line[4:]))})
563+ current = &items[len(items)-1]
564+ continue
565+ }
566+ if current == nil {
567+ return nil, false
568+ }
569+ if len(current.Body) != 0 {
570+ current.Body = append(current.Body, Inline{Kind: InlineText, Text: " "})
571+ }
572+ current.Body = append(current.Body, parseInlines(strings.TrimSpace(line))...)
573+ }
574+ return items, len(items) != 0
575+}
576+
577+func parseSequentialTaggedItems(lines []string) ([]Item, bool) {
578+ var items []Item
579+ for i := 0; i < len(lines); {
580+ for i < len(lines) && strings.TrimSpace(lines[i]) == "" {
581+ i++
582+ }
583+ if i >= len(lines) {
584+ break
585+ }
586+
587+ label := strings.TrimSpace(lines[i])
588+ if label == "" || i+1 >= len(lines) {
589+ return nil, false
590+ }
591+ first := strings.TrimSpace(lines[i+1])
592+ if !strings.HasPrefix(first, ":") {
593+ return nil, false
594+ }
595+
596+ bodyLines := []string{strings.TrimSpace(strings.TrimPrefix(first, ":"))}
597+ i += 2
598+ for i < len(lines) {
599+ line := strings.TrimSpace(lines[i])
600+ if line == "" {
601+ i++
602+ break
603+ }
604+ if i+1 < len(lines) && strings.TrimSpace(lines[i]) != "" &&
605+ strings.HasPrefix(strings.TrimSpace(lines[i+1]), ":") {
606+ break
607+ }
608+ bodyLines = append(bodyLines, line)
609+ i++
610+ }
611+
612+ items = append(items, Item{
613+ Label: parseInlines(label),
614+ Body: parseInlines(strings.Join(compactLines(bodyLines), " ")),
615+ })
616+ }
617+ return items, len(items) != 0
618+}
619+
620+func parseTaggedItem(lines []string) (Item, bool) {
621+ if len(lines) < 2 {
622+ return Item{}, false
623+ }
624+ label := strings.TrimSpace(lines[0])
625+ bodyLines := lines[1:]
626+ if label == "" {
627+ return Item{}, false
628+ }
629+
630+ first := strings.TrimSpace(bodyLines[0])
631+ if !strings.HasPrefix(first, ":") {
632+ return Item{}, false
633+ }
634+
635+ bodyLines[0] = strings.TrimSpace(strings.TrimPrefix(first, ":"))
636+ return Item{
637+ Label: parseInlines(label),
638+ Body: parseInlines(strings.Join(compactLines(bodyLines), " ")),
639+ }, true
640+}
641+
642+func parseInlines(s string) []Inline {
643+ var out []Inline
644+ var text strings.Builder
645+ flushText := func() {
646+ if text.Len() == 0 {
647+ return
648+ }
649+ out = append(out, Inline{Kind: InlineText, Text: text.String()})
650+ text.Reset()
651+ }
652+
653+ // inline semantics are parsed here so the renderer never has to recover
654+ // any richness like paths, flags, emphasis, or xrefs by reparsing raw strings
655+ for i := 0; i < len(s); {
656+ switch s[i] {
657+ case '`':
658+ end := strings.IndexByte(s[i+1:], '`')
659+ if end < 0 {
660+ text.WriteByte(s[i])
661+ i++
662+ continue
663+ }
664+ end += i + 1
665+ flushText()
666+ out = append(out, classifyInlineLiteral(s[i+1:end]))
667+ i = end + 1
668+ case '_':
669+ end := strings.IndexByte(s[i+1:], '_')
670+ if end < 0 {
671+ text.WriteByte(s[i])
672+ i++
673+ continue
674+ }
675+ end += i + 1
676+ flushText()
677+ out = append(out, Inline{
678+ Kind: InlineEmph,
679+ Children: parseInlines(s[i+1 : end]),
680+ })
681+ i = end + 1
682+ default:
683+ text.WriteByte(s[i])
684+ i++
685+ }
686+ }
687+
688+ flushText()
689+ return out
690+}
691+
692+func classifyInlineLiteral(s string) Inline {
693+ s = strings.TrimSpace(s)
694+ if match := xrefPattern.FindStringSubmatch(s); match != nil {
695+ return Inline{Kind: InlineXRef, Text: match[1], Section: match[2]}
696+ }
697+ if strings.HasPrefix(s, "/") {
698+ return Inline{Kind: InlinePath, Text: s}
699+ }
700+ if strings.HasPrefix(s, "-") {
701+ return Inline{Kind: InlineFlag, Text: strings.TrimPrefix(s, "-")}
702+ }
703+ return Inline{Kind: InlineLiteral, Text: s}
704+}
705+
706+func synthesizeSynopsis(options []Option, args string) SynopsisForm {
707+ var items []SynopsisItem
708+ for _, opt := range sortedOptions(options) {
709+ items = append(items, SynopsisItem{
710+ Kind: SynOptional,
711+ Children: cloneSynopsisItems(opt.Spec),
712+ })
713+ }
714+ items = append(items, parseSynopsis(args, false).Items...)
715+ return SynopsisForm{Items: items}
716+}
717+
718+func sortedOptions(options []Option) []Option {
719+ out := append([]Option(nil), options...)
720+ slices.SortFunc(out, func(a, b Option) int {
721+ return strings.Compare(optionSortKey(a), optionSortKey(b))
722+ })
723+ return out
724+}
725+
726+func optionSortKey(opt Option) string {
727+ flag := optionPrimaryFlag(opt)
728+ if flag == "" {
729+ return "zzzz"
730+ }
731+ r := flag[0]
732+ prefix := "2"
733+ switch {
734+ case r >= '0' && r <= '9':
735+ prefix = "0"
736+ case r >= 'A' && r <= 'Z':
737+ prefix = "1"
738+ }
739+ return prefix + flag
740+}
741+
742+func cloneSynopsisItems(items []SynopsisItem) []SynopsisItem {
743+ out := make([]SynopsisItem, len(items))
744+ for i, item := range items {
745+ out[i] = item
746+ if len(item.Children) != 0 {
747+ out[i].Children = cloneSynopsisItems(item.Children)
748+ }
749+ }
750+ return out
751+}
752+
753+func compactLines(lines []string) []string {
754+ var out []string
755+ for _, line := range lines {
756+ line = strings.TrimSpace(line)
757+ if line != "" {
758+ out = append(out, line)
759+ }
760+ }
761+ return out
762+}
763+
764+func isSeeAlsoTitle(title string) bool {
765+ return strings.EqualFold(strings.TrimSpace(title), "SEE ALSO")
766+}
767+
768+func parseXRefs(s string) ([]XRef, bool) {
769+ var refs []XRef
770+ for _, part := range strings.Split(s, ",") {
771+ part = strings.TrimSpace(part)
772+ match := xrefPattern.FindStringSubmatch(part)
773+ if match == nil {
774+ return nil, false
775+ }
776+ refs = append(refs, XRef{Name: match[1], Section: match[2]})
777+ }
778+ return refs, len(refs) != 0
779+}
780+
781+func parseSynopsis(raw string, explicit bool) SynopsisForm {
782+ tokens := tokenizeSynopsis(raw)
783+ items, _ := parseSynopsisSeq(tokens, 0, explicit, true)
784+ return SynopsisForm{Items: items}
785+}
786+
787+func tokenizeSynopsis(raw string) []string {
788+ var tokens []string
789+ var current strings.Builder
790+ flush := func() {
791+ if current.Len() != 0 {
792+ tokens = append(tokens, current.String())
793+ current.Reset()
794+ }
795+ }
796+
797+ for i := 0; i < len(raw); i++ {
798+ ch := raw[i]
799+ switch ch {
800+ case '[', ']', '|':
801+ flush()
802+ tokens = append(tokens, string(ch))
803+ case ' ', '\t', '\n':
804+ flush()
805+ default:
806+ current.WriteByte(ch)
807+ }
808+ }
809+ flush()
810+ return tokens
811+}
812+
813+func parseSynopsisSeq(tokens []string, start int, explicit bool, topLevel bool) ([]SynopsisItem, int) {
814+ var items []SynopsisItem
815+ seenNonFlag := false
816+
817+ for i := start; i < len(tokens); i++ {
818+ switch tokens[i] {
819+ case "]":
820+ return items, i
821+ case "[":
822+ children, end := parseSynopsisSeq(tokens, i+1, explicit, false)
823+ items = append(items, SynopsisItem{Kind: SynOptional, Children: children})
824+ i = end
825+ case "|":
826+ items = append(items, SynopsisItem{Kind: SynPipe, Text: "|"})
827+ default:
828+ if flags, ok := splitGroupedFlags(tokens[i]); ok {
829+ items = append(items, flags...)
830+ continue
831+ }
832+ kind := classifySynopsisToken(tokens, i, explicit, topLevel, seenNonFlag)
833+ text := tokens[i]
834+ if text == "..." && len(items) != 0 {
835+ items[len(items)-1].Text += " ..."
836+ continue
837+ }
838+ items = append(items, SynopsisItem{Kind: kind, Text: text})
839+ if kind != SynFlag && kind != SynPipe {
840+ seenNonFlag = true
841+ }
842+ }
843+ }
844+
845+ return items, len(tokens)
846+}
847+
848+func splitGroupedFlags(tok string) ([]SynopsisItem, bool) {
849+ // We expand compacted synopsis tokens like some -abcDef into separate
850+ // semantic flags so the renderer can emit a separate Fl for each one.
851+ if len(tok) < 3 || tok[0] != '-' {
852+ return nil, false
853+ }
854+ for i := 1; i < len(tok); i++ {
855+ c := tok[i]
856+ if (c < '0' || c > '9') && (c < 'A' || c > 'Z') && (c < 'a' || c > 'z') {
857+ return nil, false
858+ }
859+ }
860+
861+ items := make([]SynopsisItem, 0, len(tok)-1)
862+ for i := 1; i < len(tok); i++ {
863+ items = append(items, SynopsisItem{
864+ Kind: SynFlag,
865+ Text: "-" + string(tok[i]),
866+ })
867+ }
868+ return items, true
869+}
870+
871+func classifySynopsisToken(tokens []string, i int, explicit, topLevel, seenNonFlag bool) SynopsisItemKind {
872+ tok := tokens[i]
873+ if strings.HasPrefix(tok, "-") {
874+ return SynFlag
875+ }
876+ if strings.HasPrefix(tok, "<") && strings.HasSuffix(tok, ">") {
877+ return SynArg
878+ }
879+ if strings.Contains(tok, "...") || strings.ContainsAny(tok, "/=:") {
880+ return SynArg
881+ }
882+ if i > 0 && tokens[i-1] == "|" {
883+ if explicit {
884+ return SynCommand
885+ }
886+ return SynArg
887+ }
888+ if i+1 < len(tokens) && tokens[i+1] == "|" {
889+ if explicit {
890+ return SynCommand
891+ }
892+ return SynArg
893+ }
894+ if i > 0 && strings.HasPrefix(tokens[i-1], "-") {
895+ return SynArg
896+ }
897+ if explicit && topLevel && !seenNonFlag && i+1 < len(tokens) && tokens[i+1] == "[" {
898+ return SynCommand
899+ }
900+ return SynArg
901+}
902+
903+func (p *Page) validate() error {
904+ if p.Name == "" {
905+ return fmt.Errorf("missing manpage name")
906+ }
907+ if p.Summary == "" {
908+ return fmt.Errorf("missing manpage summary")
909+ }
910+ return nil
911+}
912+
913+func normalizePageInlines(page *Page) {
914+ for i := range page.Description {
915+ page.Description[i] = mergeAdjacentTextInBlock(page.Description[i])
916+ }
917+ for i := range page.Options {
918+ for j := range page.Options[i].Body {
919+ page.Options[i].Body[j] = mergeAdjacentTextInBlock(page.Options[i].Body[j])
920+ }
921+ }
922+ for i := range page.Sections {
923+ for j := range page.Sections[i].Blocks {
924+ page.Sections[i].Blocks[j] = mergeAdjacentTextInBlock(page.Sections[i].Blocks[j])
925+ }
926+ }
927+}
928+
929+func mergeAdjacentTextInBlock(block Block) Block {
930+ block.Inlines = mergeAdjacentText(block.Inlines)
931+ for i := range block.Items {
932+ block.Items[i].Label = mergeAdjacentText(block.Items[i].Label)
933+ block.Items[i].Body = mergeAdjacentText(block.Items[i].Body)
934+ }
935+ return block
936+}
937+
938+func mergeAdjacentText(inlines []Inline) []Inline {
939+ if len(inlines) == 0 {
940+ return nil
941+ }
942+ out := []Inline{inlines[0]}
943+ for _, inline := range inlines[1:] {
944+ last := &out[len(out)-1]
945+ if last.Kind == InlineText && inline.Kind == InlineText {
946+ last.Text += inline.Text
947+ continue
948+ }
949+ out = append(out, inline)
950+ }
951+ return out
952+}
+0,
-248
1@@ -1,248 +0,0 @@
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-}