From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp0 ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms11 with LMTPS id gN98FBL9HGBRdQAA0tVLHw (envelope-from ) for ; Fri, 05 Feb 2021 08:08:50 +0000 Received: from aspmx1.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp0 with LMTPS id B51YEBL9HGC4TgAA1q6Kng (envelope-from ) for ; Fri, 05 Feb 2021 08:08:50 +0000 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 5D5679403EA for ; Fri, 5 Feb 2021 08:08:49 +0000 (UTC) Received: from localhost ([::1]:42820 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1l7wAZ-0002QA-EN for larch@yhetil.org; Fri, 05 Feb 2021 03:08:47 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:47458) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1l7wAA-0002Pn-5Z for emacs-orgmode@gnu.org; Fri, 05 Feb 2021 03:08:22 -0500 Received: from het.cgarbs.de ([159.69.148.42]:38832 helo=mail2.cgarbs.de) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1l7wA4-0000EB-OV for emacs-orgmode@gnu.org; Fri, 05 Feb 2021 03:08:20 -0500 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=cgarbs.de; s=s20190610h; h=Content-Type:MIME-Version:Message-ID:Subject:To:From:Date: Sender:Reply-To:Cc:Content-Transfer-Encoding:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=9HCT9Kkjq9U1+bzHvmC3P39hSFAbMGNojXnXDglZ3mo=; b=E861TO5e16NrQFnBG1LL5pxt+k dObv2XmjTmOc2PykzF0O2gHpvvKzTh5P4r2WWTE1UTkuTTmhW9yFEveoau7akYCn16c8WlD/BsqMV MYgPDBC6vaV7ZpvcVhgZhuXMGDZ8N8vaKLq6MpUnqNUhZ1lM5UDz22Ml0/Os0LVFiHfAyWNbclsLj JeKrDuZMHGSKF8n58Cek1DsI64b7bNitLS2bHyHemTfx9sw/ztfrRkSTsHA8d9zm/Jh/tgxzOETK1 dPd+WGBgyzr6DXGlZYZ+ergDLBpPdyvVfjrYMTWQI076s6UnpC9zVE0wo356V0aXCNLN9aOuN5/Tx LbgyPf0Q==; Received: from localhost ([127.0.0.1] helo=mail.cgarbs.de) by mail2.cgarbs.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94) (envelope-from ) id 1l7w9v-001qmV-OD for emacs-orgmode@gnu.org; Fri, 05 Feb 2021 09:08:11 +0100 Received: from [2003:f9:e709:5c00:6072:189b:5038:13c1] (helo=derpy) by mail.cgarbs.de with esmtpsa (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94) (envelope-from ) id 1l7w9m-0011yp-NM for emacs-orgmode@gnu.org; Fri, 05 Feb 2021 09:08:07 +0100 Received: from mitch by derpy with local (Exim 4.92) (envelope-from ) id 1l7w9j-000O03-UG for emacs-orgmode@gnu.org; Fri, 05 Feb 2021 09:07:55 +0100 Date: Fri, 5 Feb 2021 09:07:55 +0100 To: emacs-orgmode@gnu.org Subject: [PATCH] ox-bb.el: Add BBCode exporter Message-ID: <20210205080755.ex55vibiqhczqrjb@cgarbs.de> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="ww3tlpne5vcpjcd3" Content-Disposition: inline User-Agent: NeoMutt/20180716 Received-SPF: pass client-ip=159.69.148.42; envelope-from=mitch@cgarbs.de; helo=mail2.cgarbs.de X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-orgmode@gnu.org X-Mailman-Version: 2.1.23 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" Reply-to: Christian Garbs From: Christian Garbs via "General discussions about Org-mode." X-Migadu-Flow: FLOW_IN X-Migadu-Spam-Score: -2.85 Authentication-Results: aspmx1.migadu.com; dkim=fail ("headers rsa verify failed") header.d=cgarbs.de header.s=s20190610h header.b=E861TO5e; dmarc=pass (policy=none) header.from=gnu.org; spf=pass (aspmx1.migadu.com: domain of emacs-orgmode-bounces@gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=emacs-orgmode-bounces@gnu.org X-Migadu-Queue-Id: 5D5679403EA X-Spam-Score: -2.85 X-Migadu-Scanner: scn0.migadu.com X-TUID: fRfupTp+8ATk --ww3tlpne5vcpjcd3 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Hi, after getting an encouraging reply to my initial proposal[1], I went forward and finished the patch to include the BBCode exporter in Org. The patch applies to current master and includes a suite of tests. Please review and/or apply this patch. Best regards, Christian [1] https://lists.gnu.org/archive/html/emacs-orgmode/2021-01/msg00151.html -- ....Christian.Garbs....................................https://www.cgarbs.de It seemed like a good idea at the time. --ww3tlpne5vcpjcd3 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0001-ox-bb.el-Add-BBCode-exporter.patch" >From 04888bb0146d0f0cf3bf82cea4f0ea1b761c1d08 Mon Sep 17 00:00:00 2001 From: Christian Garbs Date: Fri, 8 Jan 2021 20:39:29 +0100 Subject: [PATCH] ox-bb.el: Add BBCode exporter * lisp/ox-bb.el: Add export backend for BBCode format. * testing/lisp/test-ox-bb.el: Add tests for ox-bb.el. * doc/org-manual.org (Exporting): Add section about BBCode export. * etc/ORG-NEWS: Announce BBCode exporter as a new feature. --- doc/org-manual.org | 29 +++ etc/ORG-NEWS | 10 + lisp/ox-bb.el | 423 +++++++++++++++++++++++++++++++ testing/lisp/test-ox-bb.el | 503 +++++++++++++++++++++++++++++++++++++ 4 files changed, 965 insertions(+) create mode 100644 lisp/ox-bb.el create mode 100644 testing/lisp/test-ox-bb.el diff --git a/doc/org-manual.org b/doc/org-manual.org index 20a0d1d7a..ff19b5111 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -12281,6 +12281,35 @@ It's just a jump to the left... ,#+END_JUSTIFYRIGHT #+end_example +** BBCode Export +:PROPERTIES: +:DESCRIPTION: Exporting to BBCode. +:END: +#+cindex: BBCode export + +BBCode export produces an output file containing BBCode markup. Code +sections are exported as =[code]= blocks for the GeSHI formatter found +in some web forums. + +*** BBCode export commands +:PROPERTIES: +:UNNUMBERED: notoc +:END: + +#+attr_texinfo: :sep , +- {{{kbd(C-c C-e b f)}}} (~org-bb-export-to-bbcode~) :: + #+kindex: C-c C-e b f + #+findex: org-bb-export-to-bbcode + + Export as a BBCode formatted text file with a =.bbcode= extension, + overwriting without warning. + +- {{{kbd(C-c C-e b b)}}} (~org-bb-export-to-bbcode~) :: + #+kindex: C-c C-e b b + #+findex: org-bb-export-as-bbcode + + Export to a temporary buffer. Does not create a file. + ** Beamer Export :PROPERTIES: :DESCRIPTION: Producing presentations and slides. diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index ba769224f..035abba1b 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -156,6 +156,16 @@ tags including from both buffer local and user defined persistent global list (~org-tag-alist~ and ~org-tag-persistent-alist~). Now option ~org-complete-tags-always-offer-all-agenda-tags~ is honored. +*** New BBCode export backend + +A new export backend generates BBCode as used in many internet forums. +Code blocks will be exported as =[code]= blocks for the GeSHi +formatter. There are no configurable options. + +You can export to a temporary buffer using == or to a +file with extension =.bbcode= (overwritten without warning) via ==. + ** Miscellaneous *** =org-goto-first-child= now works before first heading diff --git a/lisp/ox-bb.el b/lisp/ox-bb.el new file mode 100644 index 000000000..50271e675 --- /dev/null +++ b/lisp/ox-bb.el @@ -0,0 +1,423 @@ +;;; ox-bb.el --- BBCode Back-End for Org Export Engine -*- lexical-binding: t; -*- + +;; Copyright (C) 2017-2021 Free Software Foundation, Inc. +;; Author: org, wp, bbcode + +;; This file is part of ox-bb. + +;; GNU EMacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs 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 General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Commentary: + +;; This library implements a BBCode back-end for Org exporter. Code +;; sections are formatted for the GeSHi formatter found in some web +;; forums. See Org manual for more information. + +;;; Code: + +(require 'ox) + +;;; Backend definition + +(org-export-define-backend 'bb + '((bold . org-bb-bold) + (center-block . org-bb-undefined) + (clock . org-bb-undefined) + (code . org-bb-code) + (drawer . org-bb-undefined) + (dynamic-block . org-bb-undefined) + (entity . org-bb-entity) + (example-block . org-bb-undefined) + (export-block . org-bb-undefined) + (export-snippet . org-bb-undefined) + (fixed-width . org-bb-fixed-width) + (footnote-definition . org-bb-footnote-definition) + (footnote-reference . org-bb-footnote-reference) + (headline . org-bb-headline) + (horizontal-rule . org-bb-undefined) + (inline-src-block . org-bb-undefined) + (inlinetask . org-bb-undefined) + (inner-template . org-bb-inner-template) + (italic . org-bb-italic) + (item . org-bb-item) + (keyword . org-bb-undefined) + (latex-environment . org-bb-undefined) + (latex-fragment . org-bb-undefined) + (line-break . org-bb-line-break) + (link . org-bb-link) + (node-property . org-bb-undefined) + (paragraph . org-bb-paragraph) + (plain-list . org-bb-plain-list) + (plain-text . org-bb-plain-text) + (planning . org-bb-undefined) + (property-drawer . org-bb-undefined) + (quote-block . org-bb-quote-block) + (radio-target . org-bb-undefined) + (section . org-bb-section) + (special-block . org-bb-undefined) + (src-block . org-bb-geshi-block) + (statistics-cookie . org-bb-undefined) + (strike-through . org-bb-strike-through) + (subscript . org-bb-undefined) + (superscript . org-bb-undefined) + (table . org-bb-undefined) + (table-cell . org-bb-undefined) + (table-row . org-bb-undefined) + (target . org-bb-undefined) + (template . org-bb-template) + (timestamp . org-bb-undefined) + (underline . org-bb-underline) + (verbatim . org-bb-verbatim) + (verse-block . org-bb-undefined)) + :menu-entry + '(?b "Export to BBCode" + ((?b "As BBCode buffer" org-bb-export-as-bbcode) + (?f "As BBCode file" org-bb-export-to-bbcode)))) + +;;; Helper methods + +(defun org-bb--as-block (text) + "Format TEXT as a block with leading and trailing newline." + (concat "\n" text "\n")) + +(defun org-bb--force-leading-newline (text) + "Make TEXT start with exactly one newline." + (replace-regexp-in-string "\\`\n*" "\n" text)) + +(defun org-bb--format-headline (text level) + "Format TEXT as a headline of the given LEVEL." + (let ((indent (cl-case level + (0 "") + (1 "# ") + (2 "== ") + (3 "+++ ") + (4 ":::: ") + (5 "----- ") + (t (error "Headline level `%s' is not defined yet" level))))) + (concat + (org-bb--put-in-tag + "b" (org-bb--put-in-tag + "u" (concat indent text))) + "\n\n"))) + +(defun org-bb--put-in-tag (tag contents &optional attributes) + "Puts the BBcode tag TAG around the CONTENTS string. +Optional ATTRIBUTES for the tag can be given as an alist of +key/value pairs (both strings)." + (let ((attribute-string (if attributes + (mapconcat (function (lambda (attribute) + (let ((key (car attribute)) + (value (cadr attribute))) + (format " %s=\"%s\"" key value)))) + attributes + "") + ""))) + (format "[%s%s]%s[/%s]" tag attribute-string contents tag))) + +(defun org-bb--put-in-value-tag (tag contents value) + "Puts the BBcode tag TAG around the CONTENTS string. +The VALUE is assigned directly to the tag instead of a normal +key/value pair." + (format "[%s=%s]%s[/%s]" tag value contents tag)) + +(defun org-bb--fix-url (url) + "Fix URL returned from `url-encode-url'. +Older versions of Emacs (eg. 24.3 used in the Travis CI minimal +image) prepend \"/\" to urls consisting only of an \"#anchor\" +part. We don't want this, because we need relative anchors. Fix +this the hard way." + (if (string-prefix-p "/#" url) + (substring url 1) + url)) + +(defun org-bb--put-url (contents href) + "Puts the CONTENTS inside a [url] tag pointing to HREF. +Automagically escapes the target URL." + (let* ((target (org-bb--fix-url (url-encode-url (org-link-unescape href)))) + (text (or contents target))) + (org-bb--put-in-value-tag "url" text target))) + +(defun org-bb--remove-leading-newline (text) + "Remove a leading empty line from TEXT." + (replace-regexp-in-string "\\`\n" "" text)) + +(defun org-bb--remove-trailing-newline (text) + "Remove the trailing newline from TEXT." + (replace-regexp-in-string "\n\\'" "" text)) + +(defun org-bb--map-to-geshi-language (language) + "Map LANGUAGE from Org to GeSHi." + (cond ((string= language "elisp") "lisp") + ((string= language "shell") "bash") + ((string= language "sh") "bash") + ((string= language "") "plaintext") + (language) + (t "plaintext"))) + +;;; Backend callbacks + +(defun org-bb-bold (_bold contents _info) + "Transcode a BOLD element from Org to BBCode. +CONTENTS is the bold text, as a string. INFO is + a plist used as a communication channel." + (org-bb--put-in-tag "b" contents)) + +(defun org-bb-code (code _contents _info) + "Transcode a CODE element from Org to BBCode. +CONTENTS is nil. INFO is a plist used as a communication channel." + (org-bb--put-in-value-tag "font" (org-element-property :value code) "monospace")) + +(defun org-bb-entity (entity _contents _info) + "Transcode an ENTITY element from Org to BBCode. +CONTENTS is the definition itself. INFO is a plist used as a +communication channel." + (org-element-property :html entity)) + +(defun org-bb-geshi-block (code-block _contents info) + "Transcode a CODE-BLOCK element from Org to BBCode GeSHi plugin. +CONTENTS is nil. INFO is a plist holding +contextual information." + (format "[geshi lang=%s]%s[/geshi]" + (org-bb--map-to-geshi-language (org-element-property :language code-block)) + (org-bb--remove-trailing-newline + (org-export-format-code-default code-block info)))) + +(defun org-bb-fixed-width (fixed-width _contents _info) + "Transcode a FIXED-WIDTH element from Org to BBCode. +CONTENTS is nil. INFO is a plist holding contextual information." + (org-bb--put-in-tag "code" + (concat "\n" (org-element-property :value fixed-width) "\n"))) + +(defun org-bb-footnote-reference (footnote-reference _contents info) + "Transcode a FOOTNOTE-REFERENCE element from Org to BBCode. +CONTENTS is nil. INFO is a plist holding contextual information." + (if (eq (org-element-property :type footnote-reference) 'inline) + (error "Inline footnotes not supported yet") + (let ((n (org-export-get-footnote-number footnote-reference info))) + (format "^%d" n)))) + +(defun org-bb-format-footnote-definition (fn) + "Format the footnote definition FN." + (let ((n (car fn)) + (def (cdr fn))) + (format "^%d: %s" n def))) + +(defun org-bb-footnote-section (info) + "Format the footnote section. +INFO is a plist used as a communication channel." + (let* ((fn-alist (org-export-collect-footnote-definitions info (plist-get info :parse-tree))) + (fn-alist + (cl-loop for (n _label raw) in fn-alist collect + (cons n (org-trim (org-export-data raw info))))) + (text (mapconcat 'org-bb-format-footnote-definition fn-alist "\n"))) + (if fn-alist + (concat "\n" (org-bb--format-headline "Footnotes" 0) text "\n") + ""))) + +(defun org-bb-headline (headline contents info) + "Transcode HEADLINE element from Org to BBCode. +CONTENTS is the headline contents. INFO is a plist used as +a communication channel." + (let ((title (org-export-data (org-element-property :title headline) info)) + (level (org-export-get-relative-level headline info))) + (if (org-element-property :footnote-section-p headline) + "" + (concat + (org-bb--format-headline title level) + contents)))) + +(defun org-bb-inner-template (contents info) + "Return body of document string after BBCode conversion. +CONTENTS is the transcoded contents string. INFO is a plist +holding export options." + (concat + contents + (org-bb-footnote-section info))) + +(defun org-bb-italic (_italic contents _info) + "Transcode a ITALIC element from Org to BBCode. +CONTENTS is the italic text, as a string. INFO is + a plist used as a communication channel." + (org-bb--put-in-tag "i" contents)) + +(defun org-bb-item (item contents info) + "Transcode a ITEM element from Org to BBCode. +CONTENTS is the contents of the item, as a string. INFO is + a plist used as a communication channel." + (let* ((plain-list (org-export-get-parent item)) + (type (org-element-property :type plain-list)) + (text (org-trim contents))) + (concat + "[*]" + (pcase type + (`descriptive + (let ((term (let ((tag (org-element-property :tag item))) + (and tag (org-export-data tag info))))) + (concat + (org-bb--put-in-tag "i" (concat (org-trim term) ":")) + " " + )))) + text + "\n"))) + +(defun org-bb-line-break (_line-break _contents _info) + "Transcode a LINE-BREAK object from Org to BBCode. +CONTENTS is nil. INFO is a plist holding contextual +information." + "[br]_[/br]\n") + +(defun org-bb-link (link contents _info) + "Transcode a LINK element from Org to BBCode. +CONTENTS is the contents of the link, as a string. INFO is + a plist used as a communication channel." + (let ((type (org-element-property :type link)) + (path (org-element-property :path link)) + (raw (org-element-property :raw-link link))) + (cond + ((string= type "fuzzy") + (cond + ((string-prefix-p "about:" raw) + (org-bb--put-url contents raw)) + (t (error "Unknown fuzzy LINK type encountered: `%s'" raw)))) + ((member type '("http" "https")) + (org-bb--put-url contents (concat type ":" path))) + (t (error "LINK type `%s' not yet supported" type))))) + +(defun org-bb-paragraph (_paragraph contents _info) + "Transcode a PARAGRAPH element from Org to BBCode. +CONTENTS is the contents of the paragraph, as a string. INFO is + a plist used as a communication channel." + (org-trim contents)) + +(defun org-bb-plain-list (plain-list contents _info) + "Transcode a PLAIN-LIST element from Org to BBCode. +CONTENTS is the contents of the plain-list, as a string. INFO is + a plist used as a communication channel." + (let ((type (org-element-property :type plain-list)) + (content-block (org-bb--as-block (org-trim contents)))) + (concat + (pcase type + (`descriptive (org-bb--put-in-tag "list" content-block)) + (`unordered (org-bb--put-in-tag "list" content-block)) + (`ordered (org-bb--put-in-value-tag "list" content-block "1")) + (other (error "PLAIN-LIST type `%s' not yet supported" other))) + "\n"))) + +(defun org-bb-plain-text (text _info) + "Transcode a TEXT string from Org to BBCode. +INFO is a plist used as a communication channel." + text) + +(defun org-bb-quote-block (_quote-block contents _info) + "Transcode a QUOTE-BLOCK element from Org to BBCode. +CONTENTS holds the contents of the block. INFO is a plist used +as a communication channel." + (org-bb--put-in-tag "quote" (org-bb--force-leading-newline contents))) + +(defun org-bb-section (_section contents _info) + "Transcode a SECTION element from Org to BBCode. +CONTENTS is the contents of the section, as a string. INFO is a + plist used as a communication channel." + (org-trim contents)) + +(defun org-bb-strike-through (_strike-through contents _info) + "Transcode a STRIKE-THROUGH element from Org to BBCode. +CONTENTS is the text with strike-through markup, as a string. + INFO is a plist used as a communication channel." + (org-bb--put-in-tag "s" contents)) + +(defun org-bb-template (contents _info) + "Return complete document string after BBCode conversion. +CONTENTS is the transcoded contents string. INFO is a plist +holding export options." + contents) + +(defun org-bb-undefined (element &optional _contents _info) + "Throw an error when an unsupported ELEMENT is encountered." + (error "ELEMENT type `%s' not implemented yet" (car element))) + +(defun org-bb-underline (_underline contents _info) + "Transcode a UNDERLINE element from Org to BBCode. +CONTENTS is the underlined text, as a string. INFO is + a plist used as a communication channel." + (org-bb--put-in-tag "u" contents)) + +(defun org-bb-verbatim (verbatim _contents _info) + "Transcode a VERBATIM element from Org to BBCode. +CONTENTS is nil. INFO is a plist used as a communication channel." + (org-bb--put-in-value-tag "font" (org-element-property :value verbatim) "monospace")) + +;;; Export methods + +;;;###autoload +(defun org-bb-export-as-bbcode + (&optional async subtreep visible-only) + "Export current buffer to a BBCode buffer. + +If narrowing is active in the current buffer, only export its +narrowed part. + +If a region is active, export that region. + +A non-nil optional argument ASYNC means the process should happen +asynchronously. The resulting buffer should be accessible +through the `org-export-stack' interface. + +When optional argument SUBTREEP is non-nil, export the sub-tree +at point, extracting information from the headline properties +first. + +When optional argument VISIBLE-ONLY is non-nil, don't export +contents of hidden elements. + +Export is done in a buffer named \"*Org BBCode Export*\"." + (interactive) + (org-export-to-buffer 'bb "*Org BBCode Export*" + async subtreep visible-only (lambda () (when (require 'bbcode-mode nil :noerror) + (bbcode-mode))))) + +;;;###autoload +(defun org-bb-export-to-bbcode + (&optional async subtreep visible-only) + "Export current buffer to a BBCode file. + +If narrowing is active in the current buffer, only export its +narrowed part. + +If a region is active, export that region. + +A non-nil optional argument ASYNC means the process should happen +asynchronously. The resulting buffer should be accessible +through the `org-export-stack' interface. + +When optional argument SUBTREEP is non-nil, export the sub-tree +at point, extracting information from the headline properties +first. + +When optional argument VISIBLE-ONLY is non-nil, don't export +contents of hidden elements. + +Return output file's name." + (interactive) + (let* ((extension ".bbcode") + (file (org-export-output-file-name extension subtreep)) + (org-export-coding-system org-html-coding-system)) + (org-export-to-file 'bb file + async subtreep visible-only))) + +;;; Register file + +(provide 'ox-bb) + +;;; ox-bb.el ends here diff --git a/testing/lisp/test-ox-bb.el b/testing/lisp/test-ox-bb.el new file mode 100644 index 000000000..b8ddc7f67 --- /dev/null +++ b/testing/lisp/test-ox-bb.el @@ -0,0 +1,503 @@ +;;; test-ox-bb.el --- Tests for ox-bb.el -*- lexical-binding: t; -*- + +;; Copyright (C) 2017-2019, 2021 Christian Garbs + +;; Author: Christian Garbs + +;; This file is not part of GNU Emacs. + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program 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 General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Tests for ox-bb.el. + +;;; Code: + +(require 'ox-bb) + +;;; helper functions + +(defun test-org-bb-verbatim-regression () + "Return t if verbatim blocks generate an extra newline. +This lets the tests react to a possible regression introduced +with 7d9e4da447 which was released with Org 9.1.14. See +https://lists.gnu.org/archive/html/emacs-orgmode/2021-01/msg00338.html +for details." + (not (version< (org-release) "9.1.14"))) + +;;; sanity check + +(ert-deftest test-org-bb/assert () + (should t)) + +;;; tests of internal functions + +;;; org-bb--as-block + +(ert-deftest test-org-bb/as-block/plain () + (should (equal( org-bb--as-block "some text\nline two") + "\nsome text\nline two\n"))) + +;;; org-bb--force-leading-newline + +(ert-deftest test-org-bb/force-leading-newline/add-missing-newline () + (should (equal( org-bb--force-leading-newline "some text") + "\nsome text"))) + +(ert-deftest test-org-bb/force-leading-newline/keep-existing-newline () + (should (equal( org-bb--force-leading-newline "\nonly one newline") + "\nonly one newline"))) + +(ert-deftest test-org-bb/force-leading-newline/remove-additional-newlines () + (should (equal( org-bb--force-leading-newline "\n\nsome text") + "\nsome text"))) + +(ert-deftest test-org-bb/force-leading-newline/keep-newlines-within () + (should (equal( org-bb--force-leading-newline "\nline 1\nline 2\n") + "\nline 1\nline 2\n"))) + +;;; org-bb--format-headline + +(ert-deftest test-org-bb/format-headline/level-0 () + (should (equal( org-bb--format-headline "some text" 0) + "[b][u]some text[/u][/b]\n\n"))) + +(ert-deftest test-org-bb/format-headline/level-1 () + (should (equal( org-bb--format-headline "some text" 1) + "[b][u]# some text[/u][/b]\n\n"))) + +(ert-deftest test-org-bb/format-headline/level-2 () + (should (equal( org-bb--format-headline "some text" 2) + "[b][u]== some text[/u][/b]\n\n"))) + +(ert-deftest test-org-bb/format-headline/level-3 () + (should (equal( org-bb--format-headline "some text" 3) + "[b][u]+++ some text[/u][/b]\n\n"))) + +(ert-deftest test-org-bb/format-headline/level-4 () + (should (equal( org-bb--format-headline "some text" 4) + "[b][u]:::: some text[/u][/b]\n\n"))) + +(ert-deftest test-org-bb/format-headline/level-5 () + (should (equal( org-bb--format-headline "some text" 5) + "[b][u]----- some text[/u][/b]\n\n"))) + +;;; org-bb--put-in-tag + +(ert-deftest test-org-bb/put-in-tag/no-attribute () + (should (equal (org-bb--put-in-tag "p" "foo") + "[p]foo[/p]"))) + +(ert-deftest test-org-bb/put-in-tag/single-attribute () + (should (equal (org-bb--put-in-tag "style" "foo" '(("size" "30px"))) + "[style size=\"30px\"]foo[/style]"))) + +(ert-deftest test-org-bb/put-in-tag/multiple-attributes () + (should (equal (org-bb--put-in-tag "style" "foo" '(("color" "#00FF00") ("size" "30px"))) + "[style color=\"#00FF00\" size=\"30px\"]foo[/style]"))) + +;;; org-bb--put-in-value-tag + +(ert-deftest test-org-bb/put-in-value-tag/plain () + (should (equal (org-bb--put-in-value-tag "url" "foo" "file.htm") + "[url=file.htm]foo[/url]"))) + +;;; org-bb--put-url + +(ert-deftest test-org-bb/put-url/plain () + (should (equal (org-bb--put-url "some text" "https://example.com/") + "[url=https://example.com/]some text[/url]"))) + +(ert-deftest test-org-bb/put-url/empty () + (should (equal (org-bb--put-url nil "https://example.com/") + "[url=https://example.com/]https://example.com/[/url]"))) + +(ert-deftest test-org-bb/put-url/anchor () + (should (equal (org-bb--put-url "anchor text" "#anchor") + "[url=#anchor]anchor text[/url]"))) + +(ert-deftest test-org-bb/put-url/encode-url-only-once () + (should (equal (org-bb--put-url "baz" "http://foo/%20bar") + "[url=http://foo/%20bar]baz[/url]"))) + +;;; org-bb--remove-leading-newline + +(ert-deftest test-org-bb/remove-leading-newline/remove () + (should (equal( org-bb--remove-leading-newline "\nsome text") + "some text"))) + +(ert-deftest test-org-bb/remove-leading-newline/keep-text-before-first-newline () + (should (equal( org-bb--remove-leading-newline "no empty line\nsome more text\n") + "no empty line\nsome more text\n"))) + +(ert-deftest test-org-bb/remove-leading-newline/only-remove-first-newline () + (should (equal( org-bb--remove-leading-newline "\n\nsome text") + "\nsome text"))) + +(ert-deftest test-org-bb/remove-leading-newline/keep-newlines-within () + (should (equal( org-bb--remove-leading-newline "\nline 1\nline 2") + "line 1\nline 2"))) + +(ert-deftest test-org-bb/remove-leading-newline/dont-fail-with-no-newline () + (should (equal( org-bb--remove-leading-newline "some text") + "some text"))) + +;;; org-bb--remove-trailing-newline + +(ert-deftest test-org-bb/remove-trailing-newline/remove () + (should (equal( org-bb--remove-trailing-newline "some text\n") + "some text"))) + +(ert-deftest test-org-bb/remove-trailing-newline/keep-text-after-last-newline () + (should (equal( org-bb--remove-trailing-newline "some text\nno empty line") + "some text\nno empty line"))) + +(ert-deftest test-org-bb/remove-trailing-newline/only-remove-last-newline () + (should (equal( org-bb--remove-trailing-newline "some text\n\n") + "some text\n"))) + +(ert-deftest test-org-bb/remove-trailing-newline/keep-newlines-within () + (should (equal( org-bb--remove-trailing-newline "line 1\nline 2\n") + "line 1\nline 2"))) + +(ert-deftest test-org-bb/remove-trailing-newline/dont-fail-with-no-newline () + (should (equal( org-bb--remove-trailing-newline "some text") + "some text"))) + +;;; org-bb--map-to-geshi-language + +(ert-deftest test-org-bb/map-to-geshi-language/unchanged () + (should (equal( org-bb--map-to-geshi-language "java") + "java"))) + +(ert-deftest test-org-bb/map-to-geshi-language/changed () + (should (equal( org-bb--map-to-geshi-language "elisp") + "lisp"))) + +(ert-deftest test-org-bb/map-to-geshi-language/nil () + (should (equal( org-bb--map-to-geshi-language nil) + "plaintext"))) + +(ert-deftest test-org-bb/map-to-geshi-language/empty () + (should (equal( org-bb--map-to-geshi-language "") + "plaintext"))) + +;;; end-to-end tests: compare given input file with expected export output + +(defun test-org-bb-export (input) + "Transform INPUT to BBCode and return the result." + (org-test-with-temp-text input + (org-bb-export-as-bbcode) + (with-current-buffer "*Org BBCode Export*" + (buffer-substring-no-properties (point-min) (point-max))))) + +(ert-deftest test-org-bb/export-bold () + (should (equal (test-org-bb-export "foo *BAR* baz +") + "foo [b]BAR[/b] baz +"))) + +(ert-deftest test-org-bb/export-code () + (should (equal (test-org-bb-export "foo ~BAR~ baz +") + "foo [font=monospace]BAR[/font] baz +"))) + +(ert-deftest test-org-bb/export-entity () + (should (equal (test-org-bb-export "This is *bold* and this is in \\ast{}asterisks\\ast{}. +") + "This is [b]bold[/b] and this is in ∗asterisks∗. +"))) + +(ert-deftest test-org-bb/export-fixed-width () + ;; Where does the double newline before paragraph 2 come from? + ;; in Org 9.1 there was only a single newline as expected. + ;; A regression? + (should (equal (test-org-bb-export "paragraph 1 + +: verbatim line +: indented verbatim line + +paragraph 2") + (if (test-org-bb-verbatim-regression) + "paragraph 1 + +[code] +verbatim line + indented verbatim line +[/code] + + +paragraph 2 +" + "paragraph 1 + +[code] +verbatim line + indented verbatim line +[/code] + +paragraph 2 +")))) + +(ert-deftest test-org-bb/export-footnote-multiple () + (should (equal (test-org-bb-export "foo[fn:1] bar[fn:2] +* Footnotes + +[fn:1] foo +[fn:2] bar +") + "foo^1 bar^2 + +[b][u]Footnotes[/u][/b] + +^1: foo +^2: bar +"))) + +(ert-deftest test-org-bb/export-footnote-plain () + (should (equal (test-org-bb-export "bar[fn:1] +* Footnotes + +[fn:1] foo +") + "bar^1 + +[b][u]Footnotes[/u][/b] + +^1: foo +"))) + +(ert-deftest test-org-bb/export-geshi-block-without-language () + (should (equal (test-org-bb-export "#+BEGIN_SRC +package foo; +/* dummy dummy */ +#+END_SRC +") + "[geshi lang=plaintext]package foo; +/* dummy dummy */[/geshi] +"))) + +(ert-deftest test-org-bb/export-geshi-block () + (should (equal (test-org-bb-export "#+BEGIN_SRC java +package foo; +/* dummy dummy */ +#+END_SRC +") + "[geshi lang=java]package foo; +/* dummy dummy */[/geshi] +"))) + +(ert-deftest test-org-bb/export-headline-lv1 () + (should (equal (test-org-bb-export "* TOPIC +") + "[b][u]# TOPIC[/u][/b] +"))) + +(ert-deftest test-org-bb/export-headline-lv2 () + (should (equal (test-org-bb-export "* dummy +** TOPIC +") + "[b][u]# dummy[/u][/b] + +[b][u]== TOPIC[/u][/b] +"))) + +(ert-deftest test-org-bb/export-headline-lv3 () + (should (equal (test-org-bb-export "* dummy +** dummy +*** TOPIC +") + "[b][u]# dummy[/u][/b] + +[b][u]== dummy[/u][/b] + +[b][u]+++ TOPIC[/u][/b] +"))) + +(ert-deftest test-org-bb/export-headline-lv4 () + (should (equal (test-org-bb-export "* dummy +** dummy +*** dummy +**** TOPIC +") + "[b][u]# dummy[/u][/b] + +[b][u]== dummy[/u][/b] + +[b][u]+++ dummy[/u][/b] + +[b][u]:::: TOPIC[/u][/b] +"))) + +(ert-deftest test-org-bb/export-headline-lv5 () + (should (equal (test-org-bb-export "* dummy +** dummy +*** dummy +**** dummy +***** TOPIC +") + "[b][u]# dummy[/u][/b] + +[b][u]== dummy[/u][/b] + +[b][u]+++ dummy[/u][/b] + +[b][u]:::: dummy[/u][/b] + +[b][u]----- TOPIC[/u][/b] +"))) + +(ert-deftest test-org-bb/export-italic () + (should (equal (test-org-bb-export "foo /BAR/ baz +") + "foo [i]BAR[/i] baz +"))) + +(ert-deftest test-org-bb/export-line-break () + (should (equal (test-org-bb-export "foo\\\\ +bar +") + "foo[br]_[/br] +bar +"))) + +(ert-deftest test-org-bb/export-link-about () + (should (equal (test-org-bb-export "[[about:config][bar]] +") + "[url=about:config]bar[/url] +"))) + +(ert-deftest test-org-bb/export-link-empty () + (should (equal (test-org-bb-export "http://example.com/ +") + "[url=http://example.com/]http://example.com/[/url] +"))) + +(ert-deftest test-org-bb/export-link-encode-url-only-once () + (should (equal (test-org-bb-export "[[http://foo/%20bar][baz]] +") + "[url=http://foo/%20bar]baz[/url] +"))) + +(ert-deftest test-org-bb/export-link-encode-url () + (should (equal (test-org-bb-export "[[http://foo/ bar][baz]] +") + "[url=http://foo/%20bar]baz[/url] +"))) + +(ert-deftest test-org-bb/export-link-http () + (should (equal (test-org-bb-export "[[http://foo/][bar]] +") + "[url=http://foo/]bar[/url] +"))) + +(ert-deftest test-org-bb/export-link-https () + (should (equal (test-org-bb-export "[[https://foo/][bar]] +") + "[url=https://foo/]bar[/url] +"))) + +(ert-deftest test-org-bb/export-multiline-paragraph () + (should (equal (test-org-bb-export "foo +bar +") + "foo +bar +"))) + +(ert-deftest test-org-bb/export-multiple-paragraphs () + (should (equal (test-org-bb-export "foo + +bar +") + "foo + +bar +"))) + +(ert-deftest test-org-bb/export-plain-list-descriptive () + (should (equal (test-org-bb-export "- foo :: pokey +- bar :: hokey +") + "[list] +[*][i]foo:[/i] pokey +[*][i]bar:[/i] hokey +[/list] +"))) + +(ert-deftest test-org-bb/export-plain-list-ordered () + (should (equal (test-org-bb-export "1. foo +2. bar +") + "[list=1] +[*]foo +[*]bar +[/list] +"))) + +(ert-deftest test-org-bb/export-plain-list-unordered () + (should (equal (test-org-bb-export "- foo +- bar +") + "[list] +[*]foo +[*]bar +[/list] +"))) + +(ert-deftest test-org-bb/export-quote-block () + (should (equal (test-org-bb-export "#+BEGIN_QUOTE +Somebody +said +this. +#+END_QUOTE +") + "[quote] +Somebody +said +this. +[/quote] +"))) + +(ert-deftest test-org-bb/export-single-paragraph () + (should (equal (test-org-bb-export "foo +") + "foo +"))) + +(ert-deftest test-org-bb/export-strike-through () + (should (equal (test-org-bb-export "foo +BAR+ baz +") + "foo [s]BAR[/s] baz +"))) + +(ert-deftest test-org-bb/export-underline () + (should (equal (test-org-bb-export "foo _BAR_ baz +") + "foo [u]BAR[/u] baz +"))) + +(ert-deftest test-org-bb/export-verbatim () + (should (equal (test-org-bb-export "foo =BAR= baz +") + "foo [font=monospace]BAR[/font] baz +"))) + + + +(provide 'test-ox-bb) + +;;; test-ox-bb.el ends here -- 2.20.1 --ww3tlpne5vcpjcd3--