import std/[os, unicode, critbits, options, strformat]
from std/strutils import startsWith, Whitespace, splitLines, toLowerAscii
from std/strbasics import strip
const
WhitespaceUC = toRunes("_\t\n\x0b\x0c\r\x1c\x1d\x1e\x1f \x85\xa0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000")
Warning = "<!--- DO NOT EDIT: This file is automatically generated by `directory.nim` -->\n"
Header = "# The Algorithms — Nim: Directory Hierarchy\n"
type
Category = object
readme: Option[string]
contents: CritBitTree[string]
Directory = CritBitTree[Category]
func canonicalize(s: string): string =
var isFirst = true
for word in unicode.split(s, WhitespaceUC):
if word.len > 0:
if isFirst: isFirst = false
else: result.add(' ')
result.add(word.title())
proc extractTitle(s, fpath: string): Option[string] =
var s = s.strip()
if s.startsWith("##"):
s.strip(trailing = false, chars = Whitespace + {'
if s.len > 0: some(s)
else: none(string)
else:
stderr.writeLine(&"\u26A0: \"{fpath}\". First line is not a doc comment! Deriving title from the file name.")
none(string)
proc readLn(fpath: string): Option[string] =
var f: File = nil
var s: string
if open(f, fpath):
try:
if not readLine(f, s): none(string)
else: s.extractTitle(fpath)
except CatchableError: none(string)
finally: close(f)
else: none(string)
proc collectDirectory(dir = ""): Directory =
for (pc, path) in walkDir(dir, relative = true):
if pc == pcDir and path[0] != '.':
var categoryDir: Category
for (pc, fname) in walkDir(path, relative = true):
if pc == pcFile and fname[0] != '.':
let (_, name, ext) = splitFile(fname)
let fpath = path / fname
if ext == ".nim":
let title = readLn(fpath).get(name.canonicalize())
categoryDir.contents[title] = fname
elif ext.toLowerAscii() in [".md", ".rst"] and
name.toLowerAscii() == "readme":
categoryDir.readme = some(path & '/' & fname)
if categoryDir.contents.len > 0:
result[path] = categoryDir
when isMainModule:
let directory = collectDirectory(getCurrentDir())
if directory.len > 0:
echo Warning, "\n", Header
for (categoryDir, category) in directory.pairs():
if category.contents.len > 0:
let categoryName = categoryDir.canonicalize()
let categoryHeader = if category.readme.isSome:
&"## [{categoryName}]({category.readme.get()})"
else:
&"## {categoryName}"
echo categoryHeader
for (title, fname) in category.contents.pairs():
echo &" * [{title}]({categoryDir}/{fname})"
echo ""