{-
Copyright (C) 2008-2018 John MacFarlane <jgm@berkeley.edu>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-}

{- |
   Module      : Text.Pandoc.Writers.MediaWiki
   Copyright   : Copyright (C) 2008-2018 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 ) where
import Control.Monad.Reader
import Control.Monad.State.Strict
import Data.List (intercalate)
import qualified Data.Set as Set
import Data.Text (Text, pack)
import Text.Pandoc.Class (PandocMonad, report)
import Text.Pandoc.Definition
import Text.Pandoc.ImageSize
import Text.Pandoc.Logging
import Text.Pandoc.Options
import Text.Pandoc.Pretty (render)
import Text.Pandoc.Shared
import Text.Pandoc.Templates (renderTemplate')
import Text.Pandoc.Writers.Shared
import Text.Pandoc.XML (escapeStringForXML)

data WriterState = WriterState {
    stNotes   :: Bool            -- True if there are notes
  , stOptions :: WriterOptions   -- writer options
  }

data WriterReader = WriterReader {
    options   :: WriterOptions -- Writer options
  , listLevel :: String        -- 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 }
      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 <- metaToJSON opts
              (fmap trimr . blockListToMediaWiki)
              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
  pack <$> case writerTemplate opts of
                Nothing  -> return main
                Just tpl -> renderTemplate' tpl context

-- | Escape special characters for MediaWiki.
escapeString :: String -> String
escapeString =  escapeStringForXML

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

blockToMediaWiki Null = return ""

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

-- title beginning with fig: indicates that the image is a figure
blockToMediaWiki (Para [Image attr txt (src,'f':'i':'g':':':tit)]) = do
  capt <- if null txt
             then return ""
             else ("|caption " ++) `fmap` inlineListToMediaWiki txt
  img  <- imageToMediaWiki attr
  let opt = if null txt
               then ""
               else "|alt=" ++ if null tit then capt else tit ++ capt
  return $ "[[File:" ++ src ++ "|frame|none" ++ img ++ opt ++ "]]\n"

blockToMediaWiki (Para inlines) = do
  tags <- asks useTags
  lev <- asks listLevel
  contents <- inlineListToMediaWiki inlines
  return $ if tags
              then  "<p>" ++ contents ++ "</p>"
              else 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 _ inlines) = do
  contents <- inlineListToMediaWiki inlines
  let eqs = replicate level '='
  return $ eqs ++ " " ++ contents ++ " " ++ eqs ++ "\n"

blockToMediaWiki (CodeBlock (_,classes,_) str) = do
  let at  = Set.fromList classes `Set.intersection` highlightingLangs
  return $
    case Set.toList at of
       [] -> "<pre" ++ (if null classes
                           then ">"
                           else " class=\"" ++ unwords classes ++ "\">") ++
             escapeString str ++ "</pre>"
       (l:_) -> "<source lang=\"" ++ l ++ "\">" ++ str ++ "</source>"
            -- note:  no escape!  even for <!

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

