{-# LANGUAGE CPP           #-}
{-# LANGUAGE DeriveFunctor #-}

{- |
Copyright:  (c) 2015-2019 Aelve
            (c) 2019-2020 Kowainik
SPDX-License-Identifier: MPL-2.0
Maintainer: Kowainik <xrom.xkov@gmail.com>
-}

module ShortcutLinks.All
    ( Result(..)
    , Shortcut
    , allShortcuts

      -- * Encyclopedias
    , wikipedia
    , tvtropes

      -- * Social networks
    , facebook
    , vk
    , googleplus
    , telegram

      -- * Microblogs
    , twitter
    , juick

      -- * Major search engines
    , google
    , duckduckgo
    , yandex
    , baidu

      -- * Programming language libraries
      -- ** Haskell
    , haskell
    , hackage
    , stackage
    , cabal
      -- ** Other
    , npm
    , jam
    , rubygems
    , pypi
    , metacpanPod
    , metacpanRelease
    , cargo
    , pub
    , hex
    , cran
    , swiprolog
    , dub
    , bpkg
    , pear


      -- * Code hosting
    , github
    , gitlab
    , bitbucket

      -- * OS packages
      -- ** Mobile
    , googleplay
      -- ** Windows
    , chocolatey
      -- ** OS X
    , brew
      -- ** Linux
    , debian
    , aur
    , mint
    , fedora
    , gentoo
    , opensuse

      -- * Addons
      -- ** Text editors
    , marmalade
    , melpa
    , elpa
    , packagecontrol
    , atomPackage
    , atomTheme
    , jedit
    , vim
      -- ** Browsers
    , operaExt
    , operaTheme
    , firefox
    , chrome

      -- * Manuals
    , ghcExt

      -- * Standards and databases
    , rfc
    , ecma
    , cve
    ) where

import Control.Monad (unless, when)
import Data.Char (isAlphaNum, isDigit, isPunctuation, isSpace)
import Data.Maybe (fromMaybe, isNothing)
import Data.Semigroup ((<>))
import Data.Text (Text)

import ShortcutLinks.Utils (format, formatSlash, orElse, replaceSpaces, stripPrefixCI, titleFirst,
                            tryStripPrefixCI)

import qualified Control.Monad.Fail as Fail
import qualified Data.Text as T


-- $setup
-- >>> import ShortcutLinks

-- | Resulting data type over the work of @shortcut-links@
data Result a
    = Failure String
    | Warning [String] a
    | Success a
    deriving stock (Show, Functor)

instance Applicative Result where
    pure :: a -> Result a
    pure = Success

    (<*>) :: Result (a -> b) -> Result a -> Result b
    Failure x <*> _ = Failure x
    Warning wf f <*> s = case s of
        Success a    -> Warning wf (f a)
        Warning wa a -> Warning (wf <> wa) (f a)
        Failure x    -> Failure x
    Success f <*> a = f <$> a

instance Monad Result where
#if !(MIN_VERSION_base(4,13,0))
    fail :: String -> Result a
    fail = Fail.fail
#endif
    return :: a -> Result a
    return = pure

    (>>=) :: Result a -> (a -> Result b) -> Result b
    Failure x    >>= _ = Failure x
    Warning wa a >>= f = case f a of
        Success    b -> Warning wa b
        Warning wb b -> Warning (wa ++ wb) b
        Failure x    -> Failure x
    Success    a >>= f = f a

instance Fail.MonadFail Result where
    fail :: String -> Result a
    fail = Failure

-- | Create a unit 'Warning' with a single warning message
warn :: String -> Result ()
warn s = Warning [s] ()

-- | Type alias for shortcut links 'Result' functions.
type Shortcut = Maybe Text -> Text -> Result Text

{- | A list of all functions included in this module, together with suggested
names for them.
-}
allShortcuts :: [([Text], Shortcut)]
allShortcuts =
  -- When changing something here, don't forget to update the description for
  -- the corresponding shortcut.
  let (.=) names func = (T.words names, func)
  in
    [ -- encyclopedias
      "w wikipedia"             .= wikipedia
    , "tvtropes"                .= tvtropes
      -- social networks
    , "fb facebook"             .= facebook
    , "vk vkontakte"            .= vk
    , "gp gplus googleplus"     .= googleplus
    , "tg tme telegram"         .= telegram
      -- microblogs
    , "t twitter"               .= twitter
    , "juick"                   .= juick
      -- search engines
    , "google"                  .= google
    , "ddg duckduckgo"          .= duckduckgo
    , "yandex"                  .= yandex
    , "baidu"                   .= baidu
      -- programming language libraries
        -- Haskell
    , "hackage hk" .= hackage
    , "stackage"   .= stackage
    , "haskell hs" .= haskell
    , "cabal"      .= cabal
        -- Others
    , "npm"                     .= npm
    , "jam"                     .= jam
    , "gem"                     .= rubygems
    , "pypi"                    .= pypi
    , "cpan"                    .= metacpanPod
    , "cpan-r"                  .= metacpanRelease
    , "cargo"                   .= cargo
    , "pub"                     .= pub
    , "hex"                     .= hex
    , "cran"                    .= cran
    , "swiprolog"               .= swiprolog
    , "dub"                     .= dub
    , "bpkg"                    .= bpkg
    , "pear"                    .= pear
      -- code hosting
    , "gh github"               .= github
    , "gitlab"                  .= gitlab
    , "bitbucket"               .= bitbucket
      -- OS
    , "gplay googleplay"        .= googleplay
    , "chocolatey"              .= chocolatey
    , "brew"                    .= brew
      -- OS – Linux
    , "debian"                  .= debian
    , "aur"                     .= aur
    , "mint"                    .= mint
    , "fedora"                  .= fedora
    , "gentoo"                  .= gentoo
    , "opensuse"                .= opensuse
      -- text editors
    , "marmalade"               .= marmalade
    , "melpa"                   .= melpa
    , "elpa"                    .= elpa
    , "sublimepc"               .= packagecontrol
    , "atom"                    .= atomPackage
    , "atom-theme"              .= atomTheme
    , "jedit"                   .= jedit
    , "vim"                     .= vim
      -- browsers
    , "opera"                   .= operaExt
    , "opera-theme"             .= operaTheme
    , "firefox"                 .= firefox
    , "chrome"                  .= chrome
      -- manuals
    , "ghc-ext"                 .= ghcExt
      -- standards and databases
    , "rfc"                     .= rfc
    , "ecma"                    .= ecma
    , "cve"                     .= cve
    ]

{- | <https://facebook.com Facebook> (shortcut: “fb” or “facebook”)

Link by username:

@
\[green\](\@fb)
<https://facebook.com/green>
@

Or by profile ID (are there still people without usernames, actually?):

@
\[someone something\](\@fb:164680686880529)
<https://facebook.com/profile.php?id=164680686880529>
@
-}
facebook :: Shortcut
facebook _ q
  | T.all isDigit q = return $ "https://facebook.com/profile.php?id=" <> q
  | otherwise       = return $ "https://facebook.com/" <> q

{- | <https://vk.com Vkontakte> (Вконтакте) (shortcut: “vk” or “vkontakte”)

Link by username:

@
\[green\](\@vk)
<https://vk.com/green>
@

Or by ID:

@
\[Durov\](\@vk:1)
<https://vk.com/id1>
@
-}
vk :: Shortcut
vk _ q
  | T.all isDigit q = return $ "https://vk.com/id" <> q
  | otherwise       = return $ "https://vk.com/" <> q

{- | <https://plus.google.com Google+> (shortcut: “gp”, “gplus”, or “googleplus”)

Link by username:

@
\[SergeyBrin\](\@gp)
<https://plus.google.com/+SergeyBrin>
@

It's alright if the username already starts with a “+”:

@
\[+SergeyBrin\](\@gp)
<https://plus.google.com/+SergeyBrin>
@

Since many usernames are just “your full name without spaces”, in many cases you can give a name and it's easy to make a username from it:

@
\[Sergey Brin\](\@gp)
<https://plus.google.com/+SergeyBrin>
@

You can also link by ID:

@
\[Sergey Brin\](\@gp:109813896768294978296)
<https://plus.google.com/109813896768294978296>
@

Finally, there are different links for hashtags:

@
\[#Australia\](\@gp)
<https://plus.google.com/explore/Australia>
@
-}
googleplus :: Shortcut
googleplus _ q
  | T.null q        = return url
  | T.head q == '#' = return $ format "{}/explore/{}" url (T.tail q)
  | T.head q == '+' = return $ formatSlash url q
  | T.all isDigit q = return $ formatSlash url q
  | otherwise       = return $ format "{}/+{}" url (T.concat (T.words q))
  where
    url = "https://plus.google.com"

{- | <https://t.me Telegram> (shortcut: "tg", "tme" or "telegram")

Link by username:

@
\[Kowainik telegram channel\](\@t:kowainik)
<https://t.me/kowainik>
@


It's alright if the username already starts with a “\@”:

@
\[\@kowainik\](\@t)
<https://t.me/kowainik>
@

>>> useShortcut "telegram" Nothing ""
Success "https://t.me"
>>> useShortcut "tme" Nothing "@kowainik"
Success "https://t.me/kowainik"
>>> useShortcut "telegram" Nothing "kowainik"
Success "https://t.me/kowainik"
-}
telegram :: Shortcut
telegram _ q
    | T.null q       = pure url
    | Just ('@', username) <- T.uncons q = pure $ formatSlash url username
    | otherwise      = pure $ formatSlash url q
  where
    url :: Text
    url = "https://t.me"

{- | <https://twitter.com Twitter> (shortcut: “t” or “twitter”)

Link by username:

@
\[Edward Kmett\](\@t:kmett)
<https://twitter.com/kmett>
@

It's alright if the username already starts with a “\@”:

@
\[\@kmett\](\@t)
<https://twitter.com/kmett>
@

There are different links for hashtags:

@
\[#haskell\](\@t)
<https://twitter.com/hashtag/haskell>
@
-}
twitter :: Shortcut
twitter _ q
  | T.null q        = return url
  | T.head q == '#' = return $ format "{}/hashtag/{}" url (T.tail q)
  | T.head q == '@' = return $ formatSlash url (T.tail q)
  | otherwise       = return $ formatSlash url q
  where url = "https://twitter.com"

{- | <https://juick.com Juick> (shortcut: “juick”)

Link by username:

@
\[thefish\](\@juick)
<https://juick.com/thefish>
@

It's alright if the username already starts with a “\@”:

@
\[\@thefish\](\@juick)
<https://juick.com/thefish>
@

There are different links for tags (which start with “\*” and not with “#”, by the way):

@
\[*Haskell\](\@juick)
<https://juick.com/tag/Haskell>
@
-}
juick :: Shortcut
juick _ q
  | T.null q        = return url
  | T.head q == '*' = return $ format "{}/tag/{}" url (T.tail q)
  | T.head q == '@' = return $ formatSlash url (T.tail q)
  | otherwise       = return $ formatSlash url q
  where url = "https://juick.com"

{- | <https://google.com Google> (shortcut: “google”)

Search results:

@
\[random query\](\@google)
<https://www.google.com/search?nfpr=1&q=random+query>
@
-}
google :: Shortcut
google _ q = return $
  "https://google.com/search?nfpr=1&q=" <> replaceSpaces '+' q

{- | <https://duckduckgo.com Duckduckgo> (shortcut: “ddg” or “duckduckgo”)

Search results:

@
\[random query\](\@ddg)
<https://duckduckgo.com/?q=random+query>
@
-}
duckduckgo :: Shortcut
duckduckgo _ q = return $ "https://duckduckgo.com/?q=" <> replaceSpaces '+' q

{- | <http://yandex.ru Yandex> (Russian search engine) (shortcut: “yandex”)

Search results:

@
\[random query\](\@yandex)
<http://yandex.ru/search/?noreask=1&text=random+query>
@
-}
yandex :: Shortcut
yandex _ q = return $
  "http://yandex.ru/search/?noreask=1&text=" <> replaceSpaces '+' q

{- | <http://baidu.com Baidu> (Chinese search engine) (shortcut: “baidu”)

Search results:

@
\[random query\](\@baidu)
<http://baidu.com/s?nojc=1&wd=random+query>
@
-}
baidu :: Shortcut
baidu _ q = return $ "http://baidu.com/s?nojc=1&wd=" <> replaceSpaces '+' q

----------------------------------------------------------------------------
-- Haskell
----------------------------------------------------------------------------

{- | __Haskell__ – <https://haskell.org> (shortcut: “haskell hs”)

Link to ghcup:

@
\[ghcup\](\@haskell)
<https://haskell.org/ghcup>
@

>>> useShortcut "haskell" Nothing ""
Success "https://haskell.org/"
>>> useShortcut "hs" Nothing "ghcup"
Success "https://haskell.org/ghcup"
-}
haskell :: Shortcut
haskell _ q = pure $ "https://haskell.org/" <> replaceSpaces '_' q


{- | __Haskell__ – <https://hackage.haskell.org Hackage> (shortcut: “hackage hk”)

Link to a package:

@
\[shortcut-links\](\@hackage)
<https://hackage.haskell.org/package/shortcut-links>
@

>>> useShortcut "hackage" Nothing ""
Success "https://hackage.haskell.org"
>>> useShortcut "hk" Nothing "shortcut-links"
Success "https://hackage.haskell.org/package/shortcut-links"

-}
hackage :: Shortcut
hackage _ q
    | T.null q  = pure hkUrl
    | otherwise = pure $ format "{}/package/{}" hkUrl (replaceSpaces '-' q)
  where
    hkUrl :: Text
    hkUrl = "https://hackage.haskell.org"

{- | __Haskell__ – <https://staskell.org Stackage> (shortcut: “stackage”)

Link to a package:

@
\[colourista\](\@stackage)
<https://stackage.org/lts/package/colourista>
@

>>> useShortcut "stackage" Nothing ""
Success "https://stackage.org"
>>> useShortcut "stackage" (Just "nightly") ""
Success "https://stackage.org/nightly"
>>> useShortcut "stackage" (Just "lts") ""
Success "https://stackage.org/lts"
>>> useShortcut "stackage" (Just "lts-15.0") ""
Success "https://stackage.org/lts-15.0"
>>> useShortcut "stackage" Nothing "colourista"
Success "https://stackage.org/lts/package/colourista"
>>> useShortcut "stackage" (Just "nightly") "colourista"
Success "https://stackage.org/nightly/package/colourista"
>>> useShortcut "stackage" (Just "lts-15.10") "colourista"
Success "https://stackage.org/lts-15.10/package/colourista"
-}
stackage :: Shortcut
stackage ltsNightly q
    | T.null q && isNothing ltsNightly = pure url
    | T.null q  = pure $ format "{}/{}" url lts
    | otherwise = pure $ format "{}/{}/package/{}" url lts (replaceSpaces '-' q)
  where
    url :: Text
    url = "https://stackage.org"

    lts :: Text
    lts = fromMaybe "lts" ltsNightly

{- | __Haskell__ – <https://haskell.org/cabal/users-guide Cabal> (shortcut: “cabal”)

Link to the intoduction package:

@
\[intro.html\](\@hackage)
<https://haskell.org/cabal/users-guide/intro.html>
@

>>> useShortcut "cabal" Nothing "intro.html"
Success "https://haskell.org/cabal/users-guide/intro.html"
-}
cabal :: Shortcut
cabal _ q = pure $ format "{}/{}" url (replaceSpaces '-' q)
  where
    url :: Text
    url = "https://haskell.org/cabal/users-guide"

{- | __Node.js__ – <https://npmjs.com NPM> (shortcut: “npm”)

Link to a package:

@
\[markdown\](\@npm)
<https://www.npmjs.com/package/markdown>
@
-}
npm :: Shortcut
npm _ q = return $ "https://npmjs.com/package/" <> q

{- | __Javascript__ – <http://jamjs.org/packages/#/ Jam> (shortcut: “jam”)

Link to a package:

@
\[pagedown\](\@jam)
<http://jamjs.org/packages/#/details/pagedown>
@
-}
jam :: Shortcut
jam _ q = return $ "http://jamjs.org/packages/#/details/" <> q

{- | __Ruby__ – <https://rubygems.org RubyGems.org> (shortcut: “gem”)

Link to a package:

@
\[github-markdown\](\@gem)
<https://rubygems.org/gems/github-markdown>
@
-}
rubygems :: Shortcut
rubygems _ q = return $ "https://rubygems.org/gems/" <> q

{- | __Python__ – <https://pypi.python.org/pypi PyPI> (shortcut: “pypi”)

Link to a package:

@
\[Markdown\](\@pypi)
<https://pypi.python.org/pypi/Markdown>
@
-}
pypi :: Shortcut
pypi _ q = return $ "https://pypi.python.org/pypi/" <> q

{- | __Perl__ – <https://metacpan.org MetaCPAN> (modules) (shortcut: “cpan”)

Link to a module:

@
\[Text::Markdown\](\@cpan)
<https://metacpan.org/pod/Text::Markdown>
@

To link to a release, look at 'metacpanRelease'.
-}
metacpanPod :: Shortcut
metacpanPod _ q = return $ "https://metacpan.org/pod/" <> q

{- | __Perl__ – <https://metacpan.org MetaCPAN> (releases) (shortcut: “cpan-r”)

Link to a release:

@
\[Text-Markdown\](\@cpan-r)
<https://metacpan.org/release/Text-Markdown>
@
-}
metacpanRelease :: Shortcut
metacpanRelease _ q = return $ "https://metacpan.org/release/" <> q

{- | __Rust__ – <https://crates.io Cargo> (shortcut: “cargo”)

Link to a package:

@
\[hoedown\](\@cargo)
<https://crates.io/crates/hoedown>
@
-}
cargo :: Shortcut
cargo _ q = return $ "https://crates.io/crates/" <> q

{- | __PHP__ – <http://pear.php.net PEAR> (shortcut: “pear”)

Link to a package:

@
\[Text_Wiki_Doku\](\@pear)
<http://pear.php.net/package/Text_Wiki_Doku>
@
-}
pear :: Shortcut
pear _ q = return $ "http://pear.php.net/package/" <> q

{- | __Dart__ – <https://pub.dartlang.org pub> (shortcut: “pub”)

Link to a package:

@
\[md_proc\](\@pub)
<https://pub.dartlang.org/packages/md_proc>
@
-}
pub :: Shortcut
pub _ q = return $ "https://pub.dartlang.org/packages/" <> q

{- | __R__ – <http://cran.r-project.org/web/packages/ CRAN> (shortcut: “cran”)

Link to a package:

@
\[markdown\](\@cran)
<http://cran.r-project.org/web/packages/markdown>
@
-}
cran :: Shortcut
cran _ q = return $ "http://cran.r-project.org/web/packages/" <> q

{- | __Erlang__ – <https://hex.pm Hex> (shortcut: “hex”)

Link to a package:

@
\[earmark\](\@hex)
<https://hex.pm/packages/earmark>
@
-}
hex :: Shortcut
hex _ q = return $ "https://hex.pm/packages/" <> q

{- | __SWI-Prolog__ – <http://www.swi-prolog.org/pack/list packages> (shortcut: “swiprolog”)

Link to a package:

@
\[markdown\](\@swiprolog)
<http://www.swi-prolog.org/pack/list?p=markdown>
@
-}
swiprolog :: Shortcut
swiprolog _ q = return $ "http://www.swi-prolog.org/pack/list?p=" <> q

{- | __D__ – <http://code.dlang.org DUB> (shortcut: “dub”)

Link to a package:

@
\[dmarkdown\](\@dub)
<http://code.dlang.org/packages/dmarkdown>
@
-}
dub :: Shortcut
dub _ q = return $ "http://code.dlang.org/packages/" <> q

{- | __Bash__ – <http://bpkg.io bpkg> (shortcut: “bpkg”)

Link to a package:

@
\[markdown\](\@bpkg)
<http://www.bpkg.io/pkg/markdown>
@
-}
bpkg :: Shortcut
bpkg _ q = return $ "http://bpkg.io/pkg/" <> q

{- | <https://github.com Github> (shortcut: “gh” or “github”)

Link to a user:

@
\[Aelve\](\@gh:aelve)
<https://github.com/aelve>
@

Link to a repository:

@
\[aelve/shortcut-links\](\@gh)
<https://github.com/aelve/shortcut-links>
@

The repository owner can also be given as an option (to avoid mentioning them in the link text):

@
\[shortcut-links\](\@gh(aelve))
<https://github.com/aelve/shortcut-links>
@
-}
github :: Shortcut
github mbOwner q = case mbOwner of
  Nothing    -> return $ format "https://github.com/{}" (stripAt q)
  Just owner -> return $ format "https://github.com/{}/{}" (stripAt owner) q
  where
    stripAt x = if T.head x == '@' then T.tail x else x

{- | <https://bitbucket.org Bitbucket> (shortcut: “bitbucket”)

Link to a user:

@
\[Bryan\](\@bitbucket:bos)
<https://bitbucket.org/bos>
@

Link to a repository:

@
\[bos/text\](\@bitbucket)
<https://bitbucket.org/bos/text>
@

The repository owner can also be given as an option (to avoid mentioning them in the link text):

@
\[text\](\@bitbucket(bos))
<https://bitbucket.org/bos/text>
@
-}
bitbucket :: Shortcut
bitbucket mbOwner q = case mbOwner of
  Nothing    -> return $ format "https://bitbucket.org/{}" (stripAt q)
  Just owner -> return $ format "https://bitbucket.org/{}/{}" (stripAt owner) q
  where
    stripAt x = if T.head x == '@' then T.tail x else x

{- | <https://gitlab.com Gitlab> (shortcut: “gitlab”)

Link to a user or a team (note that links like <https://gitlab.com/owner> work but are going to be automatically redirected to either <https://gitlab.com/u/owner> or <https://gitlab.com/groups/owner>, depending on whether it's a user or a team – so, it's a case when the “links have to look as authentic as possible” principle is violated, but nothing can be done with that):

@
\[CyanogenMod\](\@bitbucket)
<https://gitlab.com/CyanogenMod>
@

Link to a repository:

@
\[learnyou/lysa\](\@gitlab)
<https://gitlab.com/learnyou/lysa>
@

The repository owner can also be given as an option (to avoid mentioning them in the link text):

@
\[lysa\](\@gitlab(learnyou))
<https://gitlab.com/learnyou/lysa>
@
-}
gitlab :: Shortcut
gitlab mbOwner q = case mbOwner of
  Nothing    -> return $ format "https://gitlab.com/{}" (stripAt q)
  Just owner -> return $ format "https://gitlab.com/{}/{}" (stripAt owner) q
  where
    stripAt x = if T.head x == '@' then T.tail x else x

{- | __Android__ – <https://play.google.com Google Play> (formerly Play Market) (shortcut: “gplay” or “googleplay”)

Link to an app:

@
\[Opera Mini\](\@gplay:com.opera.mini.native)
<https://play.google.com/store/apps/details?id=com.opera.mini.native>
@
-}
googleplay :: Shortcut
googleplay _ q = return $ "https://play.google.com/store/apps/details?id=" <> q

{- | <http://braumeister.org Braumeister> (Homebrew formulas) (shortcut: “brew”)

Link to a formula:

@
\[multimarkdown\](\@brew)
<http://braumeister.org/formula/multimarkdown>
@

Since all Homebrew formulas are stored in a Github repo anyway, and various sites are merely convenient ways to browse that repo, the “brew” shortcut can point to some other site in the future, depending on which site seems better. Don't use it if you need /specifically/ Braumeister.
-}
brew :: Shortcut
brew _ q = return $ "http://braumeister.org/formula/" <> q

{- | <https://chocolatey.org Chocolatey> (shortcut: “chocolatey”)

Link to a package:

@
\[Opera\](\@chocolatey)
<https://chocolatey.org/packages/Opera>
@
-}
chocolatey :: Shortcut
chocolatey _ q = return $ "https://chocolatey.org/packages/" <> q

{- | __Debian__ – <https://debian.org/distrib/packages packages> (shortcut: “debian”)

Link to a package in stable distribution:

@
\[ghc\](\@debian)
<https://packages.debian.org/stable/ghc>
@

Distribution can be given as an option:

@
\[ghc\](\@debian(experimental))
<https://packages.debian.org/experimental/ghc>
@
-}
debian :: Shortcut
debian mbDist q = return $ format "https://packages.debian.org/{}/{}" dist q
  where
    dist = fromMaybe "stable" mbDist

{- | __Arch Linux__ – <https://aur.archlinux.org AUR> (“user repository”) (shortcut: “aur”)

Link to a package:

@
\[ghc-git\](\@aur)
<https://aur.archlinux.org/packages/ghc-git>
@
-}
aur :: Shortcut
aur _ q = return $ "https://aur.archlinux.org/packages/" <> q

{- | __Gentoo__ – <https://packages.gentoo.org packages> (shortcut: “gentoo”)

Link to a package:

@
\[dev-lang/ghc\](\@gentoo)
<https://packages.gentoo.org/package/dev-lang/ghc>
@

Category can be given as an option, to avoid cluttering link text:

@
\[ghc\](\@gentoo(dev-lang))
<https://packages.gentoo.org/package/dev-lang/ghc>
@

Note that if you don't specify any category, the link would still work – but there are a lot of packages with overlapping names (like “ace”, “csv”, “http”), and such links would lead to search pages listing several packages. So, it's better to include categories.
-}
gentoo :: Shortcut
gentoo mbCat q = return $ "https://packages.gentoo.org/package/" <> pkg
  where
    pkg = case mbCat of
      Nothing  -> q
      Just cat -> cat <> "/" <> q

{- | __openSUSE__ – <http://software.opensuse.org packages> (shortcut: “opensuse”)

Link to a package:

@
\[ghc\](\@opensuse)
<http://software.opensuse.org/package/ghc>
@
-}
opensuse :: Shortcut
opensuse _ q = return $ "http://software.opensuse.org/package/" <> q

{- | __Linux Mint__ – <http://community.linuxmint.com/software/browse packages> (shortcut: “mint”)

Link to a package:

@
\[ghc\](\@mint)
<http://community.linuxmint.com/software/view/ghc>
@
-}
mint :: Shortcut
mint _ q = return $ "http://community.linuxmint.com/software/view/" <> q

{- | __Fedora__ – <https://admin.fedoraproject.org/pkgdb packages> (shortcut: “fedora”)

Link to a package:

@
\[ghc\](\@fedora)
<https://admin.fedoraproject.org/pkgdb/package/ghc>
@
-}
fedora :: Shortcut
fedora _ q = return $ "https://admin.fedoraproject.org/pkgdb/package/" <> q

{- | __Emacs__ – <https://marmalade-repo.org Marmalade> (shortcut: “marmalade”)

Link to a package:

@
\[markdown-mode\](\@marmalade)
<https://marmalade-repo.org/packages/markdown-mode>
@
-}
marmalade :: Shortcut
marmalade _ q = return $ "https://marmalade-repo.org/packages/" <> q

{- | __Emacs__ – <http://melpa.org MELPA> (shortcut: “melpa”)

Link to a package:

@
\[markdown-mode\](\@melpa)
<http://melpa.org/#/markdown-mode>
@
-}
melpa :: Shortcut
melpa _ q = return $ "http://melpa.org/#/" <> q

{- | __Emacs__ – <https://elpa.gnu.org ELPA> (shortcut: “elpa”)

Link to a package:

@
\[undo-tree\](\@elpa)
<https://elpa.gnu.org/packages/undo-tree.html>
@
-}
elpa :: Shortcut
elpa _ q = return $ format "https://elpa.gnu.org/packages/{}.html" q

{- | __Sublime Text__ – <https://packagecontrol.io Package Control> (shortcut: “sublimepc”)

Link to a package:

@
\[MarkdownEditing\](\@sublimepc)
<https://packagecontrol.io/packages/MarkdownEditing>
@
-}
packagecontrol :: Shortcut
packagecontrol _ q = return $ "https://packagecontrol.io/packages/" <> q

{- | __Atom__ – <https://atom.io/packages packages> (shortcut: “atom”)

Link to a package:

@
\[tidy-markdown\](\@atom)
<https://atom.io/packages/tidy-markdown>
@
-}
atomPackage :: Shortcut
atomPackage _ q = return $ "https://atom.io/packages/" <> q

{- | __Atom__ – <https://atom.io/themes themes> (shortcut: “atom-theme”)

Link to a theme:

@
\[atom-material-ui\](\@atom-theme)
<https://atom.io/themes/atom-material-ui>
@
-}
atomTheme :: Shortcut
atomTheme _ q = return $ "https://atom.io/themes/" <> q

{- | __jEdit__ – <http://plugins.jedit.org plugins> (shortcut: “jedit”)

Link to a plugin:

@
\[MarkdownPlugin\](\@jedit)
<http://plugins.jedit.org/plugins/?MarkdownPlugin>
@
-}
jedit :: Shortcut
jedit _ q = return $ "http://plugins.jedit.org/plugins/?" <> q

{- | __Vim__ – <http://www.vim.org/scripts/ scripts> (shortcut: “vim”)

Link to a script (by ID):

@
\[haskell.vim\](\@vim:2062)
<http://www.vim.org/scripts/script.php?script_id=2062>
@
-}
vim :: Shortcut
vim _ q = return $ "http://www.vim.org/scripts/script.php?script_id=" <> q

{- | __Opera__ – <https://addons.opera.com/extensions/ extensions> (shortcut: “opera”)

Link to an extension:

@
\[Amazon\](\@opera:amazon-for-opera)
<https://addons.opera.com/extensions/details/amazon-for-opera>
@
-}
operaExt :: Shortcut
operaExt _ q = return $ "https://addons.opera.com/extensions/details/" <> q

{- | __Opera__ – <https://addons.opera.com/themes/ themes> (shortcut: “opera-theme”)

Link to a theme:

@
\[Space theme\](\@opera-theme:space-15)
<https://addons.opera.com/en/themes/details/space-15>
@
-}
operaTheme :: Shortcut
operaTheme _ q = return $ "https://addons.opera.com/themes/details/" <> q

{- | __Firefox__ – <https://addons.mozilla.org/firefox add-ons> (shortcut: “firefox”)

Link to an extension (or a theme):

@
\[tree-style-tab](\@firefox)
<https://addons.mozilla.org/firefox/addon/tree-style-tab>
@
-}
firefox :: Shortcut
firefox _ q = return $ "https://addons.mozilla.org/firefox/addon/" <> q

{- | __Chrome__ – <https://chrome.google.com/webstore Chrome Web Store> (shortcut: “chrome”)

Link to an extension, app, or theme (using that weird random-looking ID):

@
\[hdokiejnpimakedhajhdlcegeplioahd](\@chrome)
<https://chrome.google.com/webstore/detail/hdokiejnpimakedhajhdlcegeplioahd>
@
-}
chrome :: Shortcut
chrome _ q = return $ "https://chrome.google.com/webstore/detail/" <> q

{- | <https://www.haskell.org/ghc/ GHC> (Glasgow Haskell Compiler) extensions (shortcut: “ghc-ext”)

Link to an extension's description in the user manual:

@
\[ViewPatterns\](\@ghc-ext)
<https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#extension-ViewPatterns>
@
-}
ghcExt :: Shortcut
ghcExt _ q = return $ "https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#extension-" <> q

{- | <https://www.ietf.org/rfc.html RFCs> (shortcut: “rfc”)

Link to an RFC:

@
\[RFC 2026\](\@rfc)
<https://tools.ietf.org/html/rfc2026>
@

Precise format of recognised text: optional “rfc” (case-insensitive), then arbitrary amount of spaces and punctuation (or nothing), then the number. Examples: “RFC 2026”, “RFC-2026”, “rfc2026”, “rfc #2026”, “2026”, “#2026”.
-}
rfc :: Shortcut
rfc _ x = do
  let n = T.dropWhile (not . isAlphaNum) (tryStripPrefixCI "rfc" x)
  -- We don't use 'readMaybe' here because 'readMaybe' isn't available in GHC
  -- 7.4, which Pandoc has to be compatible with.
  unless (T.all isDigit n) $
    warn "non-digits in RFC number"
  when (T.null n) $
    warn "no RFC number"
  let n' = T.dropWhile (== '0') n `orElse` "0"
  return ("https://tools.ietf.org/html/rfc" <> n')

{- | <http://ecma-international.org/publications/index.html Ecma standards and technical reports> (shortcut: “ecma”)

Link to a standard:

@
\[ECMA-262\](\@ecma)
<http://www.ecma-international.org/publications/standards/Ecma-262.htm>
@

Link to a technical report:

@
\[TR/71\](\@ecma)
<http://ecma-international.org/publications/techreports/E-TR-071.htm>
@

Precise format of recognised text for standards: optional “ECMA” (case-insensitive), then arbitrary amount of spaces and punctuation (or nothing), then the number. Examples: “ECMA-262”, “ECMA 262”, “ecma262”, “ECMA #262”, “262”, “#262”.

Format for technical reports is the same, except that “TR” (instead of “ECMA”) is not optional (so, if there's only a number given, it's considered a standard and not a technical report).
-}
ecma :: Shortcut
ecma _ q = do
  -- TODO: move dropSeparators to Utils and use it in 'rfc' and 'cve'
  let dropSeparators = T.dropWhile (not . isAlphaNum)
  let (dropSeparators -> mbNum, isTR) = case stripPrefixCI "tr" q of
        Nothing -> (tryStripPrefixCI "ecma" q, False)
        Just q' -> (q', True)
  -- We don't use 'readMaybe' here because 'readMaybe' isn't available in GHC
  -- 7.4, which Pandoc has to be compatible with.
  unless (T.all isDigit mbNum) $
    warn "non-digits in ECMA standard number"
  when (T.null mbNum) $
    warn "no ECMA standard number"
  -- The number has to have at least 3 digits.
  let num = T.justifyRight 3 '0' mbNum
      url = "http://ecma-international.org/publications" :: Text
  return $ if isTR
    then format "{}/techreports/E-TR-{}.htm" url num
    else format "{}/standards/Ecma-{}.htm" url num

{- | <http://cve.mitre.org CVEs> (Common Vulnerabilities and Exposures) (shortcut: “cve”)

Link to a CVE:

@
\[CVE-2014-10001\](\@cve)
<http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-10001>
@

Precise format of recognised text: optional “cve” (case-insensitive), then arbitrary amount of spaces and punctuation (or nothing), then the year, “-”, and a number. Examples: “CVE-2014-10001”, “cve 2014-10001”, “2014-10001”.
-}
cve :: Shortcut
cve _ x = do
  let n = T.dropWhile (not . isAlphaNum) (tryStripPrefixCI "cve" x)
  unless (T.length n >= 9) $
    warn "CVE-ID is too short"
  let isValid = and [
        T.length n >= 9,
        T.all isDigit (T.take 4 n),
        T.index n 4 == '-',
        T.all isDigit (T.drop 5 n) ]
  unless isValid $
    warn "CVE-ID doesn't follow the <year>-<digits> format"
  return ("http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-" <> n)

{- | <https://wikipedia.org/ Wikipedia> (shortcut: “w” or “wikipedia”)

Link to an article in English Wikipedia:

@
\[grey-headed flying fox\](\@w)
<https://en.wikipedia.org/wiki/Grey-headed_flying_fox>
@

You can link to Wikipedia-in-another-language if you give language code as an option:

@
\[Haskell\](\@w(ru))
<https://ru.wikipedia.org/wiki/Haskell>
@


>>> useShortcut "wikipedia" Nothing ""
Success "https://en.wikipedia.org/wiki/"
>>> useShortcut "w" (Just "ru") ""
Success "https://ru.wikipedia.org/wiki/"
>>> useShortcut "wikipedia" Nothing "Query"
Success "https://en.wikipedia.org/wiki/Query"
>>> useShortcut "w" Nothing "multiple words query"
Success "https://en.wikipedia.org/wiki/Multiple_words_query"
>>> useShortcut "wikipedia" Nothing "grey-headed flying fox"
Success "https://en.wikipedia.org/wiki/Grey-headed_flying_fox"

>>> useShortcut "w" Nothing "pattern matching#primitive patterns"
Success "https://en.wikipedia.org/wiki/Pattern_matching#Primitive_patterns"
-}
wikipedia :: Shortcut
wikipedia (fromMaybe "en" -> lang) q = pure $
    format "https://{}.wikipedia.org/wiki/{}" lang replacedQ
  where
    replacedQ :: Text
    replacedQ = titleFirst (replaceSpaces '_' q)

{- | <http://tvtropes.org TV Tropes> (shortcut: “tvtropes”)

Link to a trope:

@
\[so bad, it's good\](\@tvtropes)
<http://tvtropes.org/pmwiki/pmwiki.php/Main/SoBadItsGood>
@

Link to anything else (a series, for example):

@
\[Elementary\](\@tvtropes(series))
<http://tvtropes.org/pmwiki/pmwiki.php/Series/Elementary>
@

Or something on Sugar Wiki:

@
\[awesome music\](\@tvtropes(sugar wiki))
<http://tvtropes.org/pmwiki/pmwiki.php/SugarWiki/AwesomeMusic>
@
-}
tvtropes :: Shortcut
tvtropes mbCat q = return $
  format "http://tvtropes.org/pmwiki/pmwiki.php/{}/{}" cat (camel q)
  where
    isSep c = (isSpace c || isPunctuation c) && c /= '\''
    -- Break into words, transform each word like “it's” → “Its”, and concat.
    -- Note that e.g. “man-made” is considered 2 separate words.
    camel = T.concat . map (titleFirst . T.filter isAlphaNum) . T.split isSep
    cat = maybe "Main" camel mbCat