~kungtotte/dtt

ref: 76f8b580ce6dc534bc9a02b76e3367cf514a1804 dtt/src/utils.nim -rw-r--r-- 3.2 KiB
76f8b580Thomas Landin Switch from docopt to argparse 9 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import os, strutils, strformat
import strscans
import unicode, tables, times

import markdown
import mustache

const
  css_dir* = "css"
  content_dir* = "content"
  template_dir* = "templates"
  output_dir* = "output"
  blog_dirs = ["blog", "blogs", "post", "posts", "articles", "journal", "journals"]

type
  BlogPost* = tuple
    slug: string
    date: string
    content: string
    url: string

func dateSort*(x, y: BlogPost): int =
  let xdate = x.date.replace("-","").parseInt
  let ydate = y.date.replace("-","").parseInt
  if xdate > ydate or xdate == ydate:
    1
  else:
    -1

proc needsUpdate*(source, dest: string): bool =
  if not existsFile(dest):
    return true
  return dest.fileNewer(source)

proc loadTemplate*(name: string = "page", dir: string = "templates"): string =
  return readFile(dir / name & ".mustache")

func renderTemplate*(context: Context, md: string, tmpl: string): string =
  context["content"] = markdown(md)
  result = tmpl.render(context)

func formatPaddedDate(year, month, day: int): string =
  # Return a zero-padded date string in the format YYYY-MM-DD
  # fmt uses parseInt behind the scenes which will kill leading
  # zeroes so 2020-01-09 becomes 2020-1-9 when using fmt"" which
  # is not what we want.
  let
    y = intToStr(year)
    m = intToStr(month, 2)
    d = intToStr(day, 2)
  result = fmt"{y}-{m}-{d}"

func findSlugAndDate(name: string): tuple[slug, date: string] =
  # Extract the (optional) YYYY-MM-DD date portion of the filename
  # as well as the remainder of the string properly formatted as a title
  # with any separating characters replaced by spaces.
  result.slug = name
  result.date = ""
  var
    y, m, d:  int
  if scanf(name, "$i-$i-$i", y, m, d):
    result.slug = name[11..^1]
    result.date = formatPaddedDate(y, m, d)
  result.slug = result.slug.title().multiReplace(("-", " "), ("_", " "))

func isBlog*(path, name: string): bool =
  var
    y, m, d: int
  if path in blog_dirs or scanf(name, "$i-$i-$i", y, m, d):
    result = true
  else:
    result = false

func findBaseDir*(starting_dir: string): string =
  if existsDir(starting_dir / content_dir) and existsDir(starting_dir / template_dir):
    return starting_dir
  let (path, _) = splitPath(starting_dir)
  for p in parentDirs(path):
    if existsDir(p / content_dir) and existsDir(p / template_dir):
      return p
  raise new IOError

func postsToContext*(posts: seq[BlogPost]): seq[Table[string, string]] =
  for post in posts:
    result.add({"id": post.slug, "post": post.content, "date": post.date, "link": post.url}.toTable)

proc buildBlogPost*(full_path: string, tmpl_dir: string): BlogPost =
  let
    (dir, name, _) = splitFile(full_path)
    target = if lastPathPart(dir) == "content": name & ".html" else: lastPathPart(dir) / name & ".html"
  var
    context = newContext(searchDirs = @[tmpl_dir])
    (slug, date) = findSlugAndDate(name)
  # Fixes posts where no datestring was part of the filename
  if date == "": date = getCreationTime(full_path).format("yyyy-MM-dd")
  context["slug"] = slug
  context["date"] = date
  context["blog_link"] = target
  let
    tmpl = loadTemplate("post", tmpl_dir)
    rendered = context.renderTemplate(readFile(full_path), tmpl)
  result.slug = slug
  result.date = date
  result.url = target
  result.content = rendered