Add kiosk mode and drop SBCL - clic - Clic is an command line interactive client for gopher written in Common LISP HTML git clone git://bitreich.org/clic/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/clic/ DIR Log DIR Files DIR Refs DIR Tags DIR README DIR LICENSE --- DIR commit fb5155bf84450fec31f6b69a49fbdc07aee5aa70 DIR parent b76072a5f201bed263c85b2776dff7ed10b6aac1 HTML Author: Solene Rapenne <solene@perso.pw> Date: Fri, 6 Apr 2018 10:08:01 +0200 Add kiosk mode and drop SBCL Diffstat: M Makefile | 17 +++++------------ M README.md | 38 +++++++++---------------------- D TODO | 14 -------------- M clic.1 | 2 +- M clic.lisp | 88 ++++++++++++++++--------------- D extension.c | 17 ----------------- D interactive-test.exp | 116 ------------------------------ M make-binary.lisp | 16 ---------------- D run-test.sh | 14 -------------- D test.lisp | 35 ------------------------------- 10 files changed, 62 insertions(+), 295 deletions(-) --- DIR diff --git a/Makefile b/Makefile @@ -2,7 +2,7 @@ # See the LICENSE file for copyright and license details. .POSIX: -VERSION = 0.1 +VERSION = 0.2 BIN = clic LISP = ecl @@ -10,17 +10,10 @@ PREFIX = /usr BINDIR = ${PREFIX}/bin MANDIR = ${PREFIX}/share/man -all: extension.so ${BIN} +all: ${BIN} -${BIN}: clic.lisp make-binary.lisp - ${LISP} --load make-binary.lisp - -standalone: clic.lisp extension.so make-binary.lisp - ${MAKE} -e LISP=sbcl - -extension.so: extension.c - ${CC} -Wall -fPIC -c extension.c - ${LD} -shared -o extension.so extension.o +${BIN}: clic.lisp make-binary.lisp + ecl -load make-binary.lisp install: ${BIN} @echo installing executable to "${DESTDIR}${PREFIX}/bin" @@ -39,7 +32,7 @@ uninstall: @rm -f ${DESTDIR}${MANDIR}/man1/clic.1 clean: - rm -f "${BIN}" clic.o clic.eclh clic.cxx bookmark-test extension.so + rm -f "${BIN}" clic.o clic.eclh clic.cxx test: clean all @sh run-test.sh ${LISP} DIR diff --git a/README.md b/README.md @@ -12,46 +12,25 @@ Requirements clic requires a few dependencies : + ANSI compatible terminal emulator - + a Common LISP interpreter + + ecl common lisp interpreter + C compiler + Linux/OpenBSD/FreeBSD/NetBSD -Both **ecl** and **sbcl** Common LISP compilers are supported. - How to build ============ `clic` binary must be compiled. -To compile it with **ecl** : +To compile it with **ecl**, it's really easy type the following +command : make -To compile it with **sbcl** : - - make LISP=sbcl - then you can use `make install` to deploy it in `/usr/bin/`. -Note : when using sbcl, a shared library extension.o is created and -then sbcl creates a binary linked against the library. But ecl will -translate the whole lisp code to C and then compile it, but linking -against ecl. - -**I (the author) recommend using ecl**. - - -Information about the binary ----------------------------- -If you compile clic with ecl, you will need ecl library installed on -the computer, the startup time is really fast. While compiling clic -with SBCL will provide a standalone binary embedding the whole SBCL -compiler, weighting approximately 10 Mb with a slower startup time. - -If you use OpenBSD and SBCL, you will need wxallowed mountflag on the -partition from where you try to start clic standalone because sbcl has -a W^X issue. +The binary will be linked to ecl shared library. You need to install +ecl if you want to deploy clic binary on others systems. How to use clic @@ -87,7 +66,12 @@ permitting to use clic with the numpad with only one hand : Command line usage ================== -clic [url|file] +clic [-k] [url|file] + +If you start clic with -k parameter, then kiosk mode is enabled, which +mean it won't call any external program or save any data on the +disk. Texts (type 0) will be shown as-this in the output. It only +allow to use texts, menus and searches. If you pass a gopher url to clic (gopher:// isn't mandatory for the url), the behavor will change depending on two parameters : DIR diff --git a/TODO b/TODO @@ -1,14 +0,0 @@ -* FEATURE - -- DONE add a bookmark "a" function to store the current page location -- DONE add a bookmark "b" function to display the bookmarks -- TODO add a dump "d" function to display the raw response -- DONE history mode which save every file into a filesystem tree to preserve data if gopherhole go offline - - -* CODE - -- TODO remove the pagination if using stdout or not in shell -- TODO make the c-termsize working on ecl -- DONE use CLOS to store data -- DONE store the whole page and deal with it later DIR diff --git a/clic.1 b/clic.1 @@ -3,7 +3,7 @@ clic \- a terminal gopher client .SH SYNOPSIS .B clic -.IR [URL] +.IR [-k] [URL] .PP .SH DESCRIPTION .B clic DIR diff --git a/clic.lisp b/clic.lisp @@ -1,23 +1,10 @@ ;;; let's hide the loading (let ((*standard-output* (make-broadcast-stream))) (require 'asdf) - #+sbcl - (require 'sb-bsd-sockets) #+ecl (require 'sockets)) ;;;; C binding to get terminal informations -;;;; SBCL only -#+sbcl -(progn - (load-shared-object #p"./extension.so") - ;; getTerminalHeight - (declaim (inline getTerminalHeight)) - (sb-alien:define-alien-routine "getTerminalHeight" unsigned-int) - (defun c-termsize () - "return terminal height" - (sb-alien:with-alien ((res unsigned-int (getTerminalHeight)))))) - #+ecl (progn (ffi:clines " @@ -41,7 +28,7 @@ (defstruct location host port type uri :predicate) -;;;; kiosk mode on/off +;;;; kiosk mode (defparameter *kiosk-mode* nil) (defmacro kiosk-mode(&body code) @@ -98,8 +85,6 @@ "return t if the output is a terminal" ;; we use this variable in case we don't want to be interactive ;; like when we use a cmd arg to get an image - #+sbcl - (interactive-stream-p *standard-output*) #+ecl (if (= 1 (c-ttyp)) t @@ -387,36 +372,44 @@ (defun parse-url(url) "parse a gopher url and return a location" - (let ((url (if (search "gopher://" url) - (subseq url 9) - url))) + (cond ((or + (string= "--help" url) + (string= "-h" url)) + (help-shell) + (quit)) - ;; splitting with / to get host:port and uri - ;; splitting host and port to get them - (let* ((infos (split url #\/)) - (host-port (split (pop infos) #\:))) + ((string= "-k" url) + (setf *kiosk-mode* t)) - ;; create the location to visit - (make-location :host (pop host-port) - - ;; default to port 70 if not supplied - :port (if host-port ;; <- empty if no port given - (parse-integer (car host-port)) - 70) - - ;; if type is empty we default to "1" - :type (let ((type (pop infos))) - (if (< 0 (length type)) type "1")) + (t + + (let ((url (if (search "gopher://" url) + (subseq url 9) + url))) + + ;; splitting with / to get host:port and uri + ;; splitting host and port to get them + (let* ((infos (split url #\/)) + (host-port (split (pop infos) #\:))) + + ;; create the location to visit + (make-location :host (pop host-port) + ;; default to port 70 if not supplied + :port (if host-port ;; <- empty if no port given + (parse-integer (car host-port)) + 70) + + ;; if type is empty we default to "1" + :type (let ((type (pop infos))) + (if (< 0 (length type)) type "1")) - ;; glue remaining args between them - :uri (format nil "~{/~a~}" infos))))) + ;; glue remaining args between them + :uri (format nil "~{/~a~}" infos))))))) (defun get-argv() "Parse argv and return it" - #+sbcl - (cadr *posix-argv*) #+ecl - (car (last (cdr (si::command-args))))) + (cdr (si::command-args))) (defun user-input(input) (cond @@ -526,11 +519,13 @@ do (formatted-output line) + ;; split and ask to scroll or to type a command (when (= row rows) (setf row 0) - (format t "~a press enter or a shell command ~a : " + (format t "~a~a press enter or a shell command ~a : " (get-color 'bg-black) + (if *kiosk-mode* "KIOSK" "") (get-color 'reset)) (force-output) (let ((first-input (read-char *standard-input* nil nil t))) @@ -659,7 +654,8 @@ (defun display-prompt() (let ((last-page (car *history*))) - (format t "gopher://~a:~a/~a~a (~as) / (p)rev (r)edisplay (h)istory : " + (format t "~agopher://~a:~a/~a~a (~as) / (p)rev (r)edisplay (h)istory : " + (if *kiosk-mode* "KIOSK " "") (location-host last-page) (location-port last-page) (location-type last-page) @@ -707,8 +703,14 @@ :type "1" :uri argv)) ;; it's not a file, create a location - (parse-url argv)) - (make-location :host "gopherproject.org" :port 70 :uri "/" :type "1"))))) + ;; it's either a list with parameters or a location + (if (listp argv) + (car (last (loop for element in argv collect (parse-url element)))) + (parse-url argv))))))) + + ;; if we didn't passed a url as parameter, use a default + (if (not (location-p destination)) + (setf destination (make-location :host "gopherproject.org" :port 70 :uri "/" :type "1"))) ;; is there an output redirection ? (if (ttyp) DIR diff --git a/extension.c b/extension.c @@ -1,17 +0,0 @@ -#include <limits.h> -#include <sys/ioctl.h> -#include <unistd.h> - -/** - * @brief Get the height of the terminal in term of visible lines - * @return the height of the terminal or UINT_MAX in case of error - */ -unsigned int getTerminalHeight() -{ - struct winsize w; - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) < 0) { - return UINT_MAX; - } - - return w.ws_row; -} DIR diff --git a/interactive-test.exp b/interactive-test.exp @@ -1,116 +0,0 @@ -proc user_input {} { - - expect "clic => " - - send "garbage\n" - expect "clic => " - send ". . ~. ~ .\n" - expect "clic => " - - send "r\n" - expect "clic => " - - send "help\n" - expect "clic => " - - send "5\n" - expect "clic => " - - send "p\n" - expect "clic => " - - send "19\n" - expect " : " - send "\nr\n" - expect " : " - - send "p\n" - expect "clic => " - - send "r\n" - expect "clic => " - - send "h\n" - expect "clic => " - - send "a\n" - expect "clic => " - - send "b\n" - expect "clic => " -} - -spawn ./clic gopher://bitreich.org -set running [user_input] -send "x\n" -expect eof - - -spawn sbcl -expect "* " - -send "(require :sb-cover) (require :sb-bsd-sockets)\n" -expect "* " - -send "(declaim (optimize sb-cover:store-coverage-data))\n" -expect "* " - -send "(compile-file \"clic.lisp\")\n" -expect "* " - -send "(load \"clic.fasl\")\n" -expect "* " - -send "(setf *offline* t)\n" -expect "* " - -send "(main)\n" -set running [user_input] -send "(pop *history*) (p) (r)\n" -expect "* " - -send "19\n" -expect " : " -send "q\n" -expect "* " - -send "(main)\n" -expect "clic => " - -send "exit\n" -expect "* " - - -# add an argv to test argv parsing -send "(setf *posix-argv* '(\"sbcl\" \"gopher://bitreich.org/0/documents/bitreich-manifesto.md\"))\n" -expect "* " - -send "(main)\n" -expect " : " -send "q\n" -expect "* " - - -# add an argv to test argv parsing -send "(setf *posix-argv* '(\"sbcl\" \"bitreich.org/1/usr/solene/\"))\n" -expect "* " -send "(main)\n" -expect "clic => " -send "q\n" -expect "* " - - -# add an argv to test argv parsing -send "(setf *posix-argv* '(\"sbcl\" \"bitreich.org:70/\"))\n" -expect "* " -send "(main)\n" -expect "clic => " -send "q\n" -expect "* " - - -send "(sb-cover:report \"report/\")\n" -expect "* " - -send "(quit)\n" -expect eof DIR diff --git a/make-binary.lisp b/make-binary.lisp @@ -1,6 +1,4 @@ ;; ecl produces a linked binary to ecl shared library -;; sbcl produces a static binary (~ 10Mb with compression / 70Mb without) - (require 'asdf) #+ecl (require 'cmp) @@ -10,20 +8,6 @@ (progn (compile-file "clic.lisp" :system-p t) (c:build-program "clic" :epilogue-code '(progn (handler-case (main) (condition () (quit)))) :lisp-files '("clic.o"))) -#+sbcl -(progn - (require 'sb-bsd-sockets) - (sb-ext:disable-debugger) - (load "clic.lisp") - #+sb-core-compression - (sb-ext:save-lisp-and-die "clic" - :executable t - :compression 5 - :toplevel 'main) - #-sb-core-compression - (sb-ext:save-lisp-and-die "clic" - :executable t - :toplevel 'main)) (format t "INFO => Compilation done (or at least it should be)~%") (quit) DIR diff --git a/run-test.sh b/run-test.sh @@ -1,14 +0,0 @@ -#!/bin/sh - -LISP=$1 - -expect -f interactive-test.exp - -${LISP} --load clic.lisp --load test.lisp - -./clic gopher://bitreich.org:70/0/ | md5sum - -./clic bitreich.org:70/0/ | md5sum - -./clic bitreich.org/0/ | md5sum - -echo "/" | nc bitreich.org 70 | md5sum - - - DIR diff --git a/test.lisp b/test.lisp @@ -1,35 +0,0 @@ -;; we can write scenario here - -(print (parse-url "gopher://perso.pw:70/0/")) -(print (parse-url "gopher://perso.pw/0/a")) -(print (parse-url "perso.pw/0/some/uri.txt")) -(print (parse-url "perso.pw")) -(print (parse-url "perso.pw:70")) - - -(setf *bookmark-file* "bookmark-test") -(load-bookmark) -(p) -(g 1) -(add-bookmark) -(getpage "bitreich.org" 70 "/") -(display-buffer "1") -(g 7) ;; going to radio -(g 1) ;; going back -(g 21) ;; banana ! -(p) ;; going back -(g 15) -(g 1) -(p) -(p) -(add-bookmark) -(show-bookmarks) -(g 1) - -(print *links*) - -(print *history*) -(format t "~%") - - -(quit)