summary refs log tree commit diff
path: root/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bin')
-rwxr-xr-xbin/tissue265
1 files changed, 150 insertions, 115 deletions
diff --git a/bin/tissue b/bin/tissue
index 4e59d73..1e45d89 100755
--- a/bin/tissue
+++ b/bin/tissue
@@ -1,9 +1,11 @@
 #!/usr/bin/env sh
-exec guile --no-auto-compile -s "$0" "$@"
+# -*- mode: scheme; -*-
+exec guile --no-auto-compile -e main -s "$0" "$@"
 !#
 
 ;;; tissue --- Text based issue tracker
-;;; Copyright © 2022 Arun Isaac <arunisaac@systemreboot.net>
+;;; Copyright © 2022, 2023 Arun Isaac <arunisaac@systemreboot.net>
+;;; Copyright © 2023 Morgan Smith <Morgan.J.Smith@outlook.com>
 ;;;
 ;;; This file is part of tissue.
 ;;;
@@ -47,6 +49,7 @@ exec guile --no-auto-compile -s "$0" "$@"
         (tissue search)
         (tissue tissue)
         (tissue utils)
+        (tissue web dev)
         (tissue web server)
         (tissue web static))
 
@@ -95,8 +98,7 @@ Search issues using SEARCH-QUERY.
           (lambda (port)
             (search-map (cut print <> <> port) db (string-join args)))
           (or (getenv "PAGER")
-              "less")
-          "--raw"))))))
+              "less")))))))
 
 (define tissue-show
   (match-lambda*
@@ -107,7 +109,7 @@ Show the text of FILE.
 "
              (command-line-program)))
     ((file)
-     (call-with-file-in-git (current-git-repository) file
+     (call-with-input-file file
        (lambda (port)
          (port-transduce
           (compose
@@ -149,10 +151,9 @@ Show the text of FILE.
           get-line-dos-or-unix
           port))))))
 
