From 46a390d6759065271b2158537691a53679c58989 Mon Sep 17 00:00:00 2001
From: Martin Fischer <martin@push-f.com>
Date: Sat, 3 Dec 2022 07:07:55 +0100
Subject: allow code display to be customized via Scribunto

---
 Code.php               | 41 ++++++++++++++++++++++++++++++-----------
 README.md              | 45 +++++++++++++++++++++++++++++++++++++++------
 examples/languages.php |  3 ---
 extension.json         |  2 +-
 4 files changed, 70 insertions(+), 21 deletions(-)

diff --git a/Code.php b/Code.php
index 0243e6f..045448e 100644
--- a/Code.php
+++ b/Code.php
@@ -4,10 +4,12 @@ namespace MediaWiki\Extension\Code;
 use Content;
 use ErrorPageError;
 use ExtensionRegistry;
+use FatalError;
 use Html;
 use MediaWiki\Content\Renderer\ContentParseParams;
 use MediaWiki\MediaWikiServices;
 use Parser;
+use ParserOptions;
 use ParserOutput;
 use Sanitizer;
 use SpecialPage;
@@ -70,15 +72,6 @@ function renderCode( string $code, $lang, $outputPage, ?array $tagAttrs = null )
 		$codeBlock = pre( $code, $tagAttrs );
 	}
 
-	foreach ( $lang['linkifiers'] as $regex => $href ) {
-		$codeBlock = preg_replace_callback( $regex, fn( $match ) =>
-			Xml::element( 'a', [
-				'href' => str_replace( '$1', $match[0], $href ),
-				'tabindex' => -1
-			], $match[0] ),
-		 $codeBlock );
-	}
-
 	$actions = '';
 
 	foreach ( $lang['actions'] as $action => $url ) {
@@ -96,7 +89,34 @@ function renderCode( string $code, $lang, $outputPage, ?array $tagAttrs = null )
 		$actions .= Xml::closeElement( 'a' ) . ' ';
 	}
 
