diff --git a/Anyfile b/Anyfile index 028b1d9..56c0cd1 100644 --- a/Anyfile +++ b/Anyfile @@ -12,13 +12,25 @@ loop.exe: scripts/build-exe.lisp loop.lisp loop.asd loop.nw sbcl --script scripts/build-exe.lisp (test -f loop.exe && cmp loop loop.exe) || cp loop loop.exe -loop.lisp: loop.nw +loop.lisp: loop.nw format-def (any tangle -Rloop.lisp < loop.nw | sh format-def | \ dos2unix > loop.tmp || \ (rm loop.tmp && exit 1)) && \ mv loop.tmp loop.lisp -release: +format-def: loop.nw + (any tangle -Rformat-def < loop.nw | \ + dos2unix > format-def.tmp || \ + (rm format-def.tmp && exit 1)) && \ + mv format-def.tmp format-def + +make-release: loop.nw + (any tangle -Rmake-release < loop.nw | \ + dos2unix > make-release.tmp || \ + (rm make-release.tmp && exit 1)) && \ + mv make-release.tmp make-release + +release: make-release ./make-release $$(git log --oneline | head -1 | awk '{print $$1}') \ loop.nw > loop.tmp && mv loop.tmp loop.nw diff --git a/format-def b/format-def index ba7be7a..5c761f0 100644 --- a/format-def +++ b/format-def @@ -4,24 +4,12 @@ usage() printf 'usage: %s [file.lisp]\n' $0 exit 1 } -## The first program finds certain definitions and inserts a new blank -## line *before* the definition. Such action makes function -## definitions separated by two blank lines in some cases. We then -## remove the excess with the second program. Notice we need the -E -## option because we're using the | metacharacter that is only -## supported by popular sed programs with the -E option. This -## violates POSIX sed, but keep in mind that we only run this when -## releasing the package. This is a building tool, not part of the -## service. + sed -E '/^\(defun |\(defmacro /{ i\ -}' "$@" | sed '/^[ \t]*$/{ +}' "$@" | \ +sed '/^[ \t]*$/{ N /^[ \t]*\n$/D }' -## We first find a blank line. Then we say N to expand the pattern -## space to include the next line. Then we delete the *first* blank -## line and not the second---that's what the D command does. This -## strategy is explained by Dale Dougherty and Arnold Robbins in ``sed -## & awk'' second edition, pages 112--114. diff --git a/loop.lisp b/loop.lisp index c9cd3ef..bb7543a 100644 --- a/loop.lisp +++ b/loop.lisp @@ -324,7 +324,6 @@ (defun remove-inactive-users! () (loop for u in *accounts* do (let ((username (account-username u))) - (format t "Username: ~a~%" username) (cond ((and (not (locked? username)) (inactive-from-never-logged-in? username)) (post-notification @@ -1463,7 +1462,6 @@ (make-response :code 200 :data "Welcome! I am LOOP a89e088. Say ``help'' for a menu."))) - (setq lisp-unit:*print-failures* t) (define-test dispatching (assert-true (equalp (empty-response) (dispatch (make-request))))) diff --git a/loop.nw b/loop.nw index 85014f9..60ef8df 100644 --- a/loop.nw +++ b/loop.nw @@ -75,23 +75,23 @@ the list of destinaries. So long as everyone replies to everyone, John, too, will start getting all the messages. If anyone violates this rule of replying to everyone involved, the loop is broken. -%% \begin{figure}[!htb] -%% \centering \includegraphics[width=0.8\linewidth]{images/gnus-summary.png} -%% \caption{Gnus, a news reader embedded in the GNU EMACS text editor.} -%% \label{fg:gnus} -%% \end{figure} -%% -%% \begin{figure}[!htb] -%% \centering \includegraphics[width=0.8\linewidth]{images/tbird-summary.png} -%% \caption{Thunderbird, a news reader produced by the Mozilla Foundation.} -%% \label{fg:bird} -%% \end{figure} -%% -%% \begin{figure}[!htb] -%% \centering \includegraphics[width=0.8\linewidth]{images/sylpheed-summary.png} -%% \caption{Sylpheed, a news reader produced by Hiroyuki Yamamoto.} -%% \label{fg:sylpheed} -%% \end{figure} +\begin{figure}[!htb] + \centering \includegraphics[width=0.8\linewidth]{images/gnus-summary.png} + \caption{Gnus, a news reader embedded in the GNU EMACS text editor.} + \label{fg:gnus} +\end{figure} + +\begin{figure}[!htb] + \centering \includegraphics[width=0.8\linewidth]{images/tbird-summary.png} + \caption{Thunderbird, a news reader produced by the Mozilla Foundation.} + \label{fg:bird} +\end{figure} + +\begin{figure}[!htb] + \centering \includegraphics[width=0.8\linewidth]{images/sylpheed-summary.png} + \caption{Sylpheed, a news reader produced by Hiroyuki Yamamoto.} + \label{fg:sylpheed} +\end{figure} There are surely inconveniences in using e-mail as conference medium. For example, after John has been added to the loop, he is not able to @@ -137,23 +137,22 @@ NNTP-aware programs. You could write your own. Figures \ref{fg:gnus}--\ref{fg:sylpheed} show a few programs for reading network news via NNTP. -\pdfbookmark[1]{Principles for a discussion group}{principles} -\label{principles} -{\bf Principles for a discussion group}. We believe a discussion group -should be small and grow slowly. By ``slowly'', we mean that each -member comes in through an invitation. This way, the group being -closed by definition, we keep spam out and give members a certain -sense of privilege. A discussion group should be formed by interested -people. If a participant doesn't log-in for a certain period of time, -\lp locks the participant's account---see Section -\ref{sec:inactive-users}. The account can be reactivated, but it will -take asking another participant (with an active account) to do so. In -other words, there's an encouragement for an uninterested member not -to come back to the \lp. The idea is to keep a certain cohesion in -the discussion groups. When an account is locked or unlocked, an -article is posted to the group {\tt local.control.news}, so everyone -knows who is leaving and arriving. This way, participants get to have -an idea of who is reading them. +\section*{Principles for a discussion group}\label{principles} +\pdfbookmark[1]{Principles for a discussion group}{principles} We +believe a discussion group should be small and grow slowly. By +``slowly'', we mean that each member comes in through an invitation. +This way, the group being closed by definition, we keep spam out and +give members a certain sense of privilege. A discussion group should +be formed by interested people. If a participant doesn't log-in for a +certain period of time, \lp\ locks the participant's account---see +Section \ref{sec:inactive-users}. The account can be reactivated, but +it will take asking another participant (with an active account) to do +so. In other words, there's an encouragement for an uninterested +member not to come back to the \lp. The idea is to keep a certain +cohesion in the discussion groups. When an account is locked or +unlocked, an article is posted to the group {\tt local.control.news}, +so everyone knows who is leaving and arriving. This way, participants +get to have an idea of who is reading them. Each invitation comes with a certain responsibility: it's possible to see who invited who. If {\tt BOB} misbehaves, everyone gets to see @@ -182,9 +181,11 @@ Hereafter, our conversation continues in Lisp. Understanding how \lp\ is made is only necessary if you intend to modify it. If you just want to use the system, you probably should stop right here. -\section{How to install} +\section*{How to install} -See \href{https://git.antartida.xyz/loop/srv/raw/branch/main/README}{[[README]]}. +See +\href{https://git.antartida.xyz/loop/srv/raw/branch/main/README}{[[README]]} +in \lp's source code. \section{Implementation strategy}\label{sec:design} @@ -1296,23 +1297,24 @@ invited who. maximizing (length (account-username u)))) (defun list-users () - (read-accounts!) - (mapcar #'(lambda (row) (cadr row)) - (sort - (loop for u in *accounts* - collect (list (account-username u) - (fmt "~v@a~a, ~a, invited ~a" - (size-of-longest-username) - (account-username u) - (if (locked? (account-username u)) - (fmt " (account locked: ~a)" - (account-pass-locked-why u)) - "") - (if (last-time-seen (account-username u)) - (fmt "last seen on ~a" (last-time-seen (account-username u))) - "never logged in") - (or (account-friends u) "nobody")))) - #'string<= :key #'(lambda (row) (car row))))) + (mapcar + #'(lambda (row) (cadr row)) + (sort + (loop for u in *accounts* + collect (list + (account-username u) + (fmt "~v@a~a, ~a, invited ~a" + (size-of-longest-username) + (account-username u) + (if (locked? (account-username u)) + (fmt " (account locked: ~a)" + (account-pass-locked-why u)) + "") + (if (last-time-seen (account-username u)) + (fmt "last seen on ~a" (last-time-seen (account-username u))) + "never logged in") + (or (account-friends u) "nobody")))) + #'string<= :key #'(lambda (row) (car row))))) (defun universal-to-human (s) (format-timestring @@ -2424,8 +2426,8 @@ Index built. \section{Deletion and locking of inactive accounts}\label{sec:inactive-users} -We now implement some of the principles exposed earlier on -page~\pageref{principles}. The program +We now implement some of the \hyperref[principles]{principles} exposed +earlier on page~\pageref{principles}. The program @<> would be run by {\tt cron} every day (at midnight, say). It checks all accounts that are inactive and either locks them (to be deleted later) or deletes them {\em for @@ -2940,13 +2942,9 @@ something to think about. <> <> <> - <> - <> - <
> - <> @ %def @@ -2980,6 +2978,86 @@ The \lp\ system definition: :components ((:file "loop"))) @ +\section{Other source code} + +The shell script {\tt format-def} is invoked whenever we build any +lisp source code. That's to format the source code a bit better for +readers that will be reading it directly. It is not what we do. We +read the documentation in PDF format and we work on the NOWEB file +{\tt loop.nw}. But we know that potential readers will not do the +same and will hack {\tt loop.lisp} directly. Paying respect to these +readers, we try to format Lisp source code as best as possible. So we +do two things: first, we produce the final source code in an order +that should produce no warnings during compilation; second, we make +sure there's one and only one blank line between procedure or macro +definition. We don't add a blank line between global variables. + +The following shell script does the job. The first {\tt sed} program +finds our definitions of interest and inserts a new blank line before +the definition. Such action makes function definitions separated by +two blank lines in some cases. We then remove the excess with the +second program. Notice we need the {\tt -E} option because we're +using the {\tt |} metacharacter. + +The second program find a blank line as its first step. Then we say +{\tt N} to expand the pattern space to include the next line. Then we +delete the {\em first} blank line and not the second---that's what the +{\tt D} command does. This strategy is explained by Dale Dougherty +and Arnold Robbins in ``sed \& awk'' second edition, pages 112--114. + +<>= +#!/bin/sh +usage() +{ + printf 'usage: %s [file.lisp]\n' $0 + exit 1 +} + +sed -E '/^\(defun |\(defmacro /{ + i\ + +}' "$@" | \ +sed '/^[ \t]*$/{ + N + /^[ \t]*\n$/D +}' +@ + +When we make a new release of \lp, we like to name its version as the +tip of the source code repository. We get the information usually +with a command line such as +% +\begin{verbatim} +$ git log --oneline | head -1 | awk '{print $1}' +52663d1 +\end{verbatim} +% +To include this version string in the executable, we need to make it +part of the source code. We get help from {\tt sed} once again. As +the usage explains, we invoke it as {\tt ./make-release 52663d1 + loop.nw}. The script then rewrites {\tt loop.nw} with the string in +the body of the chunk @<>. The {\tt sed} program is +straightforward: locate the chunk definition, move down a line, change +that line and that's all. + +<>= +#!/bin/sh +usage() +{ + printf 'usage: %s tag file\n' $0 + exit 1 +} + +test $# -lt 2 && usage + +tag="$1"; shift +sed "/<>=/ { + n; + c\\ +$tag +}" "$@" +@ + \section*{Index of chunks} \nowebchunks diff --git a/make-release b/make-release index e75a8ce..e804bd0 100644 --- a/make-release +++ b/make-release @@ -4,10 +4,11 @@ usage() printf 'usage: %s tag file\n' $0 exit 1 } + test $# -lt 2 && usage -tag="$1" -shift -sed "/<>=/ { + +tag="$1"; shift +sed "/a89e088=/ { n; c\\ $tag