From b65572e3b9bbdd07115d4b2b398d36d44203d428 Mon Sep 17 00:00:00 2001 From: vindarel Date: Mon, 24 Apr 2023 15:55:28 +0200 Subject: [PATCH] add file-notify, add webapp script [ci skip] --- README.md | 9 +++++ ciel.asd | 1 + docs/libraries.md | 34 ++++++++++++++-- docs/scripting.md | 74 +++++++++++++++++++++++++++++++++- src/packages.lisp | 1 + src/scripts/webapp-notify.lisp | 56 +++++++++++++++++++++++++ src/scripts/webapp.lisp | 25 ++++++++++++ 7 files changed, 196 insertions(+), 4 deletions(-) create mode 100755 src/scripts/webapp-notify.lisp create mode 100755 src/scripts/webapp.lisp diff --git a/README.md b/README.md index bf4274b..b9e1c42 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,15 @@ Debian system): libmagic-dev libc6-dev gcc # from magicffi +On Linux: + + inotify-tools + +On MacOS: + + fsevent + + ## With Quicklisp You need a Lisp implementation and Quicklisp installed. diff --git a/ciel.asd b/ciel.asd index d90dd3b..e6cb69c 100644 --- a/ciel.asd +++ b/ciel.asd @@ -44,6 +44,7 @@ :cl-json-pointer/synonyms :dissect :fset + :file-notify ;; needs inotify (linux) or fsevent (macos) :generic-cl ;; web diff --git a/docs/libraries.md b/docs/libraries.md index c07bcec..ea2b171 100644 --- a/docs/libraries.md +++ b/docs/libraries.md @@ -735,6 +735,23 @@ But typing `os:` and TAB in SLIME doesn't help very much with auto-discovery, so we also added a `/os` local nickname, so that we see the available symbols earlier in the autocompletion list. +We include [file-notify](https://github.com/shinmera/file-notify) to watch changes to files (using `inotify` on Linux and `fsevent` on MacOS). It is available with the `notify:` local nickname. + +~~~lisp + (notify:watch "webapp.lisp") + (notify:with-events (file change :timeout T) + ;; Print the available list of events: + ;; (print (list file change)) + (when (equal change :close-write) + (format! t "~%~%Reloading ~a…~&" file) + (handler-case + (ciel::load-without-shebang "webapp.lisp") + (reader-error () + ;; Catch some READ errors, such as parenthesis not closed, etc. + (format! t "~%~%read error, waiting for change…~&")))))) +~~~ + + ## Regular expressions @@ -945,12 +962,23 @@ Learn more with: We include [Quicksearch](https://github.com/lisp-maintainers/quicksearch), a -simple search utility for Common Lisp libraries: +simple search utility for Common Lisp libraries that searches on +GitHub, Quickdocs and Cliki. + +You can call it with CIEL's binary: + + $ ciel -s quicksearch ciel + +or by calling its wrapper script directly: + + $ quicksearch.lisp ciel # according you have it in your PATH + +or from the Lisp REPL: (qs:? "ciel" :u) -this will search on GitHub, Quickdocs and Cliki for "ciel", and it -will print the URL of search results. +this will search for the "ciel" keyword and it will print the URL of +search results (`:u`). ``` SEARCH-RESULTS: "ciel" diff --git a/docs/scripting.md b/docs/scripting.md index a595af1..cbab174 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -356,7 +356,7 @@ $ ciel -e "(-> (http:get \"https://fakestoreapi.com/products/1\") (json:read-jso Call built-in scripts with `--script ` or `-s`. -Call `ciel --scripts` to list the available scripts. +Call `ciel --scripts` to list the available ones. Those are for demo purposes and are subject to evolve. Ideas and contributions welcome. @@ -467,3 +467,75 @@ We welcome more capable, expanded scripts! --- Now, let us iron out the details ;) + +### Simple web app with routes + +See [`scr/scripts/webapp.lisp`](https://github.com/ciel-lang/CIEL/blob/master/src/scripts/webapp.lisp) for inspiration. + +This creates one route on `/` with an optional `name` parameter. Go to `localhost:4567/?name=you` and see. + +```lisp +#!/usr/bin/env ciel +;;; +;;; Run with: +;;; $ ./webapp.lisp +;;; + +(in-package :ciel-user) + +(routes:defroute route-root "/" (&get name) + (format nil "Hello ~a!" (or name (os:getenv "USER") "lisper"))) + +(defvar *server* nil) + +(defun start-webapp () + (setf *server* (make-instance 'routes:easy-routes-acceptor :port 4567)) + (hunchentoot:start *server*)) + +(defun stop-webapp () + (hunchentoot:stop *server*)) + +#+ciel +(progn + (start-webapp) + (format t "~&App started on localhost:4567…~&") + (sleep most-positive-fixnum)) +``` + +At this point you'll certainly want to live-reload your changes. + +### Auto-reload + +In this snippet: +[`webapp-notify.lisp`](https://github.com/ciel-lang/CIEL/blob/master/src/scripts/webapp-notify.lisp), +we use the [file-notify](https://github.com/shinmera/file-notify) +library (shipped in CIEL) to watch write changes to our lisp file, and load +it again. + +This allows you to have a dumb "live reload" workflow with a simple editor and a terminal. + +> WARNING: This does NOT take advantage of Common Lisp's image-based-development features at all. Install yourself a Common Lisp IDE to enjoy the interactive debugger, compiling one function at a time, trying things out in the REPL, autocompletion, code navigation… + +> INFO: you need `inotify` on Linux and `fsevent` on MacOS. + +~~~lisp +(defun simple-auto-reload () + (notify:watch "webapp.lisp") + (notify:with-events (file change :timeout T) + ;; Print the available list of events: + ;; (print (list file change)) + (when (equal change :close-write) + (format! t "~%~%Reloading ~a…~&" file) + (handler-case + (ciel::load-without-shebang "webapp.lisp") + (reader-error () + ;; Catch some READ errors, such as parenthesis not closed, etc. + (format! t "~%~%read error, waiting for change…~&")))))) + +#+ciel +(unless *server* + (start-webapp) + (format t "~&App started on localhost:4567…~&") + (simple-auto-reload) + (sleep most-positive-fixnum)) +~~~ diff --git a/src/packages.lisp b/src/packages.lisp index 1062eb9..6803584 100644 --- a/src/packages.lisp +++ b/src/packages.lisp @@ -15,6 +15,7 @@ (:os :uiop/os) ;; This other uiop module is always useful: (:filesystem :uiop/filesystem) + (:notify :org.shirakumo.file-notify) (:alex :alexandria) (:csv :cl-csv) diff --git a/src/scripts/webapp-notify.lisp b/src/scripts/webapp-notify.lisp new file mode 100755 index 0000000..ab5bb3d --- /dev/null +++ b/src/scripts/webapp-notify.lisp @@ -0,0 +1,56 @@ +#!/usr/bin/env ciel +;;; +;;; Run with: +;;; $ ./webapp-notify.lisp +;;; +;;; Watch this file for write events and load & compile it again. +;;; This redefines our web routes, so we can develop our app in a simple interactive way. +;;; +;;; If you are doing that, you'll want to setup a proper dev environment to enjoy full Common Lisp image-based development. + +(in-package :ciel-user) + +(routes:defroute route-root "/" (&get name) + (format nil "Hello ~a!" (or name (os:getenv "USER") "lisper"))) + +(routes:defroute route-hello "/hello" () + (format nil "Hello :)")) + +;; Try adding new routes. +;; (gotcha: give them unique names) + +(defvar *server* nil) + +(defun start-webapp () + (unless *server* + (setf *server* (make-instance 'routes:easy-routes-acceptor :port 4567)) + (hunchentoot:start *server*))) + +(defun stop-webapp () + (hunchentoot:stop *server*)) + +(defun dumb-auto-reload () + "Watch this file for write events and load & compile it again. + This redefines our web routes, so we can develop our app in a simple interactive way. + + If you are doing that, you'll want to setup a proper dev environment to enjoy full Common Lisp image-based development." + (format! t "~&Watching webapp.lisp…") + (notify:watch "webapp.lisp") + (notify:with-events (file change :timeout T) + ;; list of events: + ;; (print (list file change)) + (when (equal change :close-write) + (format! t "~%~%Reloading ~a…~&" file) + (handler-case + (ciel::load-without-shebang "webapp.lisp") + (reader-error () + ;; READ errors, parenthesis not closed, etc. Wait for the developer. + (format! t "~%~%read error, waiting for change…~&")))))) + + +#+ciel +(unless *server* + (start-webapp) + (format t "~&App started on localhost:4567…~&") + (dumb-auto-reload) + (sleep most-positive-fixnum)) diff --git a/src/scripts/webapp.lisp b/src/scripts/webapp.lisp new file mode 100755 index 0000000..e34b1a0 --- /dev/null +++ b/src/scripts/webapp.lisp @@ -0,0 +1,25 @@ +#!/usr/bin/env ciel +;;; +;;; Run with: +;;; $ ./webapp.lisp +;;; + +(in-package :ciel-user) + +(routes:defroute route-root "/" (&get name) + (format nil "Hello ~a!" (or name (os:getenv "USER") "lisper"))) + +(defvar *server* nil) + +(defun start-webapp () + (setf *server* (make-instance 'routes:easy-routes-acceptor :port 4567)) + (hunchentoot:start *server*)) + +(defun stop-webapp () + (hunchentoot:stop *server*)) + +#+ciel +(progn + (start-webapp) + (format t "~&App started on localhost:4567…~&") + (sleep most-positive-fixnum))