~kungtotte/dtt

ed49e6d25724bf84b13b40d25b993cf4f9874f47 — Thomas Landin 2 months ago 120d85d
Split all non-command procs out into utils.nim

This is the first stage of refactoring, just getting it out into a
separate file where it's easier to get an overview.
2 files changed, 124 insertions(+), 116 deletions(-)

M src/dtt.nim
A src/utils.nim
M src/dtt.nim => src/dtt.nim +2 -116
@@ 1,13 1,11 @@
import os, strutils, strformat
import parsecfg, strscans
import unicode, times
import algorithm
import parsecfg

import docopt
import mustache
import markdown

import tmpl
import utils

const
  bin = "dtt"


@@ 15,7 13,6 @@ const
  content_dir = "content"
  template_dir = "templates"
  output_dir = "output"
  blog_dirs = ["blog", "blogs", "post", "posts", "articles", "journal", "journals"]
  outFilePerms = {fpUserExec, fpUserWrite, fpUserRead, fpGroupExec,
                  fpGRoupRead, fpOthersExec, fpOthersRead}



@@ 75,7 72,6 @@ proc initCmd(dir: string, force: bool = false) =
  writeFile(wd / output_dir / css_dir / "style.css", tmpl.style)
  setFilePermissions(wd / output_dir / css_dir / "style.css", outFilePerms)


proc cleanCmd(all: bool = false, force: bool = false) =
  let wd = if all: os.getCurrentDir() else: os.getCurrentDir() / output_dir
  if not force and not existsFile(os.getCurrentDir() / "config.cfg"):


@@ 94,116 90,6 @@ proc cleanCmd(all: bool = false, force: bool = false) =
    stderr.writeLine(fmt"{bin}: {wd} does not exist.")
    quit(ord(DttError.DirectoryNotFound))

proc loadMetaData(cfgdict: Config, mcontext: var Context) =
  # TODO: Make dtt move backwards up the hierarchy to look for config.cfg
  # so we don't error out if someone happens to run `dtt build` inside the
  # content  directory instead of the root dtt directory.

  # Load meta-data from config file into the supplied
  # mustache context
  mcontext["charset"] = cfgdict.getSectionValue("Site", "charset")
  mcontext["language"] = cfgdict.getSectionValue("Site","language")
  mcontext["page_title"] = cfgdict.getSectionValue("Site","title")
  mcontext["content_license"] = cfgdict.getSectionValue("Site","license")
  mcontext["content_license_url"] = cfgdict.getSectionValue("Site","license_url")
  mcontext["email"] = cfgdict.getSectionValue("Author", "email")
  mcontext["author"] = cfgdict.getSectionValue("Author", "name")

proc renderTemplate(context: Context, file: string,
                    tmpl: string = "page",
                    tmpldir: string = "templates"): string =
  let
    tpl = readFile(tmpldir / tmpl & ".mustache")
    (_,_,ext) = splitFile(file)
  if ext == ".md":
    context["content"] = markdown(readFile(file))
    result = tpl.render(context)

proc findLinks(content_dir: string, excludes: seq[Table[string,string]]): seq[Table[string, string]] =
  var blog_ids: seq[string]
  for b in excludes:
    blog_ids.add(b["id"])

  for f in walkDirRec(content_dir, relative = true):
    let (dir, name, _) = splitFile(f)
    let
      target = if dir != "": dir & "/" & name else: name
      title = name.title()
    if not (name in blog_ids):
      result.add({"target": target, "title": title}.toTable)

proc findTemplate(filename: string, tmpldir: string, isBlog: bool = false): string =
  let tmpl = case isBlog:
    of true:
      "post"
    of false:
      if existsFile(tmpldir / filename & ".mustache"): filename else: "page"

  result = tmpl

proc getSlug(filename: string, hasYMD: bool): string =
  result = filename
  if hasYMD:
    result = result[11..^1]
  result = result.title().multiReplace(("-", " "), ("_", " "))

proc getBlogDate(file: string, datestring: tuple[year, month, day: int]): string =
  if datestring.year > 0:
    let
      # fmt will use parseInt and kill leading zeroes, and this is the easiest
      # way to get a proper yyyy-MM-dd format using format strings
      y = intToStr(datestring.year)
      m = intToStr(datestring.month, 2)
      d = intToStr(datestring.day, 2)
    result = fmt"{y}-{m}-{d}"
  else:
    # We guess that the creation time of the post is usually what you want as
    # the datestamp of the blog. If you don't want this, you can rename the file
    # using the yyyy-MM-dd-slug.md format.
    result = getCreationTime(file).format("yyyy-MM-dd")

proc dateSort(x, y: Table[string, string]): int =
  let xdate = x["date"].replace("-","").parseInt
  let ydate = y["date"].replace("-","").parseInt
  if xdate > ydate or xdate == ydate:
    1
  else:
    -1

proc findBlogPosts(directory: string, tmpldir: string): seq[Table[string, string]] =
  for f in walkDirRec(directory):
    let (dir, name, ext) = splitFile(f)
    if ext == ".md":
      var
        year, month, day: int
      discard scanf(name, "$i-$i-$i", year, month, day)
      let
        basedir = lastPathPart(dir)
        isBlog = if basedir in blog_dirs or year > 0: true else: false
      if isBlog:
        let
          date = getBlogDate(f, (year, month, day))
          tmpl = findTemplate(name, tmpldir, isBlog)
        var blogContext = newContext(searchDirs = @[tmpldir])
        let
          subdir = if dir != directory: lastPathPart(dir) else: ""
          target = if subdir != "": subdir & "/" & name & ".html" else: name & ".html"
        blogContext["slug"] = getSlug(name, year > 0)
        blogContext["date"] = date
        blogContext["blog_link"] = target
        let
          rendered = blogContext.renderTemplate(f, tmpl)
        let tbl = {"id": name, "post": rendered, "date": date}.toTable
        result.add(tbl)
  sort(result, dateSort, Descending)

