From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp10.migadu.com ([2001:41d0:8:6d80::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms5.migadu.com with LMTPS id 8GXpM5mE5mMrRQEAbAwnHQ (envelope-from ) for ; Fri, 10 Feb 2023 18:53:29 +0100 Received: from aspmx1.migadu.com ([2001:41d0:8:6d80::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp10.migadu.com with LMTPS id oCvtMpmE5mNW9gAAG6o9tA (envelope-from ) for ; Fri, 10 Feb 2023 18:53:29 +0100 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 839A19026 for ; Fri, 10 Feb 2023 18:53:28 +0100 (CET) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1pQXZg-00008k-5n; Fri, 10 Feb 2023 12:52:40 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pQXZc-00008Q-Ra for emacs-orgmode@gnu.org; Fri, 10 Feb 2023 12:52:38 -0500 Received: from out1-smtp.messagingengine.com ([66.111.4.25]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pQXZW-0006pw-6s for emacs-orgmode@gnu.org; Fri, 10 Feb 2023 12:52:36 -0500 Received: from compute1.internal (compute1.nyi.internal [10.202.2.41]) by mailout.nyi.internal (Postfix) with ESMTP id 58A365C00FB for ; Fri, 10 Feb 2023 12:52:26 -0500 (EST) Received: from mailfrontend2 ([10.202.2.163]) by compute1.internal (MEProxy); Fri, 10 Feb 2023 12:52:26 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=tecosaur.net; h= cc:content-type:date:date:from:from:in-reply-to:message-id :mime-version:reply-to:sender:subject:subject:to:to; s=fm3; t= 1676051546; x=1676137946; bh=tvzf1nDtcIpfGUtHa1F/qhIOtwMV96ohDot Tr4551h0=; b=AfVXp38EpfIwtEQVrYCdzoB+bCRm0FE0pGhdjLIiEBV90vyLibq YNIymk7m+bD4vaSpTfYCIWm94Ye6n7wVQW8WOKhzwodtws0mel4UtqVU4yzyX1Kr 0otcLr2sGvAAWZXQErSUQO2KWR3NltJ5PK0c5sX/oPuh6/mbxEIPS2BiYgIsvzP/ PK/YDvJXs1lYHeBNjwLTrmuhsEwT57W/yAiUDnon0iUTSd6QplEfyIx9zSFYIr+u nP+yAUKTPvL+NczRnFXK57UuZOeWL9yKLwCFX8CONXwd8gIfczWtVFY/xBqMKVS8 qE5rZtvuPr3vaBigpoZMItT5A120kKflo5g== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-type:date:date:feedback-id :feedback-id:from:from:in-reply-to:message-id:mime-version :reply-to:sender:subject:subject:to:to:x-me-proxy:x-me-proxy :x-me-sender:x-me-sender:x-sasl-enc; s=fm1; t=1676051546; x= 1676137946; bh=tvzf1nDtcIpfGUtHa1F/qhIOtwMV96ohDotTr4551h0=; b=l 5D1qFdEoponCZwcNFajhY7adVPaH4zobdd5Mk5F+aa64tszCBxAFREmpuy6T6IrX uXmVGva1V41qk/ced0E7fF1EUXaafqUYzwV/RYhIwcy9AppjX1fKYbaiHQGs+/S/ mCOiPl0VvC8LM1xE3aR1mRNclL3hoU0htU1wv6BvDVj0VFGPl92saFes4zo1YeMm 1fpMsmPtjwQQiseRuCznNcPOyn7xjMDhL1p7IjlCMWco89VKpwTBmhV5Y2QmhLz7 CaWUJwy9tfiWO8JFPI9YWQmGDy1aHuYRu1FREcHFllEVctru+E6QDiDPtLwaMwZ1 X0fZWIlWFjiaG0CkHN8aA== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvhedrudehhedguddtgecutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpqfgfvfdpuffrtefokffrpgfnqfgh necuuegrihhlohhuthemuceftddtnecunecujfgurhephffvuffffgfkgggtsehmtderre dtreejnecuhfhrohhmpefvihhmohhthhihuceoohhrghhmohguvgesthgvtgdrthgvtgho shgruhhrrdhnvghtqeenucggtffrrghtthgvrhhnpeetleevledvtdefheetiefhueevje eggedtueetleejgefhjeehveduieeiteevgfenucffohhmrghinhepohhrghhmohguvgdr ohhrghdplhhisggvrhgrphgrhidrtghomhenucevlhhushhtvghrufhiiigvpedtnecurf grrhgrmhepmhgrihhlfhhrohhmpehorhhgmhhouggvsehtvggtrdhtvggtohhsrghurhdr nhgvth X-ME-Proxy: Feedback-ID: iecf94634:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA for ; Fri, 10 Feb 2023 12:52:24 -0500 (EST) From: Timothy To: emacs-orgmode@gnu.org Subject: [PATCH] Introduce "export features" Date: Sat, 11 Feb 2023 01:20:09 +0800 User-agent: mu4e 1.8.13; emacs 28.2.50 Message-ID: <875yc95rxp.fsf@tec.tecosaur.net> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Received-SPF: none client-ip=66.111.4.25; envelope-from=orgmode@tec.tecosaur.net; helo=out1-smtp.messagingengine.com X-Spam_score_int: -26 X-Spam_score: -2.7 X-Spam_bar: -- X-Spam_report: (-2.7 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, SPF_NONE=0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-orgmode@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-orgmode-bounces+larch=yhetil.org@gnu.org Sender: emacs-orgmode-bounces+larch=yhetil.org@gnu.org X-Migadu-Flow: FLOW_IN X-Migadu-Country: US ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=tecosaur.net header.s=fm3 header.b=AfVXp38E; dkim=fail ("body hash did not verify") header.d=messagingengine.com header.s=fm1 header.b="l 5D1qFd"; spf=pass (aspmx1.migadu.com: domain of "emacs-orgmode-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="emacs-orgmode-bounces+larch=yhetil.org@gnu.org"; dmarc=none ARC-Seal: i=1; s=key1; d=yhetil.org; t=1676051609; a=rsa-sha256; cv=none; b=qFy+wVdq1M3AFMAeusf9CZNZoB6slihTEhVXjT/ltNDRgx/FH+5qah8n+u0HmOt1Ntdysk NS+tbxtY/6dwDl6NKPeMkX+q2EQ898jE+Blor+UiLqFIjfMoNDQduWIf1odRyViLM2sgRS mbeSv3F7q1faekuspMbvuDvkM+bFuTsXKoUP5KYeo4hAc+beWOBV3ogiMQfro9sX0wEmrS IP2zCksp1BUgi4lAn5fghSz1w/tZ/8DOhIOy74rOgYGrufPoN0r1ObbkHia+FQV8YruON7 YjBDBquo7xt+3U99ToZI6JcURwQwFKEmzlwrAX1u1z/zpttmOeRBtXybl/Ctww== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1676051609; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type:list-id:list-help:list-unsubscribe: list-subscribe:list-post:dkim-signature; bh=kXcPTQwQUyzODOXXlrs/a/C33tg+cIGvqq+15MpFIes=; b=Lh5Macq/8p0TzJFE+hcnJPMy+zZdlAIK4WyMGQJL7D9A5HPvm9Wjqi67yxsUnNNh7jCaHa Zc1FFjKTJd98JDsAPlgyQYOrpPfS/smlpwWte1E3U9eMnwUUwXZFeJJSXrXJg2bobqOTNV pzYU17iCJEJ1XEDc2wtfxE4oN4EjkSmKbGdaby1PSVIYttL+fEW4ahOCzDFKpVMDngFaPW OQLWKaKdC8TvwHlMqoJLr4Ftzd8MK3ov1cJ1TXQ5A7RYSthSgoe25NgXB1YWtrLQIfPS2p T3g0+KGZXNJRj0809xQ9F3AIYtnLhBHheegUpXKLAgnulrofynC33afN0hIUfQ== X-Migadu-Scanner: scn0.migadu.com X-Migadu-Spam-Score: 1.84 X-Spam-Score: 1.84 X-Migadu-Queue-Id: 839A19026 Authentication-Results: aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=tecosaur.net header.s=fm3 header.b=AfVXp38E; dkim=fail ("body hash did not verify") header.d=messagingengine.com header.s=fm1 header.b="l 5D1qFd"; spf=pass (aspmx1.migadu.com: domain of "emacs-orgmode-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="emacs-orgmode-bounces+larch=yhetil.org@gnu.org"; dmarc=none X-TUID: vwNWMeu/OnQM --=-=-= Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable Hello everyone, I=E2=80=99m thrilled to finally be presenting a feature that I=E2=80=99ve b= een incubating for a while now that I call =E2=80=9Cexport features=E2=80=9D. This work is based= on the observation that often we include content in export templates that is only relevant in particular situations. This leaves one having to choose between a =E2=80=9Ckitchen sink=E2=80=9D a= pproach where everything that could be used is all included, or a =E2=80=9Cmanual inclusi= on=E2=80=9D approach where the template is minimal and the relevant setup code must be manually included each time. I think it=E2=80=99s fair to say neither situation is ideal, and I=E2=80=99= ve become dissatisfied enough that I=E2=80=99ve sunk some time into working on a bett= er way of handling this. In this patch set it=E2=80=99s just being applied to LaTeX p= reambles, but this is just a start =E2=80=94 there are plans to apply this more broadly. =E2=80=9Cexport features=E2=80=9D allow for the specification of qualities = of the org buffer being exported that imply certain =E2=80=9Cfeatures=E2=80=9D, and how those= features may be implemented in a particular export. This is done by augmenting the backend struct with two new fields: `feature-conditions' and `feature-implementations'. The feature conditions are resolved during the annotation of `info', in the= Org buffer after `#+include' expansion and the removal of comments. The feature implementations are expanded by the backend itself, in the case= of `ox-latex' this currently means during preamble construction. With this change, `ox-latex' produces more minimal /and/ more capable expor= ts out of the box. The number of default packages has been ~halved, but OOTB capabili= ty has been improved by dynamically adding them to the preamble when needed, a= nd now `\usepackage{svg}' is automatically added when exporting a buffer that includes SVG images. This also opens new frontiers for user customisation. For instance, adding particular beamer customisations when using the `metropolis' beamer theme w= ith the following snippet: =E2=94=8C=E2=94=80=E2=94=80=E2=94=80=E2=94=80 =E2=94=82 (org-export-update-features 'beamer =E2=94=82 (beamer-metropolis =E2=94=82 :condition (string-match-p \"metropolis$\" (plist-get info :be= amer-theme)) =E2=94=82 :snippet my-org-beamer-metropolis-tweaks =E2=94=82 :order 3)) =E2=94=94=E2=94=80=E2=94=80=E2=94=80=E2=94=80 Hopefully this gives you an idea of the feature. See the patches attached f= or the implementation (and some hopefully informative code comments). Feel fre= e to toss any you may have question my way :) All the best, Timothy --=20 Timothy (=E2=80=98tecosaur=E2=80=99/=E2=80=98TEC=E2=80=99), Org mode contri= butor. Learn more about Org mode at . Support Org development at , or support my work at . --=-=-= Content-Type: text/x-patch; charset=utf-8 Content-Disposition: attachment; filename=0001-ox-Introduce-conditional-generated-preamble.patch Content-Transfer-Encoding: quoted-printable >From 5575a0f18277ef34f4003c1bccf650e4237e6048 Mon Sep 17 00:00:00 2001 From: TEC Date: Mon, 25 Jul 2022 23:37:13 +0800 Subject: [PATCH 1/6] ox: Introduce conditional/generated preamble * lisp/ox.el (org-export-detect-features, org-export-expand-features, org-export-generate-features-preamble): New functions for detecting features and generating content based on them. * lisp/ox.el (org-export-conditional-features): Customisation for feature detection. * lisp/ox.el (org-export-as): Add detected to features to info in the slot :features. --- lisp/ox.el | 217 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) diff --git a/lisp/ox.el b/lisp/ox.el index 0a48e850a..1a75ed28d 100644 --- a/lisp/ox.el +++ b/lisp/ox.el @@ -2030,6 +2030,221 @@ (defun org-export-expand (blob contents &optional w= ith-affiliated) (funcall (intern (format "org-element-%s-interpreter" type)) blob contents)))) =20 + +;;; Conditional/Generated Preamble +;; +;; Many formats have some version of a preamble, whether it be HTML's +;; ... or the content before LaTeX's \begin{document}. +;; Depending on the particular features in the Org document being +;; exported, different setup snippets will be needed. There's the +;; "everything and the kitchen sink" approach of adding absolutely +;; everything that might be needed, and the post-translation editing +;; with filters approach, but neither really solve this problem nicely. +;; +;; The conditional/generated preamble defines mechanisms of detecting +;; which "features" are used in a document, handles interactions +;; between features, and provides/generates preamble content to +;; support the features. + +(defcustom org-export-conditional-features + `(("^[ \t]*#\\+print_bibliography:" . bibliography) + (,(lambda (info) + (org-element-map (plist-get info :parse-tree) + 'link + (lambda (link) + (and (member (org-element-property :type link) + '("http" "https" "ftp" "file")) + (file-name-extension (org-element-property :path link)) + (string=3D (downcase (file-name-extension + (org-element-property :path link))) + "svg"))) + info t)) + . svg) + (,(lambda (info) + (org-element-map (plist-get info :parse-tree) + 'link + (lambda (link) + (and (member (org-element-property :type link) + '("http" "https" "ftp" "file")) + (file-name-extension (org-element-property :path link)) + (member (downcase (file-name-extension + (org-element-property :path link))) + image-file-name-extensions))) + info t)) + . image) + (,(lambda (info) + (org-element-map (plist-get info :parse-tree) + 'table #'identity info t)) + . table) + (,(lambda (info) + (org-element-map (plist-get info :parse-tree) + '(src-block inline-src-block) #'identity info t)) + . code)) + "Org feature tests and associated feature flags. + +Alist where the car is a test for the presense of the feature, +and the CDR is either a single feature symbol or a list of +feature symbols. + +Feature tests can take any of the following forms: +- Variable symbol, the value of which is fetched. +- Function symbol, which is called with the export info + as the argument. +- A string, which is used as a regexp search in the buffer. + The regexp matching is taken as confirmation of the existence + of the feature. + +When the test is a variable or function and produces a string +value, that value is itself used as a test. Any other non-nil +value will imply the existance of the feature." + :group 'org-export-general + :type '(alist :key-type + (choice (regexp :tag "Feature test regexp") + (variable :tag "Feature variable") + (function :tag "Feature test function")) + :value-type + (choice (symbol :tag "Feature symbol") + (repeat symbol :tag "Feature symbols")))) + +(defun org-export-detect-features (info) + "Detect features from `org-export-conditional-features' in INFO." + (let (case-fold-search) + (delete-dups + (mapcan + (lambda (construct-feature) + (and (let ((out (pcase (car construct-feature) + ((pred stringp) (car construct-feature)) + ((pred functionp) + (funcall (car construct-feature) info)) + ((pred symbolp) (symbol-value (car construct-fea= ture))) + (_ (error "org-export-conditional-features key %= s unable to be used" (car construct-feature)))))) + (if (stringp out) + (save-excursion + (goto-char (point-min)) + (re-search-forward out nil t)) + out)) + (if (listp (cdr construct-feature)) + (copy-sequence (cdr construct-feature)) + (list (cdr construct-feature))))) + (append org-export-conditional-features + (plist-get info :conditional-features)))))) + +(defun org-export-expand-features (info) + "Identify all implied implementations from features, in INFO. + +(plist-get info :feature-implementations) should be an alist of feature sy= mbols +and specification plists with the following keys: +- :snippet, which is either, + - A string, which should be included in the preamble verbatim. + - A variable, the value of which should be included in the preamble. + - A function, which is called with two arguments =E2=80=94 the export in= fo, + and the list of feature flags. The returned value is included in + the preamble. +- :requires, a feature or list of features this feature will enable. +- :when, a feature or list of features which are required for this + feature to be active. +- :prevents, a feature or list of features that should be masked. +- :order, for when inclusion order matters. Feature implementations + with a lower order appear first. The default is 0. + +This function processes :requires, :when, and :prevents in turn +before finally sorting according to :order. + +After resolving the features, the :features key of INFO is +updated to reflect the expanded set of features being used." + (let ((initial-features (plist-get info :features)) + (implementations (plist-get info :feature-implementations)) + required-features current-implementations) + ;; Process :requires. + (while initial-features + (push (car initial-features) required-features) + (setq initial-features + (if-let (requirements + (plist-get (alist-get (car initial-features) implemen= tations) + :requires)) + (if (consp requirements) + (append requirements (cdr initial-features)) + (cons requirements (cdr initial-features))) + (cdr initial-features)))) + ;; Get the implementations of required features. + (setq current-implementations + (mapcar (lambda (f) (assq f implementations)) + (delete-dups required-features))) + ;; Remove features with unfulfilled :when conditions. + (let ((processing t) + confirmed-features conditional-implementations + when) + ;; To correctly resolve all the various :when conditions, + ;; do not make any assumptions about which features are active. + ;; Initially only consider non-:when implementations to be + ;; active, then run through the list of unconfirmed :when + ;; implementations and check their conditions against the list + ;; of confirmed features. Continue doing this until no more + ;; features are confirmed. + (dolist (impl current-implementations) + (if (plist-get (cdr impl) :when) + (push impl conditional-implementations) + (push (car impl) confirmed-features))) + (while processing + (setq processing nil) + (dolist (impl conditional-implementations) + (setq when (plist-get (cdr impl) :when)) + (when (cond + ((symbolp when) + (memq when confirmed-features)) + ((consp when) + (not (cl-set-difference when confirmed-features)))) + (push (car impl) confirmed-features) + (setq conditional-implementations + (delq impl conditional-implementations) + processing t)))) + ;; Now all that remains is implementations with unsatisfiable + ;; :when conditions. + (dolist (impl conditional-implementations) + (setq current-implementations + (delq impl current-implementations)))) + ;; Get rid of prevented features. + (dolist (impl current-implementations) + (when-let ((prevented (pcase (plist-get (cdr impl) :prevents) + ((and (pred consp) p) p) + ((pred null) nil) + ((and (pred atom) p) (list p))))) + (setq current-implementations + (cl-remove-if + (lambda (i) (memq (car i) prevented)) + current-implementations)))) + ;; Sort by :order. + (setq current-implementations + (sort current-implementations + (lambda (impl1 impl2) + (< (or (plist-get (cdr impl1) :order) 0) + (or (plist-get (cdr impl2) :order) 0))))) + ;; Update :features to reflect the features actually used. + (plist-put info :features (mapcar #'car current-implementations)) + current-implementations)) + +(defun org-export-generate-features-preamble (info) + "Generate preamble string according to features an implementations in IN= FO. +More specifically, this function resolves feature implementations +with `org-export-expand-features' and concatenates the snippets." + (let* ((feat-impl (org-export-expand-features info)) + (feat-snippets + (mapcar + (lambda (impl) + (let ((snippet (plist-get (cdr impl) :snippet))) + (cond + ((null snippet) nil) + ((functionp snippet) + (funcall snippet info)) + ((symbolp snippet) (symbol-value snippet)) + ((stringp snippet) snippet) + (t (error "org-export feature snippet %S is invalid." snip= pet) + nil)))) + feat-impl))) + (mapconcat + #'identity + (append (delq nil feat-snippets) (list "")) + "\n"))) =20 ;;; The Filter System @@ -3186,6 +3401,8 @@ (defun org-export--annotate-info (backend info &optio= nal subtreep visible-only e ;; the output of the selected citation export processor. (org-cite-process-citations info) (org-cite-process-bibliography info) + ;; With the complete tree, detect features. + (plist-put info :features (org-export-detect-features info)) info)) =20 ;;;###autoload --=20 2.39.0 --=-=-= Content-Type: text/x-patch; charset=utf-8 Content-Disposition: attachment; filename=0002-ox-latex-Apply-new-generated-preamble-to-export.patch Content-Transfer-Encoding: quoted-printable >From 064e802169ce4ba99c019e908eb945f2a96274ba Mon Sep 17 00:00:00 2001 From: TEC Date: Mon, 6 Feb 2023 00:01:26 +0800 Subject: [PATCH 2/6] ox-latex: Apply new generated preamble to export * lisp/ox-latex.el (org-latex-template, org-latex-make-preamble, org-latex-guess-polyglossia-language, org-latex-guess-babel-language, org-latex-guess-inputenc, org-latex-generate-engraved-preamble): Refactor to make use of the generated export, and add a few new bells and whistles. * lisp/ox-beamer.el (org-beamer-template): Adjust to account for the changes in ox-latex.el. --- lisp/org.el | 15 -- lisp/ox-beamer.el | 10 +- lisp/ox-latex.el | 353 +++++++++++++++++++++++++++++----------------- 3 files changed, 223 insertions(+), 155 deletions(-) diff --git a/lisp/org.el b/lisp/org.el index 4d12084d9..4fb16d079 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -3385,14 +3385,6 @@ (defun org-get-packages-alist (var) (defcustom org-latex-default-packages-alist '(("AUTO" "inputenc" t ("pdflatex")) ("T1" "fontenc" t ("pdflatex")) - ("" "graphicx" t) - ("" "longtable" nil) - ("" "wrapfig" nil) - ("" "rotating" nil) - ("normalem" "ulem" t) - ("" "amsmath" t) - ("" "amssymb" t) - ("" "capt-of" nil) ("" "hyperref" nil)) "Alist of default packages to be inserted in the header. =20 @@ -3403,15 +3395,8 @@ (defcustom org-latex-default-packages-alist Org mode to function properly: =20 - inputenc, fontenc: for basic font and character selection -- graphicx: for including images -- longtable: For multipage tables - wrapfig: for figure placement - rotating: for sideways figures and tables -- ulem: for underline and strike-through -- amsmath: for subscript and superscript and math environments -- amssymb: for various symbols used for interpreting the entities - in `org-entities'. You can skip some of this package if you don't - use any of the symbols. - capt-of: for captions outside of floats - hyperref: for cross references =20 diff --git a/lisp/ox-beamer.el b/lisp/ox-beamer.el index 5df78d5a4..8924b412b 100644 --- a/lisp/ox-beamer.el +++ b/lisp/ox-beamer.el @@ -821,9 +821,7 @@ (defun org-beamer-template (contents info) ;; Time-stamp. (and (plist-get info :time-stamp-file) (format-time-string "%% Created %Y-%m-%d %a %H:%M\n")) - ;; LaTeX compiler - (org-latex--insert-compiler info) - ;; Document class and packages. + ;; Document class, packages, and some configuration. (org-latex-make-preamble info) ;; Insert themes. (let ((format-theme @@ -872,12 +870,6 @@ (defun org-beamer-template (contents info) (let ((template (plist-get info :latex-hyperref-template))) (and (stringp template) (format-spec template (org-latex--format-spec info)))) - ;; engrave-faces-latex preamble - (when (and (eq org-latex-src-block-backend 'engraved) - (org-element-map (plist-get info :parse-tree) - '(src-block inline-src-block) #'identity - info t)) - (org-latex-generate-engraved-preamble info)) ;; Document start. "\\begin{document}\n\n" ;; Title command. diff --git a/lisp/ox-latex.el b/lisp/ox-latex.el index dc2062df5..9794e6ecd 100644 --- a/lisp/ox-latex.el +++ b/lisp/ox-latex.el @@ -127,6 +127,8 @@ (org-export-define-backend 'latex (:description "DESCRIPTION" nil nil parse) (:keywords "KEYWORDS" nil nil parse) (:subtitle "SUBTITLE" nil nil parse) + (:conditional-features nil nil org-latex-conditional-features) + (:feature-implementations nil nil org-latex-feature-implementations) ;; Other variables. (:latex-active-timestamp-format nil nil org-latex-active-timestamp-for= mat) (:latex-caption-above nil nil org-latex-caption-above) @@ -1378,6 +1380,95 @@ (defun org-latex-generate-engraved-preamble (info) "% WARNING syntax highlighting unavailable as engrave-faces-latex w= as missing.\n") "\n"))) =20 +;;;; Generated preamble + +(defcustom org-latex-conditional-features + `((t . !announce-start) + (t . !announce-end) + (t . !guess-pollyglossia) + (t . !guess-babel) + (t . !guess-inputenc) + (,(lambda (info) + (org-element-map (plist-get info :parse-tree) + '(latex-fragment latex-environment) #'identity info t)) + . maths) + (,(lambda (info) + (org-element-map (plist-get info :parse-tree) + 'underline #'identity info t)) + . underline) + ("\\\\uu?line\\|\\\\uwave\\|\\\\sout\\|\\\\xout\\|\\\\dashuline\\|\\do= tuline\\|\\markoverwith" + . underline) + (,(lambda (info) + (org-element-map (plist-get info :parse-tree) + 'link + (lambda (link) + (and (member (org-element-property :type link) + '("http" "https" "ftp" "file")) + (file-name-extension (org-element-property :path link)) + (equal (downcase (file-name-extension + (org-element-property :path link))) + "svg"))) + info t)) + . svg) + (org-latex-tables-booktabs . booktabs) + (,(lambda (info) + (eq (plist-get info :latex-src-block-backend) 'engraved)) + . engraved-code) + ("^[ \t]*#\\+attr_latex: .*:float +wrap" + . float-wrap) + ("^[ \t]*#\\+attr_latex: .*:float +sideways" + . rotate) + ("^[ \t]*#\\+caption:\\|\\\\caption{" . caption)) + "A LaTeX-specific extension to `org-export-conditional-features', which = see.") + +(defcustom org-latex-feature-implementations + `((!announce-start + :snippet ,(lambda (info) + (format "\n%%%% ox-latex features: %s" + (plist-get info :features))) + :order -100) + (maths :snippet "\\usepackage{amsmath}\n\\usepackage{amssymb}" :order = 0.2) + (underline :snippet "\\usepackage[normalem]{ulem}" :order 0.5) + (image :snippet "\\usepackage{graphicx}" :order 2) + (svg :snippet "\\usepackage[inkscapelatex=3Dfalse]{svg}" :order 2 :whe= n image) + (longtable :snippet "\\usepackage{longtable}" :when table :order 2) + (booktabs :snippet "\\usepackage{booktabs}" :when table :order 2) + (float-wrap :snippet "\\usepackage{wrapfig}" :order 2) + (rotate :snippet "\\usepackage{rotating}" :order 2) + (caption :snippet "\\usepackage{capt-of}") + (engraved-code :snippet org-latex-generate-engraved-preamble :when cod= e) + (!guess-pollyglossia :snippet org-latex-guess-polyglossia-language) + (!guess-babel :snippet org-latex-guess-babel-language) + (!guess-inputenc :snippet org-latex-guess-inputenc) + (!announce-end :snippet "%% end ox-latex features\n" :order 100)) + "Alist describing how export features should be supported in the preambl= e. + +Implementation alist has the feature symbol as the car, with the +cdr forming a plist with the following keys: +- :snippet, which is either, + - A string, which should be included in the preamble verbatim. + - A variable, the value of which should be included in the preamble. + - A function, which is called with two arguments =E2=80=94 the export in= fo, + and the list of feature flags. The returned value is included in + the preamble. +- :requires, a feature or list of features which are needed. +- :when, a feature or list of features which imply this feature. +- :prevents, a feature or list of features that should be masked. +- :order, for when inclusion order matters. Feature implementations + with a lower order appear first. The default is 0." + :group 'org-export-general + :type '(plist :key-type + (choice (const :snippet) + (const :requires) + (const :when) + (const :prevents) + (const :order) + (const :trigger)) + :value-type + (choice (string :tag "Verbatim content") + (variable :tag "Content variable") + (function :tag "Generating function")))) + ;;;; Compilation =20 (defcustom org-latex-compiler-file-string "%% Intended LaTeX compiler: %s\= n" @@ -1620,29 +1711,29 @@ (defun org-latex--caption/label-string (element inf= o) (org-trim label) (org-export-data main info)))))) =20 -(defun org-latex-guess-inputenc (header) +(defun org-latex-guess-inputenc (info) "Set the coding system in inputenc to what the buffer is. =20 -HEADER is the LaTeX header string. This function only applies -when specified inputenc option is \"AUTO\". +INFO is the plist used as a communication channel. +This function only applies when specified inputenc option is \"AUTO\". =20 Return the new header, as a string." - (let* ((cs (or (ignore-errors - (latexenc-coding-system-to-inputenc - (or org-export-coding-system buffer-file-coding-system))) - "utf8"))) - (if (not cs) header + (let ((header (plist-get info :latex-full-header)) + (cs (or (ignore-errors + (latexenc-coding-system-to-inputenc + (or org-export-coding-system buffer-file-coding-system)= )) + "utf8"))) + (when (and cs (string-match "\\\\usepackage\\[\\(AUTO\\)\\]{inputenc}"= header)) ;; First translate if that is requested. (setq cs (or (cdr (assoc cs org-latex-inputenc-alist)) cs)) - ;; Then find the \usepackage statement and replace the option. - (replace-regexp-in-string "\\\\usepackage\\[\\(AUTO\\)\\]{inputenc}" - cs header t nil 1)))) + (plist-put info :latex-full-header + (replace-match cs t t header 1)))) + nil) =20 -(defun org-latex-guess-babel-language (header info) +(defun org-latex-guess-babel-language (info) "Set Babel's language according to LANGUAGE keyword. =20 -HEADER is the LaTeX header string. INFO is the plist used as -a communication channel. +INFO is the plist used as a communication channel. =20 Insertion of guessed language only happens when Babel package has explicitly been loaded. Then it is added to the rest of @@ -1656,50 +1747,46 @@ (defun org-latex-guess-babel-language (header info) =20 Return the new header." (let* ((language-code (plist-get info :language)) - (plist (cdr - (assoc language-code org-latex-language-alist))) - (language (plist-get plist :babel)) - (language-ini-only (plist-get plist :babel-ini-only)) - ;; If no language is set, or Babel package is not loaded, or - ;; LANGUAGE keyword value is a language served by Babel - ;; exclusively through ini files, return HEADER as-is. - (header (if (or language-ini-only - (not (stringp language-code)) - (not (string-match "\\\\usepackage\\[\\(.*\\)\\]{babel}" header))) - header - (let ((options (save-match-data - (org-split-string (match-string 1 header) ",[ \t]*")))) - ;; If LANGUAGE is already loaded, return header - ;; without AUTO. Otherwise, replace AUTO with language or - ;; append language if AUTO is not present. Languages that are - ;; served in Babel exclusively through ini files are not added - ;; to the babel argument, and must be loaded using - ;; `\babelprovide'. - (replace-match - (mapconcat (lambda (option) (if (equal "AUTO" option) language opt= ion)) - (cond ((member language options) (delete "AUTO" options)) - ((member "AUTO" options) options) - (t (append options (list language)))) - ", ") - t nil header 1))))) + (plist (cdr (assoc language-code org-latex-language-alist))) + (language (plist-get plist :babel)) + (header (plist-get info :latex-full-header)) + (language-ini-only (plist-get plist :babel-ini-only)) + (babel-header-options + ;; If no language is set, or Babel package is not loaded, or + ;; LANGUAGE keyword value is a language served by Babel + ;; exclusively through ini files, return HEADER as-is. + (and (not language-ini-only) + (stringp language-code) + (string-match "\\\\usepackage\\[\\(.*\\)\\]{babel}" header) + (let ((options (save-match-data + (org-split-string (match-string 1 header) = ",[ \t]*")))) + (cond ((member language options) (delete "AUTO" options)) + ((member "AUTO" options) options) + (t (append options (list language)))))))) + (when babel-header-options + ;; If AUTO is present in the header options, replace it with `langua= ge'. + (setq header + (replace-match + (mapconcat (lambda (option) (if (equal "AUTO" option) languag= e option)) + babel-header-options + ", ") + t nil header 1))) ;; If `\babelprovide[args]{AUTO}' is present, AUTO is ;; replaced by LANGUAGE. - (if (not (string-match "\\\\babelprovide\\[.*\\]{\\(.+\\)}" header)) - header - (let ((prov (match-string 1 header))) - (if (equal "AUTO" prov) - (replace-regexp-in-string (format - "\\(\\\\babelprovide\\[.*\\]\\)\\({\\)%s}" prov) - (format "\\1\\2%s}" - (or language language-ini-only)) - header t) - header))))) - -(defun org-latex-guess-polyglossia-language (header info) + (when (string-match "\\\\babelprovide\\[.*\\]{AUTO}" header) + (setq header + (replace-regexp-in-string + (format + "\\(\\\\babelprovide\\[.*\\]\\)\\({\\)%s}" prov) + (format "\\1\\2%s}" (or language language-ini-only)) + header t))) + (plist-put info :latex-full-header header)) + nil) + +(defun org-latex-guess-polyglossia-language (info) "Set the Polyglossia language according to the LANGUAGE keyword. =20 -HEADER is the LaTeX header string. INFO is the plist used as -a communication channel. +INFO is the plist used as a communication channel. =20 Insertion of guessed language only happens when the Polyglossia package has been explicitly loaded. @@ -1710,48 +1797,50 @@ (defun org-latex-guess-polyglossia-language (header= info) using \setdefaultlanguage and not as an option to the package. =20 Return the new header." - (let* ((language (plist-get info :language))) + (let ((header (plist-get info :latex-full-header)) + (language (plist-get info :language))) ;; If no language is set or Polyglossia is not loaded, return ;; HEADER as-is. - (if (or (not (stringp language)) - (not (string-match - "\\\\usepackage\\(?:\\[\\([^]]+?\\)\\]\\){polyglossia}\n" - header))) - header + (when (and (stringp language) + (string-match + "\\\\usepackage\\(?:\\[\\([^]]+?\\)\\]\\){polyglossia}\n" + header)) (let* ((options (org-string-nw-p (match-string 1 header))) - (languages (and options - ;; Reverse as the last loaded language is - ;; the main language. - (nreverse - (delete-dups - (save-match-data - (org-split-string - (replace-regexp-in-string - "AUTO" language options t) - ",[ \t]*")))))) - (main-language-set - (string-match-p "\\\\setmainlanguage{.*?}" header))) - (replace-match - (concat "\\usepackage{polyglossia}\n" - (mapconcat - (lambda (l) - (let* ((plist (cdr - (assoc language org-latex-language-alist))) - (polyglossia-variant (plist-get plist :polyglossia-variant)) - (polyglossia-lang (plist-get plist :polyglossia)) - (l (if (equal l language) - polyglossia-lang - l))) - (format (if main-language-set (format "\\setotherlanguage{%s}\n" l) - (setq main-language-set t) - "\\setmainlanguage%s{%s}\n") - (if polyglossia-variant - (format "[variant=3D%s]" polyglossia-variant) - "") - l))) - languages - "")) - t t header 0))))) + (languages (and options + ;; Reverse as the last loaded language is + ;; the main language. + (nreverse + (delete-dups + (save-match-data + (org-split-string + (replace-regexp-in-string + "AUTO" language options t) + ",[ \t]*")))))) + (main-language-set + (string-match-p "\\\\setmainlanguage{.*?}" header)) + (polyglossia-modified-header + (replace-match + (concat "\\usepackage{polyglossia}\n" + (mapconcat + (lambda (l) + (let* ((plist (cdr (assoc language org-latex-lan= guage-alist))) + (polyglossia-variant (plist-get plist :po= lyglossia-variant)) + (polyglossia-lang (plist-get plist :polyg= lossia)) + (l (if (equal l language) + polyglossia-lang + l))) + (format (if main-language-set (format "\\setot= herlanguage{%s}\n" l) + (setq main-language-set t) + "\\setmainlanguage%s{%s}\n") + (if polyglossia-variant + (format "[variant=3D%s]" polygloss= ia-variant) + "") + l))) + languages + "")) + t t header 0))) + (plist-put info :latex-full-header polyglossia-modified-header)))) + nil) =20 (defun org-latex--remove-packages (pkg-alist info) "Remove packages based on the current LaTeX compiler. @@ -1952,32 +2041,45 @@ (defun org-latex-make-preamble (info &optional temp= late snippet?) specified in `org-latex-default-packages-alist' or `org-latex-packages-alist'." (let* ((class (plist-get info :latex-class)) - (class-template - (or template - (let* ((class-options (plist-get info :latex-class-options)) - (header (nth 1 (assoc class (plist-get info :latex-classes))))) - (and (stringp header) - (if (not class-options) header - (replace-regexp-in-string - "^[ \t]*\\\\documentclass\\(\\(\\[[^]]*\\]\\)?\\)" - class-options header t nil 1)))) - (user-error "Unknown LaTeX class `%s'" class)))) - (org-latex-guess-polyglossia-language - (org-latex-guess-babel-language - (org-latex-guess-inputenc - (org-element-normalize-string - (org-splice-latex-header - class-template - (org-latex--remove-packages org-latex-default-packages-alist info) - (org-latex--remove-packages org-latex-packages-alist info) - snippet? - (mapconcat #'org-element-normalize-string - (list (plist-get info :latex-header) - (and (not snippet?) - (plist-get info :latex-header-extra))) - "")))) - info) - info))) + (class-template + (or template + (let* ((class-options (plist-get info :latex-class-options)) + (header (nth 1 (assoc class (plist-get info :latex-cl= asses))))) + (and (stringp header) + (if (not class-options) header + (replace-regexp-in-string + "^[ \t]*\\\\documentclass\\(\\(\\[[^]]*\\]\\)?\\)" + class-options header t nil 1)))) + (user-error "Unknown LaTeX class `%s'" class))) + generated-preamble) + (plist-put info :latex-full-header + (org-element-normalize-string + (org-splice-latex-header + class-template + (org-latex--remove-packages org-latex-default-packages-al= ist info) + (org-latex--remove-packages org-latex-packages-alist info) + snippet? + (mapconcat #'org-element-normalize-string + (list (plist-get info :latex-header) + (and (not snippet?) + (plist-get info :latex-header-extra= ))) + "")))) + (setq generated-preamble + (if snippet? + (progn + (org-latex-guess-inputenc info) + (org-latex-guess-babel-language info) + (org-latex-guess-polyglossia-language info) + "\n% Generated preamble omitted for snippets.") + (org-export-generate-features-preamble info))) + (concat + ;; Time-stamp. + (and (plist-get info :time-stamp-file) + (format-time-string "%% Created %Y-%m-%d %a %H:%M\n")) + ;; LaTeX compiler. + (org-latex--insert-compiler info) + (plist-get info :latex-full-header) + generated-preamble))) =20 (defun org-latex-template (contents info) "Return complete document string after LaTeX conversion. @@ -1986,12 +2088,7 @@ (defun org-latex-template (contents info) (let ((title (org-export-data (plist-get info :title) info)) (spec (org-latex--format-spec info))) (concat - ;; Time-stamp. - (and (plist-get info :time-stamp-file) - (format-time-string "%% Created %Y-%m-%d %a %H:%M\n")) - ;; LaTeX compiler. - (org-latex--insert-compiler info) - ;; Document class and packages. + ;; Timestamp, compiler statement, document class and packages. (org-latex-make-preamble info) ;; Possibly limit depth for headline numbering. (let ((sec-num (plist-get info :section-numbers))) @@ -2028,12 +2125,6 @@ (defun org-latex-template (contents info) (let ((template (plist-get info :latex-hyperref-template))) (and (stringp template) (format-spec template spec))) - ;; engrave-faces-latex preamble - (when (and (eq org-latex-src-block-backend 'engraved) - (org-element-map (plist-get info :parse-tree) - '(src-block inline-src-block) #'identity - info t)) - (org-latex-generate-engraved-preamble info)) ;; Document start. "\\begin{document}\n\n" ;; Title command. --=20 2.39.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0003-ox-Feature-condition-implementation-convenience.patch >From ae319fccc64f35c52a247633c67f72c924396b03 Mon Sep 17 00:00:00 2001 From: TEC Date: Sun, 5 Feb 2023 12:34:55 +0800 Subject: [PATCH 3/6] ox: Feature condition/implementation convenience * lisp/ox.el (org-export-update-features): Add a convenience function for users to edit the feature condition/implementation lists. --- lisp/ox.el | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/lisp/ox.el b/lisp/ox.el index 1a75ed28d..32f1c6016 100644 --- a/lisp/ox.el +++ b/lisp/ox.el @@ -2246,6 +2246,48 @@ (defun org-export-generate-features-preamble (info) (append (delq nil feat-snippets) (list "")) "\n"))) +(defun org-export-update-features (backend &rest feature-property-value-lists) + "For BACKEND's export spec, set all FEATURE-PROPERTY-VALUE-LISTS. + +Specifically, for each (FEATURE . PROPERTY-VALUE-LIST) entry of +FEATURE-PROPERTY-VALUE-LISTS, each :PROPERTY VALUE pair of +PROPERTY-VALUE-PAIRS is set to VALUE within the backend's feature +implementation plist. The sole exception to this is the +:condition property, the value of which is set in the backend's +feature condition plist instead. + +This can be used to both modify existing entries, and create new ones. + +\(fn BACKEND &rest (FEATURE PROPERTY-VALUE-PAIRS...)...)" + (declare (indent 1)) + (let ((backend-var (intern (format "org-%s-feature-implementations" backend))) + (backend-cf (intern (format "org-%s-conditional-features" backend)))) + (unless (boundp backend-var) + (error "Feature implementations for %s cannot be set as %s is undefined" + backend backend-var)) + (dolist (feature-property-value-set feature-property-value-lists) + (let ((feature (car feature-property-value-set)) + (property-value-pairs (copy-sequence (cdr feature-property-value-set)))) + (while property-value-pairs + (if (eq (car property-value-pairs) :condition) + (let ((condition (progn (pop property-value-pairs) + (pop property-value-pairs)))) + (unless (boundp backend-cf) + (error "Feature condition for %s cannot be set as %s is undefined" + backend backend-cf)) + (cond + ((rassoc feature (symbol-value backend-cf)) + (if condition + (setcar (rassoc feature (symbol-value backend-cf)) + condition) + (set backend-cf (delq (rassoc feature (symbol-value backend-cf)) + (symbol-value backend-cf))))) + (condition + (add-to-list backend-cf (cons condition feature))))) + (setf (alist-get feature (symbol-value backend-var)) + (plist-put (alist-get feature (symbol-value backend-var)) + (pop property-value-pairs) (pop property-value-pairs))))))))) + ;;; The Filter System ;; -- 2.39.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0004-oc-Make-use-of-conditional-preamble-for-export.patch >From 70bd6bcf11597d9865dba107a44030e1da17154b Mon Sep 17 00:00:00 2001 From: TEC Date: Mon, 6 Feb 2023 00:01:41 +0800 Subject: [PATCH 4/6] oc-*: Make use of conditional preamble for export * lisp/oc-natbib.el (org-cite-natbib-use-package): Refactor to make use of the conditional/generated preamble. * lisp/oc-csl.el (org-cite-csl-finalizer): Refactor to make use of the conditional/generated preamble. * lisp/oc-biblatex.el (org-cite-biblatex-prepare-preamble): Refactor to make use of the conditional/generated preamble. --- lisp/oc-biblatex.el | 82 ++++++++++++++++++--------------------------- lisp/oc-csl.el | 16 --------- lisp/oc-natbib.el | 33 +++++++----------- lisp/ox-latex.el | 15 +++++++++ 4 files changed, 61 insertions(+), 85 deletions(-) diff --git a/lisp/oc-biblatex.el b/lisp/oc-biblatex.el index b2d31f0f6..e8e420891 100644 --- a/lisp/oc-biblatex.el +++ b/lisp/oc-biblatex.el @@ -375,61 +375,45 @@ (defun org-cite-biblatex-export-citation (citation style _ info) (other (user-error "Invalid entry %S in `org-cite-biblatex-styles'" other)))))) -(defun org-cite-biblatex-prepare-preamble (output _keys files style &rest _) - "Prepare document preamble for \"biblatex\" usage. - -OUTPUT is the final output of the export process. FILES is the list of file -names used as the bibliography. - -This function ensures \"biblatex\" package is required. It also adds resources -to the document, and set styles." - (with-temp-buffer - (save-excursion (insert output)) - (when (search-forward "\\begin{document}" nil t) - ;; Ensure there is a \usepackage{biblatex} somewhere or add one. - ;; Then set options. - (goto-char (match-beginning 0)) - (let ((re (rx "\\usepackage" - (opt (group "[" (*? anything) "]")) - "{biblatex}"))) - (cond - ;; No "biblatex" package loaded. Insert "usepackage" command - ;; with appropriate options, including style. - ((not (re-search-backward re nil t)) - (save-excursion - (insert - (format "\\usepackage%s{biblatex}\n" - (org-cite-biblatex--package-options - org-cite-biblatex-options style))))) - ;; "biblatex" package loaded, but without any option. - ;; Include style only. - ((not (match-beginning 1)) - (search-forward "{" nil t) - (insert (org-cite-biblatex--package-options nil style))) - ;; "biblatex" package loaded with some options set. Override - ;; style-related options with ours. - (t - (replace-match - (save-match-data - (org-cite-biblatex--package-options (match-string 1) style)) - nil nil nil 1)))) - ;; Insert resources below. - (forward-line) - (insert (mapconcat (lambda (f) - (format "\\addbibresource%s{%s}" - (if (org-url-p f) "[location=remote]" "") - f)) - files - "\n") - "\n")) - (buffer-string))) +(defun org-cite-biblatex--generate-latex-preamble (info) + "Ensure that the biblatex package is loaded, and the necessary resources. +This is performed by extracting relevant information from the +INFO export plist, and modifying any existing +\\usepackage{biblatex} statement in the LaTeX header." + (let ((style (org-cite-bibliography-style info)) + (files (plist-get info :bibliography)) + (usepackage-rx (rx "\\usepackage" + (opt (group "[" (*? anything) "]")) + "{biblatex}"))) + (concat + (if (string-match usepackage-rx (plist-get info :latex-full-header)) + ;; "biblatex" package loaded, but with none (or different) options. + ;; Replace with style-including command. + (plist-put info :latex-full-header + (replace-match + (format "\\usepackage%s{biblatex}" + (save-match-data + (org-cite-biblatex--package-options nil style))) + t t + (plist-get info :latex-full-header))) + ;; No "biblatex" package loaded. Insert "usepackage" command + ;; with appropriate options, including style. + (format "\\usepackage%s{biblatex}\n" + (org-cite-biblatex--package-options + org-cite-biblatex-options style))) + ;; Load resources. + (mapconcat (lambda (f) + (format "\\addbibresource%s{%s}" + (if (org-url-p f) "[location=remote]" "") + f)) + files + "\n")))) ;;; Register `biblatex' processor (org-cite-register-processor 'biblatex :export-bibliography #'org-cite-biblatex-export-bibliography :export-citation #'org-cite-biblatex-export-citation - :export-finalizer #'org-cite-biblatex-prepare-preamble :cite-styles #'org-cite-biblatex-list-styles) (provide 'oc-biblatex) diff --git a/lisp/oc-csl.el b/lisp/oc-csl.el index 432738a97..f85646379 100644 --- a/lisp/oc-csl.el +++ b/lisp/oc-csl.el @@ -840,27 +840,11 @@ (defun org-cite-csl-render-bibliography (_keys _files _style props _backend info ;; process. (org-cite-parse-elements output))))) -(defun org-cite-csl-finalizer (output _keys _files _style _backend info) - "Add \"hanging\" package if missing from LaTeX output. -OUTPUT is the export document, as a string. INFO is the export state, as a -property list." - (org-cite-csl--barf-without-citeproc) - (if (not (eq 'org-latex (org-cite-csl--output-format info))) - output - (with-temp-buffer - (save-excursion (insert output)) - (when (search-forward "\\begin{document}" nil t) - (goto-char (match-beginning 0)) - ;; Insert the CSL-specific parts of the LaTeX preamble. - (insert (org-cite-csl--generate-latex-preamble info))) - (buffer-string)))) - ;;; Register `csl' processor (org-cite-register-processor 'csl :export-citation #'org-cite-csl-render-citation :export-bibliography #'org-cite-csl-render-bibliography - :export-finalizer #'org-cite-csl-finalizer :cite-styles '((("author" "a") ("bare" "b") ("caps" "c") ("full" "f") ("bare-caps" "bc") ("caps-full" "cf") ("bare-caps-full" "bcf")) (("noauthor" "na") ("bare" "b") ("caps" "c") ("bare-caps" "bc")) diff --git a/lisp/oc-natbib.el b/lisp/oc-natbib.el index 855be2a5c..b5cb193e7 100644 --- a/lisp/oc-natbib.el +++ b/lisp/oc-natbib.el @@ -157,32 +157,25 @@ (defun org-cite-natbib-export-citation (citation style _ info) (org-cite-natbib--build-optional-arguments citation info) (org-cite-natbib--build-arguments citation))) -(defun org-cite-natbib-use-package (output &rest _) - "Ensure output requires \"natbib\" package. -OUTPUT is the final output of the export process." - (with-temp-buffer - (save-excursion (insert output)) - (when (search-forward "\\begin{document}" nil t) - ;; Ensure there is a \usepackage{natbib} somewhere or add one. - (goto-char (match-beginning 0)) - (let ((re (rx "\\usepackage" (opt "[" (*? nonl) "]") "{natbib}"))) - (unless (re-search-backward re nil t) - (insert - (format "\\usepackage%s{natbib}\n" - (if (null org-cite-natbib-options) - "" - (format "[%s]" - (mapconcat #'symbol-name - org-cite-natbib-options - ",")))))))) - (buffer-string))) +(defun org-cite-natbib--generate-latex-preamble (info) + "Ensure that the \"natbib\" package is loaded. +INFO is a plist used as a communication channel." + (and (not (string-match + (rx "\\usepackage" (opt "[" (*? nonl) "]") "{natbib}") + (plist-get info :latex-full-header))) + (format "\\usepackage%s{natbib}\n" + (if (null org-cite-natbib-options) + "" + (format "[%s]" + (mapconcat #'symbol-name + org-cite-natbib-options + ",")))))) ;;; Register `natbib' processor (org-cite-register-processor 'natbib :export-bibliography #'org-cite-natbib-export-bibliography :export-citation #'org-cite-natbib-export-citation - :export-finalizer #'org-cite-natbib-use-package :cite-styles '((("author" "a") ("caps" "a") ("full" "f")) (("noauthor" "na") ("bare" "b")) diff --git a/lisp/ox-latex.el b/lisp/ox-latex.el index 9794e6ecd..040824f45 100644 --- a/lisp/ox-latex.el +++ b/lisp/ox-latex.el @@ -1468,6 +1468,21 @@ (defcustom org-latex-feature-implementations (choice (string :tag "Verbatim content") (variable :tag "Content variable") (function :tag "Generating function")))) +;; Citation features + +(org-export-update-features 'latex + (bibliography-csl + :condition (eq (org-cite-processor info) 'csl) + :when bibliography + :snippet org-cite-csl--generate-latex-preamble) + (bibliography-biblatex + :condition (eq (org-cite-processor info) 'biblatex) + :when bibliography + :snippet org-cite-biblatex--generate-latex-preamble) + (bibliography-natbib + :condition (eq (org-cite-processor info) 'natbib) + :when bibliography + :snippet org-cite-natbib--generate-latex-preamble)) ;;;; Compilation -- 2.39.0 --=-=-= Content-Type: text/x-patch; charset=utf-8 Content-Disposition: attachment; filename=0005-ox-Add-struct-feature-conditions-implementations.patch Content-Transfer-Encoding: quoted-printable >From 32a2610cc301572bc14a6cb18d6138b653e91e80 Mon Sep 17 00:00:00 2001 From: TEC Date: Tue, 7 Feb 2023 01:57:06 +0800 Subject: [PATCH 5/6] ox: Add struct feature conditions/implementations * lisp/ox.el (org-export--annotate-info, org-export-detect-features, org-export-define-derived-backend, org-export-define-backend, org-export-conditional-features): Refactor backend feature conditions/implementations into a struct field. This allows for parent inheritance to be properly managed, and leads into future work making features more widely used in the export process. (org-export-expand-features, org-export-resolve-feature-implementations, org-export-generate-features-preamble, org-export-expand-feature-snippets): Rework `org-export-expand-features` into `org-export-resolve-feature-implementations`, and `org-export-generate-features-preamble` into `org-export-expand-feature-snippets`. (org-export-process-features, org-export-update-features): Introduce `org-export-process-features' to simplify the application of features to INFO. * lisp/ox-latex.el (org-latex-make-preamble): Move the LaTeX feature conditions/implementations into the backend definition, and use the reworked function `org-export-expand-feature-snippets'. --- lisp/ox-latex.el | 156 +++++++-------- lisp/ox.el | 482 ++++++++++++++++++++++++++++++++++------------- 2 files changed, 415 insertions(+), 223 deletions(-) diff --git a/lisp/ox-latex.el b/lisp/ox-latex.el index 040824f45..6221cc486 100644 --- a/lisp/ox-latex.el +++ b/lisp/ox-latex.el @@ -127,8 +127,6 @@ (org-export-define-backend 'latex (:description "DESCRIPTION" nil nil parse) (:keywords "KEYWORDS" nil nil parse) (:subtitle "SUBTITLE" nil nil parse) - (:conditional-features nil nil org-latex-conditional-features) - (:feature-implementations nil nil org-latex-feature-implementations) ;; Other variables. (:latex-active-timestamp-format nil nil org-latex-active-timestamp-for= mat) (:latex-caption-above nil nil org-latex-caption-above) @@ -172,7 +170,64 @@ (org-export-define-backend 'latex (:latex-toc-command nil nil org-latex-toc-command) (:latex-compiler "LATEX_COMPILER" nil org-latex-compiler) ;; Redefine regular options. - (:date "DATE" nil "\\today" parse))) + (:date "DATE" nil "\\today" parse)) + :feature-conditions-alist + `((t !announce-start) + (t !announce-end) + (t !guess-pollyglossia) + (t !guess-babel) + (t !guess-inputenc) + (,(lambda (info) + (org-element-map (plist-get info :parse-tree) + '(latex-fragment latex-environment) #'identity info t)) + maths) + (,(lambda (info) + (org-element-map (plist-get info :parse-tree) + 'underline #'identity info t)) + underline) + ("\\\\uu?line\\|\\\\uwave\\|\\\\sout\\|\\\\xout\\|\\\\dashuline\\|\\do= tuline\\|\\markoverwith" + underline) + (,(lambda (info) + (org-element-map (plist-get info :parse-tree) + 'link + (lambda (link) + (and (member (org-element-property :type link) + '("http" "https" "ftp" "file")) + (file-name-extension (org-element-property :path link)) + (equal (downcase (file-name-extension + (org-element-property :path link))) + "svg"))) + info t)) + svg) + (org-latex-tables-booktabs booktabs) + (,(lambda (info) + (eq (plist-get info :latex-src-block-backend) 'engraved)) + engraved-code) + ("^[ \t]*#\\+attr_latex: .*:float +wrap" + float-wrap) + ("^[ \t]*#\\+attr_latex: .*:float +sideways" + rotate) + ("^[ \t]*#\\+caption:\\|\\\\caption{" caption)) + :feature-implementations-alist + `((!announce-start + :snippet ,(lambda (info) + (format "\n%%%% ox-latex features: %s" + (plist-get info :features))) + :order -100) + (maths :snippet "\\usepackage{amsmath}\n\\usepackage{amssymb}" :order = 0.2) + (underline :snippet "\\usepackage[normalem]{ulem}" :order 0.5) + (image :snippet "\\usepackage{graphicx}" :order 2) + (svg :snippet "\\usepackage[inkscapelatex=3Dfalse]{svg}" :order 2 :whe= n image) + (longtable :snippet "\\usepackage{longtable}" :when table :order 2) + (booktabs :snippet "\\usepackage{booktabs}" :when table :order 2) + (float-wrap :snippet "\\usepackage{wrapfig}" :order 2) + (rotate :snippet "\\usepackage{rotating}" :order 2) + (caption :snippet "\\usepackage{capt-of}") + (engraved-code :snippet org-latex-generate-engraved-preamble :when cod= e) + (!guess-pollyglossia :snippet org-latex-guess-polyglossia-language) + (!guess-babel :snippet org-latex-guess-babel-language) + (!guess-inputenc :snippet org-latex-guess-inputenc) + (!announce-end :snippet "%% end ox-latex features\n" :order 100))) =20 =20 @@ -1380,94 +1435,6 @@ (defun org-latex-generate-engraved-preamble (info) "% WARNING syntax highlighting unavailable as engrave-faces-latex w= as missing.\n") "\n"))) =20 -;;;; Generated preamble - -(defcustom org-latex-conditional-features - `((t . !announce-start) - (t . !announce-end) - (t . !guess-pollyglossia) - (t . !guess-babel) - (t . !guess-inputenc) - (,(lambda (info) - (org-element-map (plist-get info :parse-tree) - '(latex-fragment latex-environment) #'identity info t)) - . maths) - (,(lambda (info) - (org-element-map (plist-get info :parse-tree) - 'underline #'identity info t)) - . underline) - ("\\\\uu?line\\|\\\\uwave\\|\\\\sout\\|\\\\xout\\|\\\\dashuline\\|\\do= tuline\\|\\markoverwith" - . underline) - (,(lambda (info) - (org-element-map (plist-get info :parse-tree) - 'link - (lambda (link) - (and (member (org-element-property :type link) - '("http" "https" "ftp" "file")) - (file-name-extension (org-element-property :path link)) - (equal (downcase (file-name-extension - (org-element-property :path link))) - "svg"))) - info t)) - . svg) - (org-latex-tables-booktabs . booktabs) - (,(lambda (info) - (eq (plist-get info :latex-src-block-backend) 'engraved)) - . engraved-code) - ("^[ \t]*#\\+attr_latex: .*:float +wrap" - . float-wrap) - ("^[ \t]*#\\+attr_latex: .*:float +sideways" - . rotate) - ("^[ \t]*#\\+caption:\\|\\\\caption{" . caption)) - "A LaTeX-specific extension to `org-export-conditional-features', which = see.") - -(defcustom org-latex-feature-implementations - `((!announce-start - :snippet ,(lambda (info) - (format "\n%%%% ox-latex features: %s" - (plist-get info :features))) - :order -100) - (maths :snippet "\\usepackage{amsmath}\n\\usepackage{amssymb}" :order = 0.2) - (underline :snippet "\\usepackage[normalem]{ulem}" :order 0.5) - (image :snippet "\\usepackage{graphicx}" :order 2) - (svg :snippet "\\usepackage[inkscapelatex=3Dfalse]{svg}" :order 2 :whe= n image) - (longtable :snippet "\\usepackage{longtable}" :when table :order 2) - (booktabs :snippet "\\usepackage{booktabs}" :when table :order 2) - (float-wrap :snippet "\\usepackage{wrapfig}" :order 2) - (rotate :snippet "\\usepackage{rotating}" :order 2) - (caption :snippet "\\usepackage{capt-of}") - (engraved-code :snippet org-latex-generate-engraved-preamble :when cod= e) - (!guess-pollyglossia :snippet org-latex-guess-polyglossia-language) - (!guess-babel :snippet org-latex-guess-babel-language) - (!guess-inputenc :snippet org-latex-guess-inputenc) - (!announce-end :snippet "%% end ox-latex features\n" :order 100)) - "Alist describing how export features should be supported in the preambl= e. - -Implementation alist has the feature symbol as the car, with the -cdr forming a plist with the following keys: -- :snippet, which is either, - - A string, which should be included in the preamble verbatim. - - A variable, the value of which should be included in the preamble. - - A function, which is called with two arguments =E2=80=94 the export in= fo, - and the list of feature flags. The returned value is included in - the preamble. -- :requires, a feature or list of features which are needed. -- :when, a feature or list of features which imply this feature. -- :prevents, a feature or list of features that should be masked. -- :order, for when inclusion order matters. Feature implementations - with a lower order appear first. The default is 0." - :group 'org-export-general - :type '(plist :key-type - (choice (const :snippet) - (const :requires) - (const :when) - (const :prevents) - (const :order) - (const :trigger)) - :value-type - (choice (string :tag "Verbatim content") - (variable :tag "Content variable") - (function :tag "Generating function")))) ;; Citation features =20 (org-export-update-features 'latex @@ -2086,7 +2053,12 @@ (defun org-latex-make-preamble (info &optional templ= ate snippet?) (org-latex-guess-babel-language info) (org-latex-guess-polyglossia-language info) "\n% Generated preamble omitted for snippets.") - (org-export-generate-features-preamble info))) + (concat + "\n" + (string-join + (org-export-expand-feature-snippets info) + "\n\n") + "\n"))) (concat ;; Time-stamp. (and (plist-get info :time-stamp-file) diff --git a/lisp/ox.el b/lisp/ox.el index 32f1c6016..6c7a11b66 100644 --- a/lisp/ox.el +++ b/lisp/ox.el @@ -1030,7 +1030,7 @@ ;;; Defining Back-ends =20 (cl-defstruct (org-export-backend (:constructor org-export-create-backend) (:copier nil)) - name parent transcoders options filters blocks menu) + name parent transcoders options filters blocks menu feature-conditions f= eature-implementations) =20 ;;;###autoload (defun org-export-get-backend (name) @@ -1136,6 +1136,62 @@ (defun org-export-get-all-filters (backend) (setq filters (append filters (org-export-backend-filters backend)))) filters))) =20 +(defvar org-export-conditional-features) + +(defun org-export-get-all-feature-conditions (backend) + "Return full feature condition alist for BACKEND. + +BACKEND is an export back-end, as return by, e.g,, +`org-export-create-backend'. Return value is an alist where keys +are feature conditions, and values are feature symbols. + +Unlike `org-export-backend-feature-conditions', this function +also returns conditions inherited from parent back-ends, if any." + (when (symbolp backend) (setq backend (org-export-get-backend backend))) + (and backend + (let ((conditions (org-export-backend-feature-conditions backend)) + parent) + (while (setq parent (org-export-backend-parent backend)) + (setq backend (org-export-get-backend parent)) + (dolist (condition (org-export-backend-feature-conditions backe= nd)) + (push condition conditions))) + (dolist (condition org-export-conditional-features) + (unless (assq (car condition) conditions) + (push condition conditions))) + conditions))) + +(defun org-export-get-all-feature-implementations (backend) + "Return full feature implementation alist for BACKEND. + +BACKEND is an export back-end, as return by, e.g,, +`org-export-create-backend'. Return value is an alist where keys +are feature symbols, and values are an implementation +specification plist. + +Unlike `org-export-backend-feature-implementations', this function +also returns implementations inherited from parent back-ends, if any." + (when (symbolp backend) (setq backend (org-export-get-backend backend))) + (and backend + (let ((implementations (org-export-backend-feature-implementations = backend)) + parent) + (while (setq parent (org-export-backend-parent backend)) + (setq backend (org-export-get-backend parent)) + (dolist (implementation (org-export-backend-feature-implementat= ions backend)) + (unless (assq (car implementation) implementations) + (push implementation implementations)))) + implementations))) + +(defun org-export-install-features (info) + "Install feature conditions and implementations in the communication cha= nnel. +INFO is a plist containing the current communication channel. +Return the updated communication channel." + (plist-put info :feature-conditions + (org-export-get-all-feature-conditions + (plist-get info :back-end))) + (plist-put info :feature-implementations + (org-export-get-all-feature-implementations + (plist-get info :back-end)))) + (defun org-export-define-backend (backend transcoders &rest body) "Define a new back-end BACKEND. =20 @@ -1247,20 +1303,24 @@ (defun org-export-define-backend (backend transcode= rs &rest body) `org-export-options-alist' for more information about structure of the values." (declare (indent 1)) - (let (filters menu-entry options) + (let (filters menu-entry options feature-conditions feature-implementati= ons) (while (keywordp (car body)) (let ((keyword (pop body))) (pcase keyword (:filters-alist (setq filters (pop body))) (:menu-entry (setq menu-entry (pop body))) (:options-alist (setq options (pop body))) + (:feature-conditions-alist (setq feature-conditions (pop body))) + (:feature-implementations-alist (setq feature-implementations (pop body= ))) (_ (error "Unknown keyword: %s" keyword))))) (org-export-register-backend (org-export-create-backend :name backend :transcoders transcoders :options options :filters filters - :menu menu-entry)))) + :menu menu-entry + :feature-conditions feature-conditions + :feature-implementations feature-implement= ations)))) =20 (defun org-export-define-derived-backend (child parent &rest body) "Create a new back-end as a variant of an existing one. @@ -1307,7 +1367,7 @@ (defun org-export-define-derived-backend (child paren= t &rest body) =20 (org-export-to-buffer \\=3D'my-latex \"*Test my-latex*\")" (declare (indent 2)) - (let (filters menu-entry options transcoders) + (let (filters menu-entry options transcoders feature-conditions feature-= implementations) (while (keywordp (car body)) (let ((keyword (pop body))) (pcase keyword @@ -1315,6 +1375,8 @@ (defun org-export-define-derived-backend (child paren= t &rest body) (:menu-entry (setq menu-entry (pop body))) (:options-alist (setq options (pop body))) (:translate-alist (setq transcoders (pop body))) + (:feature-conditions-alist (setq feature-conditions (pop body))) + (:feature-implementations-alist (setq feature-implementations (pop body= ))) (_ (error "Unknown keyword: %s" keyword))))) (org-export-register-backend (org-export-create-backend :name child @@ -1322,7 +1384,9 @@ (defun org-export-define-derived-backend (child paren= t &rest body) :transcoders transcoders :options options :filters filters - :menu menu-entry)))) + :menu menu-entry + :feature-conditions feature-conditions + :feature-implementations feature-implement= ations)))) =20 =20 @@ -2031,7 +2095,7 @@ (defun org-export-expand (blob contents &optional wit= h-affiliated) blob contents)))) =20 -;;; Conditional/Generated Preamble +;;; Conditional/Generated Features ;; ;; Many formats have some version of a preamble, whether it be HTML's ;; ... or the content before LaTeX's \begin{document}. @@ -2042,12 +2106,35 @@ ;;; Conditional/Generated Preamble ;; with filters approach, but neither really solve this problem nicely. ;; ;; The conditional/generated preamble defines mechanisms of detecting -;; which "features" are used in a document, handles interactions -;; between features, and provides/generates preamble content to +;; which "export features" are used in a document, handles +;; interactions between features, and provides/generates content to ;; support the features. +;; +;; Each export feature condition takes the form of a +;; (CONDITION . FEATURES) cons cell (see `org-export-detect-features'), +;; and each implementation takes the form of a (FEATURE . (:KEY VALUE ...)) +;; associated plist (see `org-export-resolve-feature-implementations' +;; and `org-export-expand-feature-snippets'). +;; +;; This functionality is applied during export as follows: +;; 1. The export feature conditions and implementations are installed +;; into the INFO plist with `org-export-install-features'. +;; This simply applies `org-export-get-all-feature-conditions' and +;; `org-export-get-all-feature-implementations', which merges the +;; backend's conditions/implementations with all of it's parents and +;; finally the global condition list +;; `org-export-conditional-features'. +;; 2. The "export features" used in a document are detected with +;; `org-export-detect-features'. +;; 3. The interaction between different feature implementations is +;; resolved with `org-export-resolve-feature-implementations', +;; producing an ordered list of implementations to be actually used +;; in an export. +;; 4. The feature implementation's snippets are transformed into strings +;; to be inserted with `org-export-expand-feature-snippets'. =20 (defcustom org-export-conditional-features - `(("^[ \t]*#\\+print_bibliography:" . bibliography) + `(("^[ \t]*#\\+print_bibliography:" bibliography) (,(lambda (info) (org-element-map (plist-get info :parse-tree) 'link @@ -2059,7 +2146,7 @@ (defcustom org-export-conditional-features (org-element-property :path link))) "svg"))) info t)) - . svg) + svg) (,(lambda (info) (org-element-map (plist-get info :parse-tree) 'link @@ -2071,69 +2158,95 @@ (defcustom org-export-conditional-features (org-element-property :path link))) image-file-name-extensions))) info t)) - . image) + image) (,(lambda (info) (org-element-map (plist-get info :parse-tree) 'table #'identity info t)) - . table) + table) (,(lambda (info) (org-element-map (plist-get info :parse-tree) '(src-block inline-src-block) #'identity info t)) - . code)) + code)) "Org feature tests and associated feature flags. =20 Alist where the car is a test for the presense of the feature, and the CDR is either a single feature symbol or a list of feature symbols. =20 -Feature tests can take any of the following forms: -- Variable symbol, the value of which is fetched. -- Function symbol, which is called with the export info - as the argument. -- A string, which is used as a regexp search in the buffer. - The regexp matching is taken as confirmation of the existence - of the feature. - -When the test is a variable or function and produces a string -value, that value is itself used as a test. Any other non-nil -value will imply the existance of the feature." +See `org-export-detect-features' for how this is processed." :group 'org-export-general :type '(alist :key-type (choice (regexp :tag "Feature test regexp") (variable :tag "Feature variable") (function :tag "Feature test function")) :value-type - (choice (symbol :tag "Feature symbol") - (repeat symbol :tag "Feature symbols")))) + (repeat symbol :tag "Feature symbols"))) =20 (defun org-export-detect-features (info) - "Detect features from `org-export-conditional-features' in INFO." - (let (case-fold-search) - (delete-dups - (mapcan - (lambda (construct-feature) - (and (let ((out (pcase (car construct-feature) - ((pred stringp) (car construct-feature)) - ((pred functionp) - (funcall (car construct-feature) info)) - ((pred symbolp) (symbol-value (car construct-fea= ture))) - (_ (error "org-export-conditional-features key %= s unable to be used" (car construct-feature)))))) - (if (stringp out) - (save-excursion - (goto-char (point-min)) - (re-search-forward out nil t)) - out)) - (if (listp (cdr construct-feature)) - (copy-sequence (cdr construct-feature)) - (list (cdr construct-feature))))) - (append org-export-conditional-features - (plist-get info :conditional-features)))))) - -(defun org-export-expand-features (info) - "Identify all implied implementations from features, in INFO. - -(plist-get info :feature-implementations) should be an alist of feature sy= mbols -and specification plists with the following keys: + "Detect features from `org-export-conditional-features' in INFO. + +More specifically, for each (CONDITION . FEATURES) cons cell of +the :feature-conditions list in INFO, the CONDITION is evaluated +in two phases. + +In phase one, CONDITION is transformed like so: +- If a variable symbol, the value is fetched +- If a function symbol, the function is called with INFO as the + sole argument +- If a string, passed on unmodified + +In phase two, if the CONDITION result is a string, it is used as +a case-sensitive regexp search in the buffer. The regexp +matching is taken as confirmation of the existance of FEATURES. +Any other non-nil value indicates the existance of FEATURES. + +A list of all detected feature symbols is returned. + +This function should be run in the processed export Org buffer, +after includes have been expanded and commented trees removed." + (delete-dups + (mapcan + (org-export--single-feature-detector info) + (plist-get info :feature-conditions)))) + +(defun org-export--single-feature-detector (info) + "Return a feature detection lambda that operates on INFO. + +The lambda has the signature ((CONDITION . FEATURES)), and +return FEATURES if CONDITION is found to apply. + +CONDITION is evaluated in the context of INFO and the current buffer, +in accordance with the docstring of `org-export-detect-features'. +`copy-sequence' is applied to the FEATURES list so that `nconc' can +safely be applied to it." + (lambda (condition-features) + (let ((condition (car condition-features)) + (features (cdr condition-features)) + (case-fold-search nil) + matcher) + (setq matcher + (cond + ((stringp condition) condition) + ((functionp condition) (funcall condition info)) + ((symbolp condition) (symbol-value condition)) + (t (error "org-export: Feature condition %s (for %s) unable t= o be used" + condition features)))) + (and (if (stringp matcher) + (save-excursion + (goto-char (point-min)) + (re-search-forward matcher nil t)) + matcher) + (copy-sequence features))))) + +(defun org-export-resolve-feature-implementations (info &optional features= implementations) + "Resolve the IMPLEMENTATIONS of FEATURES, of INFO. + +FEATURES should be a list of all feature symbols to be resolved, +and defaults to (plist-get info :features). IMPLEMENTATIONS +should be an alist of feature symbols and specification plists, +and defaults to (plist-get info :feature-implementations). + +The following keys of the each implementation plist are recognised: - :snippet, which is either, - A string, which should be included in the preamble verbatim. - A variable, the value of which should be included in the preamble. @@ -2148,12 +2261,10 @@ (plist-get info :feature-implementations) should be= an alist of feature symbols with a lower order appear first. The default is 0. =20 This function processes :requires, :when, and :prevents in turn -before finally sorting according to :order. - -After resolving the features, the :features key of INFO is -updated to reflect the expanded set of features being used." - (let ((initial-features (plist-get info :features)) - (implementations (plist-get info :feature-implementations)) +before finally sorting according to :order. The final +implementation list is returned." + (let ((initial-features (or features (plist-get info :features))) + (implementations (or implementations (plist-get info :feature-impl= ementations))) required-features current-implementations) ;; Process :requires. (while initial-features @@ -2214,79 +2325,188 @@ (plist-get info :feature-implementations) should b= e an alist of feature symbols (lambda (i) (memq (car i) prevented)) current-implementations)))) ;; Sort by :order. - (setq current-implementations - (sort current-implementations - (lambda (impl1 impl2) - (< (or (plist-get (cdr impl1) :order) 0) - (or (plist-get (cdr impl2) :order) 0))))) - ;; Update :features to reflect the features actually used. - (plist-put info :features (mapcar #'car current-implementations)) - current-implementations)) - -(defun org-export-generate-features-preamble (info) - "Generate preamble string according to features an implementations in IN= FO. -More specifically, this function resolves feature implementations -with `org-export-expand-features' and concatenates the snippets." - (let* ((feat-impl (org-export-expand-features info)) - (feat-snippets - (mapcar - (lambda (impl) - (let ((snippet (plist-get (cdr impl) :snippet))) + (sort current-implementations + (lambda (impl1 impl2) + (< (or (plist-get (cdr impl1) :order) 0) + (or (plist-get (cdr impl2) :order) 0)))))) + +(defun org-export-expand-feature-snippets (info &optional feature-implemen= tations) + "Expand each of the feature :snippet keys in FEATURE-IMPLEMENTATIONS. +FEATURE-IMPLEMENTATIONS is expected to be a list of implementation +plists, if not provided explicitly it is extracted from the +:feature-implementations key of INFO. + +Each implementation plist's :snippet value is expanded in order, in +the following manner: +- nil values are ignored +- functions are called with INFO, and must produce a string or nil +- variable symbols use the value, which must be a string or nil +- strings are included verbatim +- all other values throw an `error'." + (let (expanded-snippets snippet value) + (dolist (impl (or feature-implementations + (plist-get info :feature-implementations))) + (setq snippet (plist-get (cdr impl) :snippet) + value (cond + ((null snippet) nil) + ((functionp snippet) (funcall snippet info)) + ((symbolp snippet) (symbol-value snippet)) + ((stringp snippet) snippet) + (t (error "org-export: The %s feature snippet %S is inv= alid (must be either nil, a function/variable symbol, or a string)" + (car impl) snippet)))) + (cond + ((stringp value) + (push value expanded-snippets)) + (value ; Non-string value, could come from function or variable. + (error "org-export: The %s feature snippet %s must give nil or a s= tring, but instead gave %S" + (car impl) (cond - ((null snippet) nil) - ((functionp snippet) - (funcall snippet info)) - ((symbolp snippet) (symbol-value snippet)) - ((stringp snippet) snippet) - (t (error "org-export feature snippet %S is invalid." snip= pet) - nil)))) - feat-impl))) - (mapconcat - #'identity - (append (delq nil feat-snippets) (list "")) - "\n"))) - -(defun org-export-update-features (backend &rest feature-property-value-li= sts) - "For BACKEND's export spec, set all FEATURE-PROPERTY-VALUE-LISTS. - -Specifically, for each (FEATURE . PROPERTY-VALUE-LIST) entry of -FEATURE-PROPERTY-VALUE-LISTS, each :PROPERTY VALUE pair of -PROPERTY-VALUE-PAIRS is set to VALUE within the backend's feature -implementation plist. The sole exception to this is the -:condition property, the value of which is set in the backend's -feature condition plist instead. + ((and (functionp snippet) (symbolp snippet)) + (format "function (`%s')" snippet)) + ((functionp snippet) "anonymous function") + (t (format "variable (`%s')" snippet))) + value)))) + (nreverse expanded-snippets))) + +(defun org-export-process-features (info) + "Install feature conditions/implementations in INFO, and resolve them. +See `org-export-detect-features' and `org-export-resolve-feature-implement= ations' for +more information on what this entails." + (org-export-install-features info) + (let* ((features (org-export-detect-features info)) + (resolved-implementations + (org-export-resolve-feature-implementations info features))) + (plist-put info :feature-implementations resolved-implementations) + (plist-put info :features (mapcar #'car resolved-implementations)))) =20 -This can be used to both modify existing entries, and create new ones. - -\(fn BACKEND &rest (FEATURE PROPERTY-VALUE-PAIRS...)...)" +;;;###autoload +(defmacro org-export-update-features (backend &rest feature-property-value= -lists) + "For BACKEND's export spec, set each FEATURE's :PROPERTY to VALUE. + +The behaviour of this macro is best behaved with an example. +For instance, to add some preamble content from the variable +\"my-org-beamer-metropolis-tweaks\" when using the metropolis theme +with beamer export: + + (org-export-update-features \\=3D'beamer + (beamer-metropolis + :condition (string-match-p \"metropolis$\" (plist-get info :beamer-th= eme)) + :snippet my-org-beamer-metropolis-tweaks + :order 3)) + +The modifies the beamer backend, either creating or updating the +\"beamer-metropolis\" feature. The :condition property adds a +condition which detects the feature, and all other properties are +applied to the feature's implementation plist. Setting +:condition to t means the feature will always be enabled, and +conversely setting :condition to nil means the feature will never +be enabled. + +When setting the :condition and :snippet properties, any sexp is +is implicitly converted to, + (lambda (info) SEXPR) + +Each (FEATURE . (:PROPERTY VALUE)) form that is processed is +taken from the single &rest argument +FEATURE-PROPERTY-VALUE-LISTS. + +\(fn BACKEND &rest (FEATURE . (:PROPERTY VALUE)...)...)" (declare (indent 1)) - (let ((backend-var (intern (format "org-%s-feature-implementations" back= end))) - (backend-cf (intern (format "org-%s-conditional-features" backend)= ))) - (unless (boundp backend-var) - (error "Feature implementations for %s cannot be set as %s is undefi= ned" - backend backend-var)) - (dolist (feature-property-value-set feature-property-value-lists) - (let ((feature (car feature-property-value-set)) - (property-value-pairs (copy-sequence (cdr feature-property-val= ue-set)))) - (while property-value-pairs - (if (eq (car property-value-pairs) :condition) - (let ((condition (progn (pop property-value-pairs) - (pop property-value-pairs)))) - (unless (boundp backend-cf) - (error "Feature condition for %s cannot be set as %s is = undefined" - backend backend-cf)) - (cond - ((rassoc feature (symbol-value backend-cf)) - (if condition - (setcar (rassoc feature (symbol-value backend-cf)) - condition) - (set backend-cf (delq (rassoc feature (symbol-value ba= ckend-cf)) - (symbol-value backend-cf))))) - (condition - (add-to-list backend-cf (cons condition feature))))) - (setf (alist-get feature (symbol-value backend-var)) - (plist-put (alist-get feature (symbol-value backend-var)) - (pop property-value-pairs) (pop property-valu= e-pairs))))))))) + (org-with-gensyms (backend-struct the-entry the-features the-condition t= he-feat-impl cond-feat) + (let ((backend-expr + (if (and (eq (car-safe backend) 'quote) + (symbolp (cadr backend)) + (not (cddr backend))) + (or (org-export-get-backend (cadr backend)) + `(org-export-get-backend ',(cadr backend))) + `(if (symbolp ,backend) + (org-export-get-backend ,backend) + backend))) + (backend-impls + (list 'aref backend-struct + (cl-struct-slot-offset 'org-export-backend 'feature-imple= mentations))) + (backend-conds + (list 'aref backend-struct + (cl-struct-slot-offset 'org-export-backend 'feature-condi= tions))) + body condition-set-p implementation-set-p) + (dolist (feature-property-value-set feature-property-value-lists) + (when (eq (car feature-property-value-set) 'quote) + (pop feature-property-value-set)) + (let ((features (car feature-property-value-set)) + (property-value-pairs (cdr feature-property-value-set)) + let-body property value) + (while property-value-pairs + (setq property (pop property-value-pairs) + value (pop property-value-pairs)) + (cond + ((consp value) + (unless (memq (car value) '(function quote)) + (if (and (memq property '(:condition :snippet)) + (not (functionp value))) + (setq value `(lambda (info) ,value)) + (setq value (list 'quote value))))) + ((memq value '(nil t))) ; Leave unmodified. + ((symbolp value) + (setq value (list 'quote value)))) + (if (eq property :condition) + (progn + (unless condition-set-p + (setq condition-set-p t)) + (push + (if value + (let ((the-features + (if (consp features) features (list features= )))) + `(let* ((,the-condition ,value) + (,the-entry (assoc ,the-condition ,backen= d-conds))) + (if ,the-entry + (setcdr ,the-entry + (append ',the-features (cdr ,the-e= ntry))) + (push (cons ,the-condition ',the-features) + ,backend-conds)))) + (let ((single-feature + (if (consp features) + (intern (string-join (mapcar #'symbol-name= features) + "-and-")) + features))) + `(dolist (,cond-feat ,backend-conds) + (cond + ((equal (cdr ,cond-feat) (list ,single-feature)) + (setf ,backend-conds (delq ,cond-feat ,backend= -conds))) + ((memq ,single-feature (cdr ,cond-feat)) + (setcdr ,cond-feat + (delq ,single-feature (cdr ,cond-feat)= ))))))) + body)) + (unless implementation-set-p + (setq implementation-set-p t)) + (push + (if let-body + `(plist-put (cdr ,the-feat-impl) ,property ,value) + `(setcdr ,the-feat-impl + (plist-put (cdr ,the-feat-impl) ,property ,value))) + let-body))) + (when let-body + (let ((the-feature + (if (consp features) + (intern (string-join (mapcar #'symbol-name features) + "-and-")) + features))) + (when (consp features) + (push + `(plist-put (cdr ,the-feat-impl) :when ',features) + let-body)) + (push + `(let ((,the-feat-impl + (or (assoc ',the-feature ,backend-impls) + (car (push (list ',the-feature ,property nil) + ,backend-impls))))) + ,@(nreverse let-body)) + body))))) + `(let ((,backend-struct ,backend-expr)) + ,@(and (not (org-export-backend-p backend-expr)) + `((unless (org-export-backend-p ,backend-struct) + (error "`%s' is not a loaded export backend" ,backend)= ))) + ,@(nreverse body) + nil)))) =20 ;;; The Filter System @@ -3443,8 +3663,8 @@ (defun org-export--annotate-info (backend info &optio= nal subtreep visible-only e ;; the output of the selected citation export processor. (org-cite-process-citations info) (org-cite-process-bibliography info) - ;; With the complete tree, detect features. - (plist-put info :features (org-export-detect-features info)) + ;; Install all the feature conditions and implementations. + (org-export-process-features info) info)) =20 ;;;###autoload --=20 2.39.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0006-ox-latex-Reformat-feature-announcement-comment.patch >From 41a5c7bf4a668c3ae296da6cf46ecf654f393465 Mon Sep 17 00:00:00 2001 From: TEC Date: Thu, 9 Feb 2023 01:17:49 +0800 Subject: [PATCH 6/6] ox-latex: Reformat feature announcement comment * lisp/ox-latex.el: Reformat the feature announcement comment to make it easier to read. --- lisp/ox-latex.el | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lisp/ox-latex.el b/lisp/ox-latex.el index 6221cc486..4e7f30f8b 100644 --- a/lisp/ox-latex.el +++ b/lisp/ox-latex.el @@ -211,8 +211,19 @@ (org-export-define-backend 'latex :feature-implementations-alist `((!announce-start :snippet ,(lambda (info) - (format "\n%%%% ox-latex features: %s" - (plist-get info :features))) + (with-temp-buffer + (setq-local left-margin 2) + (insert (string-join + (mapcar #'symbol-name + (plist-get info :features)) + ", ") + ".") + (fill-region-as-paragraph (point-min) (point-max)) + (goto-char (point-min)) + (insert "%% ox-latex features:\n% ") + (while (search-forward "\n" nil t) + (insert "%")) + (buffer-string))) :order -100) (maths :snippet "\\usepackage{amsmath}\n\\usepackage{amssymb}" :order 0.2) (underline :snippet "\\usepackage[normalem]{ulem}" :order 0.5) -- 2.39.0 --=-=-=--