Compare commits

..

18 commits
index ... main

Author SHA1 Message Date
56766dcd9b Fixes post-notification. 2026-09-08 18:15:41 -03:00
2b92954cea Corrections suggested by tin. 2026-08-24 11:31:22 -03:00
b345a8d544 Implements the SEARCH command. 2026-08-21 12:18:51 -03:00
1536193116 Implements LISTGROUP and provides bytes correctly in XOVER. 2026-08-20 16:50:39 -03:00
33b3a7da01 Rebuilds and verifies the index. 2026-08-18 18:20:23 -03:00
e1826ad9eb Give up on using catch and use conditions instead.
This is a major rewrite of how we're catching for trouble during
parsing and the posting of articles.  When fetching articles by
message-id, we don't need to enforce that a group has been chosen.
2026-08-18 15:33:26 -03:00
05be4aad57 A major rewrite: new cmd-post, the index and procedure group-high-low.
The index is now a central part of the system, as it should.  We store
articles just like git stores its objects, in a two-tier directory.
For example, article whose message id hashes to deadbeef is stored at
index/de/ad/deadbeef.  Articles in groups are now hardlinked to the
index, the main repository of articles.

Group's article names are now cached in groups/<g>/.meta/.  The meta
information is incrementally augmented every time it's necessary.
Commands LIST and GROUP are now served at constant time.

Other little fixes were done a long the way.
2026-08-17 11:43:06 -03:00
602f44b464 Cleans up LOOP completely for coherence with the new io work.
We're ready to tackle the new design on the index, so we're going live
with the current changes so that we can use it every day and spot any
mishaps we might find (while we write the new index subsystem).
2026-08-06 18:14:29 -03:00
53072ef46f Rewrites and presents the section on streaming articles to disk. 2026-08-05 16:04:42 -03:00
efb1410785 Streams POSTed articles to disk, allowing huge articles to be posted.
This commit freezes a point in which we know our implementation is
tested and we're confident in the algorithms.  But we know there are
minor bugs easily fixable.  We proceed now to a major rewrite of
everything to better accommodate the new strategy.  In the process, we
might break things, which is why we're freezing this safe point.
2026-07-31 13:11:06 -03:00
48dd285cd0 Removes use of stream-interactive-p. 2026-07-25 22:54:08 -03:00
0228f32cb1 Reduces the use of memory when reading articles. 2026-07-20 00:09:03 -03:00
5a176692db Optimizes procedures for posting the 100 MiB article.
But we're not yet ready to send such an article back to a client.  The
reason is most likely the fact that we read the entire article into
memory in fetch-article.  The new sensible idea is to not read the
body of articles into memory, but delay the body reading until the
last moment---of sending it out to the client.

Nevertheless, we're getting needlessly ambitious here because we don't
intend to support file attachments anyway.  We might do it just as an
exercise, if the end result looks good.  I think it's a good exercise
and I think we should do it.
2026-07-15 22:49:22 -03:00
6a9fcc1ffc Optimizes procedures for passing the 10 MiB article. 2026-07-15 21:50:54 -03:00
47e29139de Goes low-level for fast reading. 2026-07-15 19:33:38 -03:00
94d2e78447 Rewrites conforms? and post. All checks are done in conforms? now. 2026-06-11 01:30:06 -03:00
efa5ee3ccd Uses polymode in the GNU EMACS, not noweb-mode any longer. 2026-06-09 23:15:44 -03:00
Circling Skies
4412496fb6 Shortens the release command. 2024-12-21 18:47:23 -03:00
19 changed files with 3255 additions and 3174 deletions

View file