-	return $codeBlock . $actions;
+	$additionalOutput = '';
+
+	$scribuntoModule = $lang['scribuntoModule'] ?? null;
+	if ( $scribuntoModule && ExtensionRegistry::getInstance()->isLoaded( 'Scribunto' ) ) {
+		$callFunction = static function ( Parser $parser, string $function, array $extraArgs = [] ) use ( $scribuntoModule, $code, $lang, $tagAttrs ) {
+			$args = [ $scribuntoModule, $function, $code, $lang['tag'], $tagAttrs == null ];
+			$args = array_merge( $args, $extraArgs );
+			if ( $tagAttrs ) {
+				$args = array_merge( $args, $tagAttrs );
+			}
+			$frame = $parser->getPreprocessor()->newFrame();
+			$res = $parser->callParserFunction( $frame, '#invoke', $args );
+			if ( !$res['found'] ) {
+				throw new FatalError( "unexpected condition in Code extension: the Scribunto module is loaded but the #invoke parser function wasn't found" );
+			}
+			return Sanitizer::removeSomeTags( $res['text'], [ 'extraTags' => [ 'a' ], 'extra' ] );
+		};
+		if ( $tagAttrs ) {
+			$parser = MediaWikiServices::getInstance()->getParser();
+		} else {
+			$parser = MediaWikiServices::getInstance()->getParserFactory()->create();
+			$parser->startExternalParse( null, ParserOptions::newFromAnon(), Parser::OT_HTML );
+		}
+		$codeBlock = $callFunction( $parser, 'formatCode', [ $codeBlock ] );
+		$additionalOutput = $callFunction( $parser, 'additionalOutput' );
+	}
+
+	return $codeBlock . $actions . $additionalOutput;
 }
 
 class ContentHandler extends TextContentHandler {
@@ -118,7 +138,6 @@ class ContentHandler extends TextContentHandler {
 			$lang = [
 				'pygmentsLexer' => 'text',
 				'actions' => [],
-				'linkifiers' => [],
 			];
 		}
 		$out .= renderCode( $content->getText(), $lang, $output );
diff --git a/README.md b/README.md
index 1486f54..fbb4cf7 100644
--- a/README.md
+++ b/README.md
@@ -9,13 +9,14 @@ to provide the following (all in a configurable manner):
 * code actions  
   e.g. automatically link the [WDQS] for SPARQL code blocks[^1]
 
-* code linkification  
-  e.g. automatically link Wikidata identifiers in code blocks
-
 * code pages  
   e.g. automatically higlight pages with names ending in `.rq`
   as SPARQL (and also display the code actions for them)
 
+* customizable code display via Lua/[Scribunto]  
+  e.g. automatically link Wikidata identifiers in code blocks
+
+
 ## Example configuration
 
 ```php
@@ -28,13 +29,44 @@ $wgCode_languages[] = [
 		'run' => 'https://query.wikidata.org/#$code',
 		'embed' => 'https://query.wikidata.org/embed.html#$code',
 	],
-	'linkifiers' => [
-		'/\\b[QP][0-9]+\\b/' => 'https://www.wikidata.org/entity/$1',
-	],
 	'suffix' => '.rq',
 ];
 ```
 
+## Customizable code display
+
+The display of code blocks can be customized via Lua/[Scribunto].  For example
+if you specify e.g. `"scribuntoModule" => "QueryCode"` for a language then this
+extension will additionally invoke the `Module:QueryCode` Scribunto module for
+every code tag and code page. Such a module could look as follows:
+
+```lua
+local p = {}
+
+p.formatCode = function(frame)
+    local code, tag, is_code_page = frame.args[1], frame.args[2], frame.args[3]
+    local formattedCode = frame.args[4]
+    formattedCode = formattedCode:gsub('[QP][0-9]+', function(m)
+       local a = mw.html.create('a')
+       a:attr('href', 'https://www.wikidata.org/entity/' .. m)
+       a:wikitext(m)
+       return tostring(a)
+    end)
+    return formattedCode;
+end
+
+p.additionalOutput = function(frame)
+    local code, tag, is_code_page = frame.args[1], frame.args[2], frame.args[3]
+    -- HTML returned here is displayed after the code action links
+end
+
+return p
+```
+
+Note that the returned strings are not parsed as Wikitext they must already be
+HTML; any dangerous tags and attributes are removed via MediaWiki's builtin
+`Sanitizer` class.
+
 ## Linking code actions
 
 Code actions can be linked from other pages via the
@@ -57,4 +89,5 @@ this bears the problem that `|` has to be escaped as `{{!}}`, which can
 be quite annoying for languages like SPARQL that use `|` as an operator.
 
 [SyntaxHighlight]: https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:SyntaxHighlight
+[Scribunto]: https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:Scribunto
 [WDQS]: https://query.wikidata.org/
diff --git a/examples/languages.php b/examples/languages.php
index 3710f86..6fc4317 100644
--- a/examples/languages.php
+++ b/examples/languages.php
@@ -5,8 +5,5 @@ $wgCode_languages[] = [
 		'run' => 'https://query.wikidata.org/#$code',
 		'embed' => 'https://query.wikidata.org/embed.html#$code',
 	],
-	'linkifiers' => [
-		'/\\b[QP][0-9]+\\b/' => 'https://www.wikidata.org/entity/$1',
-	],
 	'suffix' => '.rq',
 ];
diff --git a/extension.json b/extension.json
index cfa2c2b..d1e3429 100644
--- a/extension.json
+++ b/extension.json
@@ -6,7 +6,7 @@
 	"author": "[https://push-f.com/ Martin Fischer]",
 	"url": "https://www.mediawiki.org/wiki/Extension:Code",
 	"version": "0.1.0",
-	"description": "Provides code pages, code actions and code linkification.",
+	"description": "Provides code pages, code actions and customizable code rendering.",
 	"config_prefix": "wgCode_",
 	"config": {
 		"namespacesWithCodePages": {
-- 
cgit v1.2.3