| 5 | 5 |
import qualified Data.ByteString.Char8 as BS
|
| 6 | 6 |
import qualified Data.ByteString.Lazy.Char8 as LBS
|
| 7 | 7 |
import Data.Default (def)
|
| 8 |
import qualified Data.Map.Strict as Map
|
| 8 | 9 |
import Data.Monoid ((<>))
|
| 9 | 10 |
import qualified Data.Time.Clock as Time
|
| 10 | 11 |
import qualified Data.Time.Format as Time
|
| 11 | 12 |
import qualified Data.Text as T
|
| 12 | 13 |
import qualified Data.Text.Encoding as T
|
| 13 | |
import qualified Network.Gitit.Config as Gitit
|
| 14 | 14 |
import qualified Network.Gitit.Page as Gitit
|
| 15 | 15 |
import qualified Network.Gitit.Types as Gitit
|
| 16 | 16 |
import qualified Network.HTTP.Types.Status as Http
|
| 17 | 17 |
import qualified Network.Wai as Wai
|
| 18 | 18 |
import qualified Network.Wai.Handler.Warp as Warp
|
| 19 | |
import qualified System.Console.GetOpt as Opt
|
| 20 | 19 |
import qualified System.Directory as Dir
|
| 21 | |
import qualified System.Environment as Env
|
| 22 | |
import qualified System.Exit as Exit
|
| 23 | 20 |
import System.FilePath ((</>), makeRelative)
|
| 24 | 21 |
import qualified Text.Pandoc as Pandoc
|
| 25 | 22 |
import Text.Printf (printf)
|
| 26 | 23 |
|
| 27 | |
data Protection
|
| 28 | |
= PrivateByDefault
|
| 29 | |
| PublicByDefault
|
| 30 | |
deriving (Eq, Show)
|
| 31 | |
|
| 32 | |
data FrontitOpts = FrontitOpts
|
| 33 | |
{ optPort :: Int
|
| 34 | |
, optData :: Maybe FilePath
|
| 35 | |
, optGititConfig :: Maybe FilePath
|
| 36 | |
, optTemplate :: Maybe FilePath
|
| 37 | |
, optProtection :: Protection
|
| 38 | |
} deriving (Eq, Show)
|
| 39 | |
|
| 40 | |
defaultOpts :: FrontitOpts
|
| 41 | |
defaultOpts = FrontitOpts
|
| 42 | |
{ optPort = 5000
|
| 43 | |
, optData = Nothing
|
| 44 | |
, optGititConfig = Nothing
|
| 45 | |
, optTemplate = Nothing
|
| 46 | |
, optProtection = PrivateByDefault
|
| 47 | |
}
|
| 48 | |
|
| 49 | |
frontitOptDescr :: [Opt.OptDescr (FrontitOpts -> FrontitOpts)]
|
| 50 | |
frontitOptDescr =
|
| 51 | |
[ Opt.Option ['p'] ["port"]
|
| 52 | |
(Opt.ReqArg (\ s opts -> opts { optPort = read s }) "port")
|
| 53 | |
"The port to serve on"
|
| 54 | |
, Opt.Option ['d'] ["data"]
|
| 55 | |
(Opt.ReqArg (\ s opts -> opts { optData = Just s }) "path")
|
| 56 | |
"The location of the data directory"
|
| 57 | |
, Opt.Option ['c'] ["config"]
|
| 58 | |
(Opt.ReqArg (\ s opts -> opts { optGititConfig = Just s }) "path")
|
| 59 | |
"The location of the gitit configuration"
|
| 60 | |
, Opt.Option ['t'] ["template"]
|
| 61 | |
(Opt.ReqArg (\ s opts -> opts { optTemplate = Just s }) "path")
|
| 62 | |
"The location of the desired HTML template"
|
| 63 | |
, Opt.Option ['o'] ["public-by-default"]
|
| 64 | |
(Opt.NoArg (\ opts -> opts { optProtection = PublicByDefault}))
|
| 65 | |
"Allow all pages to be browsed by default"
|
| 66 | |
]
|
| 67 | |
|
| 68 | |
optsToConfiguration :: FrontitOpts -> IO FrontitConf
|
| 69 | |
optsToConfiguration FrontitOpts { .. } = do
|
| 70 | |
fcData <- case optData of
|
| 71 | |
Just path -> return path
|
| 72 | |
Nothing -> Dir.getCurrentDirectory
|
| 73 | |
fcTemplate <- case optTemplate of
|
| 74 | |
Just path -> readFile path
|
| 75 | |
Nothing -> return defaultTemplate
|
| 76 | |
fcGititConfig <- case optGititConfig of
|
| 77 | |
Just path -> Gitit.getConfigFromFile path
|
| 78 | |
Nothing -> Gitit.getDefaultConfig
|
| 79 | |
let fcProtection = optProtection
|
| 80 | |
return FrontitConf { .. }
|
| 81 | |
|
| 82 | |
defaultTemplate :: String
|
| 83 | |
defaultTemplate = "<html><body>$body$</body></html>"
|
| 84 | |
|
| 85 | |
data FrontitConf = FrontitConf
|
| 86 | |
{ fcData :: FilePath
|
| 87 | |
, fcGititConfig :: Gitit.Config
|
| 88 | |
, fcTemplate :: String
|
| 89 | |
, fcProtection :: Protection
|
| 90 | |
}
|
| 24 |
import Config
|
| 91 | 25 |
|
| 92 | 26 |
data Result
|
| 93 | 27 |
= Found Gitit.Page
|
|
| 122 | 56 |
go Pandoc.Space = Just " "
|
| 123 | 57 |
go _ = Nothing
|
| 124 | 58 |
|
| 125 | |
renderPage :: FrontitConf -> Gitit.Page -> String
|
| 126 | |
renderPage conf pg@Gitit.Page { .. } = Pandoc.writeHtmlString writerOpts pandoc
|
| 59 |
addTitle :: T.Text -> Pandoc.Pandoc -> Pandoc.Pandoc
|
| 60 |
addTitle title p@(Pandoc.Pandoc meta@(Pandoc.Meta metaMap) bs)
|
| 61 |
| Just _ <- Pandoc.lookupMeta "title" meta = p
|
| 62 |
| otherwise = Pandoc.Pandoc newMeta bs
|
| 63 |
where titleVal = Pandoc.MetaString (T.unpack title)
|
| 64 |
newMeta = Pandoc.Meta (Map.insert "title" titleVal metaMap)
|
| 65 |
|
| 66 |
renderPage :: FrontitConf -> Gitit.Page -> T.Text -> String
|
| 67 |
renderPage conf pg@Gitit.Page { .. } title =
|
| 68 |
Pandoc.writeHtmlString writerOpts (addTitle title pandoc)
|
| 127 | 69 |
where
|
| 128 | 70 |
writerOpts = def { Pandoc.writerTemplate = fcTemplate conf
|
| 129 | 71 |
, Pandoc.writerStandalone = True
|
|
| 148 | 90 |
| req == "" = Just (Gitit.frontPage (fcGititConfig conf) <> ".page")
|
| 149 | 91 |
| otherwise = Just (T.unpack (req <> ".page"))
|
| 150 | 92 |
|
| 93 |
getTitle :: FrontitConf -> [T.Text] -> T.Text
|
| 94 |
getTitle conf [] = T.pack (Gitit.frontPage (fcGititConfig conf))
|
| 95 |
getTitle _ cs = T.toTitle (last cs)
|
| 96 |
|
| 97 |
strToByteString :: String -> LBS.ByteString
|
| 98 |
strToByteString = LBS.fromStrict . T.encodeUtf8 . T.pack
|
| 99 |
|
| 100 |
-- Our application is simple: every GET request will look up a
|
| 101 |
-- corresponding file in the data directory and serve it according
|
| 102 |
-- to roughly the same logic as Gitit.
|
| 151 | 103 |
app :: FrontitConf -> Wai.Application
|
| 152 | 104 |
app conf = \ req respond -> do
|
| 153 | |
let respond' st pg = respond (Wai.responseLBS st [] (LBS.fromStrict (T.encodeUtf8 (T.pack pg))))
|
| 105 |
let respond' st pg = respond (Wai.responseLBS st [] (strToByteString pg))
|
| 154 | 106 |
if Wai.requestMethod req == "GET"
|
| 155 | 107 |
then do
|
| 156 | 108 |
case getLocalPath conf (T.intercalate ("/") (Wai.pathInfo req)) of
|
| 157 | 109 |
Nothing -> respond' Http.status403 "invalid URL"
|
| 158 | 110 |
Just path -> do
|
| 111 |
let title = getTitle conf (Wai.pathInfo req)
|
| 159 | 112 |
result <- fetchPage conf path
|
| 160 | 113 |
case result of
|
| 161 | |
Found pg -> respond' Http.status200 (renderPage conf pg)
|
| 114 |
Found pg -> respond' Http.status200 (renderPage conf pg title)
|
| 162 | 115 |
Private -> respond' Http.status403 "private page"
|
| 163 | 116 |
NotFound -> respond' Http.status404 "not found"
|
| 164 | 117 |
else respond' Http.status403 "forbidden"
|
|
| 184 | 137 |
|
| 185 | 138 |
main :: IO ()
|
| 186 | 139 |
main = do
|
| 187 | |
args <- Env.getArgs
|
| 188 | |
opts <- case Opt.getOpt Opt.Permute frontitOptDescr args of
|
| 189 | |
(fs, [], []) -> return (foldr (.) id fs defaultOpts)
|
| 190 | |
_ -> do
|
| 191 | |
putStrLn (Opt.usageInfo "frontit" frontitOptDescr)
|
| 192 | |
Exit.exitFailure
|
| 193 | |
conf <- optsToConfiguration opts
|
| 194 | |
printf "running frontit on port %d\n" (optPort opts)
|
| 195 | |
Warp.run (optPort opts) (mkLogger (app conf))
|
| 140 |
conf <- getConfig
|
| 141 |
printf "running frontit on port %d\n" (fcPort conf)
|
| 142 |
Warp.run (fcPort conf) (mkLogger (app conf))
|