Finished Atom example
Getty Ritter
10 years ago
| 14 | 14 | main-is: Main.hs |
| 15 | 15 | default-language: Haskell2010 |
| 16 | 16 | ghc-options: -Wall |
| 17 |
build-depends: base >= 4 && < 5, feed, HTTP, SHA, xml, bytestring, directory, filepath, hostname, time, unix |
|
| 17 | build-depends: base >= 4 && < 5, feed, HTTP, SHA, xml, bytestring, directory, filepath, hostname, time, unix, random | |
| 18 | 18 | |
| 19 | 19 | source-repository head |
| 20 | 20 | type: git |
| 27 | 27 | , L.entryTitle = toString entryTitle |
| 28 | 28 | , L.entryContent = maybe "" entryToString entryContent |
| 29 | 29 | , L.entryAuthor = toPeople entryAuthors |
| 30 | , L.entryPubdate = undefined | |
| 31 | , L.entryType = undefined | |
| 30 | , L.entryPubdate = Just entryUpdated | |
| 31 | , L.entryType = fmap entryToType entryContent | |
| 32 | 32 | } |
| 33 | 33 | |
| 34 | 34 | toString :: TextContent -> String |
| 43 | 43 | entryToString (MixedContent _ _) = "[unimplemented]" |
| 44 | 44 | entryToString (ExternalContent _ _) = "[unimplemented]" |
| 45 | 45 | |
| 46 | entryToType :: EntryContent -> String | |
| 47 | entryToType HTMLContent {} = "text/html" | |
| 48 | entryToType XHTMLContent {} = "text/xhtml" | |
| 49 | entryToType _ = "text/plain" | |
| 50 | ||
| 46 | 51 | toPeople :: [Person] -> Maybe String |
| 47 | 52 | toPeople [] = Nothing |
| 48 | 53 | toPeople xs = Just (unlines (map toPerson xs)) |
| 1 | 1 | {-# LANGUAGE RecordWildCards #-} |
| 2 | {-# LANGUAGE ScopedTypeVariables #-} | |
| 3 | 2 | |
| 4 | 3 | module Lektor |
| 5 | 4 | ( Feed(..) |
| 6 | 5 | , Entry(..) |
| 7 | 6 | , mkFeed |
| 8 | 7 | , mkEntry |
| 8 | , writeAll | |
| 9 | 9 | , writeFeed |
| 10 | 10 | , writeEntry |
| 11 | 11 | ) where |
| 20 | 20 | import System.FilePath ((</>)) |
| 21 | 21 | import System.Posix.Files (createSymbolicLink) |
| 22 | 22 | import System.Posix.Process (getProcessID) |
| 23 | import System.Random (randomIO) | |
| 23 | 24 | |
| 24 | 25 | mkdirP :: FilePath -> IO () |
| 25 | 26 | mkdirP = createDirectoryIfMissing True |
| 57 | 58 | mkEntry entryId entryTitle entryContent = |
| 58 | 59 | Entry entryId entryTitle entryContent Nothing Nothing Nothing |
| 59 | 60 | |
| 61 | writeAll :: (Feed, [Entry]) -> IO () | |
| 62 | writeAll (feed, entries) = do | |
| 63 | writeFeed feed | |
| 64 | mapM_ (writeEntry feed) entries | |
| 65 | ||
| 60 | 66 | writeFeed :: Feed -> IO () |
| 61 | 67 | writeFeed Feed { .. } = do |
| 62 | 68 | let dir = "src" </> hash feedId |
| 69 | 75 | writeFileMb (dir </> "copyright") feedCopy |
| 70 | 76 | writeFileMb (dir </> "author") feedAuthor |
| 71 | 77 | |
| 72 | writeEntry :: Entry -> Feed -> IO () | |
| 73 | writeEntry (Entry { .. }) (Feed { feedId = feedId }) = do | |
| 78 | writeEntry :: Feed -> Entry -> IO () | |
| 79 | writeEntry (Feed { feedId = feedId }) (Entry { .. }) = do | |
| 74 | 80 | let feedHash = hash feedId |
| 75 | 81 | uniq <- mkUniq |
| 76 | 82 | let dir = "tmp" </> feedHash </> uniq |
| 83 | cwd <- getCurrentDirectory | |
| 77 | 84 | mkdirP dir |
| 78 | 85 | mkdirP ("new" </> hash feedId) |
| 79 | 86 | writeFile (dir </> "id") entryId |
| 82 | 89 | writeFileMb (dir </> "author") entryAuthor |
| 83 | 90 | writeFileMb (dir </> "pubdate") entryPubdate |
| 84 | 91 | writeFileMb (dir </> "type") entryType |
| 85 |
createSymbolicLink ( |
|
| 92 | createSymbolicLink (cwd </> "src" </> feedHash) (dir </> "feed") | |
| 86 | 93 | renameDirectory dir ("new" </> feedHash </> uniq) |
| 94 | ||
| 95 | integer :: Integer -> Integer | |
| 96 | integer = id | |
| 87 | 97 | |
| 88 | 98 | mkUniq :: IO String |
| 89 | 99 | mkUniq = do |
| 90 | (t :: Integer, r') <- properFraction `fmap` getPOSIXTime | |
| 91 | let r = filter isDigit (show r') | |
| 92 |
|
|
| 100 | (t, m') <- properFraction `fmap` getPOSIXTime | |
| 101 | let m = filter isDigit (show m') | |
| 102 | r <- abs `fmap` randomIO | |
| 93 | 103 | p <- getProcessID |
| 94 | 104 | h <- getHostName |
| 95 | let uniq = "P" <> show p <> "R" <> r <> "M" <> m | |
| 96 | return (show t <> "." <> uniq <> "." <> h) | |
| 105 | let uniq = "P" <> show p <> "R" <> show (integer r) <> "M" <> m | |
| 106 | return (show (integer t) <> "." <> uniq <> "." <> h) | |
| 5 | 5 | module Main where |
| 6 | 6 | |
| 7 | 7 | import qualified Lektor as L |
| 8 | ||
| 9 | 8 | import qualified Atom as A |
| 10 | 9 | |
| 11 | import Control.Monad (zipWithM_) | |
| 12 | import Data.ByteString.Lazy.Char8 (pack) | |
| 13 | import Data.Digest.Pure.SHA | |
| 14 | import Data.Monoid ((<>)) | |
| 15 | import Data.Time.Clock.POSIX (getPOSIXTime) | |
| 16 | import Network.HostName | |
| 17 | 10 | import Network.HTTP |
| 18 | 11 | import System.Directory |
| 19 | 12 | import System.Environment |
| 20 | -- import System.Exit (die) | |
| 21 | import System.FilePath ((</>)) | |
| 22 | import System.Posix.Process (getProcessID) | |
| 23 | 13 | import Text.Atom.Feed.Import (elementFeed) |
| 24 | import Text.Atom.Feed | |
| 25 | 14 | import Text.XML.Light.Input (parseXMLDoc) |
| 26 | 15 | |
| 16 | die :: String -> a | |
| 27 | 17 | die = error |
| 28 | 18 | |
| 29 | 19 | usage :: String |
| 43 | 33 | [] -> putStrLn usage |
| 44 | 34 | (url:_) -> do |
| 45 | 35 | simpleHTTP (getRequest url) >>= \case |
| 46 | Left err -> die "Unable to fetch document" | |
| 47 | Right r -> makeEntries url (rspBody r) | |
| 36 | Left _ -> die "Unable to fetch document" | |
| 37 | Right r -> writeAsAtom (rspBody r) | |
| 48 | 38 | |
| 49 | makeEntries :: String -> String -> IO () | |
| 50 | makeEntries url s = case parseXMLDoc s of | |
| 51 | Nothing -> die "Unale to parse XML document" | |
| 39 | writeAsAtom :: String -> IO () | |
| 40 | writeAsAtom s = case parseXMLDoc s of | |
| 41 | Nothing -> die "Unable to parse XML document" | |
| 52 | 42 | Just xml -> case elementFeed xml of |
| 53 | Just atom -> buildLektorDir url atom | |
| 54 | 43 | Nothing -> die "XML document not an Atom feed" |
| 55 | ||
| 56 | contentAsString :: TextContent -> String | |
| 57 | contentAsString (TextString s) = s | |
| 58 | contentAsString _ = error "..." | |
| 59 | ||
| 60 | buildLektorDir :: String -> Feed -> IO () | |
| 61 | buildLektorDir url feed = do | |
| 62 | let hash = showDigest (sha1 (pack url)) | |
| 63 | mapM_ (createDirectoryIfMissing True) | |
| 64 | [ "src" </> hash | |
| 65 | , "tmp" </> hash | |
| 66 | , "new" </> hash | |
| 67 | , "cur" </> hash | |
| 68 | ] | |
| 69 | writeFile ("src" </> hash </> "name") | |
| 70 | (contentAsString (feedTitle feed)) | |
| 71 | writeFile ("src" </> hash </> "id") url | |
| 72 | zipWithM_ (buildLektorEntry hash) [0..] (reverse (feedEntries feed)) | |
| 73 | ||
| 74 | buildLektorEntry :: String -> Int -> Entry -> IO () | |
| 75 | buildLektorEntry hash n (Entry { .. }) = do | |
| 76 | t <- fmap (floor . realToFrac) getPOSIXTime | |
| 77 | p <- getProcessID | |
| 78 | h <- getHostName | |
| 79 | let dirId = show t <> ".P" <> show p <> "Q" <> show n <> "." <> h | |
| 80 | let tmpDir = "tmp" </> hash </> dirId | |
| 81 | createDirectoryIfMissing True tmpDir | |
| 82 | writeFile (tmpDir </> "title") (contentAsString entryTitle) | |
| 83 | writeFile (tmpDir </> "id") entryId | |
| 84 | writeFile (tmpDir </> "content") $ case entryContent of | |
| 85 | Just (TextContent s) -> s | |
| 86 | Just (HTMLContent s) -> s | |
| 87 | _ -> "[unsupported content]" | |
| 88 | writeFile (tmpDir </> "type") $ case entryContent of | |
| 89 | Just (HTMLContent s) -> "text/html" | |
| 90 | _ -> "text/plain" | |
| 91 |
|
|
| 44 | Just atom -> L.writeAll (A.atomToLektor atom) | |