fix tags with spaces
add a filter that replaces tags e.g. "tag name" → {name: "tag name", slug: "tag_name"}
This commit is contained in:
parent
5108acba61
commit
be46a2fc5c
9 changed files with 87 additions and 54 deletions
7
Makefile
7
Makefile
|
@ -29,9 +29,10 @@ LAANTAS_SCRIPT = $(TMPDIR)/laantas-script
|
||||||
ALL_TAGS = $(TMPDIR)/all-tags
|
ALL_TAGS = $(TMPDIR)/all-tags
|
||||||
POST_LISTS = $(TMPDIR)/post-lists
|
POST_LISTS = $(TMPDIR)/post-lists
|
||||||
NICE_DATE = $(TMPDIR)/nice-date
|
NICE_DATE = $(TMPDIR)/nice-date
|
||||||
|
SLUG_TAGS = $(TMPDIR)/slug-tags
|
||||||
EXECS = \
|
EXECS = \
|
||||||
$(LANGFILTER) $(LAANTAS_SCRIPT) \
|
$(LANGFILTER) $(LAANTAS_SCRIPT) \
|
||||||
$(ALL_TAGS) $(POST_LISTS) $(NICE_DATE)
|
$(ALL_TAGS) $(POST_LISTS) $(NICE_DATE) $(SLUG_TAGS)
|
||||||
|
|
||||||
CABAL_FLAGS ?= -O -v0
|
CABAL_FLAGS ?= -O -v0
|
||||||
|
|
||||||
|
@ -67,7 +68,8 @@ define pandoc
|
||||||
pandoc -s --toc --template $(TEMPLATEDIR)/$(1).html -o $@ $< \
|
pandoc -s --toc --template $(TEMPLATEDIR)/$(1).html -o $@ $< \
|
||||||
-f markdown+emoji \
|
-f markdown+emoji \
|
||||||
$(SYNTAXFLAGS) \
|
$(SYNTAXFLAGS) \
|
||||||
--filter $(LANGFILTER) --filter $(NICE_DATE) --mathjax
|
--filter $(LANGFILTER) --filter $(NICE_DATE) --filter $(SLUG_TAGS) \
|
||||||
|
--mathjax
|
||||||
endef
|
endef
|
||||||
|
|
||||||
|
|
||||||
|
@ -89,6 +91,7 @@ $(LAANTAS_SCRIPT): lang/laantas-script/* ; $(call cabal-exe)
|
||||||
$(ALL_TAGS): $(BLOG_META_DEPS) ; $(call cabal-exe,blog-meta:)
|
$(ALL_TAGS): $(BLOG_META_DEPS) ; $(call cabal-exe,blog-meta:)
|
||||||
$(POST_LISTS): $(BLOG_META_DEPS) ; $(call cabal-exe,blog-meta:)
|
$(POST_LISTS): $(BLOG_META_DEPS) ; $(call cabal-exe,blog-meta:)
|
||||||
$(NICE_DATE): $(BLOG_META_DEPS) ; $(call cabal-exe,blog-meta:)
|
$(NICE_DATE): $(BLOG_META_DEPS) ; $(call cabal-exe,blog-meta:)
|
||||||
|
$(SLUG_TAGS): $(BLOG_META_DEPS) ; $(call cabal-exe,blog-meta:)
|
||||||
|
|
||||||
define cabal-exe
|
define cabal-exe
|
||||||
@echo "[build] $(notdir $@)"
|
@echo "[build] $(notdir $@)"
|
||||||
|
|
|
@ -15,7 +15,6 @@ import qualified YAML
|
||||||
import System.Environment
|
import System.Environment
|
||||||
import qualified Data.Text.IO as Text
|
import qualified Data.Text.IO as Text
|
||||||
import qualified Data.Text as Text
|
import qualified Data.Text as Text
|
||||||
import Data.Char
|
|
||||||
|
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
|
@ -37,7 +36,7 @@ makeYAML :: [Set Text] -> LazyBS.ByteString
|
||||||
makeYAML tags = "---\n" <> yaml <> "\n...\n" where
|
makeYAML tags = "---\n" <> yaml <> "\n...\n" where
|
||||||
yaml = YAML.encode1 $ YAML.obj
|
yaml = YAML.encode1 $ YAML.obj
|
||||||
[("title" ##= YAML.str "all tags"),
|
[("title" ##= YAML.str "all tags"),
|
||||||
("tags" ##= collate tags)]
|
("all-tags" ##= collate tags)]
|
||||||
|
|
||||||
makeMake :: [Set Text] -> Text
|
makeMake :: [Set Text] -> Text
|
||||||
makeMake tags' = Text.unlines $ build : allPosts : map makeRule tags where
|
makeMake tags' = Text.unlines $ build : allPosts : map makeRule tags where
|
||||||
|
@ -46,7 +45,7 @@ makeMake tags' = Text.unlines $ build : allPosts : map makeRule tags where
|
||||||
t <- ["all-tags", "index"] <> map slug' tags]
|
t <- ["all-tags", "index"] <> map slug' tags]
|
||||||
makeRule' opt title file =
|
makeRule' opt title file =
|
||||||
"$(TMPDIR)/" <> file <> ".md : $(POSTS) $(POST_LISTS)\n\
|
"$(TMPDIR)/" <> file <> ".md : $(POSTS) $(POST_LISTS)\n\
|
||||||
\\t@echo \"[post-lists] $<\"\n\
|
\\t@echo \"[post-lists] " <> file <> "\"\n\
|
||||||
\\t$(POST_LISTS) " <> opt <> " --out $@ \\\n\
|
\\t$(POST_LISTS) " <> opt <> " --out $@ \\\n\
|
||||||
\\t $(POSTSDIR) \"" <> title <> "\""
|
\\t $(POSTSDIR) \"" <> title <> "\""
|
||||||
allPosts = makeRule' "" "all posts" "index"
|
allPosts = makeRule' "" "all posts" "index"
|
||||||
|
@ -70,25 +69,19 @@ instance YAML.ToYAML Tag where
|
||||||
[("name" ##= name), ("slug" ##= slug), ("count" ##= count)]
|
[("name" ##= name), ("slug" ##= slug), ("count" ##= count)]
|
||||||
|
|
||||||
collate :: [Set Text] -> [Tag]
|
collate :: [Set Text] -> [Tag]
|
||||||
collate tags₀ =
|
collate =
|
||||||
toList $ fst $ foldl' add1 (mempty, mempty) $ foldMap toList tags₀
|
toList . foldl' add1 mempty . foldMap toList
|
||||||
where
|
where
|
||||||
add1 (tags, slugs) name
|
add1 tags name = Map.alter (add1' tag) (slug tag) tags
|
||||||
| Map.member name tags =
|
where tag = makeTag name
|
||||||
(Map.adjust incrCount name tags, slugs)
|
|
||||||
| otherwise =
|
add1' new Nothing = Just new
|
||||||
let tag = makeTag slugs name in
|
add1' new (Just old)
|
||||||
(Map.insert name tag tags,
|
| name new == name old = Just (old {count = succ (count old)})
|
||||||
Set.insert (slug tag) slugs)
|
| otherwise = error $ "slug collision between " <> show (name old) <>
|
||||||
makeTag slugs name =
|
" and " <> show (name new)
|
||||||
Tag {name, slug = makeSlug slugs name, count = 1}
|
|
||||||
makeSlug slugs name = head $ filter (`notElem` slugs) candidates where
|
makeTag name = Tag {name, slug = makeSlug name, count = 1}
|
||||||
slug₀ = Text.map toSlugChar name
|
|
||||||
toSlugChar c
|
|
||||||
| isAlphaNum c && isAscii c || c == '-' = toLower c
|
|
||||||
| otherwise = '_'
|
|
||||||
candidates = slug₀ : [slug₀ <> Text.pack (show i) | i <- [(0 :: Int) ..]]
|
|
||||||
incrCount t@(Tag {count}) = t {count = succ count}
|
|
||||||
|
|
||||||
data Options =
|
data Options =
|
||||||
Opts {
|
Opts {
|
||||||
|
|
|
@ -50,3 +50,8 @@ executable nice-date
|
||||||
import: deps, exe
|
import: deps, exe
|
||||||
hs-source-dirs: .
|
hs-source-dirs: .
|
||||||
main-is: nice-date.hs
|
main-is: nice-date.hs
|
||||||
|
|
||||||
|
executable slug-tags
|
||||||
|
import: deps, exe
|
||||||
|
hs-source-dirs: .
|
||||||
|
main-is: slug-tags.hs
|
||||||
|
|
|
@ -3,6 +3,10 @@ module Misc where
|
||||||
import qualified System.Console.GetOpt as GetOpt
|
import qualified System.Console.GetOpt as GetOpt
|
||||||
import System.Environment
|
import System.Environment
|
||||||
import System.Exit
|
import System.Exit
|
||||||
|
import Data.Text (Text)
|
||||||
|
import qualified Data.Text as Text
|
||||||
|
import Data.Char (isAlphaNum, isAscii, toLower)
|
||||||
|
import Text.Pandoc.Definition
|
||||||
|
|
||||||
-- | exception on 'Left'
|
-- | exception on 'Left'
|
||||||
unwrap :: Show a => FilePath -> Either a b -> IO b
|
unwrap :: Show a => FilePath -> Either a b -> IO b
|
||||||
|
@ -20,3 +24,34 @@ getOptionsWith hdr mkDef descrs = do
|
||||||
putStrLn $ GetOpt.usageInfo (hdr prog) descrs
|
putStrLn $ GetOpt.usageInfo (hdr prog) descrs
|
||||||
exitFailure
|
exitFailure
|
||||||
|
|
||||||
|
makeSlug :: Text -> Text
|
||||||
|
makeSlug name = Text.map toSlugChar name where
|
||||||
|
toSlugChar c
|
||||||
|
| isAlphaNum c && isAscii c || c == '-' = toLower c
|
||||||
|
| otherwise = '_'
|
||||||
|
|
||||||
|
|
||||||
|
toTextList :: MetaValue -> Maybe [Text]
|
||||||
|
toTextList (MetaList vs) = traverse toText vs
|
||||||
|
toTextList _ = Nothing
|
||||||
|
|
||||||
|
toText :: MetaValue -> Maybe Text
|
||||||
|
toText (MetaString str) = Just str
|
||||||
|
toText (MetaInlines is) = foldMap inlineText is
|
||||||
|
toText (MetaBlocks bs) = foldMap blockText bs
|
||||||
|
toText _ = Nothing
|
||||||
|
|
||||||
|
inlineText :: Inline -> Maybe Text
|
||||||
|
inlineText (Str txt) = Just txt
|
||||||
|
inlineText Space = Just " "
|
||||||
|
inlineText SoftBreak = Just " "
|
||||||
|
inlineText LineBreak = Just " "
|
||||||
|
inlineText (RawInline _ txt) = Just txt
|
||||||
|
inlineText _ = Nothing
|
||||||
|
|
||||||
|
blockText :: Block -> Maybe Text
|
||||||
|
blockText (Plain is) = foldMap inlineText is
|
||||||
|
blockText (Para is) = foldMap inlineText is
|
||||||
|
blockText Null = Just ""
|
||||||
|
blockText (RawBlock _ txt) = Just txt
|
||||||
|
blockText _ = Nothing
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
import Text.Pandoc.Definition
|
import Text.Pandoc.Definition
|
||||||
import Data.Map.Strict (Map)
|
|
||||||
import qualified Data.Map.Strict as Map
|
import qualified Data.Map.Strict as Map
|
||||||
import Data.Time
|
import Data.Time
|
||||||
import Text.Pandoc.JSON
|
import Text.Pandoc.JSON
|
||||||
import Data.Text (Text, unpack, pack)
|
import Data.Text (unpack, pack)
|
||||||
import Data.Char (toLower)
|
import Data.Char (toLower)
|
||||||
|
import Misc
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = toJSONFilter \(Pandoc (Meta m) body) -> do
|
main = toJSONFilter \(Pandoc (Meta m) body) -> do
|
||||||
m' <- niceDate m
|
m' <- Map.alterF reformat "date" m
|
||||||
pure $ Pandoc (Meta m') body
|
pure $ Pandoc (Meta m') body
|
||||||
|
|
||||||
niceDate :: Map Text MetaValue -> IO (Map Text MetaValue)
|
|
||||||
niceDate = Map.alterF reformat "date"
|
|
||||||
|
|
||||||
reformat :: Maybe MetaValue -> IO (Maybe MetaValue)
|
reformat :: Maybe MetaValue -> IO (Maybe MetaValue)
|
||||||
reformat Nothing = pure Nothing
|
reformat Nothing = pure Nothing
|
||||||
reformat (Just (toText -> Just txt)) = do
|
reformat (Just (toText -> Just txt)) = do
|
||||||
|
@ -22,24 +19,3 @@ reformat (Just (toText -> Just txt)) = do
|
||||||
let str = formatTime defaultTimeLocale "%A %-e %B %Y" (date :: Day)
|
let str = formatTime defaultTimeLocale "%A %-e %B %Y" (date :: Day)
|
||||||
pure $ Just $ MetaString $ pack $ map toLower str
|
pure $ Just $ MetaString $ pack $ map toLower str
|
||||||
reformat (Just d) = fail $ "date is\n" <> show d <> "\nwanted a string"
|
reformat (Just d) = fail $ "date is\n" <> show d <> "\nwanted a string"
|
||||||
|
|
||||||
toText :: MetaValue -> Maybe Text
|
|
||||||
toText (MetaString str) = Just str
|
|
||||||
toText (MetaInlines is) = foldMap inlineText is
|
|
||||||
toText (MetaBlocks bs) = foldMap blockText bs
|
|
||||||
toText _ = Nothing
|
|
||||||
|
|
||||||
inlineText :: Inline -> Maybe Text
|
|
||||||
inlineText (Str txt) = Just txt
|
|
||||||
inlineText Space = Just " "
|
|
||||||
inlineText SoftBreak = Just " "
|
|
||||||
inlineText LineBreak = Just " "
|
|
||||||
inlineText (RawInline _ txt) = Just txt
|
|
||||||
inlineText _ = Nothing
|
|
||||||
|
|
||||||
blockText :: Block -> Maybe Text
|
|
||||||
blockText (Plain is) = foldMap inlineText is
|
|
||||||
blockText (Para is) = foldMap inlineText is
|
|
||||||
blockText Null = Just ""
|
|
||||||
blockText (RawBlock _ txt) = Just txt
|
|
||||||
blockText _ = Nothing
|
|
||||||
|
|
21
blog-meta/slug-tags.hs
Normal file
21
blog-meta/slug-tags.hs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import Text.Pandoc.Definition
|
||||||
|
import qualified Data.Map.Strict as Map
|
||||||
|
import Text.Pandoc.JSON
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Misc
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = toJSONFilter \(Pandoc (Meta m) body) -> do
|
||||||
|
m' <- Map.alterF addSlugs "tags" m
|
||||||
|
pure $ Pandoc (Meta m') body
|
||||||
|
|
||||||
|
addSlugs :: Maybe MetaValue -> IO (Maybe MetaValue)
|
||||||
|
addSlugs Nothing = pure Nothing
|
||||||
|
addSlugs (Just (toTextList -> Just tags)) =
|
||||||
|
pure $ Just $ MetaList $ map addSlug1 tags
|
||||||
|
addSlugs (Just t) = fail $
|
||||||
|
"'tags' is\n" <> show t <> "\nwanted a list of strings"
|
||||||
|
|
||||||
|
addSlug1 :: Text -> MetaValue
|
||||||
|
addSlug1 tag = MetaMap $ Map.fromList
|
||||||
|
[("name", MetaString tag), ("slug", MetaString $ makeSlug tag)]
|
|
@ -8,7 +8,7 @@ $endif$
|
||||||
|
|
||||||
$if(posts)$
|
$if(posts)$
|
||||||
$postlist()$
|
$postlist()$
|
||||||
$elseif(tags)$
|
$elseif(all-tags)$
|
||||||
$taglist()$
|
$taglist()$
|
||||||
$endif$
|
$endif$
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ $head()$
|
||||||
tags:
|
tags:
|
||||||
<ul>
|
<ul>
|
||||||
$for(tags)$
|
$for(tags)$
|
||||||
<li><a href=/tag-$it$.html>$it$</a>
|
<li><a href=/tag-$it.slug$.html>$it.name$</a>
|
||||||
$endfor$
|
$endfor$
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<main>
|
<main>
|
||||||
<ul class=tag-list>
|
<ul class=tag-list>
|
||||||
$for(tags)$
|
$for(all-tags)$
|
||||||
<li>
|
<li>
|
||||||
<a href=tag-$it.slug$.html>$it.name$</a>
|
<a href=tag-$it.slug$.html>$it.name$</a>
|
||||||
<span class=count>($it.count$)</span>
|
<span class=count>($it.count$)</span>
|
||||||
|
|
Loading…
Reference in a new issue