blockToMediaWiki (Table capt aligns widths headers rows') = do
  caption <- if null capt
                then return ""
                else do
                   c <- inlineListToMediaWiki capt
                   return $ "|+ " ++ trimr c ++ "\n"
  let headless = all null headers
  let allrows = if headless then rows' else headers:rows'
  tableBody <- intercalate "|-\n" `fmap`
                mapM (tableRowToMediaWiki headless aligns widths)
                     (zip [1..] allrows)
  return $ "{|\n" ++ caption ++ tableBody ++ "|}\n"

blockToMediaWiki x@(BulletList items) = do
  tags <- fmap (|| 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 <- fmap (|| not (isSimpleList x)) $ asks useTags
  if tags
     then do
        contents <- local (\s -> s { useTags = True }) $ mapM listItemToMediaWiki items
        return $ "<ol" ++ listAttribsToString 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 <- fmap (|| 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 ""

-- Auxiliary functions for lists:

-- | Convert ordered list attributes to HTML attribute string
listAttribsToString :: ListAttributes -> String
listAttribsToString (startnum, numstyle, _) =
  let numstyle' = camelCaseToHyphenated $ show numstyle
  in  (if startnum /= 1
          then " start=\"" ++ show 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 String
listItemToMediaWiki items = do
  contents <- blockListToMediaWiki items
  tags <- asks useTags
  if tags
     then return $ "<li>" ++ contents ++ "</li>"
     else do
       marker <- asks listLevel
       return $ marker ++ " " ++ contents

-- | Convert definition list item (label, list of blocks) to MediaWiki.
definitionListItemToMediaWiki :: PandocMonad m
                              => ([Inline],[[Block]])
                              -> MediaWikiWriter m String
definitionListItemToMediaWiki (label, items) = do
  labelText <- inlineListToMediaWiki label
  contents <- mapM blockListToMediaWiki items
  tags <- asks useTags
  if tags
     then return $ "<dt>" ++ labelText ++ "</dt>\n" ++
           intercalate "\n" (map (\d -> "<dd>" ++ d ++ "</dd>") contents)
     else do
       marker <- asks listLevel
       return $ marker ++ " " ++ labelText ++ "\n" ++
           intercalate "\n" (map (\d -> 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 :: [String] -> String
vcat = intercalate "\n"

-- Auxiliary functions for tables:

tableRowToMediaWiki :: PandocMonad m
                    => Bool
                    -> [Alignment]
                    -> [Double]
                    -> (Int, [[Block]])
                    -> MediaWikiWriter m String
tableRowToMediaWiki headless alignments widths (rownum, cells) = do
  cells' <- mapM (tableCellToMediaWiki headless rownum)
          $ zip3 alignments widths cells
  return $ unlines cells'

tableCellToMediaWiki :: PandocMonad m
                     => Bool
                     -> Int
                     -> (Alignment, Double, [Block])
                     -> MediaWikiWriter m String
tableCellToMediaWiki headless rownum (alignment, width, bs) = do
  contents <- blockListToMediaWiki bs
  let marker = if rownum == 1 && not headless then "!" else "|"
  let percent w = show (truncate (100*w) :: Integer) ++ "%"
  let attrs = ["align=" ++ show (alignmentToString alignment) |
                 alignment /= AlignDefault && alignment /= AlignLeft] ++
              ["width=\"" ++ percent width ++ "\"" |
                 width /= 0.0 && rownum == 1]
  let attr = if null attrs
                then ""
                else unwords attrs ++ "|"
  let sep = case bs of
                 [Plain _] -> " "
                 [Para  _] -> " "
                 _         -> "\n"
  return $ marker ++ attr ++ sep ++ trimr contents

alignmentToString :: Alignment -> String
alignmentToString alignment = case alignment of
                                 AlignLeft    -> "left"
                                 AlignRight   -> "right"
                                 AlignCenter  -> "center"
                                 AlignDefault -> "left"

imageToMediaWiki :: PandocMonad m => Attr -> MediaWikiWriter m String
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=" ++ unwords cls
  return $ dims ++ classes

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

-- | Convert list of Pandoc inline elements to MediaWiki.
inlineListToMediaWiki :: PandocMonad m => [Inline] -> MediaWikiWriter m String
inlineListToMediaWiki lst =
  fmap concat $ mapM inlineToMediaWiki lst

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

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 (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>" ++ escapeString str ++ "</code>"

inlineToMediaWiki (Str str) = return $ escapeString 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 txt
  case txt of
     [Str s] | isURI src && escapeURI s == src -> return src
     _  -> return $ if isURI src
              then "[" ++ src ++ " " ++ label ++ "]"
              else "[[" ++ src' ++ "|" ++ label ++ "]]"
                     where src' = case src of
                                     '/':xs -> xs  -- with leading / it's a
                                     _      -> src -- link to a help page

inlineToMediaWiki (Image attr alt (source, tit)) = do
  img  <- imageToMediaWiki attr
  alt' <- inlineListToMediaWiki alt
  let txt = if null tit
               then if null alt
                       then ""
                       else '|' : alt'
               else '|' : tit
  return $ "[[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

highlightingLangs :: Set.Set String
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",
  "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" ]