20240517_localmd.rst (9725B)
1 Support Your Local Viewer 2 ######################### 3 4 :date: 2024-07-07 21:03:40 5 :category: Offlining 6 :author: Louis Holbrook 7 :tags: bash,markdown,pandoc,vimb,w3m,lynx,xdg 8 :slug: local-markdown 9 :summary: Bash script to render and spawn a viewer for markdown files 10 :lang: en 11 :status: published 12 13 14 Markdown is the fast-food of document formats. 15 16 That doesn't change the fact that it's everywhere. 17 18 So much everywhere, in fact, that it's kind of puzzling there is not a dedicated tool around to view it. 19 20 21 Pinning down markdown 22 ===================== 23 24 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_. 25 26 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. 27 28 But what is the equivalent of sxiv_ or feh_ for Markdown? 29 30 Honestly, I couldn't find any such thing. If there is, I'd `love to know`_. 31 32 Fortunately, there is a perfectly reasonable workaround. 33 34 35 Step by step 36 ============ 37 38 After all, it is in the spirit of \*nixes to use a choice of tools who *does one thing and does it well*. 39 40 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. 41 42 I asked a related question on Stackexchange_ long ago, and there the ``pandoc`` tool came up as a solution. 43 44 And it turns out it works beautifully in this case aswell. 45 46 Consider the following script: 47 48 .. code-block:: bash 49 50 t=$(mktemp --suffix=.html) 51 2>&1 echo $t 52 pandoc -f gfm -t html -M document-css=false --standalone $1 > $t 2> /dev/null 53 w3m $t 54 55 56 Quite simply, we generate a standalone html file in ``tmpfs``, which in turn is read and renderered by a web browser. 57 58 59 Browsing browsers 60 ================= 61 62 No appreciation for w3m_, eh? Instead want that fuzzy feel of comforting colors, smooth scroll and fancy fonts? 63 64 I can understand. I used to suffer that affliction, too. 65 66 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. 67 68 Is there a canonical way of doing that in Linux. 69 70 Kind of. 71 72 Let's review a couple of the options. 73 74 75 The environmental solution 76 -------------------------- 77 78 Some applications honor the ``$BROWSER`` environment variable. So let's cover for that: 79 80 .. code-block:: bash 81 82 browser=${BROWSER:-w3m} 83 t=$(mktemp --suffix=.html) 84 pandoc -f gfm -t html -M document-css=false --standalone $1 > $t 2> /dev/null 85 $browser $t 86 87 Now, viewing the markdown file ``README.md`` with ``lynx`` is as easy as: 88 89 .. code-block:: console 90 91 $ BROWSER=$(which lynx) bash wmd.sh README.md 92 93 94 The cross solution 95 ------------------ 96 97 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. 98 99 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. 100 101 To launch programs in a desktop environment in Linux, a `Desktop Entry Specification`_ file format [3]_ has been defined - appropriately suffixed ``.desktop``. 102 103 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. 104 105 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: 106 107 .. code-block:: ini 108 109 [Desktop Entry] 110 Name=Feh 111 Name[en_US]=feh 112 GenericName=Image viewer 113 GenericName[en_US]=Image viewer 114 Comment=Image viewer and cataloguer 115 Exec=feh --start-at %u 116 Terminal=false 117 Type=Application 118 Icon=feh 119 Categories=Graphics;2DGraphics;Viewer; 120 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; 121 NoDisplay=true 122 123 Covering this case in our script: 124 125 .. code-block:: bash 126 127 fallbackbrowsercmd=w3m 128 browsercmd= 129 # if browser env exists, then 130 # try handle it as a desktop entry 131 if [ ! -z "$BROWSER" ]; then 132 # find the xdg paths 133 XDG_DATA_DIRS=${XDG_DATA_DIRS:-/usr/share} 134 if [ ! -z "$XDG_DATA_HOME" ]; then 135 XDG_DATA_DIRS=$XDG_DATA_HOME:$XDG_DATA_DIRS 136 fi 137 # split on ":" and try dirs one by one 138 # use first matching browser desktop entry 139 _ifs=$IFS 140 IFS=: 141 dirs=("$XDG_DATA_DIRS") 142 for d in $dirs; do 143 s=$BROWSER.desktop 144 a=$d/applications/$BROWSER.desktop 145 if [ -f "$a" ]; then 146 browsercmd="gtk-launch $s" 147 fi 148 done 149 IFS= 150 fi 151 # if no browser set or could not be found in xdg, 152 # then try the browser env var as command, or 153 # ultimately the static fallback 154 if [ -z "$browsercmd" ]; then 155 browsercmd=${BROWSER:-$fallbackbrowsercmd} 156 fi 157 t=$(mktemp --suffix=.html) 158 pandoc -f gfm -t html -M document-css=false --standalone $1 > $t 2> /dev/null 159 $browsercmd $t 160 161 162 The default solution 163 -------------------- 164 165 Yes, there is such a thing as "default web browser" in ``XDG``, too. 166 167 Have a look at ``xdg-settings --list``. On my system, it shows a rather modest output: 168 169 .. code-block:: console 170 171 $ xdg-settings --list 172 Known properties: 173 default-url-scheme-handler Default handler for URL scheme 174 default-web-browser Default web browser 175 176 And the default web browser is: 177 178 .. code-block:: console 179 180 $ xdg-settings get default-web-browser 181 brave-browser.desktop 182 183 Yeah, yeah, yeah. Busted. I use graphical browsers, too. 184 185 Now let's add this to the mix, then. 186 187 .. code-block:: bash 188 189 fallbackbrowsercmd=w3m 190 browsercmd= 191 # if browser env var not set, set it with default browser 192 if [ -z "$BROWSER" ]; then 193 BROWSER=$(xdg-settings get default-web-browser) 194 BROWSER=${BROWSER%%.*} 195 fi 196 if [ ! -z "$BROWSER" ]; then 197 XDG_DATA_DIRS=${XDG_DATA_DIRS:-/usr/share} 198 if [ ! -z "$XDG_DATA_HOME" ]; then 199 XDG_DATA_DIRS=$XDG_DATA_HOME:$XDG_DATA_DIRS 200 fi 201 _ifs=$IFS 202 IFS=: 203 dirs=("$XDG_DATA_DIRS") 204 for d in $dirs; do 205 s=$BROWSER.desktop 206 a=$d/applications/$BROWSER.desktop 207 if [ -f "$a" ]; then 208 browsercmd="gtk-launch $s" 209 fi 210 done 211 IFS= 212 fi 213 if [ -z "$browsercmd" ]; then 214 browsercmd=${BROWSER:-$fallbackbrowsercmd} 215 fi 216 t=$(mktemp --suffix=.html) 217 pandoc -f gfm -t html -M document-css=false --standalone $1 > $t 2> /dev/null 218 $browsercmd $t 219 220 221 Naming the executioner 222 ====================== 223 224 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. 225 226 To make it accessible, map the script as a command alias in your ``.profile`` or ``.bashrc`` and you have the viewer at your fingertips. 227 228 I call mine ``wmd``: 229 230 231 .. code-block:: bash 232 233 alias wmd="bash $HOME/scripts/markdown.sh" 234 235 And voilá: 236 237 .. code-block:: console 238 239 $ export BROWSER=lynx 240 $ wmd README.md 241 242 .. 243 244 .. [1] Or X Desktop Group, as they were originally called. 245 246 247 .. 248 249 .. [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. 250 251 252 .. 253 254 .. [3] Actually the format is ini_. The standard is rather in the file naming, really. 255 256 .. _Free Desktop Group: https://www.freedesktop.org/ 257 258 .. _Desktop Entry Specification: https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html 259 260 .. _Atom: https://atom-editor.cc/ 261 262 .. _Geany: https://plugins.geany.org/markdown.html 263 264 .. _Markdown Viewer: https://chromewebstore.google.com/detail/markdown-viewer/ckkdlimhmcjmikdlpkmbgfkaikojcbjk 265 266 .. _Marktext: https://github.com/marktext/marktext 267 268 .. _Stackexchange: https://tex.stackexchange.com/questions/341899/latex-to-markdown-converter 269 270 .. _w3m: https://w3m.sourceforge.net 271 272 .. _lynx: https://lynx.browser.org 273 274 .. _vimb: https://fanglingsu.github.io/vimb 275 276 .. _sxiv: https://github.com/muennich/sxiv 277 278 .. _feh: https://feh.finalrewind.org 279 280 .. _hackmd: https://hackmd.io 281 282 .. _love to know: https://holbrook.no/msg 283 284 .. _ini: https://en.wikipedia.org/wiki/INI_file