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))
|