add support for approx dates
This commit is contained in:
parent
1cfb65eae9
commit
8d4ff29e56
8 changed files with 141 additions and 65 deletions
111
make-pages/Date.hs
Normal file
111
make-pages/Date.hs
Normal 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
|
|
@ -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)
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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"
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue