This page is also available in English.

Quartz est un générateur de sites statiques qui transforme du contenu Markdown en sites web. C’est la pile principale du site sur lequel vous êtes et qui me permet d’écrire mes notes sous Obsidian sans avoir à me battre avec un CMS quelconque.

Bien que l’outil soit presque parfait, il me manquait, pour certains guides, certaines notes, la possibilité de présenter du code proprement (avec mise en avant de la syntaxe) pour des langages obscurs ou tout du moins pas aussi connus que Python, JavaScript et leurs copains.

Notamment, pour un de mes guides, j’avais besoin de pouvoir présenter correctement des lignes de commandes pour les équipements réseau de MikroTik. Or, la syntaxe pour le langage RouterOS Script ne semble être supportée ni par Obsidian ni par Quartz.

Voici donc comment je m’y suis pris :

Comment fonctionne le syntax highlighter de Quartz ?

Première étape, comprendre comment fonctionne la coloration syntaxique sous Quartz.

Sur la page Syntax Highlighting de la documentation de Quartz on apprend que le système utilise la librairie TypeScript Rehype Pretty Code.

Rehype Pretty Code utilise quant à lui le moteur de rendu shiki qui s’appuie sur les définitions de langages de TextMate.

Trouver une définition TextMate pour RouterOS Script

Heureusement pour nous, les définitions TextMate sont aussi ce qu’utilise Visual Studio Code pour réaliser sa coloration syntaxique.

Parmi les extensions VSCode ajoutant le support de RouterOS Script, celle de Korzhov Mikhail (vscode_mikrotik_routeros_script) me semble celle qui propose la coloration la plus fidèle.

On trouve donc, dans le code source de l’extension, le fameux TextMate Grammar.

En voici un extrait :

rosScript.tmLanguage.json
{
  "name": "rosScript",
  "scopeName": "source.rsc",
  "fileTypes": ["rsc"],
  "patterns": [
    {
      "match": "\\b(ipsec-esp|ipsec-ah|idpr-cmtp|iso-tp4|xns-idp|udp-lite|ip-encap|icmp|igmp|ggp|st|tcp|egp|pup|udp|hmp|rdp|dccp|xtp|ddp|rsvp|gre|rspf|vmtp|ospf|ipip|etherip|encap|pim|vrrp|l2tp|sctp)\\b",
      "name": "variable.other.rosScript"
    },
    ...
    ]
    },
    "string_escaped_char": {
      "patterns": [
        {
          "match": "\\\\(\\\\|[nrt$?abfv\"?]|[0-9A-F]{2})",
          "name": "constant.character.escape.rosScript"
        },
        {
          "match": "\\\\.",
          "name": "invalid.illegal.unknown-escape.rosScript"
        }
      ]
    }
  }
}

Intégration du TextMate Grammar dans Quartz

La documentation de Rehype Pretty Code présente une page expliquant comment customiser la colorisation syntaxique, et notamment sur comment ajouter son propre langage.

La documentation de Quartz sur la colorisation syntaxique indique quant à elle que la fonctionnalité est gérée par son plugin SyntaxHighlighting. Et sur la page du plugin on trouve l’emplacement du fichier à modifier dans notre installation de Quartz : quartz/plugins/transformers/syntax.ts.

Et voici comment il faut le modifier pour inclure notre nouveau langage :

syntax.ts
import { QuartzTransformerPlugin } from "../types"
import rehypePrettyCode, { Options as CodeOptions, Theme as CodeTheme } from "rehype-pretty-code"
import { getHighlighter } from "shiki";
import { readFileSync } from "fs";
 
interface Theme extends Record<string, CodeTheme> {
  light: CodeTheme
  dark: CodeTheme
}
 
interface Options {
  theme?: Theme
  keepBackground?: boolean
  getHighlighter?(options: BundledHighlighterOptions): Promise<Highlighter>
}
 
const defaultOptions: Options = {
  theme: {
    light: "github-light",
    dark: "github-dark",
  },
  keepBackground: false,
  getHighlighter: (options) =>
    getHighlighter({
      ...options,
      langs: [
        () => JSON.parse(readFileSync("/random_location_on_your_machine/rosScript.tmLanguage.json", "utf-8")),
      ],
    }),
}
 
export const SyntaxHighlighting: QuartzTransformerPlugin<Options> = (
  userOpts?: Partial<Options>,
) => {
  const opts: Partial<CodeOptions> = { ...defaultOptions, ...userOpts }
 
  return {
    name: "SyntaxHighlighting",
    htmlPlugins() {
      return [[rehypePrettyCode, opts]]
    },
  }
}

Vérification

# RouterOS script: ip-addr-bridge
# Copyright (c) 2018-2024 Christian Hesse <mail@eworm.de>
# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
#
# enable or disable ip addresses based on bridge port state
# https://git.eworm.de/cgit/routeros-scripts/about/doc/ip-addr-bridge.md
 
:foreach Bridge in=[ /interface/bridge/find ] do={
  :local BrName [ /interface/bridge/get $Bridge name ];
  :if ([ :len [ /interface/bridge/port/find where bridge=$BrName ] ] > 0) do={
    :if ([ :len [ /interface/bridge/port/find where bridge=$BrName and inactive=no ] ] = 0) do={
      /ip/address/disable [ find where !dynamic interface=$BrName ];
    } else={
      /ip/address/enable [ find where !dynamic interface=$BrName ];
    }
  }
}