{-# LANGUAGE OverloadedStrings #-}
{- |
   Module      : Text.Pandoc.Writers.MediaWiki
   Copyright   : Copyright (C) 2008-2024 John MacFarlane
   License     : GNU GPL, version 2 or above

   Maintainer  : John MacFarlane <jgm@berkeley.edu>
   Stability   : alpha
   Portability : portable

Conversion of 'Pandoc' documents to MediaWiki markup.

MediaWiki:  <http://www.mediawiki.org/wiki/MediaWiki>
-}
module Text.Pandoc.Writers.MediaWiki ( writeMediaWiki, highlightingLangs ) where
import Control.Monad.Reader
import Control.Monad.State.Strict
import Data.Maybe (fromMaybe)
import qualified Data.List as DL
import qualified Data.Set as Set
import Data.Text (Text)
import qualified Data.Text as T
import Data.List.NonEmpty (NonEmpty((:|)))
import Text.Pandoc.Class.PandocMonad (PandocMonad, report)
import Text.Pandoc.Definition
import Text.Pandoc.ImageSize
import Text.Pandoc.Logging
import Text.Pandoc.Options
import Text.Pandoc.Walk
import Text.DocLayout (render, literal)
import Text.Pandoc.Shared
import Text.Pandoc.URI
import Text.Pandoc.Templates (renderTemplate)
import qualified Text.Pandoc.Writers.AnnotatedTable as Ann
import Text.Pandoc.Writers.Shared
import Text.Pandoc.XML (escapeStringForXML)

data WriterState = WriterState {
    stNotes   :: Bool            -- True if there are notes
  , stOptions :: WriterOptions   -- writer options
  , stInDefLabel :: Bool         -- True if in definition list label
  }

data WriterReader = WriterReader {
    options   :: WriterOptions -- Writer options
  , listLevel :: [Char]        -- String at beginning of list items, e.g. "**"
  , useTags   :: Bool          -- True if we should use HTML tags because we're in a complex list
  }

type MediaWikiWriter m = ReaderT WriterReader (StateT WriterState m)

-- | Convert Pandoc to MediaWiki.
writeMediaWiki :: PandocMonad m => WriterOptions -> Pandoc -> m Text
writeMediaWiki opts document =
  let initialState = WriterState {
        stNotes = False, stOptions = opts, stInDefLabel = False }
      env = WriterReader { options = opts, listLevel = [], useTags = False }
  in  evalStateT (runReaderT (pandocToMediaWiki document) env) initialState

-- | Return MediaWiki representation of document.
pandocToMediaWiki :: PandocMonad m => Pandoc -> MediaWikiWriter m Text
pandocToMediaWiki (Pandoc meta blocks) = do
  opts <- asks options
  metadata <- metaToContext opts
              (fmap (literal . trimr) . blockListToMediaWiki)
              (fmap (literal . trimr) . inlineListToMediaWiki)
              meta
  body <- blockListToMediaWiki blocks
  notesExist <- gets stNotes
  let notes = if notesExist
                 then "\n<references />"
                 else ""
  let main = body <> notes
  let context = defField "body" main
                $ defField "toc" (writerTableOfContents opts) metadata
  return $
    case writerTemplate opts of
         Nothing  -> main
         Just tpl -> render Nothing $ renderTemplate tpl context

-- | Escape special characters for MediaWiki.
escapeText :: Text -> Text
escapeText =  escapeStringForXML

-- | Convert Pandoc block element to MediaWiki.
blockToMediaWiki :: PandocMonad m
                 => Block         -- ^ Block element
                 -> MediaWikiWriter m Text

blockToMediaWiki (Div attrs bs) = do
  contents <- blockListToMediaWiki bs
  return $ render Nothing (tagWithAttrs "div" attrs) <> "\n\n" <>
                     contents <> "\n\n" <> "</div>"

blockToMediaWiki (Plain inlines) =
  inlineListToMediaWiki inlines

blockToMediaWiki (SimpleFigure attr txt (src, tit)) = do
  capt <- inlineListToMediaWiki txt
  img  <- imageToMediaWiki attr
  let opt = if T.null tit
               then
                 if T.null capt
                    then ""
                    else "alt=" <> capt
               else "alt=" <> tit
  return $ "[[" <>
            T.intercalate "|"
            (filter (not . T.null) ["File:" <> src
                                 , "thumb"
                                 , "none"
                                 , img
                                 , opt
                                 , capt
                                 ]) <>
            "]]\n"

blockToMediaWiki (Para inlines) = do
  tags <- asks useTags
  lev <- asks listLevel
  contents <- inlineListToMediaWiki inlines
  let initEsc = if startsWithListMarker contents -- #9700
                   then "\\"
                   else ""
  return $ if tags
              then  "<p>" <> contents <> "</p>"
              else initEsc <> contents <> if null lev then "\n" else ""

blockToMediaWiki (LineBlock lns) =
  blockToMediaWiki $ linesToPara lns

blockToMediaWiki b@(RawBlock f str)
  | f == Format "mediawiki" = return str
  | f == Format "html"      = return str
  | otherwise               = "" <$ report (BlockNotRendered b)

blockToMediaWiki HorizontalRule = return "\n-----\n"

blockToMediaWiki (Header level (ident,_,_) inlines) = do
  let autoId = T.replace " " "_" $ stringify inlines
  contents <- inlineListToMediaWiki inlines
  let eqs = T.replicate level "="
  return $
    (if T.null ident || autoId == ident
        then ""
        else "<span id=\"" <> ident <> "\"></span>\n")
    <> eqs <> " " <> contents <> " " <> eqs <> "\n"

blockToMediaWiki (CodeBlock (_,classes,keyvals) str) = do
  let at  = Set.fromList classes `Set.intersection` highlightingLangs
  let numberLines = any (`elem` ["number","numberLines", "number-lines"])
                    classes
  let start = lookup "startFrom" keyvals
  return $
    case Set.toList at of
       [] -> "<pre" <> (if null classes
                           then ">"
                           else " class=\"" <> T.unwords classes <> "\">") <>
             escapeText str <> "</pre>"
       (l:_) -> "<syntaxhighlight lang=\"" <> l <> "\"" <>
                (if numberLines then " line" else "") <>
                maybe "" (\x -> " start=\"" <> x <> "\"") start <>
                ">" <> str <>
                "</syntaxhighlight>"
            -- note:  no escape!  even for <!

blockToMediaWiki (BlockQuote blocks) = do
  contents <- blockListToMediaWiki blocks
  return $ "<blockquote>" <> contents <> "</blockquote>"

blockToMediaWiki (Table attr capt colSpecs thead tbody tfoot) = do
    tableToMediaWiki (Ann.toTable attr capt colSpecs thead tbody tfoot)

blockToMediaWiki x@(BulletList items) = do
  tags <-
    (|| not (isSimpleList x)) <$> asks useTags
  if tags
     then do
        contents <- local (\ s -> s { useTags = True }) $ mapM listItemToMediaWiki items
        return $ "<ul>\n" <> vcat contents <> "</ul>\n"
     else do
        lev <- asks listLevel
        contents <- local (\s -> s { listLevel = listLevel s <> "*" }) $ mapM listItemToMediaWiki items
        return $ vcat contents <> if null lev then "\n" else ""

blockToMediaWiki x@(OrderedList attribs items) = do
  tags <-
    (|| not (isSimpleList x)) <$> asks useTags
  if tags
     then do
        contents <- local (\s -> s { useTags = True }) $ mapM listItemToMediaWiki items
        return $ "<ol" <> listAttribsToText attribs <> ">\n" <> vcat contents <> "</ol>\n"
     else do
        lev <- asks listLevel
        contents <- local (\s -> s { listLevel = listLevel s <> "#" }) $ mapM listItemToMediaWiki items
        return $ vcat contents <> if null lev then "\n" else ""

blockToMediaWiki x@(DefinitionList items) = do
  tags <-
    (|| not (isSimpleList x)) <$> asks useTags
  if tags
     then do
        contents <- local (\s -> s { useTags = True }) $ mapM definitionListItemToMediaWiki items
        return $ "<dl>\n" <> vcat contents <> "</dl>\n"
     else do
        lev <- asks listLevel
        contents <- local (\s -> s { listLevel = listLevel s <> ";" }) $ mapM definitionListItemToMediaWiki items
        return $ vcat contents <> if null lev then "\n" else ""

blockToMediaWiki (Figure (ident, classes, kvs) _ body) =
  blockToMediaWiki (Div (ident, ["figure"] `DL.union` classes, kvs) body)

-- Auxiliary functions for lists:

-- | Convert ordered list attributes to HTML attribute string
listAttribsToText :: ListAttributes -> Text
listAttribsToText (startnum, numstyle, _) =
  let numstyle' = camelCaseToHyphenated $ tshow numstyle
  in  (if startnum /= 1
          then " start=\"" <> tshow startnum <> "\""
          else "") <>
      (if numstyle /= DefaultStyle
          then " style=\"list-style-type: " <> numstyle' <> ";\""
          else "")

-- | Convert bullet or ordered list item (list of blocks) to MediaWiki.
listItemToMediaWiki :: PandocMonad m => [Block] -> MediaWikiWriter m Text
listItemToMediaWiki items = do
  contents <- blockListToMediaWiki items
  tags <- asks useTags
  if tags
     then return $ "<li>" <> contents <> "</li>"
     else do
       marker <- asks listLevel
       return $ T.pack marker <> " " <> contents

-- | Convert definition list item (label, list of blocks) to MediaWiki.
definitionListItemToMediaWiki :: PandocMonad m
                              => ([Inline],[[Block]])
                              -> MediaWikiWriter m Text
definitionListItemToMediaWiki (label, items) = do
  modify $ \st -> st{ stInDefLabel = True }
  labelText <- inlineListToMediaWiki label
  modify $ \st -> st{ stInDefLabel = False }
  contents <- mapM blockListToMediaWiki items
  tags <- asks useTags
  if tags
     then return $ "<dt>" <> labelText <> "</dt>\n" <>
           T.intercalate "\n" (map (\d -> "<dd>" <> d <> "</dd>") contents)
     else do
       marker <- asks listLevel
       return $ T.pack marker <> " " <> labelText <> "\n" <>
           T.intercalate "\n" (map (\d -> T.pack (init marker) <> ": " <> d) contents)

-- | True if the list can be handled by simple wiki markup, False if HTML tags will be needed.
isSimpleList :: Block -> Bool
isSimpleList x =
  case x of
       BulletList items                 -> all isSimpleListItem items
       OrderedList (num, sty, _) items  -> all isSimpleListItem items &&
                                            num == 1 && sty `elem` [DefaultStyle, Decimal]
       DefinitionList items             -> all isSimpleListItem $ concatMap snd items
       _                                -> False

-- | True if list item can be handled with the simple wiki syntax.  False if
--   HTML tags will be needed.
isSimpleListItem :: [Block] -> Bool
isSimpleListItem []  = True
isSimpleListItem [x] =
  case x of
       Plain _          -> True
       Para  _          -> True
       BulletList _     -> isSimpleList x
       OrderedList _ _  -> isSimpleList x
       DefinitionList _ -> isSimpleList x
       _                -> False
isSimpleListItem [x, y] | isPlainOrPara x =
  case y of
       BulletList _     -> isSimpleList y
       OrderedList _ _  -> isSimpleList y
       DefinitionList _ -> isSimpleList y
       _                -> False
isSimpleListItem _ = False

isPlainOrPara :: Block -> Bool
isPlainOrPara (Plain _) = True
isPlainOrPara (Para  _) = True
isPlainOrPara _         = False

-- | Concatenates strings with line breaks between them.
vcat :: [Text] -> Text
vcat = T.intercalate "\n"

-- Auxiliary functions for tables:

tableToMediaWiki :: PandocMonad m => Ann.Table -> MediaWikiWriter m Text
tableToMediaWiki (Ann.Table attr capt _ thead tbodies tfoot) = do
    let (ident,classes,kvs) = attr
    caption <- case capt of
        Caption _ [] -> return mempty
        Caption _ longCapt -> do
            c <- blockListToMediaWiki longCapt
            return [ "|+ " <> trimr c ]
    head' <- tableHeadToMW thead
    bodies' <- concat <$> mapM tableBodyToMW tbodies
    foot' <- tableFootToMW tfoot
    return $ T.unlines $ [
            "{|" <> (render Nothing (htmlAttrs (ident, "wikitable":classes, kvs)))
        ] <> caption <> head' <> bodies' <> foot' <> [
            "|}"
        ]

