| 1 | #!/usr/local/bin/runghc |
|---|
| 2 | -- id:nobsun さんありがとうございます |
|---|
| 3 | |
|---|
| 4 | module Main where |
|---|
| 5 | |
|---|
| 6 | import System.FilePath |
|---|
| 7 | import System.Directory |
|---|
| 8 | import System.Time |
|---|
| 9 | import System.Locale (defaultTimeLocale) |
|---|
| 10 | |
|---|
| 11 | import Data.List |
|---|
| 12 | import Data.Maybe |
|---|
| 13 | import Data.Char |
|---|
| 14 | import Data.Eq |
|---|
| 15 | |
|---|
| 16 | import Network.CGI |
|---|
| 17 | |
|---|
| 18 | -- ref. http://i.loveruby.net/ja/stdhaskell/samples/lazylines/Template.hs.html |
|---|
| 19 | fillFlavour :: String -> String -> [(String, String)] -> IO String |
|---|
| 20 | fillFlavour flavour place params = return . fill =<< (readFile $ place ++ "." ++ flavour) |
|---|
| 21 | where |
|---|
| 22 | fill "" = "" |
|---|
| 23 | fill tmpl = case break (== '$') tmpl of |
|---|
| 24 | (s, ('$':cs)) -> s ++ expand var ++ fill cont |
|---|
| 25 | where (var, cont) = span isAlpha cs |
|---|
| 26 | (s, cont) -> s ++ fill cont |
|---|
| 27 | |
|---|
| 28 | expand var = fromMaybe ('$':var) (lookup var params) |
|---|
| 29 | |
|---|
| 30 | loadConfig :: String -> IO [(String, String)] |
|---|
| 31 | loadConfig config = do |
|---|
| 32 | { |
|---|
| 33 | ; c <- readFile config |
|---|
| 34 | ; return $ concatMap parseLine $ lines c |
|---|
| 35 | } where |
|---|
| 36 | parseLine :: String -> [(String, String)] |
|---|
| 37 | parseLine l |
|---|
| 38 | | all isSpace l = [] |
|---|
| 39 | | otherwise = let (k, (':':v)) = (break (== ':') l) |
|---|
| 40 | in [(strip k, strip v)] |
|---|
| 41 | strip = rstrip . lstrip |
|---|
| 42 | rstrip = reverse . lstrip . reverse |
|---|
| 43 | lstrip = dropWhile isSpace |
|---|
| 44 | |
|---|
| 45 | getConfig :: [(String, String)] -> String -> String |
|---|
| 46 | getConfig config key = fromJust $ lookup key config |
|---|
| 47 | |
|---|
| 48 | data Entry = Entry { |
|---|
| 49 | file :: FilePath, |
|---|
| 50 | path :: String, |
|---|
| 51 | title :: String, |
|---|
| 52 | time :: ClockTime, |
|---|
| 53 | body :: [String] |
|---|
| 54 | } |
|---|
| 55 | |
|---|
| 56 | |
|---|
| 57 | getFileEntry :: String -> FilePath -> IO Entry |
|---|
| 58 | getFileEntry d f = do |
|---|
| 59 | { |
|---|
| 60 | ; mtime <- getModificationTime f |
|---|
| 61 | ; cs <- readFile f |
|---|
| 62 | ; case lines cs of |
|---|
| 63 | h:tl -> return $ Entry { file = f |
|---|
| 64 | , path = dropExtension $ drop (length d) f |
|---|
| 65 | , title = h |
|---|
| 66 | , time = mtime |
|---|
| 67 | , body = tl |
|---|
| 68 | } |
|---|
| 69 | } |
|---|
| 70 | |
|---|
| 71 | -- http://www.tom.sfc.keio.ac.jp/~sakai/2ch/1162902266.html |
|---|
| 72 | getRecursiveContents :: FilePath -> IO [FilePath] |
|---|
| 73 | getRecursiveContents fp = getDirectoryContents fp >>= mapM (fx fp) >>= return . concat |
|---|
| 74 | where |
|---|
| 75 | fx :: FilePath -> FilePath -> IO [FilePath] |
|---|
| 76 | fx bp "." = return [bp] |
|---|
| 77 | fx bp ".." = return [] |
|---|
| 78 | fx bp f = do let np = bp ++ ('/':f) |
|---|
| 79 | b <- doesDirectoryExist np |
|---|
| 80 | if b then getRecursiveContents np |
|---|
| 81 | else return [np] |
|---|
| 82 | |
|---|
| 83 | |
|---|
| 84 | |
|---|
| 85 | getTextFileEntries :: FilePath -> IO [Entry] |
|---|
| 86 | getTextFileEntries d = do |
|---|
| 87 | { |
|---|
| 88 | ; fs <- getRecursiveContents d |
|---|
| 89 | ; es <- mapM (getFileEntry d) $ filter (isSuffixOf ".txt") fs |
|---|
| 90 | ; return es |
|---|
| 91 | } |
|---|
| 92 | |
|---|
| 93 | |
|---|
| 94 | getResult :: [(String, String)] -> [(String, String)] -> IO (String, String) |
|---|
| 95 | getResult cgiparams config = do |
|---|
| 96 | { |
|---|
| 97 | ; entries <- getTextFileEntries $ getConfig config "data-dir" |
|---|
| 98 | ; ct <- fill "content_type" [] |
|---|
| 99 | ; ch <- fill "head" [] |
|---|
| 100 | ; cs <- mapM |
|---|
| 101 | (\e -> fill "story" |
|---|
| 102 | [("title" , title e), |
|---|
| 103 | ("body" , concat (body e)), |
|---|
| 104 | ("time" , formatCalendarTime defaultTimeLocale "%Y-%m-%d %H:%M:%S" (toUTCTime (time e))), |
|---|
| 105 | ("yr" , formatCalendarTime defaultTimeLocale "%Y" (toUTCTime (time e))), |
|---|
| 106 | ("mo" , formatCalendarTime defaultTimeLocale "%b" (toUTCTime (time e))), |
|---|
| 107 | ("mo_num" , formatCalendarTime defaultTimeLocale "%m" (toUTCTime (time e))), |
|---|
| 108 | ("da" , formatCalendarTime defaultTimeLocale "%d" (toUTCTime (time e))), |
|---|
| 109 | ("dw" , formatCalendarTime defaultTimeLocale "%a" (toUTCTime (time e))), |
|---|
| 110 | ("hr" , formatCalendarTime defaultTimeLocale "%H" (toUTCTime (time e))), |
|---|
| 111 | ("min" , formatCalendarTime defaultTimeLocale "%M" (toUTCTime (time e))), |
|---|
| 112 | ("hr12" , formatCalendarTime defaultTimeLocale "%I" (toUTCTime (time e))), |
|---|
| 113 | ("ampm" , formatCalendarTime defaultTimeLocale "%p" (toUTCTime (time e))), |
|---|
| 114 | ("ti" , formatCalendarTime defaultTimeLocale "%X" (toUTCTime (time e))), |
|---|
| 115 | ("fn" , path e), |
|---|
| 116 | ("path" , path e)]) |
|---|
| 117 | $ take 7 $ reverse $ sortBy (\a b -> compare (time a) (time b)) entries |
|---|
| 118 | ; cf <- fill "foot" [] |
|---|
| 119 | ; return $ (ct, concat $ [ch, concat cs, cf]) |
|---|
| 120 | } where |
|---|
| 121 | fill place params = fillFlavour "html" place (params ++ [("title" , getConfig config "title"), |
|---|
| 122 | ("author" , getConfig config "author"), |
|---|
| 123 | ("version", "How I can get this version of GHC in program?")] ++ cgiparams) |
|---|
| 124 | |
|---|
| 125 | cgiMain :: CGI CGIResult |
|---|
| 126 | cgiMain = do |
|---|
| 127 | { |
|---|
| 128 | ; config <- liftIO $ loadConfig "config" |
|---|
| 129 | ; home <- scriptName |
|---|
| 130 | ; pathinfo <- pathInfo |
|---|
| 131 | ; (ct, cb) <- liftIO $ getResult [("home", home), ("pathinfo", pathinfo)] config |
|---|
| 132 | ; setHeader "Content-Type" $ ct |
|---|
| 133 | ; output $ cb |
|---|
| 134 | } |
|---|
| 135 | |
|---|
| 136 | main :: IO () |
|---|
| 137 | main = runCGI $ handleErrors cgiMain |
|---|
| 138 | |
|---|