commit cb0e968e2672912f853cd35be9fe8441a4c6d00b
Author: Leah (ctucx) <leah@ctu.cx>
Date: Sun, 21 Mar 2021 22:56:41 +0100
Author: Leah (ctucx) <leah@ctu.cx>
Date: Sun, 21 Mar 2021 22:56:41 +0100
init
16 files changed, 1233 insertions(+), 0 deletions(-)
A
|
77
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
283
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
352
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/.gitignore b/.gitignore @@ -0,0 +1,5 @@ + +libgit2.so +nimstagit +out/ +test.conf
diff --git a/nimstagit.nimble b/nimstagit.nimble @@ -0,0 +1,14 @@ +# Package +version = "0.1.0" +author = "Leah (ctucx)" +description = "stagit reimplementation in nimlang" +license = "GPL-3.0" +srcDir = "src" +bin = @["nimstagit"] + + +# Dependencies +requires "nim >= 1.4.4" +requires "moustachu" +requires "markdown" +requires "https://cgit.ctu.cx/nimgit"
diff --git a/src/assets.nim b/src/assets.nim @@ -0,0 +1,9 @@ +const + assetStyleCss* = staticRead "assets/style.css" + templateOverview* = staticRead "assets/overview.html" + templateRepoSummary* = staticRead "assets/repoSummary.html" + templateRepoLog* = staticRead "assets/repoLog.html" + templateRepoCommit* = staticRead "assets/repoCommit.html" + templateRepoTree* = staticRead "assets/repoTree.html" + templateRepoBlob* = staticRead "assets/repoBlob.html" + templateRepoRefs* = staticRead "assets/repoRefs.html"
diff --git a/src/assets/overview.html b/src/assets/overview.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<html> + <head> + <title>{{repoName}} - {{siteTitle}}</title> + + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="{{description}}"> + <meta name="generator" content="https://cgit.ctu.cx/nimstagit"> + + <link href="/style.css" rel="stylesheet" /> + </head> + <body> + <header> + <h1>{{siteTitle}}</h1> + <p>{{description}}</p> + </header> + <nav> + <a class="active" href="index.html">index</a> + </nav> + <main> + <h3>Overview</h3> + <table> + <thead> + <tr> + <td>Name</td> + <td>Description</td> + <td>Idle</td> + </tr> + </thead> + {{#categories}} + {{#name}} + <tr> + <td colspan="4" class="reposection">{{name}}</td> + </tr> + {{/name}} + {{#repos}} + <tr> + <td class="sublevel-repo"><a href="{{repoUrl}}/">{{repoName}}</a></td> + <td><a href="{{repoUrl}}/">{{description}}</a></td> + <td><span class="age-hours"><a href="/{{repoUrl}}/log/{{objId}}.html">{{lastActivity}}</a></span></td> + </tr> + {{/repos}} + {{/categories}} + </table> + </main> + <footer>Generated on {{generated}}</footer> + </body> +</html> +
diff --git a/src/assets/repoBlob.html b/src/assets/repoBlob.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html> + <head> + <title>{{repoName}} - {{siteTitle}}</title> + + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="{{description}}"> + <meta name="generator" content="https://cgit.ctu.cx/nimstagit"> + + <link href="/style.css" rel="stylesheet" /> + </head> + <body> + <header> + <h1><a href="/">{{siteTitle}}</a>: {{repoName}}</h1> + <p>{{description}}</p> + </header> + <nav> + <a href="/{{repoUrl}}">summary</a> + <a href="/{{repoUrl}}/log">log</a> + <a href="/{{repoUrl}}/tree" class="active">tree</a> + <a href="/{{repoUrl}}/refs">refs</a> + </nav> + <main> + {{#path}}<a href="{{url}}">{{name}}</a> / {{/path}} {{filename}} (<a href="{{filename}}">plain</a>)<br> + blob: {{id}} {{filesize}} + {{#isBinary}} + <pre>Binary file.</pre> + {{/isBinary}} + {{^isBinary}} + <div class="code"> + <pre class="lines">{{#lines}}<a id="L{{.}}" href="#L{{.}}">{{.}}</a> +{{/lines}}</pre> + <pre class="highlight">{{content}}</pre> + </div> + {{/isBinary}} + </main> + <footer>Generated on {{generated}}</footer> + <script src="https://git.sr.ht/static/linelight.js"></script> + </body> +</html> +
diff --git a/src/assets/repoCommit.html b/src/assets/repoCommit.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html> + <head> + <title>{{repoName}} - {{siteTitle}}</title> + + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="{{description}}"> + <meta name="generator" content="https://cgit.ctu.cx/nimstagit"> + + <link href="/style.css" rel="stylesheet" /> + </head> + <body> + <header> + <h1><a href="/">{{siteTitle}}</a>: {{repoName}}</h1> + <p>{{description}}</p> + </header> + <nav> + <a href="/{{repoUrl}}">summary</a> + <a href="/{{repoUrl}}/log" class="active">log</a> + <a href="/{{repoUrl}}/tree">tree</a> + <a href="/{{repoUrl}}/refs">refs</a> + </nav> + <main> + <div class="event-list"> + <div class="event"> + <a class="right" href="/{{repoUrl}}/log/{{id}}.html">{{when}} ago</a> + commit: {{id}}<br> + {{#parents}} + parent: <a href="/{{repoUrl}}/log/{{.}}.html">{{.}}</a><br> + {{/parents}} + author: {{authorName}}<br> + committer: {{committerName}}<br><br> + <pre>{{message}}</pre> + </div> + </div> + </main> + <footer>Generated on {{generated}}</footer> + </body> +</html> +
diff --git a/src/assets/repoLog.html b/src/assets/repoLog.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html> + <head> + <title>{{repoName}} - {{siteTitle}}</title> + + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="{{description}}"> + <meta name="generator" content="https://cgit.ctu.cx/nimstagit"> + + <link href="/style.css" rel="stylesheet" /> + </head> + <body> + <header> + <h1><a href="/">{{siteTitle}}</a>: {{repoName}}</h1> + <p>{{description}}</p> + </header> + <nav> + <a href="/{{repoUrl}}">summary</a> + <a href="/{{repoUrl}}/log" class="active">log</a> + <a href="/{{repoUrl}}/tree">tree</a> + <a href="/{{repoUrl}}/refs">refs</a> + </nav> + <main> + <div class="events"> + {{#commits}} + <div class="event"> + <a href="{{id}}.html">{{shortId}}</a> — {{authorName}} + <small class="right">{{when}} ago</small> + <pre>{{message}}</pre> + </div> + {{/commits}} + </div> + <!-- <table> + <thead> + <tr> + <td>id</td> + <td>Commit message</td> + <td>Author</td> + <td>Date</td> + </tr> + </thead> + {{#commits}} + <tr> + <td><a href="{{id}}.html">{{shortId}}</a></td> + <td style="text-overflow: ellipsis;"><a href="{{id}}.html">{{summary}}</a></td> + <td>{{authorName}}</td> + <td>{{when}} ago</td> + </tr> + {{/commits}} + </table> --> + </main> + <footer>Generated on {{generated}}</footer> + </body> +</html> +
diff --git a/src/assets/repoRefs.html b/src/assets/repoRefs.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html> + <head> + <title>{{repoName}} - {{siteTitle}}</title> + + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="{{description}}"> + <meta name="generator" content="https://cgit.ctu.cx/nimstagit"> + + <link href="/style.css" rel="stylesheet" /> + </head> + <body> + <header> + <h1><a href="/">{{siteTitle}}</a>: {{repoName}}</h1> + <p>{{description}}</p> + </header> + <nav> + <a href="/{{repoUrl}}">summary</a> + <a href="/{{repoUrl}}/log">log</a> + <a href="/{{repoUrl}}/tree">tree</a> + <a href="/{{repoUrl}}/refs" class="active">refs</a> + </nav> + <main> + </main> + <footer>Generated on {{generated}}</footer> + </body> +</html> +
diff --git a/src/assets/repoSummary.html b/src/assets/repoSummary.html @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<html> + <head> + <title>{{repoName}} - {{siteTitle}}</title> + + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="{{description}}"> + <meta name="generator" content="https://cgit.ctu.cx/nimstagit"> + + <link href="/style.css" rel="stylesheet" /> + </head> + <body> + <header> + <h1><a href="/">{{siteTitle}}</a>: {{repoName}}</h1> + <p>{{description}}</p> + </header> + <nav> + <a href="/{{repoUrl}}" class="active">summary</a> + <a href="/{{repoUrl}}/log">log</a> + <a href="/{{repoUrl}}/tree">tree</a> + <a href="/{{repoUrl}}/refs">refs</a> + </nav> + <main> + <div class="container"> + <div class="row"> + <div class="col-8"> + <h3>last commits</h3> + <div class="events"> + {{#lastCommits}} + <div class="event"> + <a href="/{{repoUrl}}/log/{{id}}.html">{{shortId}}</a> — {{authorName}} + <small class="right">{{when}} ago</small> + <pre>{{summary}}</pre> + </div> + {{/lastCommits}} + </div> + <!-- <table> + <thead> + <tr> + <td>id</td> + <td>Commit message</td> + <td>author</td> + <td>date</td> + </tr> + </thead> + {{#lastCommits}} + <tr> + <td>{{shortId}}</td> + <td>{{summary}}</td> + <td>{{committerName}}</td> + <td>{{when}} ago</td> + </tr> + {{/lastCommits}} + </table> --> + </div> + <div class="col-4"> + <h3>clone</h3> + <dl> + <dt>read-only</dt> + <dd><a href="https://git.ctu.cx/{{repoUrl}}">https://git.ctu.cx/{{repoName}}</a></dd> + <dt>read/write</dt> + <dd>git@wanderduene.ctu.cx:{{repoName}}</dd> + </dl> + </div> + </div> + </div> + <div class="responsive"> + {{{readmeContent}}} + </div> + </main> + <footer><p>Generated on {{generated}}</p></footer> + </body> +</html> + + + \ No newline at end of file
diff --git a/src/assets/repoTree.html b/src/assets/repoTree.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<html> + <head> + <title>{{repoName}} - {{siteTitle}}</title> + + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="{{description}}"> + <meta name="generator" content="https://cgit.ctu.cx/nimstagit"> + + <link href="/style.css" rel="stylesheet" /> + </head> + <body> + <header> + <h1><a href="/">{{siteTitle}}</a>: {{repoName}}</h1> + <p>{{description}}</p> + </header> + <nav> + <a href="/{{repoUrl}}">summary</a> + <a href="/{{repoUrl}}/log">log</a> + <a href="/{{repoUrl}}/tree" class="active">tree</a> + <a href="/{{repoUrl}}/refs">refs</a> + </nav> + <main> + {{#isSubdir}} + {{#path}}<a href="{{url}}">{{name}}</a> / {{/path}} {{dirName}} + {{/isSubdir}} + <table> + <thead> + <tr> + <td style="width: 10%">Mode</td> + <td style="width: 80%">Name</td> + <td style="width: 10%">Size</td> + </tr> + </thead> + {{#isSubdir}} + <tr> + <td></td> + <td><a href="..">..</a></td> + <td></td> + </tr> + {{/isSubdir}} + {{#entries}} + <tr> + <td>{{mode}}</td> + {{#isDir}} + <td><a href="{{name}}/">{{name}}</a></td> + {{/isDir}} + {{^isDir}} + <td><a href="{{name}}.html">{{name}}</a></td> + {{/isDir}} + <td>{{size}}</td> + </tr> + {{/entries}} + </table> + </main> + <footer>Generated on {{generated}}</footer> + </body> +</html> +
diff --git a/src/assets/style.css b/src/assets/style.css @@ -0,0 +1,283 @@ +* { + font-family: monospace; + line-height: 1.25; +} + +*, ::after, ::before { + box-sizing: border-box; +} + +body { + margin: 0 auto; + max-width: 960px; +} + +nav a { + color: #777; + text-decoration: none; +} + +a { + color: black; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +header { + margin: 10px; +} + +header h1 { + color: #000; + font-size: 2em; + font-weight: bold; + margin-bottom: 0px; +} + +header p { + color: #777; + margin-top: 0px; +} + +nav { + margin-top: 2em; + padding: 0px 1em; + vertical-align: bottom; +} + +nav a { + padding: 2px 0.75em; + font-size: 110%; +} + +nav a.active { + color: #000; + background-color: #ccc; +} + +main { + padding: 2em; +} + +pre { + background: #e9ecef; + padding: .25rem; + display: block; + font-size: 87.5%; + color: #212529; + margin: 0; + overflow: auto; +} + +table { + width: 100%; +} + +thead { + font-weight: bold; +} + +footer { + margin-top: 0; + text-align: center; + font-size: 1em; + color: #ccc; +} + + + +/*** + * + * events (e.g. commit) + * + ***/ + +.event { + text-overflow: ellipsis; + overflow: hidden; + padding: .5rem; + margin: .5rem 0; + background: #f8f9fa; +} + +.event :last-child { + margin-bottom: 0; +} + +.event pre { + text-overflow: ellipsis; + overflow: none; + padding-left: 0; + padding-right: 0; + background: 0 0; + display: block; + font-size: 87.5%; + color: #212529; +} + + + +/*** + * + * code viewer (blob page) + * + ***/ + +.code { + display: grid; + grid-template-columns: auto auto auto 1fr; + grid-template-rows: auto; + background: #e9ecef; +} + +.code .lines { + grid-column-start: 1; + grid-row-start: 1; + text-align: right; + padding-left: .5rem; + padding-right: .5rem; + padding-bottom: 1rem; + background: #eee; + border-right: 1px solid #444; + z-index: 0; +} + +.code .lines a.selected::before, .code .lines a:target::before { + display: block; + content: ""; + width: calc(960px - 4em - 5px); + z-index: -1; + position: absolute; + background: #b3d7ff; + margin-left: -6px; +} + +.code .highlight { + grid-column-start: 2; + grid-row-start: 1; + padding-left: 1rem; + padding-bottom: 1rem; + background: 0 0; + z-index: 0; +} + + + +/*** + * + * overview page + * + ***/ + +td.sublevel-repo { + padding-left: 1.5em; +} + +td.reposection { + color: #888; + font-style: italic; +} + + + +/*** + * + * times + * + ***/ + +span.age-mins { + font-weight: bold; + color: #080; +} + +span.age-hours { + color: #080; +} + +span.age-days { + color: #040; +} + +span.age-weeks { + color: #444; +} + +span.age-months { + color: #888; +} + +span.age-years { + color: #bbb; +} + + + +/*** + * + * grid-system + * + ***/ + +.container { + width: 100%; + margin-left: -2%; + margin-right: -2%; +} + +.row { + position: relative; + width: 100%; +} + +.row [class^="col"] { + float: left; + margin: 0 2%; + min-height: 0.125rem; +} + +.row::after { + content: ""; + display: table; + clear: both; +} + + +.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12 { + width: 96%; +} + +@media only screen and (min-width: 45em) { /* 720px */ + .col-1 { width: 4.33%; } + .col-2 { width: 12.66%; } + .col-3 { width: 21%; } + .col-4 { width: 29.33%; } + .col-5 { width: 37.66%; } + .col-6 { width: 46%; } + .col-7 { width: 54.33%; } + .col-8 { width: 62.66%; } + .col-9 { width: 71%; } + .col-10 { width: 79.33%; } + .col-11 { width: 87.66%; } + .col-12 { width: 96%; } +} + + + +/*** + * + * helper + * + ***/ + +.responsive { + width: 100%; + overflow: auto; +} + +.right { + float: right; +}
diff --git a/src/index.nim b/src/index.nim @@ -0,0 +1,67 @@ +import os, posix, times, json, tables, algorithm, uri +import nimgit + +import types, utils + + +type + Repository = object + repoName: string + repoUrl: string + description: string + lastActivity: string + objId: string + + Category = object + name: string + repos: seq[Repository] + +proc reposOverview* (config: Config): seq[Category] = + template getRepoMetadata = + try: + let + repoName = nameFromPath(repoPath) + repository = openGitRepository(repoPath) + config = repository.config + objId = repository.lookupObjectIdByName("HEAD") + commit = repository.lookupCommit(objId) + category = config.get("gitweb.category") + + if not categoriesTable.hasKey(category): + categoriesTable[category] = newSeq[Repository]() + + categoriesTable[category].add(Repository( + repoName: repoName, + repoUrl: encodeUrl(repoName), + description: config.get("gitweb.description"), + lastActivity: relativeTimeFromNow(commit.time.time), + objId: $objId + )) + + free(commit) + free(objId) + free(config) + free(repository) + + except: + echo "This seems to not be a git-repo: " & repoPath + echo "Error:\n" & getCurrentExceptionMsg() + + var categoriesTable = initTable[string, seq[Repository]]() + + if config.projectsList.len != 0: + for element in config.projectsList: + let repoPath = joinPath(config.scanPath, element) + getRepoMetadata() + + for name, repos in categoriesTable: + var repos = repos + + repos.sort(proc (x, y: Repository): int = cmp(x.repoName, y.repoName)) + + result.add(Category( + name: name, + repos: repos + )) + + result.sort(proc (x, y: Category): int = cmp(x.name, y.name))
diff --git a/src/nimstagit.nim b/src/nimstagit.nim @@ -0,0 +1,58 @@ +import os, times, json +import nimgit, moustachu + +import types, utils, assets + +import index, repoGenerator + + +var config {.threadvar.}: Config + +proc main = + if paramCount() == 0: + echo "No options given!" + quit(QuitFailure) + + if paramCount() == 1: + echo "No config given!" + quit(QuitFailure) + + discard git_libgit2_init() + + config = readConfig(paramStr(2)) + + if not dirExists(config.outputDirectory): + echo "Output-directory does not exist!" + quit(QuitFailure) + + case paramStr(1): + of "index": + let templateContext = %* { + "siteTitle": config.title, + "description": config.description, + "generated": $now(), + "categories": reposOverview(config) + } + + echo "Generate page: index.html" + writeFile(joinPath(config.outputDirectory, "index.html"), render(templateOverview, templateContext)) + echo "Create asset: style.css" + writeFile(joinPath(config.outputDirectory, "style.css"), assetStyleCss) + + of "repo": + if paramCount() > 2: + if not config.projectsList.contains(paramStr(3)): + echo "The repo '" & paramStr(3) & "' is not in the projectsList/scanDir!" + quit(QuitFailure) + + repoGenerator(config, paramStr(3)) + + else: + for repo in config.projectsList: + repoGenerator(config, repo) + + else: + echo "Unknown option: " & paramStr(1) + echo "Valid options are: index," + +main()+ \ No newline at end of file
diff --git a/src/repoGenerator.nim b/src/repoGenerator.nim @@ -0,0 +1,352 @@ +import os, json, times, uri, strutils, sequtils +import moustachu, nimgit, markdown + +import types, utils, assets + +type + CommitSummary = object + id: string + shortId: string + `when`: string + committerIsAuthor: bool + authorName: string + authorMail: string + committerName: string + committerMail: string + summary: string + body: string + message: string + + RepoLog = object + description: string + category: string + commits: seq[CommitSummary] + + RepoTreeEntry = object + isDir: bool + name: string + mode: string + size: string + + RepoTree = object + description: string + category: string + entries: seq[RepoTreeEntry] + + PathObj = object + name: string + url: string + + + +proc createCommitSummary (commit: GitCommit): CommitSummary = + let author = commit.author + let committer = commit.committer + + result.id = $commit.id + result.shortId = commit.shortId + result.when = relativeTimeFromNow(commit.time.time) + result.committerIsAuthor = (author.email == committer.email) + result.authorName = author.name + result.authorMail = author.email + result.committerName = committer.name + result.committerMail = committer.email + result.summary = commit.summary + result.body = commit.body + result.message = commit.message + + +proc generateCommitPage (path: string, templateContext: JsonNode, commit: GitCommit) = +# if fileExists(joinPath(joinPath(path, $commit.id & ".html"))): return + + let + id = $commit.id + author = commit.author + committer = commit.committer + + var parents: seq[string] + + if commit.parentCount != 0: + for id in commit.parentIds: + parents.add($id) + + + writeFile( + joinPath(joinPath(path, id & ".html")), + render( + templateRepoCommit, + mergeJson(templateContext, %* { + "id": id, + "shortId": commit.shortId, + "when": relativeTimeFromNow(commit.time.time), + "message": commit.message, + "committerIsAuthor": (committer.email == author.email), + "committerName": committer.name, + "committerMail": committer.email, + "authorName": author.name, + "authorMail": author.email, + "parents": parents + }) + ) + ) + + +proc generateRepoBlobPage (path: string, pathSeq: seq[PathObj], templateContext: JsonNode, entry: GitTreeEntry, blob: GitBlob) = + let content = blob.content + + var blobData = %*{ + "path": pathSeq, + "id": $entry.id, + "filename": entry.name, + "filesize": formatSize(blob.size), + "isBinary": blob.isBinary + } + + if not blob.isBinary: + blobData["content"] = %content + blobData["lines"] = %toSeq(1..content.countLines) + + writeFile(joinPath(path, entry.name), content) + + writeFile( + joinPath(joinPath(path, entry.name & ".html")), + render( + templateRepoBlob, + mergeJson(templateContext, blobData) + ) + ) + +proc generateRepoTreePage (isSubdir: bool, path: string, pathSeq: seq[PathObj], dirName: string, templateContext: JsonNode, tree: GitTree) = + discard existsOrCreateDir(joinPath(path)) + + var + entries : seq[RepoTreeEntry] + tempPathSeq = pathSeq + + if isSubdir: + tempPathSeq.add(PathObj( + name: dirName, + url: pathSeq[pathSeq.len-1].url & "/" & encodeUrl(dirName) + )) + + for entry in tree: + var size: int + + if entry.type == goBlob: + let blob = tree.owner.lookupBlob(entry.id) + + tempPathSeq.add(PathObj( + name: dirName, + url: tempPathSeq[tempPathSeq.len-1].url & "/" & encodeUrl(dirName) + )) + + tempPathSeq.delete(tempPathSeq.len-1) + + generateRepoBlobPage(path, tempPathSeq, templateContext, entry, blob) + + size = blob.size + + free(blob) + + entries.add(RepoTreeEntry( + isDir: (entry.type != goBlob), + name: entry.name, + mode: entry.modeStr, + size: formatSize(size) + )) + + if entry.type == goTree: + let subtree = tree.owner.lookupTree(entry.id) + + generateRepoTreePage(true, joinPath(path, entry.name), tempPathSeq, entry.name, templateContext, subtree) + + free(subtree) + + free(entry) + + writeFile( + joinPath(path, "index.html"), + render( + templateRepoTree, + mergeJson(templateContext, %*{ + "isSubdir": isSubdir, + "path": pathSeq, + "dirName": dirName, + "entries": entries + } + ) + ) + ) + + +proc repoGenerator* (config: Config, name: string) = + var + repository: GitRepository + repoConfig: GitConfig + + try: + repository = openGitRepository(joinPath(config.scanPath, name)) + repoConfig = repository.config + + let + repoName = nameFromPath(name) + description = repoConfig.get("gitweb.description") + category = repoConfig.get("gitweb.category") + templateContext = %* { + "siteTitle": config.title, + "repoName": repoName, + "repoUrl": encodeUrl(repoName), + "generated": $now(), + "description": description, + "category": category, + } + + + echo "Generate pages for repo: " & repoName + discard existsOrCreateDir(joinPath(config.outputDirectory, repoName)) + discard existsOrCreateDir(joinPath(config.outputDirectory, repoName, "log")) + discard existsOrCreateDir(joinPath(config.outputDirectory, repoName, "tree")) + discard existsOrCreateDir(joinPath(config.outputDirectory, repoName, "refs")) + + + # + # Summary page + # + + let + headObjId = repository.lookupObjectIdByName("HEAD") + headCommit = repository.lookupCommit(headObjId) + tree = headCommit.tree + parentCount = headCommit.parentCount + + var lastCommits: seq[CommitSummary] + var readmeContent: string + + lastCommits.add(createCommitSummary(headCommit)) + + if parentCount != 0: + let parentCommit = repository.lookupCommit(headCommit.parentIds[0]) + lastCommits.add(createCommitSummary(parentCommit)) + free(parentCommit) + + for readmeFile in config.readmeFiles: + if tree.entries.contains(readmeFile): + let + entry = tree.entry(readmeFile) + blob = repository.lookupBlob(entry.id) + + if not config.renderMarkdown: + readmeContent = blob.content + else: + readmeContent = markdown(blob.content, config=initGfmConfig()) + + free(blob) + free(entry) + break + + + echo "Generate repo-summary page" + writeFile( + joinPath(config.outputDirectory, repoName, "index.html"), + render( + templateRepoSummary, + mergeJson( + templateContext, + %* { + "lastCommits": lastCommits, + "readmeContent": readmeContent + } + ) + ) + ) + + # + # END Summary page + # + + # + # Tree pages + # + + var pathSeq: seq[PathObj] + + pathSeq.add(PathObj( + name: "root", + url: "/" & encodeUrl(repoName) & "/tree" + )) + + generateRepoTreePage(false, joinPath(config.outputDirectory, repoName, "tree"), pathSeq, "", templateContext, tree) + + # + # END Tree pages + # + + + # + # Log page and Commit pages + # + + let gitRevisionWalker = repository.walk() + var commits: seq[CommitSummary] + + gitRevisionWalker.sort(GIT_SORT_TOPOLOGICAL) + gitRevisionWalker.pushHead() + + for objectId in gitRevisionWalker: + let commit = repository.lookupCommit(objectId) + + generateCommitPage(joinPath(config.outputDirectory, repoName, "log"), templateContext, commit) + commits.add(createCommitSummary(commit)) + + free(commit) + + echo "Generate repo-commit and log page(s)" + writeFile( + joinPath(config.outputDirectory, repoName, "log/index.html"), + render( + templateRepoLog, + mergeJson( + templateContext, + %* { + "commits": commits + } + ) + ) + ) + + # + # END Log page and Commit pages + # + + + # + # Log page and Commit pages + # + + echo "Generate repo-refs page!" + writeFile( + joinPath(config.outputDirectory, repoName, "refs/index.html"), + render( + templateRepoRefs, + mergeJson( + templateContext, + %* { + } + ) + ) + ) + + # + # END Log page and Commit pages + # + + free(tree) + free(headCommit) + free(headObjId) + free(repoConfig) + free(repository) + + except: + free(repoConfig) + free(repository) + echo "Error:\n", getCurrentExceptionMsg()
diff --git a/src/types.nim b/src/types.nim @@ -0,0 +1,9 @@ +type + Config* = object + outputDirectory*: string + title*: string + description*: string + scanPath*: string + projectsList*: seq[string] + readmeFiles*: seq[string] + renderMarkdown*: bool+ \ No newline at end of file
diff --git a/src/utils.nim b/src/utils.nim @@ -0,0 +1,79 @@ +import os, times, parsecfg, strutils, json + +import types + +proc readConfig* (path: string): types.Config = + if not fileExists(path): + echo "Error: config-file does not exist!" + quit(QuitFailure) + + let configFile = loadConfig(path) + + let + outputDirectory = configFile.getSectionValue("", "outputDirectory", "") + title = configFile.getSectionValue("", "title", "nimstagit") + description = configFile.getSectionValue("", "description", "") + scanPath = configFile.getSectionValue("", "scanPath", "") + projectsFile = configFile.getSectionValue("", "projectsFile", "") + readmeFiles = configFile.getSectionValue("", "readmeFiles", "README, readme, README.md, readme.md") + renderMarkdown = configFile.getSectionValue("", "renderMarkdown", "true").parseBool + + var projectsList: seq[string] + + if scanPath == "": + echo "Error: config-value 'outputDirectory' has to be set!" + quit(QuitFailure) + + if scanPath == "": + echo "Error: config-value 'scanPath' has to be set!" + quit(QuitFailure) + + if projectsFile != "": + if not fileExists(projectsFile): + echo "Error: projectsFile does not exist!" + quit(QuitFailure) + + for element in readFile(projectsFile).splitLines: + if element != "": projectsList.add(element) + + else: + for kind, element in walkDir(scanPath): + if kind != pcDir: continue + projectsList.add(splitPath(element).tail) + + result = types.Config( + outputDirectory: outputDirectory, + title: title, + description: description, + scanPath: scanPath, + projectsList: projectsList, + readmeFiles: readmeFiles.split(", "), + renderMarkdown: renderMarkdown + ) + +proc mergeJson* (a: JsonNode, b: JsonNode): JsonNode = + result = a + for key, val in b: + result[key] = val + +proc nameFromPath* (path: string): string = result = splitPath(path).tail; removeSuffix(result, ".git") + +proc relativeTimeFromNow* (time: Time): string = + let + timeNow = now().toTime + timeDiff = timeNow - time + weeks = inWeeks(timeDiff) + days = inDays(timeDiff) + hours = inHours(timeDiff) + minutes = inMinutes(timeDiff) + + if weeks > 0: + result = $weeks & " week(s)" + elif days > 0: + result = $days & " day(s)" + elif hours > 0: + result = $hours & " hour(s)" + elif minutes > 2: + result = $minutes & " Minutes" + else: + result = "now"