Incorporates format-def, make-release into loop.nw.
This commit is contained in:
parent
3850c72b6d
commit
eb2bd3cb36
5 changed files with 158 additions and 81 deletions
16
Anyfile
16
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
|
||||
|
||||
|
|
18
format-def
18
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.
|
||||
|
|
|
@ -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)))))
|
||||
|
|
196
loop.nw
196
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
|
||||
@<<cron-remove-inactive-users.lisp@>> 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.
|
|||
<<Command users>>
|
||||
<<Command dd>>
|
||||
<<Command repl>>
|
||||
|
||||
<<Broadcasting>>
|
||||
|
||||
<<Command-line parsing>>
|
||||
|
||||
<<Main loop>>
|
||||
|
||||
<<Test procedures>>
|
||||
@ %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.
|
||||
|
||||
<<format-def>>=
|
||||
#!/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 @<<Version@>>. The {\tt sed} program is
|
||||
straightforward: locate the chunk definition, move down a line, change
|
||||
that line and that's all.
|
||||
|
||||
<<make-release>>=
|
||||
#!/bin/sh
|
||||
usage()
|
||||
{
|
||||
printf 'usage: %s tag file\n' $0
|
||||
exit 1
|
||||
}
|
||||
|
||||
test $# -lt 2 && usage
|
||||
|
||||
tag="$1"; shift
|
||||
sed "/<<Version>>=/ {
|
||||
n;
|
||||
c\\
|
||||
$tag
|
||||
}" "$@"
|
||||
@
|
||||
|
||||
\section*{Index of chunks}
|
||||
\nowebchunks
|
||||
|
||||
|
|
|
@ -4,10 +4,11 @@ usage()
|
|||
printf 'usage: %s tag file\n' $0
|
||||
exit 1
|
||||
}
|
||||
|
||||
test $# -lt 2 && usage
|
||||
tag="$1"
|
||||
shift
|
||||
sed "/<<Version>>=/ {
|
||||
|
||||
tag="$1"; shift
|
||||
sed "/a89e088=/ {
|
||||
n;
|
||||
c\\
|
||||
$tag
|
||||
|
|
Loading…
Reference in a new issue