tableHeadToMW :: PandocMonad m => Ann.TableHead -> MediaWikiWriter m [Text]
tableHeadToMW (Ann.TableHead _ rows) = headerRowsToMW rows

tableFootToMW :: PandocMonad m => Ann.TableFoot -> MediaWikiWriter m [Text]
tableFootToMW (Ann.TableFoot _ rows) = headerRowsToMW rows

tableBodyToMW :: PandocMonad m => Ann.TableBody -> MediaWikiWriter m [Text]
tableBodyToMW (Ann.TableBody _ _ headerRows bodyRows) = do
  headerRows' <- headerRowsToMW headerRows
  bodyRows' <- bodyRowsToMW bodyRows
  return $ headerRows' <> bodyRows'

headerRowsToMW :: PandocMonad m => [Ann.HeaderRow] -> MediaWikiWriter m [Text]
headerRowsToMW rows = (\x -> mconcat x) <$> mapM headerRowToMW rows

headerRowToMW :: PandocMonad m => Ann.HeaderRow -> MediaWikiWriter m [Text]
headerRowToMW (Ann.HeaderRow attr _ cells) = do
  cells' <- (\x -> mconcat x) <$> mapM (cellToMW "!") cells
  return $ ["|-" <> (render Nothing (htmlAttrs attr))] <> cells'