-(define* (load-config)
+(define (load-config)
   "Load configuration and return <tissue-configuration> object."
-  (call-with-file-in-git (current-git-repository) "tissue.scm"
-    (compose eval-string get-string-all)))
+  (load (canonicalize-path "tissue.scm")))
 
 (define tissue-repl
   (match-lambda*
@@ -241,6 +242,49 @@ port."
              socket-or-port)
      (make-tcp-server-socket #:port socket-or-port)))))
 
+(define (load-config-from-repository repository-directory)
+  "Load configuration from @var{repository-directory}, and return a
+@code{<tissue-configuration>} object."
+  (parameterize ((%current-git-repository
+                  (repository-open repository-directory)))
+    (call-with-temporary-checkout repository-directory
+      (lambda (temporary-checkout)
+        (call-with-current-directory temporary-checkout
+          load-config)))))
+
+(define (normalize-project state-directory project)
+  "Normalize @var{project} from @file{tissue.conf} adding in default and
+extra parameters for convenient processing later on.
+@var{state-directory} is the tissue state directory."
+  (match project
+    ((name . parameters)
+     (let ((repository-directory
+            (string-append state-directory "/" name "/repository")))
+       (cons name
+             `((base-path . ,(or (assq-ref parameters 'base-path)
+                                 (string-append "/" name)))
+               (project . ,(load-config-from-repository repository-directory))
+               (repository-directory . ,repository-directory)
+               (website-directory . ,(string-append state-directory
+                                                    "/" name "/website"))
+               (xapian-directory . ,(string-append state-directory
+                                                   "/" name "/xapian"))
+               ,@parameters))))))
+
+(define (normalize-host state-directory host)
+  "Normalize @var{host} from @file{tissue.conf} adding in default and
+extra parameters for convenient processing later on.
+@var{state-directory} is the tissue state directory."
+  (match host
+    ((host-name . host-parameters)
+     ;; Add repository directory, website directory, xapian directory
+     ;; settings for each host.
+     (cons host-name
+           `((projects
+              ,@(map (cut normalize-project state-directory <>)
+                     (assq-ref host-parameters 'projects)))
+             ,@host-parameters)))))
+
 (define tissue-web
   (match-lambda*
     (("--help")
@@ -266,58 +310,20 @@ Serve repositories specified in CONFIG-FILE over HTTP.
        (when (assq-ref args 'listen-repl)
          (start-repl (assq-ref args 'listen-repl)))
        (start-web-server (listen->socket-address (assq-ref args 'listen))
-                         (map (match-lambda
-                                ((name parameters ...)
-                                 ;; Add CSS, repository directory,
-                                 ;; website directory, xapian
-                                 ;; directory settings for each host.
-                                 (let* ((state-directory (assq-ref args 'state-directory))
-                                        (repository-directory
-                                         (string-append state-directory "/" name "/repository")))
-                                   (parameterize ((%current-git-repository
-                                                   (repository-open repository-directory)))
-                                     (cons name
-                                           `((css . ,(tissue-configuration-web-css (load-config)))
-                                             (repository-directory . ,repository-directory)
-                                             (website-directory . ,(string-append state-directory "/" name "/website"))
-                                             (xapian-directory . ,(string-append state-directory "/" name "/xapian"))
-                                             ,@parameters))))))
+                         (map (cut normalize-host
+                                   (assq-ref args 'state-directory)
+                                   <>)
                               (assq-ref args 'hosts)))))))
 
-(define tissue-web-build
-  (match-lambda*
-    (("--help")
-     (format #t "Usage: ~a web-build WEBSITE-DIRECTORY
-Build website of current repository.
-"
-             (command-line-program)))
-    ((website-directory)
-     (let ((config (load-config)))
-       (guard (c (else (format (current-error-port)
-                               "Building website failed.~%")
-                       (raise c)))
-         (call-with-temporary-directory
-          (lambda (temporary-output-directory)
-            (build-website (git-top-level)
-                           temporary-output-directory
-                           (tissue-configuration-web-css config)
-                           (tissue-configuration-web-files config))
-            (delete-file-recursively website-directory)
-            (rename-file temporary-output-directory
-                         website-directory)
-            (chmod website-directory #o755))
-          (dirname website-directory))))
-     (format (current-error-port)
-             "Built website.~%"))))
-
 (define tissue-web-dev
   (match-lambda*
     (("--help")
-     (format #t "Usage: ~a web-dev WEBSITE-DIRECTORY
-Serve built website and issues of current repository.
+     (format #t "Usage: ~a web-dev
+Serve website and issues of current repository.
 
   --port=PORT       run web server listening on PORT (default: 8080)
   --listen-repl=P   run REPL server listening on port or path P
+  --base-path=PATH  serve website at PATH
 "
              (command-line-program)))
     (args
@@ -328,23 +334,21 @@ Serve built website and issues of current repository.
                                           (lambda (opt name arg result)
                                             (acons 'port
                                                    (string->number arg)
+                                                   result)))
+                                  (option '(#\b "base-path")
+                                          #t #f
+                                          (lambda (opt name arg result)
+                                            (acons 'base-path arg
                                                    result))))
                             invalid-option
-                            (lambda (arg result)
-                              (acons 'website-directory arg result))
-                            '((port . 8080)))))
-       (unless (assq-ref args 'website-directory)
-         (raise (condition (make-user-error-condition)
-                           (make-message-condition "Argument WEBSITE-DIRECTORY is required."))))
+                            unrecognized-argument
+                            '((port . 8080)
+                              (base-path . "/")))))
        (when (assq-ref args 'listen-repl)
          (start-repl (assq-ref args 'listen-repl)))
-       (start-web-server (make-socket-address
-                          AF_INET (inet-pton AF_INET "127.0.0.1") (assq-ref args 'port))
-                         `(("localhost"
-                            (css . ,(tissue-configuration-web-css (load-config)))
-                            (repository-directory . ,(repository-directory (current-git-repository)))
-                            (website-directory . ,(assq-ref args 'website-directory))
-                            (xapian-directory . ,%xapian-index))))))))
+       (start-dev-web-server (assq-ref args 'port)
+                             (assq-ref args 'base-path)
+                             %xapian-index load-config)))))
 
 (define (print-usage)
   (format #t "Usage: ~a COMMAND [OPTIONS] [ARGS]
@@ -356,8 +360,7 @@ COMMAND must be one of the sub-commands listed below:
   repl      run a Guile script in a tissue environment
 
 Develop:
-  web-build build website of current repository
-  web-dev   serve built website and issues of current repository
+  web-dev   serve website and issues of current repository
 
 Deploy:
   web       serve one or more repositories over HTTP
@@ -370,7 +373,7 @@ To get usage information for one of these sub-commands, run
           (command-line-program)
           (command-line-program)))
 
-(define (index db-path)
+(define (index db-path indexed-documents)
   "Index current repository into xapian database at DB-PATH."
   (guard (c (else (delete-file-recursively db-path)
                   (format (current-error-port)
@@ -383,7 +386,7 @@ To get usage information for one of these sub-commands, run
                     (replace-document! db
                                        (document-id-term document)
                                        (TermGenerator-get-document (document-term-generator document))))
-                  (tissue-configuration-indexed-documents (load-config)))
+                  indexed-documents)
         (WritableDatabase-set-metadata
          db "commit" (oid->string (reference-name->oid
                                    (current-git-repository) "HEAD")))))))
@@ -436,36 +439,47 @@ HOSTNAME."
                                     (format (current-error-port)
                                             "Cloned upstream repository.~%")
                                     repository)))))
-              (let ((config (load-config)))
-                (parameterize ((%aliases (tissue-configuration-aliases config))
-                               (%project-name (tissue-configuration-project config)))
-                  ;; Index.
-                  (let ((xapian-directory "xapian"))
-                    (index xapian-directory)
-                    (format (current-error-port)
-                            "Indexed latest changes.~%"))
-                  ;; Build website.
-                  (let ((website-directory "website"))
-                    (guard (c (else (format (current-error-port)
-                                            "Building website failed.~%")
-                                    (raise c)))
-                      (call-with-temporary-directory
-                       (lambda (temporary-output-directory)
-                         (build-website (git-top-level)
-                                        temporary-output-directory
-                                        (tissue-configuration-web-css config)
-                                        (tissue-configuration-web-files config))
-                         (delete-file-recursively website-directory)
-                         (rename-file temporary-output-directory
-                                      website-directory)
-                         (chmod website-directory #o755))))
-                    (format (current-error-port)
-                            "Built website.~%")))))))))))
+              (call-with-temporary-checkout (git-top-level)
+                (lambda (temporary-repository-clone)
+                  (let ((config (call-with-current-directory temporary-repository-clone
+                                  load-config)))
+                    (parameterize ((%aliases (tissue-configuration-aliases config)))
+                      ;; Add the top level of the git repository to the
+                      ;; load path since there may be user-written
+                      ;; modules in the repository.
+                      (add-to-load-path temporary-repository-clone)
+                      ;; Index.
+                      (unless (file-exists? "xapian")
+                        (mkdir "xapian"))
+                      (let ((xapian-directory (canonicalize-path "xapian")))
+                        (call-with-current-directory temporary-repository-clone
+                          (cut index
+                               xapian-directory
+                               (tissue-configuration-indexed-documents config)))
+                        (format (current-error-port)
+                                "Indexed latest changes.~%"))
+                      ;; Build website.
+                      (let ((website-directory "website"))
+                        (guard (c (else (format (current-error-port)
+                                                "Building website failed.~%")
+                                        (raise c)))
+                          (call-with-temporary-directory
+                           (lambda (temporary-output-directory)
+                             (call-with-current-directory temporary-repository-clone
+                               (cut build-website
+                                    temporary-output-directory
+                                    (tissue-configuration-web-files config)))
+                             (delete-file-recursively website-directory)
+                             (rename-file temporary-output-directory
+                                          website-directory)))
+                          (chmod website-directory #o755)
+                          (format (current-error-port)
+                                  "Built website.~%"))))))))))))))
 
 (define tissue-pull
   (match-lambda*
     (("--help")
-     (format #t "Usage: ~a pull [HOST]
+     (format #t "Usage: ~a pull [OPTIONS] PROJECT
 Pull latest from upstream repositories.
 
   -C, --config=CONFIG-FILE    read configuration parameters from CONFIG-FILE
@@ -480,8 +494,8 @@ Pull latest from upstream repositories.
                                                       read)
                                                     result))))
                             invalid-option
-                            (lambda (host result)
-                              (acons 'host host result))
+                            (lambda (project result)
+                              (acons 'project project result))
                             (default-configuration))))
        (let ((state-directory (assq-ref args 'state-directory)))
          ;; Error out if state directory does not exist.
@@ -490,18 +504,30 @@ Pull latest from upstream repositories.
                    "State directory ~a does not exist.~%"
                    state-directory)
            (exit #f))
-         ;; Pull state for specific host, or for all hosts when none
-         ;; are specified on the command-line.
-         (for-each (match-lambda
-                     ((hostname . parameters)
-                      (when (or (not (assq-ref args 'host))
-                                (string=? hostname (assq-ref args 'host)))
-                        (pull state-directory
-                              hostname
-                              (assq-ref parameters 'upstream-repository)))))
-                   (assq-ref args 'hosts)))))))
-
-(define (main . args)
+         ;; Pull state for specificied project.
+         ;; It is not a good idea to pull for all configured projects
+         ;; when no project is specified on the command line. Since
+         ;; pulling requires executing code in each repository,
+         ;; pulling for multiple projects in a single process can
+         ;; cause interaction of code across projects.
+         (let ((project-name (assq-ref args 'project)))
+           (cond
+            ((assoc-ref (append-map (match-lambda
+                                      ((_ . parameters)
+                                       (assq-ref parameters 'projects)))
+                                    (assq-ref args 'hosts))
+                        project-name)
+             => (lambda (parameters)
+                  (pull state-directory
+                        project-name
+                        (assq-ref parameters 'upstream-repository))))
+            (else
+             (format (current-error-port)
+                     "Project ~a not found in configuration file."
+                     project-name)
+             (exit #f)))))))))
+
+(define (main args)
   (guard (c ((condition-git-error c)
              => (lambda (git-error)
                   (display (git-error-message git-error) (current-error-port))
@@ -512,6 +538,17 @@ Pull latest from upstream repositories.
              (display (condition-message c) (current-error-port))
              (newline (current-error-port))
              (exit #f)))
+    ;; Unless LESS is already configured, pass command-line options to
+    ;; less by setting LESS. This idea is inspired by
+    ;; git. https://git-scm.com/docs/git-config#git-config-corepager
+    (unless (getenv "LESS")
+      (setenv "LESS" "FRX"))
+    ;; Add the top level of the git repository to the load path since
+    ;; there may be user-written modules in the repository.
+    (match args
+      ((_ (or "repl" "web-dev") _ ...)
+       (add-to-load-path (git-top-level)))
+      (_ #t))
     (match args
       ((_ (or "-h" "--help"))
        (print-usage))
@@ -534,13 +571,13 @@ Pull latest from upstream repositories.
                               (string=? (call-with-database %xapian-index
                                           (cut Database-get-metadata <> "commit"))
                                         current-head))
-                   (index %xapian-index)))
+                   (index %xapian-index
+                          (tissue-configuration-indexed-documents config))))
                ;; Handle sub-command.
                (apply (match command
                         ("search" tissue-search)
                         ("show" tissue-show)
                         ("repl" tissue-repl)
-                        ("web-build" tissue-web-build)
                         ("web-dev" tissue-web-dev)
                         (invalid-command
                          (format (current-error-port) "Invalid command `~a'~%~%"
@@ -549,7 +586,5 @@ Pull latest from upstream repositories.
                          (exit #f)))
                       args))))))
       ;; tissue is an alias for `tissue search'
-      ((_)
-       (main "tissue" "search")))))
-
-(apply main (command-line))
+      ((tissue)
+       (main (list tissue "search"))))))