add support for approx dates

This commit is contained in:
Rhiannon Morris 2020-09-25 23:08:44 +02:00
parent 1cfb65eae9
commit 8d4ff29e56
8 changed files with 141 additions and 65 deletions

111
make-pages/Date.hs Normal file
View file

@ -0,0 +1,111 @@
module Date
(Date (..),
Day (..), dayNum, exact,
formatLong, formatShort, formatRSS, formatSlash,
parseP, parseS, parseA)
where
import Control.Applicative
import qualified Text.ParserCombinators.ReadP as ReadP
import Text.ParserCombinators.ReadP (ReadP, readS_to_P, readP_to_S, (<++))
import Data.Time hiding (Day)
import Data.Char (isSpace)
import BuilderQQ
import Data.Function (on)
import Data.Maybe (fromMaybe)
import Data.YAML (FromYAML (..))
import qualified Data.YAML as YAML
import Data.Text (unpack)
data Date = Date {year, month :: Int, day :: Day}
deriving (Eq, Ord, Show)
data Day =
Exact Int
| Approx Int
| Unknown
deriving (Eq, Show)
dayNum :: Day -> Maybe Int
dayNum (Exact x) = Just x
dayNum (Approx x) = Just x
dayNum Unknown = Nothing
instance Ord Day where compare = compare `on` dayNum
exact :: Day -> Bool
exact (Exact _) = True
exact _ = False
formatLong :: Date -> Builder
formatLong (Date {year, month, day}) =
case dayN of
Nothing -> monthYear
Just (nth -> d) -> [b|$approx$weekday $d $monthYear|]
where
dayN = dayNum day
day' = fromMaybe 1 dayN
formatted str = fromString $
formatTime defaultTimeLocale str $
fromGregorian (toInteger year) month day'
monthYear = formatted "%B %Y"
weekday = formatted "%a"
approx = if exact day then "" else [b|approx. $&|]
formatShort :: Date -> Builder
formatShort (Date {month, day}) = [b|$day'$month'|] where
day' = case day of
Exact d -> [b|$d $&|]
Approx d -> [b|$d? $&|]
Unknown -> ""
month' = formatTime defaultTimeLocale "%b" $ fromGregorian 1 month 1
formatRSS :: Date -> Builder
formatRSS = fromString . format . toTime where
format = formatTime defaultTimeLocale "%a, %d %b %_Y %T GMT"
toTime (Date {year, month, day}) =
let year' = toInteger year; day' = fromMaybe 1 $ dayNum day in
UTCTime (fromGregorian year' month day') 15600
formatSlash :: Date -> Builder
formatSlash (Date {year, month, day}) = case dayNum day of
Nothing -> [b|$year/$month|]
Just d -> [b|$year/$month/$d$q|]
where q = if exact day then "" else [b|<span class=q>?</span>|]
nth :: Int -> Builder
nth n = [b|$n$suf|] where
suf | n >= 10, n <= 19 = [b|th|]
| n `mod` 10 == 1 = [b|st|]
| n `mod` 10 == 2 = [b|nd|]
| n `mod` 10 == 3 = [b|rd|]
| otherwise = [b|th|]
parseP :: ReadP Date
parseP = do
year <- readp
char_ '-'
month <- readp
day <- option Unknown do
char_ '-'
d <- readp
approx <- option Exact (Approx <$ ReadP.char '?')
pure $ approx d
pure $ Date year month day
where
readp = readS_to_P reads
char_ c = () <$ ReadP.char c
option k p = p <++ pure k
parseS :: ReadS Date
parseS = readP_to_S parseP
parseA :: Alternative f => String -> f Date
parseA str = case parseS str of
[(d, rest)] | all isSpace rest -> pure d
_ -> empty
instance FromYAML Date where
parseYAML = YAML.withStr "date" $ parseA . unpack

View file

