massive changes incl. support for images, markdown

i started this off by trying to learn more about how scheme does file
i/o. it seems like many of its functions just expect you'll want them to
write to (current-output-port) instead of returning a string. so i
thought, i wonder what it would look like if i tweak these content
generator functions to just (display) their stuff, and
call (with-output-to-file) and apply the html template each time i call
them?

and whew it kindof got away from me

i totally understand if you feel like this is an unpleasant overhaul of
your whole project and don't want to merge this change!

anyway let's see if i can summarize the changes:

- image support!!
- svg image support!! it shows both the svg and its source code!
- markdown support; we now render all .md files instead of showing source
- using string->goodHTML instead of clean-html. turns out, it's a little
  annoying, in that it only returns a string if it makes no changes, but
  if it does, it returns a list of strings. it expects to be passed to
  one of the other functions in the sxml library, so that's what i do.
- moved "wrap the template" outside of various "generate content"
  functions
- simple command line use: from any git work-tree OR bare repo, run
  repo2html /path/to/www/output/repo-foo-bar and it will create that
  directory and use "repo-foo-bar" as the repo name.
- made our example post-receive a bit more robust
  - changed env var names to have REPO2HTML_ prefix
  - better repo name detection and automatic caching of it in git config
  - moved the post-receive-specific logic to avoid generating if we're
    not updating HEAD into the post-receive hook itself, along with some
    status messages
  - checked directory permissions in the hook so it doesn't even attempt
    to run repo2html and avoids a crash
This commit is contained in:
pho4cexa 2022-12-07 01:00:52 -08:00 committed by m455
parent 1a3b8b1aa1
commit 50bbb3686d
2 changed files with 136 additions and 103 deletions

169
main.scm
View file

