20240517_localmd.rst (9882B)
1 Support Your Local Viewer 2 ######################### 3 4 :date: 2024-07-07 21:03:40 5 :updated: 2024-07-21 01:53:05 6 :category: Offlining 7 :author: Louis Holbrook 8 :tags: bash,markdown,pandoc,vimb,w3m,lynx,xdg 9 :slug: local-markdown 10 :summary: Bash script to render and spawn a viewer for markdown files 11 :lang: en 12 :status: published 13 14 15 Markdown is the fast-food of document formats. 16 17 That doesn't change the fact that it's everywhere. 18 19 So much everywhere, in fact, that it's kind of puzzling there is not a dedicated tool around to view it. 20 21 22 Pinning down markdown 23 ===================== 24 25 There is no shortage of applications that *can* render markdown. Among the alternatives are free code editors like Atom_ or Geany_, the browser plugin `Markdown Viewer`_ and even a dedicated markdown editor like Marktext_, retext_ and ghostwriter_. 26 27 And of course, there exist SaaS offerings such as hackmd_. But seeing as those are not alternatives for offline use, we don't concern ourselves with those here. 28 29 But what is the equivalent of sxiv_ or feh_ for Markdown? 30 31 Honestly, I couldn't find any such thing. If there is, I'd `love to know`_. 32 33 Fortunately, there is a perfectly reasonable workaround. 34 35 36 Step by step 37 ============ 38 39 After all, it is in the spirit of \*nixes to use a choice of tools who *does one thing and does it well*. 40 41 So, no matter how bizarre it feels, it may make sense that a lurid format like *Markdown* should be treated in a separate step to produce a more well-established - and less ambiguous - format. 42 43 I asked a related question on Stackexchange_ long ago, and there the ``pandoc`` tool came up as a solution. 44 45 And it turns out it works beautifully in this case aswell. 46 47 Consider the following script: 48 49 .. code-block:: bash 50 51 t=$(mktemp --suffix=.html) 52 2>&1 echo $t 53 pandoc -f gfm -t html -M document-css=false --standalone $1 > $t 2> /dev/null 54 w3m $t 55 56 57 Quite simply, we generate a standalone html file in ``tmpfs``, which in turn is read and renderered by a web browser. 58 59 60 Browsing browsers 61 ================= 62 63 No appreciation for w3m_, eh? Instead want that fuzzy feel of comforting colors, smooth scroll and fancy fonts? 64 65 I can understand. I used to suffer that affliction, too. 66 67 But nonetheless; it's an important point. For example, what if I wanted to use lynx_ or vimb_ instead? Choosing the browser to view with should definitely be the caller's call. 68 69 Is there a canonical way of doing that in Linux. 70 71 Kind of. 72 73 Let's review a couple of the options. 74 75 76 The environmental solution 77 -------------------------- 78 79 Some applications honor the ``$BROWSER`` environment variable. So let's cover for that: 80 81 .. code-block:: bash 82 83 browser=${BROWSER:-w3m} 84 t=$(mktemp --suffix=.html) 85 pandoc -f gfm -t html -M document-css=false --standalone $1 > $t 2> /dev/null 86 $browser $t 87 88 Now, viewing the markdown file ``README.md`` with ``lynx`` is as easy as: 89 90 .. code-block:: console 91 92 $ BROWSER=$(which lynx) bash wmd.sh README.md 93 94 95 The cross solution 96 ------------------ 97 98 So, have you heard about the Cross Desktop Group? [1]_ These are the guys you should be sending a thought of gratitude every time you intuitively look in your ``~/.config`` or ``~/.local`` folder for application data. And if you're a **HUIf** [2]_ coder, chances are you have seen the string ``xdg`` in some function call somewhere. 99 100 These days they go by the name `Free Desktop Group`_, and among other things they have negotiated something particularly useful to us in this particular case. 101 102 To launch programs in a desktop environment in Linux, a `Desktop Entry Specification`_ file format [3]_ has been defined - appropriately suffixed ``.desktop``. 103 104 Take a look at a random ``*.desktop`` file in ``/usr/share/applications`` (your most likely default ``$XDG_DATA_DIRS`` location, where xdg searches for desktop files). Every one will contain an top-level ``Exec=`` entry. 105 106 For example, my ``feh.desktop`` entry has ``Exec=feh --start-at %u`` which means find ``feh`` in ``$PATH`` and execute it with the ``--start-at`` switch and one single *url* as argument: 107 108 .. code-block:: ini 109 110 [Desktop Entry] 111 Name=Feh 112 Name[en_US]=feh 113 GenericName=Image viewer 114 GenericName[en_US]=Image viewer 115 Comment=Image viewer and cataloguer 116 Exec=feh --start-at %u 117 Terminal=false 118 Type=Application 119 Icon=feh 120 Categories=Graphics;2DGraphics;Viewer; 121 MimeType=image/bmp;image/gif;image/jpeg;image/jpg;image/pjpeg;image/png;image/tiff;image/webp;image/x-bmp;image/x-pcx;image/x-png;image/x-portable-anymap;image/x-portable-bitmap;image/x-portable-graymap;image/x-portable-pixmap;image/x-tga;image/x-xbitmap;image/heic; 122 NoDisplay=true 123 124 Covering this case in our script: 125 126 .. code-block:: bash 127 128 fallbackbrowsercmd=w3m 129 browsercmd= 130 # if browser env exists, then 131 # try handle it as a desktop entry 132 if [ ! -z "$BROWSER" ]; then 133 # find the xdg paths 134 XDG_DATA_DIRS=${XDG_DATA_DIRS:-/usr/share} 135 if [ ! -z "$XDG_DATA_HOME" ]; then 136 XDG_DATA_DIRS=$XDG_DATA_HOME:$XDG_DATA_DIRS 137 fi 138 # split on ":" and try dirs one by one 139 # use first matching browser desktop entry 140 _ifs=$IFS 141 IFS=: 142 dirs=("$XDG_DATA_DIRS") 143 for d in $dirs; do 144 s=$BROWSER.desktop 145 a=$d/applications/$BROWSER.desktop 146 if [ -f "$a" ]; then 147 browsercmd="gtk-launch $s" 148 fi 149 done 150 IFS= 151 fi 152 # if no browser set or could not be found in xdg, 153 # then try the browser env var as command, or 154 # ultimately the static fallback 155 if [ -z "$browsercmd" ]; then 156 browsercmd=${BROWSER:-$fallbackbrowsercmd} 157 fi 158 t=$(mktemp --suffix=.html) 159 pandoc -f gfm -t html -M document-css=false --standalone $1 > $t 2> /dev/null 160 $browsercmd $t 161 162 163 The default solution 164 -------------------- 165 166 Yes, there is such a thing as "default web browser" in ``XDG``, too. 167 168 Have a look at ``xdg-settings --list``. On my system, it shows a rather modest output: 169 170 .. code-block:: console 171 172 $ xdg-settings --list 173 Known properties: 174 default-url-scheme-handler Default handler for URL scheme 175 default-web-browser Default web browser 176 177 And the default web browser is: 178 179 .. code-block:: console 180 181 $ xdg-settings get default-web-browser 182 brave-browser.desktop 183 184 Yeah, yeah, yeah. Busted. I use graphical browsers, too. 185 186 Now let's add this to the mix, then. 187 188 .. code-block:: bash 189 190 fallbackbrowsercmd=w3m 191 browsercmd= 192 # if browser env var not set, set it with default browser 193 if [ -z "$BROWSER" ]; then 194 BROWSER=$(xdg-settings get default-web-browser) 195 BROWSER=${BROWSER%%.*} 196 fi 197 if [ ! -z "$BROWSER" ]; then 198 XDG_DATA_DIRS=${XDG_DATA_DIRS:-/usr/share} 199 if [ ! -z "$XDG_DATA_HOME" ]; then 200 XDG_DATA_DIRS=$XDG_DATA_HOME:$XDG_DATA_DIRS 201 fi 202 _ifs=$IFS 203 IFS=: 204 dirs=("$XDG_DATA_DIRS") 205 for d in $dirs; do 206 s=$BROWSER.desktop 207 a=$d/applications/$BROWSER.desktop 208 if [ -f "$a" ]; then 209 browsercmd="gtk-launch $s" 210 fi 211 done 212 IFS= 213 fi 214 if [ -z "$browsercmd" ]; then 215 browsercmd=${BROWSER:-$fallbackbrowsercmd} 216 fi 217 t=$(mktemp --suffix=.html) 218 pandoc -f gfm -t html -M document-css=false --standalone $1 > $t 2> /dev/null 219 $browsercmd $t 220 221 222 Naming the executioner 223 ====================== 224 225 So now we have a markdown viewer. And it can be as lean or as heavy as you want it to be. It's all to the browser you choose. 226 227 To make it accessible, map the script as a command alias in your ``.profile`` or ``.bashrc`` and you have the viewer at your fingertips. 228 229 I call mine ``wmd``: 230 231 232 .. code-block:: bash 233 234 alias wmd="bash $HOME/scripts/markdown.sh" 235 236 And voilá: 237 238 .. code-block:: console 239 240 $ export BROWSER=lynx 241 $ wmd README.md 242 243 .. 244 245 .. [1] Or X Desktop Group, as they were originally called. 246 247 248 .. 249 250 .. [2] Human User Interface. Don't bother looking - I made it up. As the cause of AI, robots and machines inevitably will become appropriated by the woke intersectionality complex, the terminology is probably going to need such distictions. 251 252 253 .. 254 255 .. [3] Actually the format is ini_. The standard is rather in the file naming, really. 256 257 .. _Free Desktop Group: https://www.freedesktop.org/ 258 259 .. _Desktop Entry Specification: https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html 260 261 .. _Atom: https://atom-editor.cc/ 262 263 .. _Geany: https://plugins.geany.org/markdown.html 264 265 .. _Markdown Viewer: https://chromewebstore.google.com/detail/markdown-viewer/ckkdlimhmcjmikdlpkmbgfkaikojcbjk 266 267 .. _Marktext: https://github.com/marktext/marktext 268 269 .. _Stackexchange: https://tex.stackexchange.com/questions/341899/latex-to-markdown-converter 270 271 .. _w3m: https://w3m.sourceforge.net 272 273 .. _lynx: https://lynx.browser.org 274 275 .. _vimb: https://fanglingsu.github.io/vimb 276 277 .. _sxiv: https://github.com/muennich/sxiv 278 279 .. _feh: https://feh.finalrewind.org 280 281 .. _hackmd: https://hackmd.io 282 283 .. _love to know: https://holbrook.no/msg 284 285 .. _ini: https://en.wikipedia.org/wiki/INI_file 286 287 .. _retext: https://github.com/retext-project/retext 288 289 .. _ghostwriter: https://ghostwriter.kde.org/