@ -1,6 +1,10 @@
{-# LANGUAGE TransformListComp #-} {-# LANGUAGE TransformListComp #-}
module GalleryPage (make) where module GalleryPage (make) where
import BuilderQQ
import Date
import Info
import Data.Foldable import Data.Foldable
import Data.Function (on, (&)) import Data.Function (on, (&))
import qualified Data.HashMap.Strict as HashMap import qualified Data.HashMap.Strict as HashMap
@ -8,13 +12,9 @@ import Data.HashSet (HashSet)
import qualified Data.HashSet as HashSet import qualified Data.HashSet as HashSet
import Data.List (intersperse, groupBy, sortOn) import Data.List (intersperse, groupBy, sortOn)
import qualified Data.Text.Lazy as Lazy import qualified Data.Text.Lazy as Lazy
import qualified Data.Time as Time
import System.FilePath (takeDirectory, joinPath, splitPath) import System.FilePath (takeDirectory, joinPath, splitPath)
import GHC.Exts (Down (..), the) import GHC.Exts (Down (..), the)
import BuilderQQ
import Info
make :: Text -> GalleryInfo -> [(FilePath, Info)] -> Lazy.Text make :: Text -> GalleryInfo -> [(FilePath, Info)] -> Lazy.Text
make root ginfo infos = toLazyText $ make' root ginfo infos make root ginfo infos = toLazyText $ make' root ginfo infos
@ -118,7 +118,7 @@ makeFilter prefix initial tag count = [b|@8
checked = if HashSet.member tag initial then [b| checked|] else "" checked = if HashSet.member tag initial then [b| checked|] else ""
makeYearItems :: Bool -- ^ nsfw makeYearItems :: Bool -- ^ nsfw
-> Integer -- ^ year -> Int -- ^ year
-> [(FilePath, Info)] -> [(FilePath, Info)]
-> Builder -> Builder
makeYearItems nsfw year infos = [b|@4 makeYearItems nsfw year infos = [b|@4
@ -149,11 +149,6 @@ makeItem nsfw file info@(Info {title, bg}) = [b|@4
thumb = getThumb dir info thumb = getThumb dir info
nsfw' = if nsfw && #anyNsfw info then [b| nsfw|] else "" nsfw' = if nsfw && #anyNsfw info then [b| nsfw|] else ""
tags' = fold $ intersperse ";" $ map fromText $ tagsFor nsfw info tags' = fold $ intersperse ";" $ map fromText $ tagsFor nsfw info
date' = formatDateShort $ #latestDate info date' = formatShort $ #latestDate info
updated' = if #updated info then [b|true|] else [b|false|] updated' = if #updated info then [b|true|] else [b|false|]
bgStyle = ifJust bg \col -> [b| style="background: $col"|] bgStyle = ifJust bg \col -> [b| style="background: $col"|]
formatDateShort :: Time.Day -> Builder
formatDateShort date = [b|$day $month|] where
(_, m, day) = Time.toGregorian date
month = words "jan feb mar apr may jun jul aug sep oct nov dec" !! (m - 1)

View file

@ -7,9 +7,10 @@ module Info
readArtistFilter, matchArtist, readNsfwFilter, matchNsfw, matchFilters, readArtistFilter, matchArtist, readNsfwFilter, matchNsfw, matchFilters,
NoThumb (..), getThumb, thumbFile, pageFile, NoThumb (..), getThumb, thumbFile, pageFile,
-- ** Reexports -- ** Reexports
Day (..), Text) Date (..), Day (..), Text)
where where
import Date
import Records import Records
import Control.Applicative import Control.Applicative
@ -27,21 +28,19 @@ import Data.Ord (comparing)
import Data.String (IsString) import Data.String (IsString)
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as Text import qualified Data.Text as Text
import Data.Time.Calendar (Day (..), toGregorian)
import Data.YAML (FromYAML (..), (.:), (.:?), (.!=)) import Data.YAML (FromYAML (..), (.:), (.:?), (.!=))
import qualified Data.YAML as YAML import qualified Data.YAML as YAML
import System.FilePath ((</>), takeBaseName, takeExtension, splitExtension) import System.FilePath ((</>), takeBaseName, takeExtension, splitExtension)
import Text.Read (readMaybe)
data Info = data Info =
Info { Info {
date :: !Day, date :: !Date,
-- extra sort key after date -- extra sort key after date
-- e.g. multiple things on the same day might have a,b,c in @sortEx@ to -- e.g. multiple things on the same day might have a,b,c in @sortEx@ to
-- put them in the right order in the gallery -- put them in the right order in the gallery
sortEx :: !Text, sortEx :: !Text,
updates :: !(Map Day Text), updates :: !(Map Date Text),
title :: !Text, title :: !Text,
artist :: !(Maybe Artist), -- nothing = me, obv artist :: !(Maybe Artist), -- nothing = me, obv
nsfwOnly :: !Bool, nsfwOnly :: !Bool,
@ -105,17 +104,11 @@ instance HasField "thumb" Info (Maybe FilePath) where
instance HasField "mine" Info Bool where getField = isNothing . #artist instance HasField "mine" Info Bool where getField = isNothing . #artist
instance HasField "notMine" Info Bool where getField = isJust . #artist instance HasField "notMine" Info Bool where getField = isJust . #artist
instance HasField "dmy" Info (Integer, Int, Int) where instance HasField "latestDate" Info Date where
getField = toGregorian . #date
instance HasField "year" Info Integer where getField = #first . #dmy
instance HasField "month" Info Int where getField = #second . #dmy
instance HasField "day" Info Int where getField = #third . #dmy
instance HasField "latestDate" Info Day where
getField (Info {date, updates}) = maximum (date : Map.keys updates) getField (Info {date, updates}) = maximum (date : Map.keys updates)
instance HasField "latestYear" Info Integer where instance HasField "latestYear" Info Int where
getField = #first . toGregorian . #latestDate getField = #year . #latestDate
instance HasField "updated" Info Bool where getField = not . Map.null . #updates instance HasField "updated" Info Bool where getField = not . Map.null . #updates
@ -328,12 +321,6 @@ instance (FromYAML a, FromYAML b) => FromYAML (Pair a b) where
_ -> fail "expected exactly one pair" _ -> fail "expected exactly one pair"
instance FromYAML Day where
parseYAML = YAML.withStr "date" \str ->
case readMaybe $ Text.unpack str of
Just d -> pure d
Nothing -> fail $ "couldn't parse date " ++ show str
instance {-# OVERLAPPING #-} FromYAML String where instance {-# OVERLAPPING #-} FromYAML String where
parseYAML y = Text.unpack <$> parseYAML y parseYAML y = Text.unpack <$> parseYAML y

View file

@ -1,5 +1,6 @@
module RSS (make, make') where module RSS (make, make') where
import Date
import Info import Info
import BuilderQQ import BuilderQQ
import Records () import Records ()
@ -9,7 +10,6 @@ import Data.Maybe (isJust)
import Data.Ord (Down (..)) import Data.Ord (Down (..))
import qualified Data.Text as Strict import qualified Data.Text as Strict
import qualified Data.Text.Lazy as Lazy import qualified Data.Text.Lazy as Lazy
import qualified Data.Time as Time
import System.FilePath (takeDirectory) import System.FilePath (takeDirectory)
@ -69,11 +69,4 @@ makeItem root prefix path (Info {title, desc, date, artist}) = [b|@4
</description> </description>
|] |]
else "" else ""
date' = formatDate date date' = formatRSS date
formatDate :: Day -> Builder
formatDate d =
fromString $ Time.formatTime Time.defaultTimeLocale format $
Time.UTCTime d 15669
where
format = "%a, %d %b %_Y %T GMT"

View file

@ -1,5 +1,6 @@
module SinglePage (make) where module SinglePage (make) where
import Date
import Info import Info
import BuilderQQ import BuilderQQ
import Records () import Records ()
@ -9,7 +10,6 @@ import qualified Data.Map.Strict as Map
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import qualified Data.Text as Strict import qualified Data.Text as Strict
import qualified Data.Text.Lazy as Lazy import qualified Data.Text.Lazy as Lazy
import qualified Data.Time as Time
import System.FilePath (joinPath, splitPath, (</>)) import System.FilePath (joinPath, splitPath, (</>))
import qualified System.Process as Proc import qualified System.Process as Proc
import Text.Read (readMaybe) import Text.Read (readMaybe)
@ -43,7 +43,7 @@ make' root prefix nsfw dataDir dir
let artistTag = ifJust artist makeArtist let artistTag = ifJust artist makeArtist
let formattedDate = formatDate date let formattedDate = formatLong date
let buttonBar = makeButtonBar title images let buttonBar = makeButtonBar title images
let (image0@(Image {path = path0, download = download0'}), let (image0@(Image {path = path0, download = download0'}),
@ -79,7 +79,7 @@ make' root prefix nsfw dataDir dir
Nothing -> "by niss" Nothing -> "by niss"
let thumb = getThumb "" info let thumb = getThumb "" info
let updateDate = ifJust (Map.lookupMax updates) \(formatDate -> u, _) -> let updateDate = ifJust (Map.lookupMax updates) \(formatLong -> u, _) ->
[b|<br> <span class=updated>updated $u</span>|] [b|<br> <span class=updated>updated $u</span>|]
pure [b|@0 pure [b|@0
@ -227,7 +227,7 @@ extLink (Link {title, url}) = [b|@8
</a> </a>
|] |]
makeUpdates :: [(Day, Text)] -> Builder makeUpdates :: [(Date, Text)] -> Builder
makeUpdates ups = makeUpdates ups =
if null ups then "" else [b|@4 if null ups then "" else [b|@4
<section id=updates class=info-section> <section id=updates class=info-section>
@ -239,31 +239,12 @@ makeUpdates ups =
|] |]
where updateList = map (uncurry makeUpdate) ups where updateList = map (uncurry makeUpdate) ups
makeUpdate :: Day -> Text -> Builder makeUpdate :: Date -> Text -> Builder
makeUpdate date txt = [b|@8 makeUpdate date txt = [b|@8
<dt>$date' <dt>$date'
<dd>$txt <dd>$txt
|] |]
where date' = Time.formatTime Time.defaultTimeLocale "%-d/%-m/%Y" date where date' = formatSlash date
formatDate :: Day -> Builder
formatDate date = [b|$week $day $month $year|] where
(year, month', day') = Time.toGregorian date
week' = Time.dayOfWeek date
day = nth day'
month = Strict.words "january february march april may june july \
\august september october november december"
!! (month' - 1)
week = Strict.words "mon tue wed thu fri sat sun" !! (fromEnum week' - 1)
nth :: Int -> Builder
nth n = [b|$n$suf|] where
suf | n >= 10, n <= 19 = [b|th|]
| n `mod` 10 == 1 = [b|st|]
| n `mod` 10 == 2 = [b|nd|]
| n `mod` 10 == 3 = [b|rd|]
| otherwise = [b|th|]
data Size = Size {width, height :: !Int} deriving (Eq, Show) data Size = Size {width, height :: !Int} deriving (Eq, Show)

View file

@ -15,6 +15,7 @@ executable make-pages
main-is: Main.hs main-is: Main.hs
other-modules: other-modules:
BuilderQQ, BuilderQQ,
Date,
Depend, Depend,
GalleryPage, GalleryPage,
Info, Info,

View file

@ -135,6 +135,8 @@ figcaption .date, figcaption .title {
figcaption .date { top: -1px; left: -1px; } figcaption .date { top: -1px; left: -1px; }
figcaption .title { bottom: -1px; left: -1px; } figcaption .title { bottom: -1px; left: -1px; }
.date { text-transform: lowercase; }
.year-marker { .year-marker {
grid-area: auto / 1; grid-area: auto / 1;
padding: var(--border-thickness); padding: var(--border-thickness);

View file

@ -100,6 +100,8 @@ body {
content: url(../18_plus.svg); content: url(../18_plus.svg);
} }
#date { text-transform: lowercase; }
#info { #info {
max-width: 80%; max-width: 80%;
margin: auto; margin: auto;
@ -149,6 +151,10 @@ body {
grid-area: auto / 2; grid-area: auto / 2;
} }
#updates .q {
font-weight: 500;
}
.updated { .updated {
font-size: 90%; font-size: 90%;
font-style: italic; font-style: italic;