bodyRowsToMW :: PandocMonad m => [Ann.BodyRow] -> MediaWikiWriter m [Text]
bodyRowsToMW rows = (\x -> mconcat x) <$> mapM bodyRowToMW rows

bodyRowToMW :: PandocMonad m => Ann.BodyRow -> MediaWikiWriter m [Text]
bodyRowToMW (Ann.BodyRow attr _ headCells bodyCells) = do
  headCells' <- (\x -> mconcat x) <$> mapM (cellToMW "!") headCells
  bodyCells' <- (\x -> mconcat x) <$> mapM (cellToMW "|") bodyCells
  return $ ["|-" <> (render Nothing (htmlAttrs attr))] <> headCells' <> bodyCells'

cellToMW :: PandocMonad m => Text -> Ann.Cell -> MediaWikiWriter m [Text]
cellToMW marker (Ann.Cell (colSpec :| _) _ (Cell attr align rowspan colspan content)) = do
  content' <- blockListToMediaWiki content
  let (ident,classes,keyVals) = attr

  let align' = case align of
        AlignDefault -> fst colSpec
        _            -> align
  let keyVals' = case (htmlAlignmentToString align') of
                  Nothing -> keyVals
                  Just alignStr -> htmlAddStyle ("text-align", alignStr) keyVals
  let rowspan' = case rowspan of
                  RowSpan 1 -> mempty
                  RowSpan n -> [("rowspan", T.pack(show n))]
  let colspan' = case colspan of
                  ColSpan 1 -> mempty
                  ColSpan n -> [("colspan", T.pack(show n))]
  let attrs' = addPipeIfNotEmpty (render Nothing (htmlAttrs (ident, classes, rowspan' <> colspan' <> keyVals')))
  return [marker <> attrs' <> addSpaceIfNotEmpty(content')]