@ -3,13 +3,25 @@ include Makefile
default: all default: all
all: loop loop.lisp loop.asd scripts/build-exe.lisp \ all: loop.exe loop.lisp loop.asd scripts/build-exe.lisp \
scripts/build-index-from-fs.lisp \ scripts/build-index-from-fs.lisp \
scripts/cron-remove-inactive-users.lisp \ scripts/cron-remove-inactive-users.lisp \
scripts/migrate-add-creation-date.lisp scripts/migrate-add-creation-date.lisp \
scripts/verify-index.lisp \
scripts/find-duplicates.lisp \
version.lisp TAGS
loop: scripts/build-exe.lisp loop.lisp loop.asd loop.nw version.lisp: loop.lisp
echo "(in-package :loop)" > version.lisp
echo "(defparameter *git-hash* \"$$(git rev-parse --short HEAD)\")" >> version.lisp
TAGS: loop.lisp
etags $(PWD)/loop.lisp
sed -i 's|$(PWD)/loop.lisp|$(PWD)/loop.nw|g' TAGS
loop.exe: scripts/build-exe.lisp loop.lisp loop.asd loop.nw
sbcl --script scripts/build-exe.lisp sbcl --script scripts/build-exe.lisp
(test -f loop.exe && cmp loop loop.exe) || cp loop loop.exe
loop.lisp: loop.nw format-def loop.lisp: loop.nw format-def
(no tangle -Rloop.lisp < loop.nw | sh format-def > loop.tmp || \ (no tangle -Rloop.lisp < loop.nw | sh format-def > loop.tmp || \
@ -17,20 +29,9 @@ loop.lisp: loop.nw format-def
mv loop.tmp loop.lisp mv loop.tmp loop.lisp
format-def: loop.nw format-def: loop.nw
(no tangle -Rformat-def < loop.nw > format-def.tmp || \ (no tangle -Rformat-def.sh < loop.nw > format-def.tmp || \
(rm format-def.tmp && exit 1)) && \ (rm format-def.tmp && exit 1)) && \
mv format-def.tmp format-def && \ mv format-def.tmp format-def
chmod 0755 format-def
make-release: loop.nw
(no tangle -Rmake-release < loop.nw > make-release.tmp || \
(rm make-release.tmp && exit 1)) && \
mv make-release.tmp make-release && \
chmod 0755 make-release
release: make-release
./make-release $$(git log --oneline -1 | awk '{print $$1}') \
loop.nw > loop.tmp && mv loop.tmp loop.nw
loop.asd: loop.nw loop.asd: loop.nw
(no tangle -Rloop.asd < loop.nw > loop-asd.tmp || \ (no tangle -Rloop.asd < loop.nw > loop-asd.tmp || \
@ -48,6 +49,18 @@ scripts/build-index-from-fs.lisp: loop.nw
(rm build-index-from-fs.tmp && exit 1)) && \ (rm build-index-from-fs.tmp && exit 1)) && \
mv build-index-from-fs.tmp scripts/build-index-from-fs.lisp mv build-index-from-fs.tmp scripts/build-index-from-fs.lisp
scripts/verify-index.lisp: loop.nw
(no tangle -Rverify-index.lisp < loop.nw > \
verify-index.tmp || \
(rm verify-index.tmp && exit 1)) && \
mv verify-index.tmp scripts/verify-index.lisp
scripts/find-duplicates.lisp: loop.nw
(no tangle -Rfind-duplicates.lisp < loop.nw > \
find-duplicates.tmp || \
(rm find-duplicates.tmp && exit 1)) && \
mv find-duplicates.tmp scripts/find-duplicates.lisp
scripts/cron-remove-inactive-users.lisp: loop.nw scripts/cron-remove-inactive-users.lisp: loop.nw
(no tangle -Rcron-remove-inactive-users.lisp < loop.nw > \ (no tangle -Rcron-remove-inactive-users.lisp < loop.nw > \
cron-remove-inactive-users.tmp || \ cron-remove-inactive-users.tmp || \
@ -62,12 +75,6 @@ scripts/migrate-add-creation-date.lisp: loop.nw
mv migrate-add-creation-date.tmp \ mv migrate-add-creation-date.tmp \
scripts/migrate-add-creation-date.lisp scripts/migrate-add-creation-date.lisp
run: loop.nw
(no tangle -Rrun < loop.nw > run.tmp || \
(rm run.tmp && exit 1)) && \
mv run.tmp run && \
chmod 0755 run
loop.tex: loop.nw loop.tex: loop.nw
no weave -delay -index loop.nw > loop.tex no weave -delay -index loop.nw > loop.tex
@ -75,9 +82,6 @@ loop.pdf: loop.tex
latexmk -pdf loop latexmk -pdf loop
clean: clean:
rm -f loop loop.asd loop.lisp loop.tex loop.pdf \ rm -f loop loop.exe loop.asd loop.lisp loop.tex loop.pdf \
*.fasl *.db .*~ *~ *.out *.aux *.log *.fls *.fdb_latexmk *.fasl *.db .*~ *~ *.out *.aux *.log *.fls *.fdb_latexmk \
TAGS
tags: loop.nw
etags -l tex loop.nw
etags -a -l lisp loop.nw

View file

@ -5,5 +5,5 @@ loop: loop.asd loop.lisp scripts/build-exe.lisp
install: loop install: loop
mkdir -p `head -1 conf-home` && \ mkdir -p `head -1 conf-home` && \
cp -R loop accounts.lisp index groups scripts \ cp -R loop accounts.lisp groups scripts \
`head -1 conf-home` `head -1 conf-home`

25
README
View file

@ -21,7 +21,7 @@ install it as a local project. Go to
and say and say
$ git clone https://git.antartida.xyz/loop/srv loop $ git clone https://git.atlantida.space/root/loop
$ cd loop $ cd loop
$ echo /path/to/loop/home > conf-home $ echo /path/to/loop/home > conf-home
$ make install $ make install
@ -71,12 +71,12 @@ $ tcpserver -v -HR 0.0.0.0 1024 ./loop -s
tcpserver: status: 0/40 tcpserver: status: 0/40
--8<-------------------------------------------------------->8--- --8<-------------------------------------------------------->8---
Now telnet to your host on port 1024: Using another terminal, telnet to your host on port 1024:
--8<-------------------------------------------------------->8--- --8<-------------------------------------------------------->8---
$ telnet localhost 1024 $ telnet localhost 1024
Trying 127.0.0.1... Trying 127.0.0.1...
Connected to antartida.xyz. Connected to 127.0.0.1.
Escape character is '^]'. Escape character is '^]'.
200 Welcome! Say ``help'' for a menu. 200 Welcome! Say ``help'' for a menu.
quit quit
@ -122,23 +122,6 @@ $ crontab -l
We installed LOOP just fine on We installed LOOP just fine on
OpenBSD 7.6 with SBCL 2.4.8.openbsd.sbcl-2.4.8. Void GNU/Linux release ``rolling'' with SBCL 2.6.6.void.1
FreeBSD 14.1, 14.2 with SBCL 2.4.9. FreeBSD 14.1, 14.2 with SBCL 2.4.9.
Debian GNU/Linux 8.11 codename jessie with SBCL 1.2.4.debian. Debian GNU/Linux 8.11 codename jessie with SBCL 1.2.4.debian.
(*) Systems with installation issues
We installed LOOP on Ubuntu 24.04 (24.01.1 LTS) codename noble with
SBCL 2.2.9.debian. We found that CLSQL could not load the shared
object libsqlite3.so because ``apt install libsqlite3'' installs the
library at
/usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6
with a symbolic link to libsqlite3.so.0, but not to libsqlite3.so.
SBCL is trying to load libsqlite3.so, so a solution is to just tell
your system to
ln -s libsqlite3.so.0 libsqlite3.so
at /usr/lib/x86_64-linux-gnu.

290
TAGS
View file

@ -1,290 +0,0 @@
loop.nw,3549
\def\nwendcode{\nwendcode35,939
\newcommand{\lxxp}\lxxp39,1017
\newcommand{\Lp}\Lp40,1048
\newcommand{\lp}\lp41,1077
\newcommand{\bug}\bug42,1099
\newcommand{\symlink}\symlink43,1128
\newcommand{\symlinks}\symlinks44,1171
\label{fg:gnus}fg:gnus81,2706
\label{fg:bird}fg:bird87,2911
\label{fg:sylpheed}fg:sylpheed93,3111
\ref{fg:gnus}fg:gnus137,5733
\section*{*140,5822
Section \ref{sec:inactive-users}sec:inactive-users148,6361
\section*{*184,8177
\section{Implementation strategy}\label{sec:design}Implementation190,8309
illustrated in Figures~\ref{fg:gnus}fg:gnus319,12490
Section~\ref{sec:repl}sec:repl323,12759
\section{NNTP protocol}NNTP328,12992
\section{It's a network server}It's342,13734
through the command line as illustrated in Section~\ref{sec:design}sec:design369,15206
\section{REPL for \lp}\label{sec:repl}REPL371,15276
\section{Representation of a client}Representation437,17374
\section{Representation of a command}Representation449,17770
\section{Representation of requests and responses}Representation517,20737
\section{Main loop}Main594,23947
\section{Parsing of requests}Parsing669,25880
\section{Parsing of command-line arguments}Parsing688,26641
\section{Request dispatching mechanism}Request961,36913
\section{Representation and parsing of articles}Representation982,37706
\section{How to extract articles from the database}How1065,40809
\section{Commands}Commands1115,42605
\subsection{{\tt HELP}}1117,42625
\subsection{{\tt AUTHINFO}}\label{sec:authinfo}1151,43910
Section~\ref{sec:login}sec:login1157,44202
\subsection{{\tt CREATE-ACCOUNT}}1202,45894
\subsection{{\tt UNLOCK-ACCOUNT}}1320,50452
\ref{sec:inactive-users}sec:inactive-users1323,50541
\subsection{{\tt LOGIN}}\label{sec:login}1356,51852
Besides {\tt AUTHINFO} in Section~\ref{sec:authinfo}sec:authinfo1358,51895
\subsection{{\tt PASSWD}}\label{sec:passwd}1384,52856
\subsection{{\tt USERS}}\label{sec:users}1430,54730
\subsection{{\tt LIST}}\label{sec:list}1480,56381
\subsection{{\tt GROUP}}\label{sec:group}1632,62446
\subsection{{\tt BODY}, {\tt HEAD}, {\tt ARTICLE} e {\tt NEXT}}1662,63270
\label{sec:typical-cmds}sec:typical-cmds1663,63334
Section~\ref{sec:index}sec:index1673,63902
\subsection{{\tt XOVER}}\label{sec:xover}1775,68503
\subsection{{\tt MODE READER}}\label{sec:mode-reader}1833,71074
\subsection{{\tt DATE}}\label{sec:date}1842,71321
\subsection{{\tt QUIT}}\label{sec:quit}1855,71664
\subsection{{\tt DD}}\label{sec:dd}1865,71927
\subsection{{\tt POST}}\label{sec:post}1878,72420
\subsection{{\tt CREATE-GROUP}}2241,87684
\subsection{{\tt REPL}}2278,89424
\section{Publication of news}Publication2305,90274
\section{Algorithm of {\tt split-vector}}Algorithm2346,91833
\section{Article index}\label{sec:index}Article2377,93028
\section{Essential operations relative to the index}Essential2457,96627
\section{Procedure to import the index from the file system}Procedure2502,98639
\section{Deletion and locking of inactive accounts}\label{sec:inactive-users}Deletion2568,100703
\section{Macros to ease writing}\label{sec:dsl}Macros2734,107284
\section{Other procedures}Other2797,109416
\section{Tests}Tests2887,112172
\section{Executable, how to build it}Executable,2960,115048
\section{Log of \lp's communication, how to produce it}Log2981,115628
\section{Package {\tt loop.lisp} as the compiler needs it}Package3018,116977
\section{Other source code}Other3118,120026
\section*{*3209,123229
\section*{*3212,123270
loop.nw,7251
(defun repl 406,16344
(defstruct client 444,17635
(defparameter *client* 445,17707
(defstruct command 460,18266
(defparameter *commands-assoc* 461,18306
(defun table-of-commands 465,18369
(defun set-up-tables! 490,19679
(defun get-command 503,20175
(defstruct request 532,21437
(defstruct response 533,21472
(defun empty-response 537,21569
(defun prepend-response-with 538,21648
(defun append-crlf-if-needed 549,22038
(defun send-response! 559,22358
(defun my-write 588,23770
(defun main-loop 609,24648
(defun request-quit? 617,24871
(defun response-quit? 618,24938
(defun server-start 620,25011
(defun main 625,25086
(defun send-banner! 633,25219
(defun parse-request 676,26130
(defun root/command 717,28103
(defun root/options 726,28330
(defun root/handlers 745,28781
(defun root/main 812,31505
(defun account/command 829,32050
(defun account/options 836,32217
(defun account/manager 880,33246
(defun account/list-accounts 929,35415
(defun account/create-account! 932,35499
(defun account/change-passwd! 948,36285
(defun dispatch 972,37440
(defun dispatch-line 978,37595
(defstruct article 1001,38661
(defun parse-article 1005,38761
(defun hs-space-collapsed 1017,39117
(defun hs-lines 1020,39233
(defun parse-header 1022,39291
(defun parse-head 1033,39765
(defun string-integer? 1037,39888
(defun get-header-from-article 1044,40120
(defun get-header 1047,40222
(defun fetch-headers 1051,40328
(defun enrich-headers 1057,40538
(defun nlines 1062,40720
(defun fetch-article 1073,41166
(defun read-file-raw 1077,41253
(defun fetch-body 1088,41548
(defun encode-body 1099,42004
(defun extract-mid 1109,42415
(defun lookup 1111,42509
(defun cmd-help 1132,43409
(defun menu 1140,43672
(defun display-fn 1144,43750
(defun cmd-authinfo 1167,44575
(defun authinfo-check 1189,45558
(defun auth? 1192,45627
(defun log-user-in! 1195,45681
(defun user-name-conforms? 1222,46556
(defun cmd-create-account 1226,46661
(defstruct account 1243,47431
(defparameter *accounts* 1244,47525
(defun read-accounts! 1248,47602
(defun string->array 1256,47799
(defun string->sha256 1261,47960
(defun new-account! 1265,48108
(defun write-accounts! 1298,49724
(defun get-account 1314,50246
(defun cmd-unlock-account 1327,50650
(defun unlock-account! 1344,51440
(defun cmd-login 1364,52175
(defun log-user-in-as! 1379,52743
(defun cmd-passwd 1391,53128
(defun pass? 1410,54030
(defun change-passwd! 1421,54453
(defun cmd-list-users 1436,54874
(defun size-of-longest-username 1444,55101
(defun list-users 1448,55216
(defun universal-to-human 1468,56050
(defun last-time-seen 1474,56169
(defstruct group 1490,56831
(defun cmd-list 1492,56864
(defun build-groups-lines 1499,57124
(defun build-groups-structs 1506,57288
(defun between? 1513,57553
(defun filesize 1517,57628
(defun zero-file? 1521,57700
(defun temporary-article? 1524,57751
(defun article-ready? 1528,57860
(defun get-articles 1533,57977
(defun group-high-low 1543,58365
(defun articles->integers 1550,58592
(defun list-groups 1557,58829
(defun last-char 1562,59026
(defun basename 1570,59239
(defun loop-directory* 1591,60259
(defun loop-list-files 1601,60921
(defun loop-list-directories 1610,61276
(defun cmd-group 1637,62577
(defun group? 1648,62987
(defun xgroup? 1652,63057
(defun set-group! 1655,63097
(defun typical-cmd-head-body-article 1676,63998
(defun cmd-head 1692,64865
(defun cmd-body 1694,64938
(defun cmd-article 1696,65011
(defun article-response 1699,65091
(defun head-response 1701,65190
(defun body-response 1703,65303
(defun typical-cmd-response 1732,66889
(defun cmd-next 1753,67671
(defun article-next! 1764,68125
(defun mid-by-name 1771,68392
(defun cmd-xover 1784,68864
(defun xover 1803,69821
(defun xover-format-line 1824,70743
(defun xover-headers 1829,70965
(defun cmd-mode 1838,71220
(defun cmd-date 1848,71508
(defun cmd-quit 1861,71841
(defun cmd-test 1873,72224
(defun suggest-message-id 1890,72964
(defun random-string 1893,73056
(defun unparse-article 1907,73469
(defun ensure-header 1924,74079
(defun get-date 1937,74556
(defun ensure-mid 1944,74798
(defun ensure-date 1946,74877
(defun cmd-post 1974,75958
(defun post 2009,77527
(defun newsgroups-header->list 2033,78605
(defun update-last-post-date! 2048,79301
(defun rename-no-extension 2066,80230
(defun save-article-try 2069,80333
(defun save-article-insist 2103,81607
(defun get-next-article-id 2109,81828
(defun nntp-read-article 2125,82527
(defun nntp-read-line 2144,83403
(defun list->bytes 2153,83766
(defun vector->bytes 2156,83820
(defun data->bytes 2159,83889
(defun add-crlf-between 2167,84158
(defun string->bytes 2171,84310
(defun bytes->string 2174,84365
(defun conforms? 2194,85304
(defun text/plain? 2207,85952
(defun headers-required-from-clients 2216,86295
(defun cmd-create-group 2251,88133
(defun group-name-conforms? 2274,89300
(defun cmd-repl 2284,89577
(defparameter *enable-nntp-repl* 2302,90234
(defun notify-group-created 2312,90507
(defun notify-user-created 2317,90718
(defun notify-user-unlocked 2322,90916
(defun post-notification 2328,91133
(defun make-news 2334,91423
(defun split-vector 2357,92329
(defun split-vector-helper 2361,92471
(defun create-index! 2411,94531
(defun rm-rf 2414,94595
(defun drop-create-index! 2417,94676
(defun insert-index 2477,97606
(defun lookup-index 2494,98361
(defun index-from-fs! 2524,99476
(defun remake-index-from-fs 2533,99889
(defun remove-inactive-users! 2592,101636
(defun remove-account! 2628,103539
(defun lock-account! 2636,103831
(defun loop-epoch 2652,104565
(defun migrate-add-creation-and-post-date! 2655,104626
(defparameter *months-inactive-allowed* 2677,105392
(defparameter *months-never-logged-in* 2678,105435
(defun user-inactive? 2682,105521
(defun inactive-from-never-logged-in? 2686,105647
(defun locked? 2693,105901
(defun inactive-from-last-post? 2696,105988
(defun inactive-from-last-seen? 2704,106346
(defun inactive-from? 2713,106695
(defun ever-logged-in? 2721,106902
(defun never-logged-in? 2724,106978
(defun list-inactive-users 2727,107050
(defmacro in-dir 2742,107560
(defmacro in-groups 2756,108079
(defun in-group-lambda 2758,108141
(defmacro in-group 2760,108197
(defmacro with-group 2763,108297
(defmacro with-n-args 2773,108619
(defmacro with-group-set 2783,108996
(defmacro with-auth 2790,109201
(defun conforms-to? 2808,109948
(defun print/finish 2815,110199
(defun word-plural 2819,110285
(defun plural 2826,110536
(defun out 2833,110720
(defun stderr 2836,110791
(defun stdout 2841,110917
(defun println 2844,110993
(defun enumerate 2847,111105
(defun ucs-2->ascii 2851,111222
(defun bad-input 2855,111319
(defun integer->string 2858,111416
(defun mkstr 2861,111467
(defun data 2865,111572
(defun crlf 2868,111655
(defun crlf-string 2871,111689
(defun flatten 2874,111755
(defmacro mac 2883,112041
(define-test dispatching2918,113875
(defun unix->nntp 2921,113970
(defvar a-post 2925,114082
(defvar a-bad-post 2933,114231
(define-test post-wrong-newsgroup2941,114426
(define-test post-okay2946,114593
(defpackage #:loop3040,118020
(defparameter *debug* 3090,119274
(asdf:defsystem :<<Name>>3111,119857

0
format-def Executable file → Normal file
View file

View file

@ -1,5 +1,5 @@
Date: 2024-03-07 21:44:31 GMT-3 Date: 2024-03-07 21:44:31 GMT-3
Message-Id: <edjocyeqzqqhnswlbrbo@loop> Message-Id: <xdjocyeqzqqhnswlbrbo@loop>
From: Loop From: Loop
Subject: let there be light Subject: let there be light
Newsgroups: local.control.news Newsgroups: local.control.news

BIN
images/gnus-article.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

BIN
images/sylpheed-reading.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -1,7 +1,8 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; -*- ;;; -*- Mode: LISP; Syntax: COMMON-LISP; -*-
(asdf:defsystem :LOOP (asdf:defsystem :LOOP
:version "ec13a0c" :version "0.0.1"
:description "An NNTP server for a circle of friends." :description "An NNTP server for a circle of friends."
:depends-on (:lisp-unit :str :uiop :cl-ppcre :local-time :clsql-sqlite3 :clingon :depends-on (:lisp-unit :str :uiop :cl-ppcre :local-time :clsql-sqlite3 :clingon
:filesystem-utils :ironclad/digest/sha256) :filesystem-utils :ironclad/digest/sha256)
:components ((:file "loop"))) :components ((:file "loop")
(:file "version")))

1571
loop.lisp

File diff suppressed because it is too large Load diff

3288
loop.nw

File diff suppressed because it is too large Load diff

View file

@ -8,8 +8,8 @@ usage()
test $# -lt 2 && usage test $# -lt 2 && usage
tag="$1"; shift tag="$1"; shift
sed "/<<Version>>=/ { sed "/0.0.1=/ {
n n;
c\\ c\\
$tag $tag
}" "$@" }" "$@"

989
noweb.sty
View file

@ -1,989 +0,0 @@
% noweb.sty -- LaTeX support for noweb
% DON'T read or edit this file! Use ...noweb-source/tex/support.nw instead.
{\obeyspaces\AtBeginDocument{\global\let =\ }} % from texbook, p 381
\def\nwopt@nomargintag{\let\nwmargintag=\@gobble}
\def\nwopt@margintag{%
\def\nwmargintag##1{\leavevmode\llap{##1\kern\nwmarginglue\kern\codemargin}}}
\def\nwopt@margintag{%
\def\nwmargintag##1{\leavevmode\kern-\codemargin\nwthemargintag{##1}\kern\codemargin}}
\def\nwthemargintag#1{\llap{#1\kern\nwmarginglue}}
\nwopt@margintag
\newdimen\nwmarginglue
\nwmarginglue=0.3in
\def\nwtagstyle{\footnotesize\Rm}
% make \hsize in code sufficient for 88 columns
\setbox0=\hbox{\tt m}
\newdimen\codehsize
\codehsize=91\wd0 % 88 columns wasn't enough; I don't know why
\newdimen\codemargin
\codemargin=0pt
\newdimen\nwdefspace
\nwdefspace=\codehsize
% need to use \textwidth in {\LaTeX} to handle styles with
% non-standard margins (David Bruce). Don't know why we sometimes
% wanted \hsize. 27 August 1997.
%% \advance\nwdefspace by -\hsize\relax
\ifx\textwidth\undefined
\advance\nwdefspace by -\hsize\relax
\else
\advance\nwdefspace by -\textwidth\relax
\fi
\chardef\other=12
\def\setupcode{%
\chardef\\=`\\
\chardef\{=`\{
\chardef\}=`\}
\catcode`\$=\other
\catcode`\&=\other
\catcode`\#=\other
\catcode`\%=\other
\catcode`\~=\other
\catcode`\_=\other
\catcode`\^=\other
\catcode`\"=\other % fixes problem with german.sty
\obeyspaces\Tt
}
%\let\nwlbrace=\{
%\let\nwrbrace=\}
\def\nwendquote{\relax\ifhmode\spacefactor=1000 \fi}
{\catcode`\^^M=\active % make CR an active character
\gdef\newlines{\catcode`\^^M=\active % make CR an active character
\def^^M{\par\startline}}%
\gdef\eatline#1^^M{\relax}%
}
%%% DON'T \gdef^^M{\par\startline}}% in case ^^M appears in a \write
\def\startline{\noindent\hskip\parindent\ignorespaces}
\def\nwnewline{\ifvmode\else\hfil\break\leavevmode\hbox{}\fi}
\def\setupmodname{%
\catcode`\$=3
\catcode`\&=4
\catcode`\#=6
\catcode`\%=14
\catcode`\~=13
\catcode`\_=8
\catcode`\^=7
\catcode`\ =10
\catcode`\^^M=5
\let\nwlbrace\lbrace
\let\nwrbrace\rbrace
\let\{\nwlbrace
\let\}\nwrbrace
% bad news --- don't know what catcode to give "
\Rm}
\def\LA{\begingroup\maybehbox\bgroup\setupmodname\It$\langle$}
\def\RA{\/$\rangle$\egroup\endgroup}
\def\code{\leavevmode\begingroup\setupcode\newlines}
\def\edoc{\endgroup}
\let\maybehbox\relax
\newbox\equivbox
\setbox\equivbox=\hbox{$\equiv$}
\newbox\plusequivbox
\setbox\plusequivbox=\hbox{$\mathord{+}\mathord{\equiv}$}
% \moddef can't have an argument because there might be \code...\edoc
\def\moddef{\leavevmode\kern-\codemargin\LA}
\def\endmoddef{\RA\ifmmode\equiv\else\unhcopy\equivbox\fi
\nobreak\hfill\nobreak}
\def\plusendmoddef{\RA\ifmmode\mathord{+}\mathord{\equiv}\else\unhcopy\plusequivbox\fi
\nobreak\hfill\nobreak}
\def\chunklist{%
\errhelp{I changed \chunklist to \nowebchunks.
I'll try to avoid such incompatible changes in the future.}%
\errmessage{Use \string\nowebchunks\space instead of \string\chunklist}}
\def\nowebchunks{\message{<Warning: You need noweave -x to use \string\nowebchunks>}}
\def\nowebindex{\message{<Warning: You need noweave -index to use \string\nowebindex>}}
% here is support for the new-style (capitalized) font-changing commands
% thanks to Dave Love
\ifx\documentstyle\undefined
\let\Rm=\rm \let\It=\it \let\Tt=\tt % plain
\else\ifx\selectfont\undefined
\let\Rm=\rm \let\It=\it \let\Tt=\tt % LaTeX OFSS
\else % LaTeX NFSS
\def\Rm{\reset@font\rm}
\def\It{\reset@font\it}
\def\Tt{\reset@font\tt}
\def\Bf{\reset@font\bf}
\fi\fi
\ifx\reset@font\undefined \let\reset@font=\relax \fi
\def\noweboptions#1{%
\def\@nwoptionlist{#1}%
\@for\@nwoption:=\@nwoptionlist\do{%
\@ifundefined{nwopt@\@nwoption}{%
\@latexerr{There is no such noweb option as '\@nwoption'}\@eha}{%
\csname nwopt@\@nwoption\endcsname}}}
\codemargin=10pt
\advance\codehsize by \codemargin % make room for indentation of code
\advance\nwdefspace by \codemargin % and fix adjustment for def/use
\def\setcodemargin#1{%
\advance\codehsize by -\codemargin % make room for indentation of code
\advance\nwdefspace by -\codemargin % and fix adjustment for def/use
\codemargin=#1
\advance\codehsize by \codemargin % make room for indentation of code
\advance\nwdefspace by \codemargin % and fix adjustment for
% def/use
}
\def\nwopt@shift{%
\dimen@=-0.8in
\if@twoside % Values for two-sided printing:
\advance\evensidemargin by \dimen@
\else % Values for one-sided printing:
\advance\evensidemargin by \dimen@
\advance\oddsidemargin by \dimen@
\fi
% \advance \marginparwidth -\dimen@
}
\let\nwopt@noshift\@empty
\def\nwbegincode#1{%
\begingroup
\topsep \nwcodetopsep
\@beginparpenalty \@highpenalty
\@endparpenalty -\@highpenalty
\@begincode }
\def\nwendcode{\endtrivlist \endgroup \filbreak} % keeps code on 1 page
\newenvironment{webcode}{%
\@begincode
}{%
\endtrivlist}
\def\@begincode{%
\trivlist \item[]%
\leftskip\@totalleftmargin \advance\leftskip\codemargin
\rightskip\hsize \advance\rightskip -\codehsize
\parskip\z@ \parindent\z@ \parfillskip\@flushglue
\linewidth\codehsize
\@@par
\def\par{\leavevmode\null \@@par \penalty\nwcodepenalty}%
\obeylines
\@noligs \ifx\verbatim@nolig@list\undefined\else
\let\do=\nw@makeother \verbatim@nolig@list \do@noligs\`
\fi
\setupcode \frenchspacing \@vobeyspaces
\nowebsize \setupcode
\let\maybehbox\mbox }
\newskip\nwcodetopsep \nwcodetopsep = 3pt plus 1.2pt minus 1pt
\let\nowebsize=\normalsize
\def\nwopt@tinycode{\let\nowebsize=\tiny}
\def\nwopt@footnotesizecode{\let\nowebsize=\footnotesize}
\def\nwopt@scriptsizecode{\let\nowebsize=\scriptsize}
\def\nwopt@smallcode{\let\nowebsize=\small}
\def\nwopt@normalsizecode{\let\nowebsize=\normalsize}
\def\nwopt@largecode{\let\nowebsize=\large}
\def\nwopt@Largecode{\let\nowebsize=\Large}
\def\nwopt@LARGEcode{\let\nowebsize=\LARGE}
\def\nwopt@hugecode{\let\nowebsize=\huge}
\def\nwopt@Hugecode{\let\nowebsize=\Huge}
\newcount\nwcodepenalty \nwcodepenalty=\@highpenalty
\def\nw@makeother#1{\catcode`#1=12 }
\def\nwbegindocs#1{\ifvmode\noindent\fi}
\let\nwenddocs=\relax
\let\nwdocspar=\filbreak
\def\@nwsemifilbreak#1{\vskip0pt plus#1\penalty-200\vskip0pt plus -#1}
\newdimen\nwbreakcodespace
\nwbreakcodespace=0.2in % by default, leave no more than this on a page
\def\nwopt@breakcode{%
\def\nwdocspar{\@nwsemifilbreak{0.2in}}%
\def\nwendcode{\endtrivlist\endgroup} % ditches filbreak
}
\raggedbottom
\def\code{\leavevmode\begingroup\setupcode\@vobeyspaces\obeylines}
\let\edoc=\endgroup
\newdimen\@original@textwidth
\def\ps@noweb{%
\@original@textwidth=\textwidth
\let\@mkboth\@gobbletwo
\def\@oddfoot{}\def\@evenfoot{}% No feet.
\if@twoside % If two-sided printing.
\def\@evenhead{\hbox to \@original@textwidth{%
\Rm \thepage\qquad{\Tt\leftmark}\hfil\today}}% Left heading.
\def\@oddhead{\hbox to \@original@textwidth{%
\Rm \today\hfil{\Tt\leftmark}\qquad\thepage}}% Right heading.
\else % If one-sided printing.
\def\@oddhead{\hbox to \@original@textwidth{%
\Rm \today\hfil{\Tt\leftmark}\qquad\thepage}}% Right heading.
\let\@evenhead\@oddhead
\fi
\let\chaptermark\@gobble
\let\sectionmark\@gobble
\let\subsectionmark\@gobble
\let\subsubsectionmark\@gobble
\let\paragraphmark\@gobble
\let\subparagraphmark\@gobble
\def\nwfilename{\begingroup\let\do\@makeother\dospecials
\catcode`\{=1 \catcode`\}=2 \nw@filename}
\def\nw@filename##1{\endgroup\markboth{##1}{##1}\let\nw@filename=\nw@laterfilename}%
}
\def\nw@laterfilename#1{\endgroup\clearpage \markboth{#1}{#1}}
\let\nwfilename=\@gobble
\def\nwcodecomment#1{\@@par\penalty\nwcodepenalty
\if@firstnwcodecomment
\vskip\nwcodecommentsep\penalty\nwcodepenalty\@firstnwcodecommentfalse
\fi%
\hspace{-\codemargin}{%
\rightskip=0pt plus1in
\interlinepenalty\nwcodepenalty
\let\\\relax\footnotesize\Rm #1\@@par\penalty\nwcodepenalty}}
\def\@nwalsodefined#1{\nwcodecomment{\@nwlangdepdef\ \nwpageprep\ \@pagesl{#1}.}}
\def\@nwused#1{\nwcodecomment{\@nwlangdepcud\ \nwpageprep\ \@pagesl{#1}.}}
\def\@nwnotused#1{\nwcodecomment{\@nwlangdeprtc.}}
\def\nwoutput#1{\nwcodecomment{\@nwlangdepcwf\ {\Tt \@stripstar#1*\stripped}.}}
\def\@stripstar#1*#2\stripped{#1}
\newcommand{\nwprevdefptr}[1]{%
\mbox{$\mathord{\triangleleft}\,\mathord{\mbox{\subpageref{#1}}}$}}
\newcommand{\nwnextdefptr}[1]{%
\mbox{$\mathord{\mbox{\subpageref{#1}}}\,\mathord{\triangleright}$}}
\newcommand{\@nwprevnextdefs}[2]{%
{\nwtagstyle
\ifx\relax#1\else ~~\nwprevdefptr{#1}\fi
\ifx\relax#2\else ~~\nwnextdefptr{#2}\fi}}
\newcommand{\@nwusesondefline}[1]{{\nwtagstyle~~(\@pagenumsl{#1})}}
\newcommand{\@nwstartdeflinemarkup}{\nobreak\hskip 1.5em plus 1fill\nobreak}
\newcommand{\@nwenddeflinemarkup}{\nobreak\hskip \nwdefspace minus\nwdefspace\nobreak}
\def\nwopt@longxref{%
\let\nwalsodefined\@nwalsodefined
\let\nwused\@nwused
\let\nwnotused\@nwnotused
\let\nwprevnextdefs\@gobbletwo
\let\nwusesondefline\@gobble
\let\nwstartdeflinemarkup\relax
\let\nwenddeflinemarkup\relax
}
\def\nwopt@shortxref{%
\let\nwalsodefined\@gobble
\let\nwused\@gobble
\let\nwnotused\@gobble
\let\nwprevnextdefs\@nwprevnextdefs
\let\nwusesondefline\@nwusesondefline
\let\nwstartdeflinemarkup\@nwstartdeflinemarkup
\let\nwenddeflinemarkup\@nwenddeflinemarkup
}
\def\nwopt@noxref{%
\let\nwalsodefined\@gobble
\let\nwused\@gobble
\let\nwnotused\@gobble
\let\nwprevnextdefs\@gobbletwo
\let\nwusesondefline\@gobble
\let\nwstartdeflinemarkup\relax
\let\nwenddeflinemarkup\relax
}
\nwopt@shortxref % to hell with backward compatibility!
\newskip\nwcodecommentsep \nwcodecommentsep=3pt plus 1pt minus 1pt
\newif\if@firstnwcodecomment\@firstnwcodecommenttrue
\newcount\@nwlopage\newcount\@nwhipage % range lo..hi-1
\newcount\@nwlosub % subpage of lo
\newcount\@nwhisub % subpage of hi
\def\@nwfirstpage#1#2#3{% subpage page xref-tag
\@nwlopage=#2 \@nwlosub=#1
\def\@nwloxreftag{#3}%
\advance\@nwpagecount by \@ne
\@nwhipage=\@nwlopage\advance\@nwhipage by \@ne }
\def\@nwnextpage#1#2#3{% subpage page xref-tag
\ifnum\@nwhipage=#2
\advance\@nwhipage by \@ne
\advance\@nwpagecount by \@ne
\@nwhisub=#1
\def\@nwhixreftag{#3}\else
\ifnum#2<\@nwlopage \advance\@nwhipage by \m@ne
\ifnum\@nwhipage=\@nwlopage
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}}%
\else
\count@=\@nwhipage \advance\count@ by \m@ne
\ifnum\count@=\@nwlopage % consecutive pages
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}%
\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwhisub}{\number\@nwhipage}}
{\@nwhixreftag}}}%
\else \ifnum\@nwlopage<110 \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100 \multiply\count@ by 100
\ifnum\count@=\@nwlopage \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100
\@nwpagetemp=\@nwhipage \divide\@nwpagetemp by 100
\ifnum\count@=\@nwpagetemp % lo--least 2 digits of hi
\multiply\@nwpagetemp by 100
\advance \@nwhipage by -\@nwpagetemp
\edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\else \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\fi
\fi
\fi%
\fi
\fi%
\edef\@tempa{\noexpand\nwix@cons\noexpand\nw@pages{\@tempa}}\@tempa\@nwfirstpage{#1}{#2}{#3}\else
\ifnum#2>\@nwhipage \advance\@nwhipage by \m@ne
\ifnum\@nwhipage=\@nwlopage
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}}%
\else
\count@=\@nwhipage \advance\count@ by \m@ne
\ifnum\count@=\@nwlopage % consecutive pages
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}%
\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwhisub}{\number\@nwhipage}}
{\@nwhixreftag}}}%
\else \ifnum\@nwlopage<110 \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100 \multiply\count@ by 100
\ifnum\count@=\@nwlopage \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100
\@nwpagetemp=\@nwhipage \divide\@nwpagetemp by 100
\ifnum\count@=\@nwpagetemp % lo--least 2 digits of hi
\multiply\@nwpagetemp by 100
\advance \@nwhipage by -\@nwpagetemp
\edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\else \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\fi
\fi
\fi%
\fi
\fi%
\edef\@tempa{\noexpand\nwix@cons\noexpand\nw@pages{\@tempa}}\@tempa\@nwfirstpage{#1}{#2}{#3}\else
\@nwlosub=0 \@nwhisub=0
\fi\fi\fi
}
\newcount\@nwpagetemp
\newcount\@nwpagecount
\def\@nwfirstpagel#1{% label
\@ifundefined{r@#1}{\@warning{Reference `#1' on page \thepage \space undefined}%
\nwix@cons\nw@pages{\\{\bf ??}}}{%
\edef\@tempa{\noexpand\@nwfirstpage\subpagepair{#1}{#1}}\@tempa}}
\def\@nwnextpagel#1{% label
\@ifundefined{r@#1}{\@warning{Reference `#1' on page \thepage \space undefined}%
\nwix@cons\nw@pages{\\{\bf ??}}}{%
\edef\@tempa{\noexpand\@nwnextpage\subpagepair{#1}{#1}}\@tempa}}
\def\@pagesl#1{% list of labels
\gdef\nw@pages{}\@nwpagecount=0
\def\\##1{\@nwfirstpagel{##1}\let\\=\@nwnextpagel}#1%
\advance\@nwhipage by \m@ne
\ifnum\@nwhipage=\@nwlopage
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}}%
\else
\count@=\@nwhipage \advance\count@ by \m@ne
\ifnum\count@=\@nwlopage % consecutive pages
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}%
\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwhisub}{\number\@nwhipage}}
{\@nwhixreftag}}}%
\else \ifnum\@nwlopage<110 \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100 \multiply\count@ by 100
\ifnum\count@=\@nwlopage \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100
\@nwpagetemp=\@nwhipage \divide\@nwpagetemp by 100
\ifnum\count@=\@nwpagetemp % lo--least 2 digits of hi
\multiply\@nwpagetemp by 100
\advance \@nwhipage by -\@nwpagetemp
\edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\else \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\fi
\fi
\fi%
\fi
\fi%
\edef\@tempa{\noexpand\nwix@cons\noexpand\nw@pages{\@tempa}}\@tempa\def\\##1{\@nwhyperpagenum##1}%
\ifnum\@nwpagecount=1 \nwpageword \else \nwpagesword\fi~\commafy{\nw@pages}}
\def\@nwhyperpagenum#1#2{\nwhyperreference{#2}{#1}}
\def\@pagenumsl#1{% list of labels -- doesn't include word `pages', commas, or `and'
\gdef\nw@pages{}\@nwpagecount=0
\def\\##1{\@nwfirstpagel{##1}\let\\=\@nwnextpagel}#1%
\advance\@nwhipage by \m@ne
\ifnum\@nwhipage=\@nwlopage
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}}%
\else
\count@=\@nwhipage \advance\count@ by \m@ne
\ifnum\count@=\@nwlopage % consecutive pages
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}%
\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwhisub}{\number\@nwhipage}}
{\@nwhixreftag}}}%
\else \ifnum\@nwlopage<110 \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100 \multiply\count@ by 100
\ifnum\count@=\@nwlopage \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100
\@nwpagetemp=\@nwhipage \divide\@nwpagetemp by 100
\ifnum\count@=\@nwpagetemp % lo--least 2 digits of hi
\multiply\@nwpagetemp by 100
\advance \@nwhipage by -\@nwpagetemp
\edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\else \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\fi
\fi
\fi%
\fi
\fi%
\edef\@tempa{\noexpand\nwix@cons\noexpand\nw@pages{\@tempa}}\@tempa%
\def\\##1{\@nwhyperpagenum##1\let\\=\@nwpagenumslrest}\nw@pages}
\def\@nwpagenumslrest#1{~\@nwhyperpagenum#1}
\def\subpages#1{% list of {{subpage}{page}}
\gdef\nw@pages{}\@nwpagecount=0
\def\\##1{\edef\@tempa{\noexpand\@nwfirstpage##1{}}\@tempa
\def\\####1{\edef\@tempa{\noexpand\@nwnextpage####1}\@tempa}}#1%
\advance\@nwhipage by \m@ne
\ifnum\@nwhipage=\@nwlopage
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}}%
\else
\count@=\@nwhipage \advance\count@ by \m@ne
\ifnum\count@=\@nwlopage % consecutive pages
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}%
\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwhisub}{\number\@nwhipage}}
{\@nwhixreftag}}}%
\else \ifnum\@nwlopage<110 \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100 \multiply\count@ by 100
\ifnum\count@=\@nwlopage \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100
\@nwpagetemp=\@nwhipage \divide\@nwpagetemp by 100
\ifnum\count@=\@nwpagetemp % lo--least 2 digits of hi
\multiply\@nwpagetemp by 100
\advance \@nwhipage by -\@nwpagetemp
\edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\else \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\fi
\fi
\fi%
\fi
\fi%
\edef\@tempa{\noexpand\nwix@cons\noexpand\nw@pages{\@tempa}}\@tempa\def\\##1{\@firstoftwo##1}%
\ifnum\@nwpagecount=1 \nwpageword \else \nwpagesword\fi~\commafy{\nw@pages}}
\def\@nwaddrange{\advance\@nwhipage by \m@ne
\ifnum\@nwhipage=\@nwlopage
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}}%
\else
\count@=\@nwhipage \advance\count@ by \m@ne
\ifnum\count@=\@nwlopage % consecutive pages
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}%
\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwhisub}{\number\@nwhipage}}
{\@nwhixreftag}}}%
\else \ifnum\@nwlopage<110 \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100 \multiply\count@ by 100
\ifnum\count@=\@nwlopage \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100
\@nwpagetemp=\@nwhipage \divide\@nwpagetemp by 100
\ifnum\count@=\@nwpagetemp % lo--least 2 digits of hi
\multiply\@nwpagetemp by 100
\advance \@nwhipage by -\@nwpagetemp
\edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\else \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\fi
\fi
\fi%
\fi
\fi%
\edef\@tempa{\noexpand\nwix@cons\noexpand\nw@pages{\@tempa}}\@tempa}
\def\nwpageword{\@nwlangdepchk} % chunk, was page
\def\nwpagesword{\@nwlangdepchks} % chunk, was page
\def\nwpageprep{\@nwlangdepin} % in, was on
\newcommand\nw@genericref[2]{% what to do, name of ref
\expandafter\nw@g@nericref\csname r@#2\endcsname#1{#2}}
\newcommand\nw@g@nericref[3]{% control sequence, what to do, name
\ifx#1\relax
\ref{#3}% trigger the standard `undefined ref' mechanisms
\else
\expandafter#2#1.\\%
\fi}
\def\nw@selectone#1#2#3\\{#1}
\def\nw@selecttwo#1#2#3\\{#2}
\def\nw@selectonetwo#1#2#3\\{{#1}{#2}}
\newcommand{\subpageref}[1]{%
\nwhyperreference{#1}{\nw@genericref\@subpageref{#1}}}
\def\@subpageref#1#2#3\\{%
\@ifundefined{2on#2}{#2}{\nwthepagenum{#1}{#2}}}
\newcommand{\subpagepair}[1]{% % produces {subpage}{page}
\@ifundefined{r@#1}%
{{0}{0}}%
{\nw@genericref\@subpagepair{#1}}}
\def\@subpagepair#1#2#3\\{%
\@ifundefined{2on#2}{{0}{#2}}{{#1}{#2}}}
\newcommand{\sublabel}[1]{%
\leavevmode % needed to make \@bsphack work
\@bsphack
\nwblindhyperanchor{#1}%
\if@filesw {\let\thepage\relax
\def\protect{\noexpand\noexpand\noexpand}%
\edef\@tempa{\write\@auxout{\string
\newsublabel{#1}{{}{\thepage}}}}%
\expandafter}\@tempa
\if@nobreak \ifvmode\nobreak\fi\fi\fi\@esphack}
\newcommand{\nosublabel}[1]{%
\@bsphack\if@filesw {\let\thepage\relax
\def\protect{\noexpand\noexpand\noexpand}%
\edef\@tempa{\write\@auxout{\string
\newlabel{#1}{{0}{\thepage}}}}%
\expandafter}\@tempa
\if@nobreak \ifvmode\nobreak\fi\fi\fi\@esphack}
\newcommand\newsublabel{%
\nw@settrailers
\global\let\newsublabel\@newsublabel
\@newsublabel}
\newcommand{\@newsublabel}[2]{%
\edef\this@page{\@cdr#2\@nil}%
\ifx\this@page\last@page\else
\sub@page=\z@
\fi
\edef\last@page{\this@page}
\advance\sub@page by \@ne
\ifnum\sub@page=\tw@
\global\@namedef{2on\this@page}{}%
\fi
\pendingsublabel{#1}%
\edef\@tempa##1{\noexpand\newlabel{##1}%
{{\number\sub@page}{\this@page}\nw@labeltrailers}}%
\pending@sublabels
\def\pending@sublabels{}}
\newcommand\nw@settrailers{% -- won't work on first run
\@ifpackageloaded{nameref}%
{\gdef\nw@labeltrailers{{}{}{}}}%
{\gdef\nw@labeltrailers{}}}
\renewcommand\nw@settrailers{%
\@ifundefined{@secondoffive}%
{\gdef\nw@labeltrailers{}}%
{\gdef\nw@labeltrailers{{}{}{}}}}
\newcommand{\nextchunklabel}[1]{%
\nwblindhyperanchor{#1}% % looks slightly bogus --- nr
\@bsphack\if@filesw {\let\thepage\relax
\edef\@tempa{\write\@auxout{\string\pendingsublabel{#1}}}%
\expandafter}\@tempa
\if@nobreak \ifvmode\nobreak\fi\fi\fi\@esphack}
\newcommand\pendingsublabel[1]{%
\def\@tempa{\noexpand\@tempa}%
\edef\pending@sublabels{\noexpand\@tempa{#1}\pending@sublabels}}
\def\pending@sublabels{}
\def\last@page{\relax}
\newcount\sub@page
\def\@alphasubpagenum#1#2{#2\ifnum#1=0 \else\@alph{#1}\fi}
\def\@nosubpagenum#1#2{#2}
\def\@numsubpagenum#1#2{#2\ifnum#1=0 \else.\@arabic{#1}\fi}
\def\nwopt@nosubpage{\let\nwthepagenum=\@nosubpagenum\nwopt@nomargintag}
\def\nwopt@numsubpage{\let\nwthepagenum=\@numsubpagenum}
\def\nwopt@alphasubpage{\let\nwthepagenum=\@alphasubpagenum}
\nwopt@alphasubpage
\newcount\@nwalph@n
\let\@nwalph@d\@tempcnta
\let\@nwalph@bound\@tempcntb
\def\@nwlongalph#1{{%
\@nwalph@n=#1\advance\@nwalph@n by-1
\@nwalph@bound=26
\loop\ifnum\@nwalph@n<\@nwalph@bound\else
\advance\@nwalph@n by -\@nwalph@bound
\multiply\@nwalph@bound by 26
\repeat
\loop\ifnum\@nwalph@bound>1
\divide\@nwalph@bound by 26
\@nwalph@d=\@nwalph@n\divide\@nwalph@d by \@nwalph@bound
% d := d * bound ; n -:= d; d := d / bound --- saves a temporary
\multiply\@nwalph@d by \@nwalph@bound
\advance\@nwalph@n by -\@nwalph@d
\divide\@nwalph@d by \@nwalph@bound
\advance\@nwalph@d by 1 \@alph{\@nwalph@d}%
\repeat
}}
\newcount\nw@chunkcount
\nw@chunkcount=\@ne
\newcommand{\weblabel}[1]{%
\@bsphack
\nwblindhyperanchor{#1}%
\if@filesw {\let\thepage\relax
\def\protect{\noexpand\noexpand\noexpand}%
\edef\@tempa{\write\@auxout{\string
\newsublabel{#1}{{}{\number\nw@chunkcount}}}}%
\expandafter}\@tempa
\global\advance\nw@chunkcount by \@ne
\if@nobreak \ifvmode\nobreak\fi\fi\fi\@esphack}
\def\nwopt@webnumbering{%
\let\sublabel=\weblabel
\def\nwpageword{chunk}\def\nwpagesword{chunks}%
\def\nwpageprep{in}}
% \nwindexdefn{printable name}{identifying label}{label of chunk}
% \nwindexuse{printable name}{identifying label}{label of chunk}
\def\nwindexdefn#1#2#3{\@auxix{\protect\nwixd}{#2}{#3}}
\def\nwindexuse#1#2#3{\@auxix{\protect\nwixu}{#2}{#3}}
\def\@auxix#1#2#3{% {marker}{id label}{subpage label}
\@bsphack\if@filesw {\let\nwixd\relax\let\nwixu\relax
\def\protect{\noexpand\noexpand\noexpand}%
\edef\@tempa{\write\@auxout{\string\nwixadd{#1}{#2}{#3}}}%
\expandafter}\@tempa
\if@nobreak \ifvmode\nobreak\fi\fi\fi\@esphack}
% \nwixadd{marker}{idlabel}{subpage label}
\def\nwixadd#1#2#3{%
\@ifundefined{nwixl@#2}%
{\global\@namedef{nwixl@#2}{#1{#3}}}%
{\expandafter\nwix@cons\csname nwixl@#2\endcsname{#1{#3}}}}
\def\@nwsubscriptident#1#2{\mbox{$\mbox{#1}_{\mathrm{\subpageref{#2}}}$}}
\def\@nwnosubscriptident#1#2{#1}
\def\@nwhyperident#1#2{\leavevmode\nwhyperreference{#2}{#1}}
\def\nwopt@subscriptidents{%
\let\nwlinkedidentq\@nwsubscriptident
\let\nwlinkedidentc\@nwsubscriptident
}
\def\nwopt@nosubscriptidents{%
\let\nwlinkedidentq\@nwnosubscriptident
\let\nwlinkedidentc\@nwnosubscriptident
}
\def\nwopt@hyperidents{%
\let\nwlinkedidentq\@nwhyperident
\let\nwlinkedidentc\@nwhyperident
}
\def\nwopt@nohyperidents{%
\let\nwlinkedidentq\@nwnosubscriptident
\let\nwlinkedidentc\@nwnosubscriptident
}
\def\nwopt@subscriptquotedidents{%
\let\nwlinkedidentq\@nwsubscriptident
}
\def\nwopt@nosubscriptquotedidents{%
\let\nwlinkedidentq\@nwnosubscriptident
}
\def\nwopt@hyperquotedidents{%
\let\nwlinkedidentq\@nwhyperident
}
\def\nwopt@nohyperquotedidents{%
\let\nwlinkedidentq\@nwnosubscriptident
}
\nwopt@hyperidents
\newcount\@commacount
\def\commafy#1{%
{\nwix@listcount{#1}\@commacount=\nwix@counter
\let\@comma@each=\\%
\ifcase\@commacount\let\\=\@comma@each\or\let\\=\@comma@each\or
\def\\{\def\\{ \@nwlangdepand\ \@comma@each}\@comma@each}\else
\def\\{\def\\{, %
\advance\@commacount by \m@ne
\ifnum\@commacount=1 \@nwlangdepand~\fi\@comma@each}\@comma@each}\fi
#1}}
\def\nwix@cons#1#2{% {list}{\marker{element}}
{\toks0=\expandafter{#1}\def\@tempa{#2}\toks2=\expandafter{\@tempa}%
\xdef#1{\the\toks0 \the\toks2 }}}
\def\nwix@uses#1{% {label}
\def\nwixu{\\}\let\nwixd\@gobble\@nameuse{nwixl@#1}}
\def\nwix@defs#1{% {label}
\def\nwixd{\\}\let\nwixu\@gobble\@nameuse{nwixl@#1}}
\newcount\nwix@counter
\def\nwix@listcount#1{% {list with \\}
{\count@=0
\def\\##1{\advance\count@ by \@ne }%
#1\global\nwix@counter=\count@ }}
\def\nwix@usecount#1{\nwix@listcount{\nwix@uses{#1}}}
\def\nwix@defcount#1{\nwix@listcount{\nwix@defs{#1}}}
\def\nwix@id@defs#1{% index pair
{{\Tt \@car#1\@nil}%
\def\\##1{\nwix@defs@space\subpageref{##1}}\nwix@defs{\@cdr#1\@nil}}}
% useful above to change ~ into something that can break
% this option is undocumented because I think breakdefs is always right
\def\nwopt@breakdefs{\def\nwix@defs@space{\penalty200\ }}
\def\nwopt@nobreakdefs{\def\nwix@defs@space{~}} % old code
\nwopt@breakdefs
\def\nwidentuses#1{% list of index pairs
\nwcodecomment{\@nwlangdepuss\ \let\\=\nwix@id@defs\commafy{#1}.}}
\def\nwix@totaluses#1{% list of index pairs
{\count@=0
\def\\##1{\nwix@usecount{\@cdr##1\@nil}\advance\count@ by\nwix@counter}%
#1\global\nwix@counter\count@ }}
\def\nwix@id@uses#1#2{% {ident}{label}
\nwix@usecount{#2}\ifnum\nwix@counter>0
{\advance\leftskip by \codemargin
\nwcodecomment{{\Tt #1}, \@nwlangdepusd\ \nwpageprep\ \@pagesl{\nwix@uses{#2}}.}}%
\else
\ifnw@hideunuseddefs\else
{\advance\leftskip by \codemargin \nwcodecomment{{\Tt #1}, \@nwlangdepnvu.}}%
\fi
\fi}
\def\nwidentdefs#1{% list of index pairs
\ifnw@hideunuseddefs\nwix@totaluses{#1}\else\nwix@listcount{#1}\fi
\ifnum\nwix@counter>0
\nwcodecomment{\@nwlangdepdfs:}%
{\def\\##1{\nwix@id@uses ##1}#1}%
\fi}
\newif\ifnw@hideunuseddefs\nw@hideunuseddefsfalse
\def\nwopt@hideunuseddefs{\nw@hideunuseddefstrue}
\def\nwopt@noidentxref{%
\let\nwidentdefs\@gobble
\let\nwidentuses\@gobble}
\def\nw@underlinedefs{% {list with \nwixd, \nwixu}
\let\\=\relax\def\nw@comma{, }
\def\nwixd##1{\\\underline{\subpageref{##1}}\let\\\nw@comma}%
\def\nwixu##1{\\\subpageref{##1}\let\\\nw@comma}}
\def\nw@indexline#1#2{%
{\indent {\Tt #1}: \nw@underlinedefs\@nameuse{nwixl@#2}\par}}
\newenvironment{thenowebindex}{\parindent=-10pt \parskip=\z@
\advance\leftskip by 10pt
\advance\rightskip by 0pt plus1in\par\@afterindenttrue
\def\\##1{\nw@indexline##1}}{}
\def\nowebindex{%
\@ifundefined{nwixs@i}%
{\@warning{The \string\nowebindex\space is empty}}%
{\begin{thenowebindex}\@nameuse{nwixs@i}\end{thenowebindex}}}
\def\nowebindex@external{%
{\let\nwixadds@c=\@gobble
\def\nwixadds@i##1{\nw@indexline##1}%
\def\nwixaddsx##1##2{\@nameuse{nwixadds@##1}{##2}}%
\begin{thenowebindex}\@input{\jobname.nwi}\end{thenowebindex}}}
\def\nwixlogsorted#1#2{% list data
\@bsphack\if@filesw
\toks0={#2}\immediate\write\@auxout{\string\nwixadds{#1}{\the\toks0}}
\if@nobreak \ifvmode\nobreak\fi\fi\fi\@esphack}
\def\nwixadds#1#2{%
\@ifundefined{nwixs@#1}%
{\global\@namedef{nwixs@#1}{\\{#2}}}%
{\expandafter\nwix@cons\csname nwixs@#1\endcsname{\\{#2}}}}
\let\nwixaddsx=\@gobbletwo
\def\nwopt@externalindex{%
\ifx\nwixadds\@gobbletwo % already called
\else
\let\nwixaddsx=\nwixadds \let\nwixadds=\@gobbletwo
\let\nowebindex=\nowebindex@external
\let\nowebchunks=\nowebchunks@external
\fi}
\def\nowebchunks{%
\@ifundefined{nwixs@c}%
{\@warning{The are no \string\nowebchunks}}%
{\begin{thenowebchunks}\@nameuse{nwixs@c}\end{thenowebchunks}}}
\def\nowebchunks@external{%
{\let\nwixadds@i=\@gobble
\def\nwixadds@c##1{\nw@onechunk##1}%
\def\nwixaddsx##1##2{\@nameuse{nwixadds@##1}{##2}}%
\begin{thenowebchunks}\@input{\jobname.nwi}\end{thenowebchunks}}}
\@namedef{r@nw@notdef}{{0}{(\@nwlangdepnvd)}}
\def\nw@chunkunderlinedefs{% {list of labels with \nwixd, \nwixu}
\let\\=\relax\def\nw@comma{, }
\def\nwixd##1{\\\underline{\subpageref{##1}}\let\\\nw@comma}%
\def\nwixu##1{\\\subpageref{##1}\let\\\nw@comma}}
\def\nw@onechunk#1#2#3{% {name}{label of first definition}{list with \nwixd, \nwixu}
\@ifundefined{r@#2}{}{%
\indent\LA #1~{\nwtagstyle\subpageref{#2}}\RA
\if@nwlongchunks{~\nw@chunkunderlinedefs#3}\fi\par}}
\newenvironment{thenowebchunks}{\vskip3pt
\parskip=\z@\parindent=-10pt \advance\leftskip by 10pt
\advance\rightskip by 0pt plus10pt \@afterindenttrue
\def\\##1{\nw@onechunk##1}}{}
\newif\if@nwlongchunks
\@nwlongchunksfalse
\let\nwopt@longchunks\@nwlongchunkstrue
\newcommand\@nw@hyper@ref{\hyperreference} % naras
\newcommand\@nw@hyper@anc{\blindhyperanchor} % naras
\newcommand\@nw@hyperref@ref[2]{\hyperlink{noweb.#1}{#2}} % nr
\newcommand\@nw@hyperref@anc[1]{\hypertarget{noweb.#1}{\relax}} % nr
%%\renewcommand\@nw@hyperref@ref[2]{{#2}} % nr
%%\renewcommand\@nw@hyperref@anc[1]{} % nr
\newcommand\nwhyperreference{%
\@ifundefined{hyperlink}
{\@ifundefined{hyperreference}
{\global\let\nwhyperreference\@gobble}
{\global\let\nwhyperreference\@nw@hyper@ref}}
{\global\let\nwhyperreference\@nw@hyperref@ref}%
\nwhyperreference
}
\newcommand\nwblindhyperanchor{%
\@ifundefined{hyperlink}
{\@ifundefined{hyperreference}
{\global\let\nwblindhyperanchor\@gobble}
{\global\let\nwblindhyperanchor\@nw@hyper@anc}}
{\global\let\nwblindhyperanchor\@nw@hyperref@anc}%
\nwblindhyperanchor
}
\newcommand\nwanchorto{%
\begingroup\let\do\@makeother\dospecials
\catcode`\{=1 \catcode`\}=2 \nw@anchorto}
\newcommand\nw@anchorto[1]{\endgroup\def\nw@next{#1}\nw@anchortofin}
\newcommand\nw@anchortofin[1]{#1\footnote{See URL \texttt{\nw@next}.}}
\let\nwanchorname\@gobble
\newif\ifhtml
\htmlfalse
\let\nwixident=\relax
\def\nwbackslash{\char92}
\def\nwlbrace{\char123}
\def\nwrbrace{\char125}
\def\nwopt@english{%
\def\@nwlangdepdef{This definition is continued}%
\def\@nwlangdepcud{This code is used}%
\def\@nwlangdeprtc{Root chunk (not used in this document)}%
\def\@nwlangdepcwf{This code is written to file}%
\def\@nwlangdepchk{chunk}%
\def\@nwlangdepchks{chunks}%
\def\@nwlangdepin{in}%
\def\@nwlangdepand{and}%
\def\@nwlangdepuss{Uses}%
\def\@nwlangdepusd{used}%
\def\@nwlangdepnvu{never used}%
\def\@nwlangdepdfs{Defines}%
\def\@nwlangdepnvd{never defined}%
}
\let\nwopt@american\nwopt@english
\def\nwopt@portuges{%
\def\@nwlangdepdef{Defini\c{c}\~ao continuada em}%
% This definition is continued
\def\@nwlangdepcud{C\'odigo usado em}%
% This code is used
\def\@nwlangdeprtc{Fragmento de topo (sem uso no documento)}%
% Root chunk (not used in this document)
\def\@nwlangdepcwf{Este c\'odigo foi escrito no ficheiro}%
% This code is written to file
\def\@nwlangdepchk{fragmento}%
% chunk
\def\@nwlangdepchks{fragmentos}%
% chunks
\def\@nwlangdepin{no(s)}%
% in
\def\@nwlangdepand{e}%
% and
\def\@nwlangdepuss{Usa}%
% Uses
\def\@nwlangdepusd{usado}%
% used
\def\@nwlangdepnvu{nunca usado}%
% never used
\def\@nwlangdepdfs{Define}%
% Defines
\def\@nwlangdepnvd{nunca definido}%
% never defined
}
\def\nwopt@brazil{%
\def\@nwlangdepdef{Defini\c{c}\~{a}o continua}%
% This definition is continued
\def\@nwlangdepcud{C\'odigo usado}%
% This code is used
\def\@nwlangdeprtc{Trecho raiz, isto é, este trecho come\c{c}a aqui}%
% Root chunk (not used in this document)
\def\@nwlangdepcwf{C\'odigo escrito no arquivo}%
% This code is written to file
\def\@nwlangdepchk{trecho}%
% chunk
\def\@nwlangdepchks{trechos}%
% chunks
\def\@nwlangdepin{@}%
% in
\def\@nwlangdepand{e}%
% and
\def\@nwlangdepuss{Usa}%
% Uses
\def\@nwlangdepusd{usado}%
% used
\def\@nwlangdepnvu{nunca usado}%
% never used
\def\@nwlangdepdfs{Define}%
% Defines
\def\@nwlangdepnvd{nunca definido}%
% never defined
}
\def\nwopt@frenchb{%
\def\@nwlangdepdef{Suite de la d\'efinition}%
% This definition is continued
\def\@nwlangdepcud{Ce code est employ\'e}%
% This code is used
\def\@nwlangdeprtc{Morceau racine (pas employ\'e dans ce document)}%
% Root chunk (not used in this document)
\def\@nwlangdepcwf{Ce code est \'ecrit dans le fichier}%
% This code is written to file
\def\@nwlangdepchk{le morceau}%
% chunk
\def\@nwlangdepchks{les morceaux}%
% chunks
\def\@nwlangdepin{dans}%
% in
\def\@nwlangdepand{et}%
% and
\def\@nwlangdepuss{Utilise}%
% Uses
\def\@nwlangdepusd{utilis\'{e}}%
% used
\def\@nwlangdepnvu{jamais employ\'{e}}%
% never used
\def\@nwlangdepdfs{D\'{e}finit}%
% Defines
% Cannot use the accent here: \def\@nwlangdepnvd{jamais d\'{e}fini}%
\def\@nwlangdepnvd{jamais defini}%
% never defined
}
\let\nwopt@french\nwopt@frenchb
\def\nwopt@german{%
\def\@nwlangdepdef{Diese Definition wird fortgesetzt}%
% This definition is continued
\def\@nwlangdepcud{Dieser Code wird benutzt}%
% This code is used
\def\@nwlangdeprtc{Hauptteil (nicht in diesem Dokument benutzt)}%
% Root chunk (not used in this document)
%\def\@nwlangdepcwf{Dieser Code schreibt man zum File}%
\def\@nwlangdepcwf{Dieser Code geht in Datei}%
% This code is written to file
\def\@nwlangdepchk{dem Teil}%
% chunk
%\def\@nwlangdepchks{Teils}%
\def\@nwlangdepchks{den Teilen}%
% chunks
\def\@nwlangdepin{in}%
% in
\def\@nwlangdepand{und}%
% and
\def\@nwlangdepuss{Benutzt}%
% Uses
\def\@nwlangdepusd{benutzt}%
% used
\def\@nwlangdepnvu{nicht benutzt}%
% never used
\def\@nwlangdepdfs{Definiert}%
% Defines
\def\@nwlangdepnvd{nicht definiert}%
% never defined
}
\def\nwopt@german{%
\def\@nwlangdepdef{Diese Definition wird fortgesetzt}%
% This definition is continued
\def\@nwlangdepcud{Dieser Code wird benutzt}%
% This code is used
\def\@nwlangdeprtc{Hauptteil (nicht in diesem Dokument benutzt)}%
% Root chunk (not used in this document)
\def\@nwlangdepcwf{Dieser Code schreibt man zum File}%
% This code is written to file
\def\@nwlangdepchk{Teil}%
% chunk
\def\@nwlangdepchks{Teils}%
% chunks
\def\@nwlangdepin{im}%
% in
\def\@nwlangdepand{und}%
% and
\def\@nwlangdepuss{Benutzt}%
% Uses
\def\@nwlangdepusd{benutzt}%
% used
\def\@nwlangdepnvu{nicht benutzt}%
% never used
\def\@nwlangdepdfs{Definiert}%
% Defines
\def\@nwlangdepnvd{nicht definiert}%
% never defined
}
\let\nwopt@ngerman\nwopt@german
\ifx\languagename\undefined % default is English
\noweboptions{english}
\else
\@ifundefined{nwopt@\languagename}
{\noweboptions{english}}
{\expandafter\noweboptions\expandafter{\languagename}}
\fi
\let\obeyedspace\@xobeysp

View file

@ -5,6 +5,57 @@
(load quicklisp-init))) (load quicklisp-init)))
(ql:quickload :loop :silent t) (ql:quickload :loop :silent t)
(in-package #:loop) (in-package #:loop)
(drop-create-index!) (defun index-from-fs! ()
(loop for path in (in-groups (directory "**/*"))
do (let* ((g (str:trim (first (last (pathname-directory path)))))
(i (str:trim (pathname-name path)))
(m (ignore-errors (str:trim (extract-mid (fetch-article g i))))))
(when (and m (> (length m) 0))
(let* ((hash (index-hash m))
(idx-path (index-path hash)))
(ensure-index-directories! hash)
;; If index file exists, verify if it shares our inode. If not, force-link it.
(cond
((probe-file idx-path)
(unless (= (handler-case (sb-posix:stat-ino (sb-posix:stat (uiop:native-namestring path)))
(sb-posix:syscall-error () 0))
(handler-case (sb-posix:stat-ino (sb-posix:stat (uiop:native-namestring idx-path)))
(sb-posix:syscall-error () -1)))
;; Overwrite stale/mismatched index entry with the correct hard link
(ignore-errors (delete-file idx-path))
(sb-posix:link (uiop:native-namestring path) idx-path)
(format t "article ~a/~a force-linked to index ~a~%" g i hash)))
(t
(handler-case
(progn
(sb-posix:link (uiop:native-namestring path) idx-path)
(format t "article ~a/~a indexed by ~a hash ~a~%" g i m hash))
(sb-posix:syscall-error (c)
(if (= (sb-posix:syscall-errno c) sb-posix:eexist)
(format t "article ~a/~a already indexed concurrently.~%" g i)
(error c)))))))))))
(defun index-from-fs!-old ()
(loop for path in (in-groups (directory "**/*"))
do (let* ((g (str:trim (first (last (pathname-directory path)))))
(i (str:trim (pathname-name path)))
(m (ignore-errors (str:trim (extract-mid (fetch-article g i))))))
(when (and m (> (length m) 0))
(let* ((hash (index-hash m))
(idx-path (index-path hash)))
(if (probe-file idx-path) ;; article exists in index: unify inodes by re-linking group file to index
(unless (= (get-file-inode path) (get-file-inode idx-path))
(delete-file path)
(sb-posix:link (uiop:native-namestring idx-path) (uiop:native-namestring path))
(format t "article ~a/~a relinked-to-existing-index ~a~%" g i hash))
(progn
(ensure-index-directories! hash)
(handler-case
(progn
(sb-posix:link (uiop:native-namestring path) idx-path)
(format t "article ~a/~a mid ~a hashes-to ~a~%" g i m hash))
(sb-posix:syscall-error (c)
(if (= (sb-posix:syscall-errno c) sb-posix:eexist)
(format t "article ~a/~a indexed due to a cross-post? EEXIST.~%" g i)
(error c)))))))))))
(index-from-fs!) (index-from-fs!)
(format t "Index built.~%")

View file

@ -0,0 +1,37 @@
#-quicklisp
(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp"
(user-homedir-pathname))))
(when (probe-file quicklisp-init)
(load quicklisp-init)))
(ql:quickload :loop :silent t)
(in-package #:loop)
(defun find-duplicate-mids ()
"Displays any duplicated message-ids across all group articles."
(let ((mid-map (make-hash-table :test 'equal))
(found-dupes nil))
;; collect all articles and group by message-id
(loop for path in (in-groups (directory "**/*"))
do (let* ((dir-list (pathname-directory path))
(g (when dir-list (str:trim (first (last dir-list)))))
(i (pathname-name path))
(i-str (when i (str:trim i))))
;; process numeric article files only
(when (and g i-str (parse-integer i-str :junk-allowed t))
(let ((mid (ignore-errors (get-mid-from-path path))))
(when (and mid (> (length (str:trim mid)) 0))
;; push the (group . article-number) pair into the hash map
(push (cons g i-str) (gethash mid mid-map)))))))
;; remove duplicates
(maphash (lambda (mid locations)
(when (> (length locations) 1)
(push (cons mid locations) found-dupes)))
mid-map)
;; displays
(when found-dupes
(loop for (mid . locs) in found-dupes
do (format t "message-id: ~a~%" mid)
;; sort by group
(let ((sorted-locs (sort locs #'string-lessp :key #'car)))
(loop for (g . i) in sorted-locs
do (format t "~a/~a~%" g i)))))))
(find-duplicate-mids)

37
scripts/verify-index.lisp Normal file
View file

@ -0,0 +1,37 @@
#-quicklisp
(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp"
(user-homedir-pathname))))
(when (probe-file quicklisp-init)
(load quicklisp-init)))
(ql:quickload :loop :silent t)
(in-package #:loop)
(defun verify-index-inodes ()
"Iterates over all group articles and prints whether they share an inode with index/."
(loop for path in (in-groups (directory "**/*"))
do (let* ((dir-list (pathname-directory path))
(g (when dir-list (str:trim (first (last dir-list)))))
(i (pathname-name path))
(i-str (when i (str:trim i))))
;; ignore metadata and directories
(when (and g i-str (parse-integer i-str :junk-allowed t))
(let ((mid (ignore-errors (get-mid-from-path path))))
(if (or (null mid) (zerop (length (str:trim mid))))
(format t "no-mid ~a/~a couldn't find message-id~%" g i-str)
(let* ((hash (index-hash mid))
(idx-path (index-path hash))
(grp-inode (get-file-inode path))
(idx-inode (get-file-inode idx-path)))
(cond
((null idx-inode)
(stdout "no-index ~a/~a mid ~a hashes-to ~a~%"
g i-str mid idx-path))
((= grp-inode idx-inode)
(stdout "okay ~a/~a shares-inode ~a with ~a~%"
g i-str grp-inode idx-path))
(t
(stdout "mismatch ~a/~a inode ~a =/= inode ~a hash ~a~%"
g i-str grp-inode idx-inode idx-path))))))))))
(defun verify-index ()
(verify-index-inodes))
(verify-index)

2
version.lisp Normal file
View file

@ -0,0 +1,2 @@
(in-package :loop)
(defparameter *git-hash* "da757c7")