@ -11,6 +11,7 @@
(chicken pathname) (chicken pathname)
(chicken file) (chicken file)
sxml-transforms sxml-transforms
(clojurian syntax)
) )
(define WEB-DIRECTORY (or (get-environment-variable "GIT_WWW") "/var/www/git")) (define WEB-DIRECTORY (or (get-environment-variable "GIT_WWW") "/var/www/git"))
@ -19,11 +20,8 @@
(define DESCRIPTION (or (get-environment-variable "GIT_WWW_DESCRIPTION") "my git repositories")) (define DESCRIPTION (or (get-environment-variable "GIT_WWW_DESCRIPTION") "my git repositories"))
(define H1 (or (get-environment-variable "GIT_WWW_H1") "git.example.com")) (define H1 (or (get-environment-variable "GIT_WWW_H1") "git.example.com"))
(define REPOSITORY-NAME (pathname-strip-directory (current-directory))) (define (populate-html-template repo-name display-body-thunk)
(define REPOSITORY-DIRECTORY (make-pathname WEB-DIRECTORY REPOSITORY-NAME)) (display #<#string-block
(define (populate-html-template body)
#<#string-block
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -59,113 +57,103 @@ hr {
</head> </head>
<body> <body>
<h1>#{H1}</h1> <h1>#{H1}</h1>
<h2>#{REPOSITORY-NAME}</h2> <h2>#{repo-name}</h2>
<p>clone url: #{CLONE-URL}/#{REPOSITORY-NAME}</p> <p>clone url: #{CLONE-URL}/#{repo-name}</p>
<nav> <nav>
<a href="/#{REPOSITORY-NAME}/index.html">about</a> <a href="/#{repo-name}/index.html">about</a>
<a href="/#{REPOSITORY-NAME}/files.html">files</a> <a href="/#{repo-name}/files.html">files</a>
<a href="/#{REPOSITORY-NAME}/contributors.html">contributors</a> <a href="/#{repo-name}/contributors.html">contributors</a>
</nav> </nav>
<hr> <hr>
#{body} string-block
)
(display-body-thunk)
(display #<#string-block
</body> </body>
</html> </html>
string-block string-block
) ))
(define (write-file file contents) (define (display-escaped-html str)
(call-with-output-file file (lambda (port) (write-line contents port)))) (SRV:send-reply (string->goodHTML str)))
(define (in-git-directory?) (define (in-git-directory?)
(equal? (call-with-input-pipe "git rev-parse --is-bare-repository 2> /dev/null" read-line) "true")) (not (eof-object? (call-with-input-pipe "git rev-parse --git-dir" read-line))))
(define (git-repository->paths-list) (define (git-repository->paths-list)
(call-with-input-pipe "git ls-tree -r --name-only HEAD" read-lines)) (call-with-input-pipe "git ls-tree -r --name-only HEAD" read-lines))
(define (git-file->string path) (define (git-file->string path)
(string-intersperse (->
(call-with-input-pipe (format "git show HEAD:~a" path) read-lines) (format "git show HEAD:~a" path)
"\n")) (call-with-input-pipe read-lines)
(string-intersperse "\n")))
(define (display-source-html source-file) ;; src/main.scm
(format #t "<p id=\"file-path\">~a</p>" source-file)
(case (string->symbol (or (pathname-extension source-file) ""))
((md markdown)
(markdown->html (git-file->string source-file)))
((jpg jpeg png gif webp webm apng avif svgz ico)
(format #t "<p><img src=\"~a\" /></p>" source-file)
)
((svg)
(format #t "<p><img src=\"~a\" /></p>" source-file)
(display "<pre>")
(display-escaped-html (git-file->string source-file))
(display "</pre>"))
(else
(display "<pre>")
(display-escaped-html (git-file->string source-file))
(display "</pre>"))))
(define (clean-html str) (define (display-files-html source-files-list)
(string-translate* str '(("&" . "&amp;") (display "<ul>\n")
("<" . "&lt;") (for-each
(">" . "&gt;") (lambda (source-file)
("\"" . "&quot;") (format #t "<li><a href=\"~a.html\">~a</a></li>\n" source-file source-file))
("'" . "&#39;")))) source-files-list)
(display "</ul>\n"))
(define (md->html markdown-string) (define (display-readme-html)
(with-output-to-string (markdown->html (git-file->string "README.md")))
(lambda ()
(markdown->html markdown-string))))
(define (generate-list-of-files source-files-list) (define (display-contributors-html)
(if (null? source-files-list)
""
(let* ((source-file (car source-files-list))
(link-url (string-append source-file ".html"))) ;; src/main.scm.html
(string-append (format "<li><a href=\"~a\">~a</a></li>\n" link-url source-file)
(generate-list-of-files (cdr source-files-list))))))
(define (generate-source-file source-file) ;; src/main.scm
(let* ((source-file-directory (pathname-directory source-file)) ;; src or #f
(output-directory (if source-file-directory
(make-pathname REPOSITORY-DIRECTORY source-file-directory) ;; <WEB-DIRECTORY>/<repository-name>/src
REPOSITORY-DIRECTORY))) ;; <WEB-DIRECTORY>/<repository-name>
;; create directories that mimic the path of the source file, so when
;; someone clicks a link to view the contents of a source file, the URL
;; matches up with the path of the source file.
;; i guess another reason to do this is to avoid conflicts with files that
;; have the same name, but exist in different directories.
(create-directory output-directory #t)
(write-file
(make-pathname output-directory (pathname-strip-directory source-file) "html")
(populate-html-template (string-append "<p id=\"file-path\">" source-file "</p>"
"<pre>\n"
(clean-html (git-file->string source-file))
"</pre>")))))
(define (generate-source-files source-files-list)
(for-each (lambda (source-file) (generate-source-file source-file))
source-files-list))
(define (generate-files-page files-list-page-path source-files-list)
(write-file
files-list-page-path
(populate-html-template (string-append "<ul>\n"
(generate-list-of-files source-files-list)
"</ul>\n"))))
(define (generate-readme-page index-page-path)
(write-file
index-page-path
(populate-html-template (md->html (git-file->string "README.md")))))
(define (generate-contributors-html)
(populate-html-template
(with-output-to-string
(lambda ()
(SXML->HTML (SXML->HTML
`((h1 "Contributors") `((h1 "Contributors")
(ul ,(map (ul ,(map
(lambda (line) (lambda (line)
(let-values (((commits . author) (apply values (string-split line "\t")))) (let-values (((commits . author) (apply values (string-split line "\t"))))
`(li ,author))) `(li ,author)))
(call-with-input-pipe "git shortlog -ns HEAD" read-lines))))))))) (call-with-input-pipe "git shortlog -ns HEAD" read-lines))))))
(define (generate-repository-directory) (define (generate-html-files html-repo-path)
(when (directory-exists? REPOSITORY-DIRECTORY) (let ((source-files-list (git-repository->paths-list))
(delete-directory REPOSITORY-DIRECTORY #t)) (repository-name (pathname-strip-directory html-repo-path)))
(create-directory REPOSITORY-DIRECTORY #t))
(define (generate-html-files) (define (write-with-template filename display-body-thunk)
(let ((source-files-list (git-repository->paths-list))) (let ((destination-directory (pathname-directory filename)))
(generate-repository-directory) (when destination-directory (create-directory pathname-directory #t)))
(generate-readme-page (make-pathname REPOSITORY-DIRECTORY "index.html")) (with-output-to-file (make-pathname html-repo-path filename)
(generate-files-page (make-pathname REPOSITORY-DIRECTORY "files.html") source-files-list) (lambda () (populate-html-template repository-name display-body-thunk))))
(write-file (make-pathname REPOSITORY-DIRECTORY "contributors.html") (generate-contributors-html))
(generate-source-files source-files-list))) (create-directory html-repo-path #t)
(write-with-template "index.html" (lambda () (display-readme-html)))
(write-with-template "files.html" (lambda () (display-files-html source-files-list)))
(write-with-template "contributors.html" (lambda () (display-contributors-html)))
(for-each
(lambda (source-file)
(write-with-template
(string-append source-file ".html")
(lambda () (display-source-html source-file)))
(case (string->symbol (or (pathname-extension source-file) ""))
((jpg jpeg png gif webp webm svg apng avif svgz ico)
(system (format "git-show HEAD:~a > ~a "
source-file
(make-pathname html-repo-path source-file)
)))))
source-files-list)))
(define (bail . args) (define (bail . args)
(let-optionals args ((status 1) (msg "")) (let-optionals args ((status 1) (msg ""))
@ -173,13 +161,14 @@ string-block
(exit status))) (exit status)))
(define (main args) (define (main args)
(let-optionals args ((html-repo-path '()))
(unless (null? args) (when (null? html-repo-path)
(bail 1 "woops, i dont take args")) (bail 1 "please specify a destination directory for html files"))
(unless (in-git-directory?) (unless (in-git-directory?)
(bail 1 "woops that's not a bare git directory")) (bail 1 "woops this isn't a git directory"))
(generate-html-files)) (generate-html-files html-repo-path)))
(main (command-line-arguments)) (main (command-line-arguments))

56
post-receive Normal file → Executable file
View file

@ -3,9 +3,53 @@
# - place this file in the 'hooks' directory of a bare git repository # - place this file in the 'hooks' directory of a bare git repository
# - this assumes that repo2html is in your path # - this assumes that repo2html is in your path
export GIT_WWW=/var/www/git/ # The toplevel path containing directories of static pages
export GIT_WWW_CLONE_URL=git://git.example.com [ "$REPO2HTML_PREFIX" ] || export REPO2HTML_PREFIX=/var/www/git
export GIT_WWW_TITLE=git.example.com # The toplevel clone url for repos.
export GIT_WWW_DESCRIPTION="sherry's git repositories" export REPO2HTML_CLONE_URL=git://git.example.com
export GIT_WWW_H1=git.example.com export REPO2HTML_TITLE=git.example.com
repo2html export REPO2HTML_DESCRIPTION="sherry's git repositories"
export REPO2HTML_H1=git.example.com
# hueristic attempt to detect a reasonable default for the name of this repo
# you may want to adjust this if you have e.g. sub-directories containing repos
# whose structure you want to preserve on the webserver.
if repo_name="$(git config repo2html.dirname)"; then
: # repo name from git config; do nothing else
elif [ "$(git rev-parse --is-bare-repository)" = "true" ]; then
# bare /foo/bar/baz/myrepo.git -> myrepo
repo_name="$(basename "$(pwd)" .git)"
git config repo2html.dirname "$repo_name"
else
# /foo/bar/baz/myrepo/.git -> myrepo
repo_name="$(basename "$(dirname "$(pwd)")")"
git config repo2html.dirname "$repo_name"
fi
# only generate if default branch is modified
headref="$(git symbolic-ref -q HEAD)"
while read -r _ _ ref
do [ "$ref" = "$headref" ] && go=1
done
if [ "$go" ]; then
echo "$headref was updated; regenerating HTML at: $REPO2HTML_PREFIX/$repo_name"
else
echo "($headref was not updated so HTML will not be regenerated.)"
exit 0
fi
# check to see if we can even write to the specified directory
if mkdir -p "$REPO2HTML_PREFIX/$repo_name" && \
[ -x "$REPO2HTML_PREFIX/$repo_name" ] && \
[ -w "$REPO2HTML_PREFIX/$repo_name" ]
then
: # we can write to the directory; seems good
else
echo "Error: can't generate HTML due to insufficient permissions on path:"
echo " $REPO2HTML_PREFIX/$repo_name"
echo "Set REPO2HTML_PREFIX in the environment or this script:"
echo " $0"
exit 1
fi
repo2html "$REPO2HTML_PREFIX/$repo_name"