;;; wiki-utils.el -- wiki utility functions -*- lexical-binding: t -*- ;; Copyright (C) 2023 Free Software Foundation, Inc. ;; Author: Yuchen Pei ;; This file is part of wiki.el. ;; wiki.el is free software: you can redistribute it and/or modify it under ;; the terms of the GNU Affero General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; wiki.el is distributed in the hope that it will be useful, but WITHOUT ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General ;; Public License for more details. ;; You should have received a copy of the GNU Affero General Public ;; License along with wiki.el. If not, see . ;;; Commentary: ;; wiki utility functions. ;;; Code: (require 'url-parse) (require 'json) (defvar wiki-local-dir (locate-user-emacs-file "wiki") "Path to local directory of wiki files.") (defvar wiki-fetch-prefer-local t "If non-nil, visit the local file if exists when fetching.") (defvar wiki-extension ".wiki" "The extension of local wiki files.") (add-to-list 'auto-mode-alist `(,(format "\\%s\\'" wiki-extension) . wiki-mode)) (defun wiki-kill-http-header () "Kill the http header in current buffer. Assuming the current buffer to be a `url-retrieve' response buffer." (kill-region (point-min) (progn (wiki-skip-http-header) (point)))) (defun wiki-delete-http-header () "Delete the http header in current buffer. Assuming the current buffer to be a `url-retrieve' response buffer." (delete-region (point-min) (progn (wiki-skip-http-header) (point)))) (defun wiki-skip-http-header () "Skip the http header in current buffer. Assuming the current buffer to be a `url-retrieve' response buffer." (goto-char (point-min)) (re-search-forward "\r?\n\r?\n")) ;; TODO: generalise fandom ;; mandatory fields: id and host (defvar wiki-sites '((local) (archwiki :host "https://wiki.archlinux.org" :base-url "https://wiki.archlinux.org/title" :display-name "ArchWiki") (debian-wiki :host "https://wiki.debian.org" :engine moinmoin) (emacswiki :host "https://www.emacswiki.org" :base-url "https://www.emacswiki.org/emacs" :engine oddmuse :display-name "EmacsWiki") (esp :host "https://wiki.endsoftwarepatents.org" :display-name "ESP Wiki") (fandom-recipes :host "https://recipes.fandom.com") (fsd :host "https://directory.fsf.org" :display-name "Free Software Directory") (haskell-wiki :host "https://wiki.haskell.org") (libreplanet :host "https://libreplanet.org") (oddmuse :host "https://oddmuse.org" :engine oddmuse :display-name "Oddmuse") (parabolawiki :host "https://wiki.parabola.nu" :display-name "ParabolaWiki") (python-wiki :host "https://wiki.python.org" :base-url "https://wiki.python.org/moin" :engine moinmoin) (termux-wiki :host "https://wiki.termux.com") (ubuntu-wiki :host "https://wiki.ubuntu.com" :engine moinmoin) (ubuntu-community-help-wiki :host "https://help.ubuntu.com/community" :engine moinmoin) (wikihow :host "https://www.wikihow.com" :api-base-url "https://www.wikihow.com" :display-name "wikiHow" :fetcher wiki-engine-mediawiki-api-fetch) (wikiindex :host "https://wikiindex.org" :base-url "https://wikiindex.org" :display-name "WikiIndex") (wikipedia-en :host "https://en.wikipedia.org" :display-name "Wikipedia EN") (wikipedia-zh :host "https://zh.wikipedia.org" :display-name "Wikipedia ZH") (wikivoyage-en :host "https://en.wikivoyage.org" :display-name "Wikivoyage EN") (wiktionary-en :host "https://en.wiktionary.org" :display-name "Wiktionary EN") ) "Alist of wiki sites. Each item is in the form of (identifier . properties), where identifier is a symbol, and properties is a plist of site properties. One of the sites is (local), meaning a local filesystem. The only mandatory field is `:host'. All other fields, i.e. `:engine', `:base-url', `:api-base-url', `display-name' may be computed from `wiki-engine-compute-engine', `wiki-engine-compute-base-url', `wiki-engine-compute-api-base-url', or `wiki-engine-compute-display-name'.") ;; FIXME: does it make sense to fallback to wiki-find-file here? (defun wiki-site-fetcher (site-id) "Return the fetcher function for wiki site with SITE-ID." (if site-id (intern (format "wiki-%s-fetch" site-id)) 'wiki-find-file)) (defun wiki-site-searcher (site-id) "Return the fetcher function for wiki site with SITE-ID." (if site-id (intern (format "wiki-%s-search" site-id)) (error "Unknown site id: %s" site-id))) (defvar wiki-client-buffer-name "*wiki api*" "Name of the buffer recording wiki API calls.") (defun wiki-parse-http-header (text) "Parse the http header TEXT." (let ((status) (fields)) (with-temp-buffer (insert text) (goto-char (point-min)) (re-search-forward "^HTTP.*\\([0-9]\\{3\\}\\).*$") (setq status (match-string 1)) (while (re-search-forward "^\\(.*?\\): \\(.*\\)$" nil t) (push (cons (intern (match-string 1)) (match-string 2)) fields))) (list (cons 'status status) (cons 'fields fields)))) (defun wiki-url-fetch-internal (url processor &optional decompression with-header) "Fetch from URL and process the response payload using PROCESSOR. PROCESSOR is a function that takes no argument and processes the current buffer. With non-nil DECOMPRESSION, decompress the response. With non-nil WITH-HEADER, include the header in the result." (with-current-buffer (get-buffer-create wiki-client-buffer-name) (goto-char (point-max)) (insert "[" (current-time-string) "] Request: " url "\n")) (with-current-buffer (url-retrieve-synchronously url t) (let ((header) (status) (fields)) (wiki-kill-http-header) (goto-char (point-min)) (setq header (wiki-parse-http-header (car kill-ring)) status (alist-get 'status header) fields (alist-get 'fields header)) (with-current-buffer wiki-client-buffer-name (insert "[" (current-time-string) "] Response: " status "\n")) (when decompression (call-process-region (point) (point-max) "gunzip" t t t) (goto-char (point-min))) (call-interactively 'delete-trailing-whitespace) (if (string= status "200") (unless (= (point) (point-max)) (if with-header (list (cons 'header fields) (cons 'json (funcall processor))) (funcall processor))) (error "HTTP error: %s" (buffer-substring (point) (point-max))))))) (defun wiki-url-fetch-json (url &optional decompression with-header) "Fetch and parse a json object from URL. With non-nil DECOMPRESSION, decompress the response. With non-nil WITH-HEADER, include the header in the result." (wiki-url-fetch-internal url 'json-read decompression with-header)) (provide 'wiki-utils) ;;; wiki-utils.el ends here