barley-0.3: seed/lib/DevUtils.hs
-- Copyright 2010 Google Inc.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
module DevUtils (
devpage,
Doc(..), documents,
legalPath,
SrcInfo(..), getSrcInfo,
SrcClass(..),
previewPath,
previewLink, editLink, downloadLink, fileLink,
htmlResponse,
finishWithError, errorBadRequest,
) where
import qualified Data.ByteString.Char8 as C
import qualified Data.Map as M
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Snap.Types
import System.Directory
import System.FilePath ((</>), dropExtension, joinPath,
splitDirectories, takeExtension)
import System.Time (ClockTime, getClockTime)
import Text.Html hiding ((</>))
--
-- DEV PAGE LAYOUT
--
devpage :: HTML a => String -> a -> [Html] -> [String] -> Html
devpage pageTitle contents modules scriptSrcs =
header << [
thelink ! [href "/static/barley.css", rel "stylesheet",
thetype "text/css"] << noHtml,
thetitle << fullTitle
] +++
body ! [theclass "with-topbar"] << [
h1 ! [identifier "logo"] << "Barley",
thediv ! ([identifier "content"] ++ withSidebar modules) <<
(toHtml contents +++ toHtml (sidebar modules)),
topbar,
scripts scriptSrcs
]
where
fullTitle = if null pageTitle then "Barley" else "Barley - " ++ pageTitle
sidebar :: [Html] -> Html
sidebar [] = noHtml
sidebar modules = thediv ! [identifier "sidebar"] <<
map (thediv ! [theclass "module"]) modules
withSidebar :: [Html] -> [HtmlAttr]
withSidebar [] = []
withSidebar _ = [theclass "with-sidebar"]
topbar :: Html
topbar = thediv ! [identifier "topbar"] << [
ulist ! [theclass "left"] << li <<
[ anchor ! [href "http://haskell.org/", theclass "logo"] << "Haskell"
, unordList $ map makelink communityLinks
]
, ulist ! [theclass "right"] << map li siteLinks
]
where
makelink (title, url) = anchor ! [href url] << title
makeDocLink doc = anchor ! [href $ docUrl doc, target "_blank"]
<< docName doc
communityLinks =
[ ("Platform", "http://hackage.haskell.org/platform/")
, ("Hackage", "http://hackage.haskell.org/packages/hackage.html")
, ("Reddit", "http://www.reddit.com/r/haskell/")
, ("Stack Overflow",
"http://stackoverflow.com/questions/tagged?tagnames=haskell")
]
siteLinks =
[ makelink ("Home", "/")
, makelink ("Project", "/project")
, makelink ("Documentation", "/documentation")
+++ unordList (map makeDocLink documents)
, makelink ("Help", "/help")
]
scripts :: [String] -> Html
scripts = toHtml . map script
where
script s = tag "script" ! [ thetype "text/javascript", src s ] << noHtml
data Doc = Doc { docId :: String, docName :: String, docUrl :: String }
documents :: [Doc]
documents =
[ Doc "ghclibs" "Library" "http://www.haskell.org/ghc/docs/6.12.2/html/libraries/frames.html"
, Doc "html" "Text.Html" "http://hackage.haskell.org/packages/archive/html/1.0.1.2/doc/html/Text-Html.html"
, Doc "snap" "Snap" "http://snapframework.com/docs/latest/snap-core/index.html"
]
docLocalUrl :: Doc -> String
docLocalUrl doc = "/documentation?doc=" ++ docId doc
--
-- PROJECT FILE UTILITIES
--
data SrcInfo = SrcInfo { srcPath :: FilePath -- | path relative to project
, srcFullPath :: FilePath -- | absolute path
, srcExists :: Bool
, srcWritable :: Bool
, srcModTime :: Maybe ClockTime
, srcClass :: SrcClass
}
data SrcClass = SCPage | SCImage | SCScript | SCText | SCOther | SCDir
deriving (Eq)
getSrcInfo :: FilePath -> IO SrcInfo
getSrcInfo path = do
cwd <- getCurrentDirectory
isDir <- doesDirectoryExist path
isFile <- doesFileExist path
let exists = isDir || isFile
canWrite <- if exists
then writable `fmap` getPermissions path
else return False -- TODO: should be writable of containing dir
modTime <- if exists
then Just `fmap` getModificationTime path
else return Nothing
let cls = if isDir
then SCDir
else M.findWithDefault SCOther (takeExtension path) extToSrcClass
return SrcInfo { srcPath = path
, srcFullPath = cwd </> path
, srcExists = exists
, srcWritable = canWrite
, srcModTime = modTime
, srcClass = cls
}
extToSrcClass = M.fromList
[ (".html", SCPage)
, (".xhtml",SCPage)
, (".txt", SCPage)
, (".hs", SCScript)
, (".css", SCText)
, (".js", SCText)
, (".json", SCText)
, (".xml", SCText)
, (".gif", SCImage)
, (".jpeg", SCImage)
, (".jpg", SCImage)
, (".pdf", SCImage)
, (".png", SCImage)
, (".svg", SCImage)
]
previewPath :: SrcInfo -> Maybe FilePath
previewPath si = if not (srcExists si) || libDir
then Nothing
else pp $ srcClass si
where
libDir = case splitDirectories path of
("lib":_) -> True
_ -> False
path = srcPath si
pp SCPage = Just path
pp SCScript = Just $ dropExtension path
pp SCImage = Just path
pp _ = Nothing
previewLink :: SrcInfo -> Maybe Html
previewLink si = build (srcClass si) `fmap` previewPath si
where
build fc p = anchor ! [href p, target "_blank", theclass "op-preview",
title ("View the " ++ long fc ++ "in another window")]
<< thespan << ("View " ++ short fc)
short SCPage = "Page"
short SCScript = "Page"
short SCImage = "Image"
short SCText = "Text"
short SCOther = "File"
short SCDir = "Dir"
long SCPage = "page"
long SCScript = "generated page"
long SCImage = "image"
long SCText = "text"
long SCOther = "file"
long SCDir = "dir"
editLink :: SrcInfo -> Maybe Html
editLink si = build `fmap` ee (srcClass si)
where
build n = anchor ! [href src, theclass "op-edit", title ("Edit the " ++ n)]
<< thespan << "Edit"
src = "source?file=" ++ srcPath si
ee SCPage = Just "page"
ee SCScript = Just "script"
ee SCText = Just "text"
ee _ = Nothing
downloadLink :: SrcInfo -> Maybe Html
downloadLink si = build `fmap` dd (srcClass si)
where
build n = anchor ! [href (srcPath si), theclass "op-download",
title ("Download the " ++ n)]
<< thespan << "Download"
dd SCText = Just "text"
dd SCScript = Just "script"
dd _ = Nothing
fileLink :: SrcInfo -> Maybe Html
fileLink si = Just $ anchor ! [href ("file://" ++ srcFullPath si),
theclass "op-file",
title "Provides a file:// scheme URL to the local file"]
<< thespan << "Local File"
-- copied from Barley.Utils for now as there is no way to import it
legalPath :: FilePath -> Maybe FilePath
legalPath p =
if any illegalComponent components
then Nothing
else Just $ joinPath components
where
components = splitDirectories p
illegalComponent "" = True
illegalComponent ('.':_) = True
illegalComponent s = any (`elem` "/\\:") s
--
-- SNAP UTILITIES
--
htmlResponse :: HTML a => a -> Snap ()
htmlResponse html = do
modifyResponse $ setContentType (C.pack "text/html; charset=UTF-8")
writeBS $ (T.encodeUtf8 . T.pack) $ renderHtml html
-- copied from Barley.Utils for now as there is no way to import it
-- | Immediately finish with an HTTP error status
finishWithError :: Int -> String -> Snap ()
finishWithError status message =
finishWith $ setResponseStatus status (C.pack message) emptyResponse
-- | Common HTTP error statuses
errorBadRequest :: Snap ()
errorBadRequest = finishWithError 400 "Bad Request"