addPipeIfNotEmpty :: Text -> Text
addPipeIfNotEmpty f = if T.null f then f else f <> "|"

addSpaceIfNotEmpty :: Text -> Text
addSpaceIfNotEmpty f = if T.null f then f else " " <> f

imageToMediaWiki :: PandocMonad m => Attr -> MediaWikiWriter m Text
imageToMediaWiki attr = do
  opts <- gets stOptions
  let (_, cls, _) = attr
      toPx = fmap (showInPixel opts) . checkPct
      checkPct (Just (Percent _)) = Nothing
      checkPct maybeDim           = maybeDim
      go (Just w) Nothing  = w <> "px"
      go (Just w) (Just h) = w <> "x" <> h <> "px"
      go Nothing  (Just h) = "x" <> h <> "px"
      go Nothing  Nothing  = ""
      dims = go (toPx $ dimension Width attr) (toPx $ dimension Height attr)
      classes = if null cls
                   then ""
                   else "class=" <> T.unwords cls
  return $ T.intercalate "|" $ filter (not . T.null) [dims, classes]

-- | Convert list of Pandoc block elements to MediaWiki.
blockListToMediaWiki :: PandocMonad m
                     => [Block]       -- ^ List of block elements
                     -> MediaWikiWriter m Text
blockListToMediaWiki blocks =
  vcat <$> mapM blockToMediaWiki blocks

