packages feed

emanote-1.2.0.0: src/Emanote/Model/StaticFile.hs

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE TemplateHaskell #-}

module Emanote.Model.StaticFile where

import Commonmark.Extensions.WikiLink qualified as WL
import Data.Aeson qualified as Aeson
import Data.IxSet.Typed (Indexable (..), IxSet, ixFun, ixList)
import Data.Map.Strict qualified as Map
import Data.Time (UTCTime)
import Emanote.Route qualified as R
import Optics.TH (makeLenses)
import Relude
import System.FilePath (takeExtension)

data StaticFile = StaticFile
  { _staticFileRoute :: R.R 'R.AnyExt
  , _staticFilePath :: FilePath
  , _staticFileTime :: UTCTime
  -- ^ Indicates that this file was updated no latter than the given time.
  , _staticFileInfo :: Maybe StaticFileInfo
  -- ^ This file might have its content read
  }
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (Aeson.ToJSON)

type StaticFileIxs = '[R.R 'R.AnyExt, WL.WikiLink]

type IxStaticFile = IxSet StaticFileIxs StaticFile

instance Indexable StaticFileIxs StaticFile where
  indices =
    ixList
      (ixFun $ one . _staticFileRoute)
      (ixFun $ toList . staticFileSelfRefs)

staticFileSelfRefs :: StaticFile -> NonEmpty WL.WikiLink
staticFileSelfRefs =
  fmap snd
    . WL.allowedWikiLinks
    . R.unRoute
    . _staticFileRoute

data StaticFileInfo where
  StaticFileInfoImage :: StaticFileInfo
  StaticFileInfoAudio :: StaticFileInfo
  StaticFileInfoVideo :: StaticFileInfo
  StaticFileInfoPDF :: StaticFileInfo
  StaticFileInfoCode ::
    -- | File code language name
    CodeLanguage ->
    -- | File content
    Text ->
    StaticFileInfo
  deriving stock (Eq, Show, Ord, Generic)
  deriving anyclass (Aeson.ToJSON)

newtype CodeLanguage = CodeLanguage Text
  deriving stock (Eq, Show, Ord, Generic)
  deriving anyclass (Aeson.ToJSON)

-- | Return the ${name} in the corresponding templates/filters/embed-${name}.tpl
staticFileInfoTemplateName :: (IsString s) => StaticFileInfo -> s
staticFileInfoTemplateName = \case
  StaticFileInfoImage -> "image"
  StaticFileInfoAudio -> "audio"
  StaticFileInfoVideo -> "video"
  StaticFileInfoPDF -> "pdf"
  StaticFileInfoCode _ _ -> "code"

readStaticFileInfo ::
  (Monad m) =>
  FilePath ->
  (FilePath -> m Text) ->
  m (Maybe StaticFileInfo)
readStaticFileInfo fp readFilePath = do
  let extension = toText (takeExtension fp)
  if
      | extension `elem` imageExts ->
          pure $ Just StaticFileInfoImage
      | extension `elem` videoExts ->
          pure $ Just StaticFileInfoVideo
      | extension `elem` audioExts ->
          pure $ Just StaticFileInfoAudio
      | extension == ".pdf" ->
          pure $ Just StaticFileInfoPDF
      | Just lang <- Map.lookup extension codeExts -> do
          code <- readFilePath fp
          pure $ Just $ StaticFileInfoCode lang code
      | otherwise -> return Nothing
  where
    imageExts = [".jpg", ".jpeg", ".png", ".svg", ".gif", ".bmp", ".webp"]
    videoExts = [".mp4", ".webm", ".ogv"]
    audioExts = [".aac", ".caf", ".flac", ".mp3", ".ogg", ".wav", ".wave"]
    codeExts =
      CodeLanguage
        <$> Map.fromList
          [ (".hs", "haskell")
          , (".nix", "nix")
          , (".sh", "bash")
          , (".py", "python")
          , (".js", "javascript")
          , (".java", "java")
          , (".c", "c")
          , (".cpp", "cpp")
          , (".cs", "cs")
          , (".rb", "ruby")
          , (".go", "go")
          , (".swift", "swift")
          , (".kt", "kotlin")
          , (".rs", "rust")
          , (".ts", "typescript")
          , (".php", "php")
          ]

makeLenses ''StaticFile