proc findConfigFile(full_path: string): Config =
  let (path, name) = splitPath(full_path)
  for p in parentDirs(path):
      if existsFile(p / name):
        return loadConfig(p / name)
  raise new IOError

proc buildCmd() =
  # TODO: There's a bug where if you run build outside the main dir it won't
  # find things properly, so we need to start with finding the config.cfg file

A src/utils.nim => src/utils.nim +122 -0
@@ 0,0 1,122 @@
import os, strutils, strformat
import parsecfg, strscans
import unicode, times
import algorithm, tables

import markdown
import mustache

const
  blog_dirs = ["blog", "blogs", "post", "posts", "articles", "journal", "journals"]

proc dateSort(x, y: Table[string, string]): int =
  let xdate = x["date"].replace("-","").parseInt
  let ydate = y["date"].replace("-","").parseInt
  if xdate > ydate or xdate == ydate:
    1
  else:
    -1

proc loadMetaData*(cfgdict: Config, mcontext: var Context) =
  # TODO: Make dtt move backwards up the hierarchy to look for config.cfg
  # so we don't error out if someone happens to run `dtt build` inside the
  # content  directory instead of the root dtt directory.

  # Load meta-data from config file into the supplied
  # mustache context
  mcontext["charset"] = cfgdict.getSectionValue("Site", "charset")
  mcontext["language"] = cfgdict.getSectionValue("Site","language")
  mcontext["page_title"] = cfgdict.getSectionValue("Site","title")
  mcontext["content_license"] = cfgdict.getSectionValue("Site","license")
  mcontext["content_license_url"] = cfgdict.getSectionValue("Site","license_url")
  mcontext["email"] = cfgdict.getSectionValue("Author", "email")
  mcontext["author"] = cfgdict.getSectionValue("Author", "name")

proc renderTemplate*(context: Context, file: string,
                    tmpl: string = "page",
                    tmpldir: string = "templates"): string =
  let
    tpl = readFile(tmpldir / tmpl & ".mustache")
    (_,_,ext) = splitFile(file)
  if ext == ".md":
    context["content"] = markdown(readFile(file))
    result = tpl.render(context)


proc findLinks*(content_dir: string, excludes: seq[Table[string,string]]): seq[Table[string, string]] =
  var blog_ids: seq[string]
  for b in excludes:
    blog_ids.add(b["id"])

  for f in walkDirRec(content_dir, relative = true):
    let (dir, name, _) = splitFile(f)
    let
      target = if dir != "": dir & "/" & name else: name
      title = name.title()
    if not (name in blog_ids):
      result.add({"target": target, "title": title}.toTable)

proc findTemplate*(filename: string, tmpldir: string, isBlog: bool = false): string =
  let tmpl = case isBlog:
    of true:
      "post"
    of false:
      if existsFile(tmpldir / filename & ".mustache"): filename else: "page"

  result = tmpl

proc getSlug*(filename: string, hasYMD: bool): string =
  result = filename
  if hasYMD:
    result = result[11..^1]
  result = result.title().multiReplace(("-", " "), ("_", " "))


proc getBlogDate*(file: string, datestring: tuple[year, month, day: int]): string =
  if datestring.year > 0:
    let
      # fmt will use parseInt and kill leading zeroes, and this is the easiest
      # way to get a proper yyyy-MM-dd format using format strings
      y = intToStr(datestring.year)
      m = intToStr(datestring.month, 2)
      d = intToStr(datestring.day, 2)
    result = fmt"{y}-{m}-{d}"
  else:
    # We guess that the creation time of the post is usually what you want as
    # the datestamp of the blog. If you don't want this, you can rename the file
    # using the yyyy-MM-dd-slug.md format.
    result = getCreationTime(file).format("yyyy-MM-dd")

proc findBlogPosts*(directory: string, tmpldir: string): seq[Table[string, string]] =
  for f in walkDirRec(directory):
    let (dir, name, ext) = splitFile(f)
    if ext == ".md":
      var
        year, month, day: int
      discard scanf(name, "$i-$i-$i", year, month, day)
      let
        basedir = lastPathPart(dir)
        isBlog = if basedir in blog_dirs or year > 0: true else: false
      if isBlog:
        let
          date = getBlogDate(f, (year, month, day))
          tmpl = findTemplate(name, tmpldir, isBlog)
        var blogContext = newContext(searchDirs = @[tmpldir])
        let
          subdir = if dir != directory: lastPathPart(dir) else: ""
          target = if subdir != "": subdir & "/" & name & ".html" else: name & ".html"
        blogContext["slug"] = getSlug(name, year > 0)
        blogContext["date"] = date
        blogContext["blog_link"] = target
        let
          rendered = blogContext.renderTemplate(f, tmpl)
        let tbl = {"id": name, "post": rendered, "date": date}.toTable
        result.add(tbl)
  sort(result, dateSort, Descending)

proc findConfigFile*(full_path: string): Config =
  let (path, name) = splitPath(full_path)
  for p in parentDirs(path):
      if existsFile(p / name):
        return loadConfig(p / name)
  raise new IOError