packages feed

maid-2010.9.9: src/maid.hs

{-# LANGUAGE NamedFieldPuns, QuasiQuotes #-}


import Hack.Handler.Hyena
import System (getArgs)
import MPS.Env hiding (div, log)
import MPS.TH
import MPS.Extra (u2b, b2u, strip, purify)
import MPSUTF8 (ls)
import Prelude ()
import Data.Default (def)
import Hack.Contrib.Middleware.Config
import Hack.Contrib.Middleware.ContentLength
import Hack.Contrib.Middleware.Static
-- import Hack.Contrib.Middleware.SimpleAccessLogger
import Hack.Contrib.Middleware.UserMime
import Hack.Contrib.Mime
import Hack.Contrib.Utils (use, unescape_uri)
import Hack.Contrib.Response (set_content_type)
import Hack
import Data.Map (toAscList)
import Data.ByteString.Lazy.Char8 (pack)
import System.Posix.Files
import Data.List (isInfixOf, sort)
import Text.HTML.Moe hiding ((/), body, head, select)
import Control.Monad ((>=>))
import System.Process
import Control.Concurrent



main :: IO ()
main = do
  args <- getArgs
  let { port = 
    if length args == 0
      then 3000
      else read - head args
    }
  
  let { app =
    dir_serve - \env -> do
      let static_app = static (Just ".") [""] (const - return not_found)
      
      r <- static_app env
      if r.status == 200
        then do
          _content_type <- readProcess "file" ["-b", "--mime-type", "." + env.pathInfo.unescape_uri] ""
          return - r.set_content_type (_content_type.strip)
        else
          return r          
    }

  putStrLn ""
  putStrLn - " ❂ Maid serving on port: " ++ show port
  putStrLn ""
  
  runWithConfig def {port} - use middleware_stack app
  -- run port - use middleware_stack app
  
  where
    dir_serve app = cascade
      [
        app
      , config (\env -> env {pathInfo = pathInfo env / "index.htm"}) app
      , config (\env -> env {pathInfo = pathInfo env / "index.html"}) app
      , list_dir
      ]

{-
log :: String -> IO ()
log x = jailed - putStrLn x
  where
    
    sync_lock :: MVar ()
    sync_lock = purify - newMVar ()

    jailed :: IO a -> IO a
    jailed io = do
      withMVar sync_lock (const io)
-}

middleware_stack :: [Middleware]
middleware_stack =
  [
    no_favicon
  , content_length
--  , simple_access_logger - Just log
  , user_mime - mime_types.toAscList.map_fst (drop 1)
  -- , \app env -> do
  --   r <- app env
  --   print - r.headers
  --   return r
  ]

cascade :: [Application] -> Application
cascade [] = const - return def {status = 404}
cascade (x:xs) = \env -> do
  r <- x env
  if r.status == 404
    then
      cascade xs env
    else
      return r


no_favicon :: Middleware
no_favicon app = \env -> do
  -- putStrLn - "pathInfo is: " + env.pathInfo
  if env.pathInfo.is "/favicon.ico"
    then
      return not_found
    else
      app env

not_found :: Response
not_found = def { status = 404 }

is_directory :: String -> IO Bool
is_directory = u2b > getFileStatus >=> isDirectory > return

list_dir :: Application
list_dir env = do
  let _path = "." + env.pathInfo .unescape_uri .b2u
  
  -- print _path
  
  -- no hack please
  if ".." `isInfixOf` _path
    then
      return not_found
    else do
      is_dir <- is_directory _path
      
      if not is_dir
        then
          return not_found
        else do
          _paths <- ls _path

          -- print _paths
        
          let _full_paths = _paths.map (_path.drop 2 /)
          
          dir_tags <- _full_paths.mapM is_directory

          let tagged = zip _paths dir_tags
          
              dirs = tagged.select snd.sort .map_fst (+ "/")
              files =  tagged.reject snd.sort 

              sorted = dirs + files
              
          return - def
            {
              status = 200
            , body = pack - dir_template sorted
            }
            .set_content_type "text/html; charset=utf-8"
    

dir_template :: [(String, Bool)] -> String
dir_template xs = render -
  html' - do
    head' - do
      meta [http_equiv "Content-Type", content "text/html; charset=utf-8"]
      style' - str css_style
    body' - do
      div [_class "container"] - do
        ul' - do
          xs.mapM_ (\(path, dir_tag) ->
            li' - do
              
              let path_dom = a [href - path] - str - path
                
              if dir_tag
                then
                  div [_class "directory"] -
                    path_dom
                else
                  path_dom
            )
          


css_style :: String
css_style = [$here|

body {
line-height: 1.5em;
font-size: 1.3em;
}

  .directory a
, .directory a:visited {
color: grey;
}

  a
, a:visited {
text-decoration: none;
color: #222;
display: block;
background: #eee;
padding: 3px;
padding-left: 20px;
}

a:hover {
background: #ccc;
}

li {
list-style-type: none;

width: 80%;
margin: 5px;
}


|]