{-# LANGUAGE OverloadedStrings #-}
{-
Copyright (C) 2009 Gwern Branwen <gwern0@gmail.com> and
John MacFarlane <jgm@berkeley.edu>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-}

-- | Functions for creating Atom feeds for Gitit wikis and pages.

module Network.Gitit.Feed (FeedConfig(..), filestoreToXmlFeed) where

import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import Data.Time (UTCTime, formatTime, getCurrentTime, addUTCTime)
#if MIN_VERSION_time(1,5,0)
import Data.Time (defaultTimeLocale)
#else
import System.Locale (defaultTimeLocale)
#endif
import Data.Foldable as F (concatMap)
import Data.List (intercalate, sortBy, nub)
import Data.Maybe (fromMaybe)
import Data.Ord (comparing)
import Network.URI (isUnescapedInURI, escapeURIString)
import System.FilePath (dropExtension, takeExtension, (<.>))
import Data.FileStore.Generic (Diff, PolyDiff(..), diff)
import Data.FileStore.Types (history, retrieve, Author(authorName), Change(..),
         FileStore, Revision(..), TimeRange(..), RevisionId)
import Text.Atom.Feed (nullEntry, nullFeed, nullLink, nullPerson,
         Date, Entry(..), Feed(..), Link(linkRel), Generator(..),
         Person(personName), EntryContent(..), TextContent(TextString))
import Text.Atom.Feed.Export (xmlFeed)
import Text.XML.Light as XML (showContent, Content(..), Element(..), blank_element, QName(..), blank_name, CData(..), blank_cdata)
import Text.XML as Text.XML (renderText, Document(..), Element(..),
                             Prologue(..), def, fromXMLElement)
import Data.Version (showVersion)
import Paths_gitit (version)

data FeedConfig = FeedConfig {
    FeedConfig -> [Char]
fcTitle    :: String
  , FeedConfig -> [Char]
fcBaseUrl  :: String
  , FeedConfig -> Integer
fcFeedDays :: Integer
 } deriving (ReadPrec [FeedConfig]
ReadPrec FeedConfig
Int -> ReadS FeedConfig
ReadS [FeedConfig]
(Int -> ReadS FeedConfig)
-> ReadS [FeedConfig]
-> ReadPrec FeedConfig
-> ReadPrec [FeedConfig]
-> Read FeedConfig
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
$creadsPrec :: Int -> ReadS FeedConfig
readsPrec :: Int -> ReadS FeedConfig
$creadList :: ReadS [FeedConfig]
readList :: ReadS [FeedConfig]
$creadPrec :: ReadPrec FeedConfig
readPrec :: ReadPrec FeedConfig
$creadListPrec :: ReadPrec [FeedConfig]
readListPrec :: ReadPrec [FeedConfig]
Read, Int -> FeedConfig -> ShowS
[FeedConfig] -> ShowS
FeedConfig -> [Char]
(Int -> FeedConfig -> ShowS)
-> (FeedConfig -> [Char])
-> ([FeedConfig] -> ShowS)
-> Show FeedConfig
forall a.
(Int -> a -> ShowS) -> (a -> [Char]) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> FeedConfig -> ShowS
showsPrec :: Int -> FeedConfig -> ShowS
$cshow :: FeedConfig -> [Char]
show :: FeedConfig -> [Char]
$cshowList :: [FeedConfig] -> ShowS
showList :: [FeedConfig] -> ShowS
Show)

gititGenerator :: Generator
gititGenerator :: Generator
gititGenerator = Generator {genURI :: Maybe NCName
genURI = NCName -> Maybe NCName
forall a. a -> Maybe a
Just NCName
"http://github.com/jgm/gitit"
                                   , genVersion :: Maybe NCName
genVersion = NCName -> Maybe NCName
forall a. a -> Maybe a
Just ([Char] -> NCName
T.pack (Version -> [Char]
showVersion Version
version))
                                   , genText :: NCName
genText = NCName
"gitit"}

filestoreToXmlFeed :: FeedConfig -> FileStore -> Maybe FilePath -> IO String
filestoreToXmlFeed :: FeedConfig -> FileStore -> Maybe [Char] -> IO [Char]
filestoreToXmlFeed FeedConfig
cfg FileStore
f = (Feed -> [Char]) -> IO Feed -> IO [Char]
forall a b. (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Feed -> [Char]
xmlFeedToString (IO Feed -> IO [Char])
-> (Maybe [Char] -> IO Feed) -> Maybe [Char] -> IO [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FeedConfig -> Generator -> FileStore -> Maybe [Char] -> IO Feed
generateFeed FeedConfig
cfg Generator
gititGenerator FileStore
f

xmlFeedToString :: Feed -> String
xmlFeedToString :: Feed -> [Char]
xmlFeedToString Feed
elt = Text -> [Char]
TL.unpack (Text -> [Char]) -> (Document -> Text) -> Document -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. RenderSettings -> Document -> Text
renderText RenderSettings
forall a. Default a => a
def (Document -> [Char]) -> Document -> [Char]
forall a b. (a -> b) -> a -> b
$
  Document{ documentPrologue :: Prologue
documentPrologue = Prologue{ prologueBefore :: [Miscellaneous]
prologueBefore = []
                                       , prologueDoctype :: Maybe Doctype
prologueDoctype = Maybe Doctype
forall a. Maybe a
Nothing
                                       , prologueAfter :: [Miscellaneous]
prologueAfter = [] }
          , documentRoot :: Element
documentRoot = (Set NCName -> Element)
-> (Element -> Element) -> Either (Set NCName) Element -> Element
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (Element -> Set NCName -> Element
forall a b. a -> b -> a
const (Element -> Set NCName -> Element)
-> Element -> Set NCName -> Element
forall a b. (a -> b) -> a -> b
$ Name -> Map Name NCName -> [Node] -> Element
Text.XML.Element Name
"feed" Map Name NCName
forall a. Monoid a => a
mempty []) Element -> Element
forall a. a -> a
id
                            (Either (Set NCName) Element -> Element)
-> Either (Set NCName) Element -> Element
forall a b. (a -> b) -> a -> b
$ Element -> Either (Set NCName) Element
fromXMLElement (Element -> Either (Set NCName) Element)
-> Element -> Either (Set NCName) Element
forall a b. (a -> b) -> a -> b
$ Feed -> Element
xmlFeed Feed
elt
          , documentEpilogue :: [Miscellaneous]
documentEpilogue = [] }

generateFeed :: FeedConfig -> Generator -> FileStore -> Maybe FilePath -> IO Feed
generateFeed :: FeedConfig -> Generator -> FileStore -> Maybe [Char] -> IO Feed
generateFeed FeedConfig
cfg Generator
generator FileStore
fs Maybe [Char]
mbPath = do
  UTCTime
now <- IO UTCTime
getCurrentTime
  [Revision]
revs <- Integer -> FileStore -> Maybe [Char] -> UTCTime -> IO [Revision]
changeLog (FeedConfig -> Integer
fcFeedDays FeedConfig
cfg) FileStore
fs Maybe [Char]
mbPath UTCTime
now
  [[([Char], [Diff [[Char]]])]]
diffs <- (Revision -> IO [([Char], [Diff [[Char]]])])
-> [Revision] -> IO [[([Char], [Diff [[Char]]])]]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM (FileStore -> Revision -> IO [([Char], [Diff [[Char]]])]
getDiffs FileStore
fs) [Revision]
revs
  let home :: [Char]
home = FeedConfig -> [Char]
fcBaseUrl FeedConfig
cfg [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ [Char]
"/"
  -- TODO: 'nub . sort' `persons` - but no Eq or Ord instances!
      persons :: [Person]
persons = (Author -> Person) -> [Author] -> [Person]
forall a b. (a -> b) -> [a] -> [b]
map Author -> Person
authorToPerson ([Author] -> [Person]) -> [Author] -> [Person]
forall a b. (a -> b) -> a -> b
$ [Author] -> [Author]
forall a. Eq a => [a] -> [a]
nub ([Author] -> [Author]) -> [Author] -> [Author]
forall a b. (a -> b) -> a -> b
$ (Author -> Author -> Ordering) -> [Author] -> [Author]
forall a. (a -> a -> Ordering) -> [a] -> [a]
sortBy ((Author -> [Char]) -> Author -> Author -> Ordering
forall a b. Ord a => (b -> a) -> b -> b -> Ordering
comparing Author -> [Char]
authorName) ([Author] -> [Author]) -> [Author] -> [Author]
forall a b. (a -> b) -> a -> b
$ (Revision -> Author) -> [Revision] -> [Author]
forall a b. (a -> b) -> [a] -> [b]
map Revision -> Author
revAuthor [Revision]
revs
      basefeed :: Feed
basefeed = Generator
-> [Char] -> [Char] -> Maybe [Char] -> [Person] -> NCName -> Feed
generateEmptyfeed Generator
generator (FeedConfig -> [Char]
fcTitle FeedConfig
cfg) [Char]
home Maybe [Char]
mbPath [Person]
persons ([Char] -> NCName
T.pack (UTCTime -> [Char]
formatFeedTime UTCTime
now))
      revisions :: [Entry]
revisions = ((Revision, [([Char], [Diff [[Char]]])]) -> Entry)
-> [(Revision, [([Char], [Diff [[Char]]])])] -> [Entry]
forall a b. (a -> b) -> [a] -> [b]
map ([Char] -> (Revision, [([Char], [Diff [[Char]]])]) -> Entry
revisionToEntry [Char]
home) ([Revision]
-> [[([Char], [Diff [[Char]]])]]
-> [(Revision, [([Char], [Diff [[Char]]])])]
forall a b. [a] -> [b] -> [(a, b)]
zip [Revision]
revs [[([Char], [Diff [[Char]]])]]
diffs)
  Feed -> IO Feed
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Feed
basefeed {feedEntries = revisions}

-- | Get the last N days history.
changeLog :: Integer -> FileStore -> Maybe FilePath -> UTCTime -> IO [Revision]
changeLog :: Integer -> FileStore -> Maybe [Char] -> UTCTime -> IO [Revision]
changeLog Integer
days FileStore
a Maybe [Char]
mbPath UTCTime
now' = do
  let files :: [[Char]]
files = ([Char] -> [[Char]]) -> Maybe [Char] -> [[Char]]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
F.concatMap (\[Char]
f -> [[Char]
f, [Char]
f [Char] -> ShowS
<.> [Char]
"page"]) Maybe [Char]
mbPath
  let startTime :: UTCTime
startTime = NominalDiffTime -> UTCTime -> UTCTime
addUTCTime (Integer -> NominalDiffTime
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Integer -> NominalDiffTime) -> Integer -> NominalDiffTime
forall a b. (a -> b) -> a -> b
$ -Integer
60 Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
* Integer
60 Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
* Integer
24 Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
* Integer
days) UTCTime
now'
  [Revision]
rs <- FileStore -> [[Char]] -> TimeRange -> Maybe Int -> IO [Revision]
history FileStore
a [[Char]]
files TimeRange{timeFrom :: Maybe UTCTime
timeFrom = UTCTime -> Maybe UTCTime
forall a. a -> Maybe a
Just UTCTime
startTime, timeTo :: Maybe UTCTime
timeTo = UTCTime -> Maybe UTCTime
forall a. a -> Maybe a
Just UTCTime
now'}
          (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
200) -- hard limit of 200 to conserve resources
  [Revision] -> IO [Revision]
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ([Revision] -> IO [Revision]) -> [Revision] -> IO [Revision]
forall a b. (a -> b) -> a -> b
$ (Revision -> Revision -> Ordering) -> [Revision] -> [Revision]
forall a. (a -> a -> Ordering) -> [a] -> [a]
sortBy ((Revision -> Revision -> Ordering)
-> Revision -> Revision -> Ordering
forall a b c. (a -> b -> c) -> b -> a -> c
flip ((Revision -> Revision -> Ordering)
 -> Revision -> Revision -> Ordering)
-> (Revision -> Revision -> Ordering)
-> Revision
-> Revision
-> Ordering
forall a b. (a -> b) -> a -> b
$ (Revision -> UTCTime) -> Revision -> Revision -> Ordering
forall a b. Ord a => (b -> a) -> b -> b -> Ordering
comparing Revision -> UTCTime
revDateTime) [Revision]
rs

getDiffs :: FileStore -> Revision -> IO [(FilePath, [Diff [String]])]
getDiffs :: FileStore -> Revision -> IO [([Char], [Diff [[Char]]])]
getDiffs FileStore
fs Revision{ revId :: Revision -> [Char]
revId = [Char]
to, revDateTime :: Revision -> UTCTime
revDateTime = UTCTime
rd, revChanges :: Revision -> [Change]
revChanges = [Change]
rv } = do
  [Revision]
revPair <- FileStore -> [[Char]] -> TimeRange -> Maybe Int -> IO [Revision]
history FileStore
fs [] (Maybe UTCTime -> Maybe UTCTime -> TimeRange
TimeRange Maybe UTCTime
forall a. Maybe a
Nothing (Maybe UTCTime -> TimeRange) -> Maybe UTCTime -> TimeRange
forall a b. (a -> b) -> a -> b
$ UTCTime -> Maybe UTCTime
forall a. a -> Maybe a
Just UTCTime
rd) (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
2)
  let from :: Maybe [Char]
from = if [Revision] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Revision]
revPair Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
2
                then [Char] -> Maybe [Char]
forall a. a -> Maybe a
Just ([Char] -> Maybe [Char]) -> [Char] -> Maybe [Char]
forall a b. (a -> b) -> a -> b
$ Revision -> [Char]
revId (Revision -> [Char]) -> Revision -> [Char]
forall a b. (a -> b) -> a -> b
$ [Revision]
revPair [Revision] -> Int -> Revision
forall a. HasCallStack => [a] -> Int -> a
!! Int
1
                else Maybe [Char]
forall a. Maybe a
Nothing
  [[Diff [[Char]]]]
diffs <- (Change -> IO [Diff [[Char]]]) -> [Change] -> IO [[Diff [[Char]]]]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM (FileStore
-> Maybe [Char] -> Maybe [Char] -> Change -> IO [Diff [[Char]]]
getDiff FileStore
fs Maybe [Char]
from ([Char] -> Maybe [Char]
forall a. a -> Maybe a
Just [Char]
to)) [Change]
rv
  [([Char], [Diff [[Char]]])] -> IO [([Char], [Diff [[Char]]])]
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ([([Char], [Diff [[Char]]])] -> IO [([Char], [Diff [[Char]]])])
-> [([Char], [Diff [[Char]]])] -> IO [([Char], [Diff [[Char]]])]
forall a b. (a -> b) -> a -> b
$ (([Char], [Diff [[Char]]]) -> ([Char], [Diff [[Char]]]))
-> [([Char], [Diff [[Char]]])] -> [([Char], [Diff [[Char]]])]
forall a b. (a -> b) -> [a] -> [b]
map ([Char], [Diff [[Char]]]) -> ([Char], [Diff [[Char]]])
forall {a}. ([Char], [a]) -> ([Char], [a])
filterPages ([([Char], [Diff [[Char]]])] -> [([Char], [Diff [[Char]]])])
-> [([Char], [Diff [[Char]]])] -> [([Char], [Diff [[Char]]])]
forall a b. (a -> b) -> a -> b
$ [[Char]] -> [[Diff [[Char]]]] -> [([Char], [Diff [[Char]]])]
forall a b. [a] -> [b] -> [(a, b)]
zip ((Change -> [Char]) -> [Change] -> [[Char]]
forall a b. (a -> b) -> [a] -> [b]
map Change -> [Char]
getFP [Change]
rv) [[Diff [[Char]]]]
diffs
  where getFP :: Change -> [Char]
getFP (Added [Char]
fp) = [Char]
fp
        getFP (Modified [Char]
fp) = [Char]
fp
        getFP (Deleted [Char]
fp) = [Char]
fp
        filterPages :: ([Char], [a]) -> ([Char], [a])
filterPages ([Char]
fp, [a]
d) = case (ShowS
forall a. [a] -> [a]
reverse [Char]
fp) of
                                   Char
'e':Char
'g':Char
'a':Char
'p':Char
'.':[Char]
x -> (ShowS
forall a. [a] -> [a]
reverse [Char]
x, [a]
d)
                                   [Char]
_ -> ([Char]
fp, [])

getDiff :: FileStore -> Maybe RevisionId -> Maybe RevisionId -> Change -> IO [Diff [String]]
getDiff :: FileStore
-> Maybe [Char] -> Maybe [Char] -> Change -> IO [Diff [[Char]]]
getDiff FileStore
fs Maybe [Char]
from Maybe [Char]
_ (Deleted [Char]
fp) = do
  [Char]
contents <- FileStore -> forall a. Contents a => [Char] -> Maybe [Char] -> IO a
retrieve FileStore
fs [Char]
fp Maybe [Char]
from
  [Diff [[Char]]] -> IO [Diff [[Char]]]
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return [[[Char]] -> Diff [[Char]]
forall a b. a -> PolyDiff a b
First ([[Char]] -> Diff [[Char]]) -> [[Char]] -> Diff [[Char]]
forall a b. (a -> b) -> a -> b
$ [Char] -> [[Char]]
lines [Char]
contents]
getDiff FileStore
fs Maybe [Char]
from Maybe [Char]
to (Modified [Char]
fp) = FileStore
-> [Char] -> Maybe [Char] -> Maybe [Char] -> IO [Diff [[Char]]]
diff FileStore
fs [Char]
fp Maybe [Char]
from Maybe [Char]
to
getDiff FileStore
fs Maybe [Char]
_ Maybe [Char]
to (Added [Char]
fp) = do
  [Char]
contents <- FileStore -> forall a. Contents a => [Char] -> Maybe [Char] -> IO a
retrieve FileStore
fs [Char]
fp Maybe [Char]
to
  [Diff [[Char]]] -> IO [Diff [[Char]]]
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return [[[Char]] -> Diff [[Char]]
forall a b. b -> PolyDiff a b
Second ([[Char]] -> Diff [[Char]]) -> [[Char]] -> Diff [[Char]]
forall a b. (a -> b) -> a -> b
$ [Char] -> [[Char]]
lines [Char]
contents]

generateEmptyfeed :: Generator -> String ->String ->Maybe String -> [Person] -> Date -> Feed
generateEmptyfeed :: Generator
-> [Char] -> [Char] -> Maybe [Char] -> [Person] -> NCName -> Feed
generateEmptyfeed Generator
generator [Char]
title [Char]
home Maybe [Char]
mbPath [Person]
authors NCName
now =
  Feed
baseNull {feedAuthors = authors,
            feedGenerator = Just generator,
            feedLinks = [ (nullLink $ T.pack $
                            home ++ "_feed/" ++ escape (fromMaybe "" mbPath))
                           {linkRel = Just (Left "self")}]
            }
    where baseNull :: Feed
baseNull = NCName -> TextContent -> NCName -> Feed
nullFeed ([Char] -> NCName
T.pack [Char]
home) (NCName -> TextContent
TextString ([Char] -> NCName
T.pack [Char]
title)) NCName
now

-- Several of the following functions have been given an alternate
-- version patched in using CPP specifically when the `feed` library
-- used is version 1.2.x
--
-- The 1.2.0.0 release of `feed` introduced an API change that was
-- reverted in 1.3.0.0.  When feed-1.2.x is sufficiently out of use,
-- the dependency lower-bound for feed should be updated to >= 1.3 and
-- the second alternative (#else condition) in each of the CPP
-- invocations below can be removed.
--
-- https://github.com/bergmark/feed/issues/35

revisionToEntry :: String -> (Revision, [(FilePath, [Diff [String]])]) -> Entry
revisionToEntry :: [Char] -> (Revision, [([Char], [Diff [[Char]]])]) -> Entry
revisionToEntry [Char]
home (Revision{ revId :: Revision -> [Char]
revId = [Char]
rid, revDateTime :: Revision -> UTCTime
revDateTime = UTCTime
rdt,
                               revAuthor :: Revision -> Author
revAuthor = Author
ra, revDescription :: Revision -> [Char]
revDescription = [Char]
rd,
                               revChanges :: Revision -> [Change]
revChanges = [Change]
rv}, [([Char], [Diff [[Char]]])]
diffs) =
  Entry
baseEntry{ entryContent = Just $ HTMLContent content
           , entryAuthors = [authorToPerson ra], entryLinks = [ln] }
   where baseEntry :: Entry
baseEntry = NCName -> TextContent -> NCName -> Entry
nullEntry ([Char] -> NCName
T.pack [Char]
url) TextContent
title
                          ([Char] -> NCName
T.pack ([Char] -> NCName) -> [Char] -> NCName
forall a b. (a -> b) -> a -> b
$ UTCTime -> [Char]
formatFeedTime UTCTime
rdt)
         url :: [Char]
url = [Char]
home [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ ShowS
escape (Change -> [Char]
extract (Change -> [Char]) -> Change -> [Char]
forall a b. (a -> b) -> a -> b
$ [Change] -> Change
forall a. HasCallStack => [a] -> a
head [Change]
rv) [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ [Char]
"?revision=" [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ [Char]
rid
         ln :: Link
ln = (NCName -> Link
nullLink ([Char] -> NCName
T.pack [Char]
url)) {linkRel = Just (Left "alternate")}
         title :: TextContent
title = NCName -> TextContent
TextString (NCName -> TextContent) -> NCName -> TextContent
forall a b. (a -> b) -> a -> b
$ [Char] -> NCName
T.pack ([Char] -> NCName) -> [Char] -> NCName
forall a b. (a -> b) -> a -> b
$ ((Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Char
'\n' Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/=) [Char]
rd) [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ [Char]
" - " [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ ([Char] -> [[Char]] -> [Char]
forall a. [a] -> [[a]] -> [a]
intercalate [Char]
", " ([[Char]] -> [Char]) -> [[Char]] -> [Char]
forall a b. (a -> b) -> a -> b
$ (Change -> [Char]) -> [Change] -> [[Char]]
forall a b. (a -> b) -> [a] -> [b]
map Change -> [Char]
forall a. Show a => a -> [Char]
show [Change]
rv)
         df :: [Content]
df = (([Char], [Diff [[Char]]]) -> Content)
-> [([Char], [Diff [[Char]]])] -> [Content]
forall a b. (a -> b) -> [a] -> [b]
map ([Char], [Diff [[Char]]]) -> Content
diffFile [([Char], [Diff [[Char]]])]
diffs
#if MIN_VERSION_feed(1, 3, 0) || (! MIN_VERSION_feed(1, 2, 0))
         content :: NCName
content = [Char] -> NCName
T.pack ([Char] -> NCName) -> [Char] -> NCName
forall a b. (a -> b) -> a -> b
$ [[Char]] -> [Char]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([[Char]] -> [Char]) -> [[Char]] -> [Char]
forall a b. (a -> b) -> a -> b
$ (Content -> [Char]) -> [Content] -> [[Char]]
forall a b. (a -> b) -> [a] -> [b]
map Content -> [Char]
showContent [Content]
df
#else
         content = XMLTypes.Element (XMLTypes.Name "div" Nothing Nothing) [] df
#endif

#if MIN_VERSION_feed(1, 3, 0) || (! MIN_VERSION_feed(1, 2, 0))
diffFile :: (FilePath, [Diff [String]]) -> Content
diffFile :: ([Char], [Diff [[Char]]]) -> Content
diffFile ([Char]
fp, [Diff [[Char]]]
d) =
    [Char] -> [Content] -> Content
enTag [Char]
"div" ([Content] -> Content) -> [Content] -> Content
forall a b. (a -> b) -> a -> b
$ Content
header Content -> [Content] -> [Content]
forall a. a -> [a] -> [a]
: [Content]
text
  where
    header :: Content
header = [Char] -> Content -> Content
enTag1 [Char]
"h1" (Content -> Content) -> Content -> Content
forall a b. (a -> b) -> a -> b
$ [Char] -> Content
enText [Char]
fp
    text :: [Content]
text = (Content -> Content) -> [Content] -> [Content]
forall a b. (a -> b) -> [a] -> [b]
map ([Char] -> Content -> Content
enTag1 [Char]
"p") ([Content] -> [Content]) -> [Content] -> [Content]
forall a b. (a -> b) -> a -> b
$ [[Content]] -> [Content]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([[Content]] -> [Content]) -> [[Content]] -> [Content]
forall a b. (a -> b) -> a -> b
$ (Diff [[Char]] -> [Content]) -> [Diff [[Char]]] -> [[Content]]
forall a b. (a -> b) -> [a] -> [b]
map Diff [[Char]] -> [Content]
diffLines [Diff [[Char]]]
d
#else
diffFile :: (FilePath, [Diff [String]]) -> XMLTypes.Node
diffFile (fp, d) =
    enTag "div" $ header : text
  where
    header = enTag1 "h1" $ enText $ T.pack fp
    text = map (enTag1 "p") $ concat $ map diffLines d
#endif

#if MIN_VERSION_feed(1, 3, 0) || (! MIN_VERSION_feed(1, 2, 0))
diffLines :: Diff [String] -> [Content]
diffLines :: Diff [[Char]] -> [Content]
diffLines (First [[Char]]
x) = ([Char] -> Content) -> [[Char]] -> [Content]
forall a b. (a -> b) -> [a] -> [b]
map ([Char] -> Content -> Content
enTag1 [Char]
"s" (Content -> Content) -> ([Char] -> Content) -> [Char] -> Content
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> Content
enText) [[Char]]
x
diffLines (Second [[Char]]
x) = ([Char] -> Content) -> [[Char]] -> [Content]
forall a b. (a -> b) -> [a] -> [b]
map ([Char] -> Content -> Content
enTag1 [Char]
"b" (Content -> Content) -> ([Char] -> Content) -> [Char] -> Content
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> Content
enText) [[Char]]
x
diffLines (Both [[Char]]
x [[Char]]
_) = ([Char] -> Content) -> [[Char]] -> [Content]
forall a b. (a -> b) -> [a] -> [b]
map [Char] -> Content
enText [[Char]]
x
#else
diffLines :: Diff [String] -> [XMLTypes.Node]
diffLines (First x) = map (enTag1 "s" . enText . T.pack) x
diffLines (Second x) = map (enTag1 "b" . enText . T.pack) x
diffLines (Both x _) = map (enText . T.pack) x
#endif

#if MIN_VERSION_feed(1, 3, 0) || (! MIN_VERSION_feed(1, 2, 0))
enTag :: String -> [Content] -> Content
enTag :: [Char] -> [Content] -> Content
enTag [Char]
tag [Content]
content = Element -> Content
Elem Element
blank_element{ elName=blank_name{qName=tag}
                                      , elContent=content
                                      }
#else
enTag :: T.Text -> [XMLTypes.Node] -> XMLTypes.Node
enTag tag content = XMLTypes.NodeElement $
  XMLTypes.Element
  (XMLTypes.Name tag Nothing Nothing)
  [] content
#endif

#if MIN_VERSION_feed(1, 3, 0) || (! MIN_VERSION_feed(1, 2, 0))
enTag1 :: String -> Content -> Content
#else
enTag1 :: T.Text -> XMLTypes.Node -> XMLTypes.Node
#endif
enTag1 :: [Char] -> Content -> Content
enTag1 [Char]
tag Content
content = [Char] -> [Content] -> Content
enTag [Char]
tag [Content
content]

#if MIN_VERSION_feed(1, 3, 0) || (! MIN_VERSION_feed(1, 2, 0))
enText :: String -> Content
enText :: [Char] -> Content
enText [Char]
content = CData -> Content
Text CData
blank_cdata{cdData=content}
#else
enText :: T.Text -> XMLTypes.Node
enText content = XMLTypes.NodeContent (XMLTypes.ContentText content)
#endif

-- gitit is set up not to reveal registration emails
authorToPerson :: Author -> Person
authorToPerson :: Author -> Person
authorToPerson Author
ra = Person
nullPerson {personName = T.pack $ authorName ra}

-- TODO: replace with Network.URI version of shortcut if it ever is added
escape :: String -> String
escape :: ShowS
escape = (Char -> Bool) -> ShowS
escapeURIString Char -> Bool
isUnescapedInURI

formatFeedTime :: UTCTime -> String
formatFeedTime :: UTCTime -> [Char]
formatFeedTime = TimeLocale -> [Char] -> UTCTime -> [Char]
forall t. FormatTime t => TimeLocale -> [Char] -> t -> [Char]
formatTime TimeLocale
defaultTimeLocale [Char]
"%FT%TZ"

-- TODO: this boilerplate can be removed by changing Data.FileStore.Types to say
-- data Change = Modified {extract :: FilePath} | Deleted {extract :: FilePath} | Added
--                   {extract :: FilePath}
-- so then it would be just 'escape (extract $ head rv)' without the 4 line definition
extract :: Change -> FilePath
extract :: Change -> [Char]
extract Change
x = ShowS
dePage ShowS -> ShowS
forall a b. (a -> b) -> a -> b
$ case Change
x of {Modified [Char]
n -> [Char]
n; Deleted [Char]
n -> [Char]
n; Added [Char]
n -> [Char]
n}
          where dePage :: ShowS
dePage [Char]
f = if ShowS
takeExtension [Char]
f [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
== [Char]
".page" then ShowS
dropExtension [Char]
f else [Char]
f

-- TODO: figure out how to create diff links in a non-broken manner
{-
diff :: String -> String -> Revision -> Link
diff home path' Revision{revId = rid} =
                        let n = nullLink (home ++ "_diff/" ++ escape path' ++ "?to=" ++ rid) -- ++ fromrev)
                        in n {linkRel = Just (Left "alternate")}
-}