-- | Convert list of Pandoc inline elements to MediaWiki.
inlineListToMediaWiki :: PandocMonad m => [Inline] -> MediaWikiWriter m Text
inlineListToMediaWiki lst =
  fmap T.concat $ mapM inlineToMediaWiki $ fixup lst
    where
     fixup [] = []
     fixup (Str t : x : xs)
       | not (T.null t) && T.last t == '['
       , isLinkOrImage x =
          Str t : RawInline (Format "mediawiki") "<nowiki/>" : x : fixup xs
     fixup (x:xs) = x : fixup xs
     isLinkOrImage Link{}  = True
     isLinkOrImage Image{} = True
     isLinkOrImage _         = False

-- | Convert Pandoc inline element to MediaWiki.
inlineToMediaWiki :: PandocMonad m => Inline -> MediaWikiWriter m Text

inlineToMediaWiki (Span attrs ils) = do
  contents <- inlineListToMediaWiki ils
  return $ render Nothing (tagWithAttrs "span" attrs) <> contents <> "</span>"

inlineToMediaWiki (Emph lst) = do
  contents <- inlineListToMediaWiki lst
  return $ "''" <> contents <> "''"

inlineToMediaWiki (Underline lst) = do
  contents <- inlineListToMediaWiki lst
  return $ "<u>" <> contents <> "</u>"

inlineToMediaWiki (Strong lst) = do
  contents <- inlineListToMediaWiki lst
  return $ "'''" <> contents <> "'''"

inlineToMediaWiki (Strikeout lst) = do
  contents <- inlineListToMediaWiki lst
  return $ "<s>" <> contents <> "</s>"

inlineToMediaWiki (Superscript lst) = do
  contents <- inlineListToMediaWiki lst
  return $ "<sup>" <> contents <> "</sup>"

inlineToMediaWiki (Subscript lst) = do
  contents <- inlineListToMediaWiki lst
  return $ "<sub>" <> contents <> "</sub>"

inlineToMediaWiki (SmallCaps lst) = inlineListToMediaWiki lst

inlineToMediaWiki (Quoted SingleQuote lst) = do
  contents <- inlineListToMediaWiki lst
  return $ "\8216" <> contents <> "\8217"

inlineToMediaWiki (Quoted DoubleQuote lst) = do
  contents <- inlineListToMediaWiki lst
  return $ "\8220" <> contents <> "\8221"

inlineToMediaWiki (Cite _  lst) = inlineListToMediaWiki lst

inlineToMediaWiki (Code _ str) =
  return $ "<code>" <> escapeText str <> "</code>"

inlineToMediaWiki (Str str) = do
  inDefLabel <- gets stInDefLabel
  return $
    if inDefLabel
       then T.intercalate "<nowiki>:</nowiki>" $
              map escapeText $ T.splitOn ":" str
       else escapeText str

inlineToMediaWiki (Math mt str) = return $
  "<math display=\"" <>
  (if mt == DisplayMath then "block" else "inline") <>
  "\">" <> str <> "</math>"
  -- note:  str should NOT be escaped

inlineToMediaWiki il@(RawInline f str)
  | f == Format "mediawiki" = return str
  | f == Format "html"      = return str
  | otherwise               = "" <$ report (InlineNotRendered il)

inlineToMediaWiki LineBreak = return "<br />\n"

inlineToMediaWiki SoftBreak = do
  wrapText <- gets (writerWrapText . stOptions)
  listlevel <- asks listLevel
  case wrapText of
       WrapAuto     -> return " "
       WrapNone     -> return " "
       WrapPreserve -> if null listlevel
                          then return "\n"
                          else return " "

inlineToMediaWiki Space = return " "

inlineToMediaWiki (Link _ txt (src, _)) = do
  label <- inlineListToMediaWiki (removeLinks txt)
  case txt of
     [Str s] | isURI src && escapeURI s == src -> return src
     _  -> return $ if isURI src
       then "[" <> src <> " " <> label <> "]"
       else
         if src == label
           then "[[" <> src' <> "]]"
           else "[[" <> src' <> "|" <> label <> "]]"
       -- with leading / it's a link to a help page
       where src' = fromMaybe src $ T.stripPrefix "/" src

inlineToMediaWiki (Image attr alt (source, tit)) = do
  img  <- imageToMediaWiki attr
  alt' <- inlineListToMediaWiki alt
  let txt = if T.null alt'
               then if T.null tit
                       then ""
                       else tit
               else alt'
  return $ "[[" <>
           T.intercalate "|"
           (filter (not . T.null)
            [ "File:" <> source
            , img
            , txt
            ]) <> "]]"

inlineToMediaWiki (Note contents) = do
  contents' <- blockListToMediaWiki contents
  modify (\s -> s { stNotes = True })
  return $ "<ref>" <> stripTrailingNewlines contents' <> "</ref>"
  -- note - does not work for notes with multiple blocks

-- We need to remove links from link text, because an <a> element is
-- not allowed inside another <a> element.
removeLinks :: [Inline] -> [Inline]
removeLinks = walk go
 where
  go (Link _ ils _) = SmallCaps ils
  go x = x

highlightingLangs :: Set.Set Text
highlightingLangs = Set.fromList [
  "abap",
  "abl",
  "abnf",
  "aconf",
  "actionscript",
  "actionscript3",
  "ada",
  "ada2005",
  "ada95",
  "adl",
  "agda",
  "ahk",
  "alloy",
  "ambienttalk",
  "ambienttalk/2",
  "antlr",
  "antlr-actionscript",
  "antlr-as",
  "antlr-c#",
  "antlr-cpp",
  "antlr-csharp",
  "antlr-java",
  "antlr-objc",
  "antlr-perl",
  "antlr-python",
  "antlr-rb",
  "antlr-ruby",
  "apache",
  "apacheconf",
  "apl",
  "applescript",
  "arduino",
  "arexx",
  "as",
  "as3",
  "asm",
  "aspectj",
  "aspx-cs",
  "aspx-vb",
  "asy",
  "asymptote",
  "at",
  "autohotkey",
  "autoit",
  "awk",
  "b3d",
  "basemake",
  "bash",
  "basic",
  "bat",
  "batch",
  "bbcode",
  "because",
  "befunge",
  "bf",
  "blitzbasic",
  "blitzmax",
  "bmax",
  "bnf",
  "boo",
  "boogie",
  "bplus",
  "brainfuck",
  "bro",
  "bsdmake",
  "bugs",
  "c",
  "c#",
  "c++",
  "c++-objdumb",
  "c-objdump",
  "ca65",
  "cadl",
  "camkes",
  "cbmbas",
  "ceylon",
  "cf3",
  "cfc",
  "cfengine3",
  "cfg",
  "cfm",
  "cfs",
  "chai",
  "chaiscript",
  "chapel",
  "cheetah",
  "chpl",
  "cirru",
  "cl",
  "clay",
  "clipper",
  "clj",
  "cljs",
  "clojure",
  "clojurescript",
  "cmake",
  "cobol",
  "cobolfree",
  "coffee",
  "coffee-script",
  "coffeescript",
  "common-lisp",
  "componentpascal",
  "console",
  "control",
  "coq",
  "cp",
  "cpp",
  "cpp-objdump",
  "cpsa",
  "crmsh",
  "croc",
  "cry",
  "cryptol",
  "csh",
  "csharp",
  "csound",
  "csound-csd",
  "csound-document",
  "csound-orc",
  "csound-sco",
  "csound-score",
  "css",
  "css+django",
  "css+erb",
  "css+genshi",
  "css+genshitext",
  "css+jinja",
  "css+lasso",
  "css+mako",
  "css+mozpreproc",
  "css+myghty",
  "css+php",
  "css+ruby",
  "css+smarty",
  "cu",
  "cucumber",
  "cuda",
  "cxx-objdump",
  "cypher",
  "cython",
  "d",
  "d-objdump",
  "dart",
  "debcontrol",
  "debsources",
  "delphi",
  "dg",
  "diff",
  "django",
  "docker",
  "dockerfile",
  "dosbatch",
  "doscon",
  "dosini",
  "dpatch",
  "dtd",
  "duby",
  "duel",
  "dylan",
  "dylan-console",
  "dylan-lid",
  "dylan-repl",
  "earl-grey",
  "earlgrey",
  "easytrieve",
  "ebnf",
  "ec",
  "ecl",
  "eg",
  "eiffel",
  "elisp",
  "elixir",
  "elm",
  "emacs",
  "erb",
  "erl",
  "erlang",
  "evoque",
  "ex",
  "exs",
  "ezhil",
  "f#",
  "factor",
  "fan",
  "fancy",
  "felix",
  "fish",
  "fishshell",
  "flx",
  "fortran",
  "fortranfixed",
  "foxpro",
  "fsharp",
  "fy",
  "gap",
  "gas",
  "gawk",
  "genshi",
  "genshitext",
  "gherkin",
  "glsl",
  "gnuplot",
  "go",
  "golo",
  "gooddata-cl",
  "gosu",
  "groff",
  "groovy",
  "gst",
  "haml",
  "handlebars",
  "haskell",
  "haxe",
  "haxeml",
  "hexdump",
  "hs",
  "html",
  "html+cheetah",
  "html+django",
  "html+erb",
  "html+evoque",
  "html+genshi",
  "html+handlebars",
  "html+jinja",
  "html+kid",
  "html+lasso",
  "html+mako",
  "html+myghty",
  "html+php",
  "html+ruby",
  "html+smarty",
  "html+spitfire",
  "html+twig",
  "html+velocity",
  "htmlcheetah",
  "htmldjango",
  "http",
  "hx",
  "hxml",
  "hxsl",
  "hy",
  "hybris",
  "hylang",
  "i6",
  "i6t",
  "i7",
  "idl",
  "idl4",
  "idr",
  "idris",
  "iex",
  "igor",
  "igorpro",
  "ik",
  "inform6",
  "inform7",
  "ini",
  "io",
  "ioke",
  "irb",
  "irc",
  "isabelle",
  "j",
  "jade",
  "jags",
  "jasmin",
  "jasminxt",
  "java",
  "javascript",
  "javascript+cheetah",
  "javascript+django",
  "javascript+erb",
  "javascript+genshi",
  "javascript+genshitext",
  "javascript+jinja",
  "javascript+lasso",
  "javascript+mako",
  "javascript+mozpreproc",
  "javascript+myghty",
  "javascript+php",
  "javascript+ruby",
  "javascript+smarty",
  "javascript+spitfire",
  "jbst",
  "jcl",
  "jinja",
  "jl",
  "jlcon",
  "jproperties",
  "js",
  "js+cheetah",
  "js+django",
  "js+erb",
  "js+genshi",
  "js+genshitext",
  "js+jinja",
  "js+lasso",
  "js+mako",
  "js+myghty",
  "js+php",
  "js+ruby",
  "js+smarty",
  "js+spitfire",
  "json",
  "json-ld",
  "jsonld",
  "jsonml+bst",
  "jsp",
  "julia",
  "kal",
  "kconfig",
  "kernel-config",
  "kid",
  "koka",
  "kotlin",
  "ksh",
  "lagda",
  "lasso",
  "lassoscript",
  "latex",
  "lcry",
  "lcryptol",
  "lean",
  "less",
  "lhaskell",
  "lhs",
  "lid",
  "lidr",
  "lidris",
  "lighttpd",
  "lighty",
  "limbo",
  "linux-config",
  "liquid",
  "lisp",
  "literate-agda",
  "literate-cryptol",
  "literate-haskell",
  "literate-idris",
  "live-script",
  "livescript",
  "llvm",
  "logos",
  "logtalk",
  "lsl",
  "lua",
  "m2",
  "make",
  "makefile",
  "mako",
  "man",
  "maql",
  "mask",
  "mason",
  "mathematica",
  "matlab",
  "matlabsession",
  "mawk",
  "menuconfig",
  "mf",
  "minid",
  "mma",
  "modelica",
  "modula2",
  "moin",
  "monkey",
  "moo",
  "moocode",
  "moon",
  "moonscript",
  "mozhashpreproc",
  "mozpercentpreproc",
  "mq4",
  "mq5",
  "mql",
  "mql4",
  "mql5",
  "msc",
  "mscgen",
  "mupad",
  "mxml",
  "myghty",
  "mysql",
  "nasm",
  "nawk",
  "nb",
  "nemerle",
  "nesc",
  "newlisp",
  "newspeak",
  "nginx",
  "nim",
  "nimrod",
  "nit",
  "nix",
  "nixos",
  "nroff",
  "nsh",
  "nsi",
  "nsis",
  "numpy",
  "obj-c",
  "obj-c++",
  "obj-j",
  "objc",
  "objc++",
  "objdump",
  "objdump-nasm",
  "objective-c",
  "objective-c++",
  "objective-j",
  "objectivec",
  "objectivec++",
  "objectivej",
  "objectpascal",
  "objj",
  "ocaml",
  "octave",
  "odin",
  "ooc",
  "opa",
  "openbugs",
  "openedge",
  "pacmanconf",
  "pan",
  "parasail",
  "pas",
  "pascal",
  "pawn",
  "pcmk",
  "perl",
  "perl6",
  "php",
  "php3",
  "php4",
  "php5",
  "pig",
  "pike",
  "pkgconfig",
  "pl",
  "pl6",
  "plpgsql",
  "po",
  "posh",
  "postgres",
  "postgres-console",
  "postgresql",
  "postgresql-console",
  "postscr",
  "postscript",
  "pot",
  "pov",
  "powershell",
  "praat",
  "progress",
  "prolog",
  "properties",
  "proto",
  "protobuf",
  "ps1",
  "ps1con",
  "psm1",
  "psql",
  "puppet",
  "py",
  "py3",
  "py3tb",
  "pycon",
  "pypy",
  "pypylog",
  "pyrex",
  "pytb",
  "python",
  "python3",
  "pyx",
  "qbasic",
  "qbs",
  "qml",
  "qvt",
  "qvto",
  "r",
  "racket",
  "ragel",
  "ragel-c",
  "ragel-cpp",
  "ragel-d",
  "ragel-em",
  "ragel-java",
  "ragel-objc",
  "ragel-rb",
  "ragel-ruby",
  "raw",
  "rb",
  "rbcon",
  "rconsole",
  "rd",
  "rebol",
  "red",
  "red/system",
  "redcode",
  "registry",
  "resource",
  "resourcebundle",
  "rest",
  "restructuredtext",
  "rexx",
  "rhtml",
  "rkt",
  "roboconf-graph",
  "roboconf-instances",
  "robotframework",
  "rout",
  "rql",
  "rsl",
  "rst",
  "rts",
  "ruby",
  "rust",
  "s",
  "sage",
  "salt",
  "sass",
  "sc",
  "scala",
  "scaml",
  "scheme",
  "scilab",
  "scm",
  "scss",
  "sh",
  "shell",
  "shell-session",
  "shen",
  "slim",
  "sls",
  "smali",
  "smalltalk",
  "smarty",
  "sml",
  "snobol",
  "sources.list",
  "sourceslist",
  "sp",
  "sparql",
  "spec",
  "spitfire",
  "splus",
  "sql",
  "sqlite3",
  "squeak",
  "squid",
  "squid.conf",
  "squidconf",
  "ssp",
  "st",
  "stan",
  "supercollider",
  "sv",
  "swift",
  "swig",
  "systemverilog",
  "tads3",
  "tap",
  "tcl",
  "tcsh",
  "tcshcon",
  "tea",
  "termcap",
  "terminfo",
  "terraform",
  "tex",
  "text",
  "tf",
  "thrift",
  "todotxt",
  "trac-wiki",
  "trafficscript",
  "treetop",
  "ts",
  "turtle",
  "twig",
  "typescript",
  "udiff",
  "urbiscript",
  "v",
  "vala",
  "vapi",
  "vb.net",
  "vbnet",
  "vctreestatus",
  "velocity",
  "verilog",
  "vfp",
  "vgl",
  "vhdl",
  "vim",
  "winbatch",
  "winbugs",
  "x10",
  "xbase",
  "xml",
  "xml+cheetah",
  "xml+django",
  "xml+erb",
  "xml+evoque",
  "xml+genshi",
  "xml+jinja",
  "xml+kid",
  "xml+lasso",
  "xml+mako",
  "xml+myghty",
  "xml+php",
  "xml+ruby",
  "xml+smarty",
  "xml+spitfire",
  "xml+velocity",
  "xq",
  "xql",
  "xqm",
  "xquery",
  "xqy",
  "xslt",
  "xten",
  "xtend",
  "xul+mozpreproc",
  "yaml",
  "yaml+jinja",
  "zephir" ]

startsWithListMarker :: Text -> Bool
startsWithListMarker t =
  case T.uncons t of
    Nothing -> False
    Just (c,_) -> c == '#' || c == ':' || c == ';' || c == '*'
