startgit

Static page generator for git repositories
git clone git://git.dimitrijedobrota.com/startgit.git
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING

commit fe50b5aad2d23362849307ca081f588e9758bb21
parent b13c4211c2adafdc4bc0b98112a9ff39a94a2ab1
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Fri, 2 May 2025 15:12:38 +0200

Consistency improvements

Diffstat:
M source/branch.cpp | ++++ -
M source/diff.cpp | ++ --
M source/document.cpp | ++ --
M source/html.cpp | +++++++++++++++++++++++++ --------------------------
M source/startgit-index.cpp | +++++++++++++++++++++++++++++++++++++++++ -----------------------------------------
M source/startgit.cpp | +++++++++++++++++++++++++++++++++++++ ---------------------------------------------
M source/utils.cpp | ++++++++++++ ------------

7 files changed, 160 insertions(+), 172 deletions(-)


diff --git a/ source/branch.cpp b/ source/branch.cpp

@@ -42,7 +42,10 @@ branch::branch(git2wrap::branch brnch, repository& repo)

case GIT_OBJ_TREE:
traverse(entry.to_tree(), full_path);
continue;
default:
case GIT_OBJECT_ANY:
case GIT_OBJECT_INVALID:
case GIT_OBJECT_COMMIT:
case GIT_OBJECT_TAG:
continue;
}

diff --git a/ source/diff.cpp b/ source/diff.cpp

@@ -14,10 +14,10 @@ diff::diff(const git2wrap::commit& cmmt)

git2wrap::diff_options opts;
git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION);

// NOLINTBEGIN hicpp-signed-bitwise
// NOLINTBEGIN(*hicpp-signed-bitwise*)
opts.flags = GIT_DIFF_DISABLE_PATHSPEC_MATCH | GIT_DIFF_IGNORE_SUBMODULES
| GIT_DIFF_INCLUDE_TYPECHANGE;
// NOLINTEND hicpp-signed-bitwise
// NOLINTEND(*hicpp-signed-bitwise*)

m_diff = git2wrap::diff::tree_to_tree(ptree, cmmt.get_tree(), &opts);
m_stats = m_diff.get_stats();

diff --git a/ source/document.cpp b/ source/document.cpp

@@ -50,7 +50,7 @@ void document::render(std::ostream& ost, const content_t& content) const

}},

// Rss feed
!m_has_feed ? element {} : [&]() -> hemplate::element
!m_has_feed ? element {} : [&]() -> element
{
return link {{
{"rel", "alternate"},

@@ -61,7 +61,7 @@ void document::render(std::ostream& ost, const content_t& content) const

}(),

// Atom feed
!m_has_feed ? element {} : [&]() -> hemplate::element
!m_has_feed ? element {} : [&]() -> element
{
return link {{
{"rel", "alternate"},

diff --git a/ source/html.cpp b/ source/html.cpp

@@ -225,20 +225,17 @@ void md_html::render_url_escaped(const MD_CHAR* data, MD_SIZE size)

if (off < size) {
std::array<char, 3> hex = {0};

switch (data[off]) { // NOLINT
case '&':
render_verbatim("&amp;");
break;
default:
hex[0] = '%';
hex[1] = hex_chars // NOLINT
[(static_cast<unsigned>(data[off]) >> 4) // NOLINT
& 0xf]; // NOLINT
hex[2] = hex_chars // NOLINT
[(static_cast<unsigned>(data[off]) >> 0) // NOLINT
& 0xf]; // NOLINT
render_verbatim(hex.data(), 3);
break;
if (data[off] == '&') { // NOLINT
render_verbatim("&amp;");
} else {
hex[0] = '%';
hex[1] = hex_chars // NOLINT
[(static_cast<unsigned>(data[off]) >> 4) // NOLINT
& 0xf]; // NOLINT
hex[2] = hex_chars // NOLINT
[(static_cast<unsigned>(data[off]) >> 0) // NOLINT
& 0xf]; // NOLINT
render_verbatim(hex.data(), 3);
}
off++;
} else {

@@ -343,17 +340,17 @@ void md_html::render_attribute(const MD_ATTRIBUTE* attr, append_fn fn_append)

MD_SIZE size = attr->substr_offsets[i + 1] - off; // NOLINT
const MD_CHAR* text = attr->text + off; // NOLINT

switch (type) {
case MD_TEXT_NULLCHAR:
render_utf8_codepoint(0x0000, &md_html::render_verbatim);
break;
case MD_TEXT_ENTITY:
render_entity(text, size, fn_append);
break;
default:
std::invoke(fn_append, this, text, size);
break;
if (type == MD_TEXT_NULLCHAR) {
render_utf8_codepoint(0x0000, &md_html::render_verbatim);
continue;
}

if (type == MD_TEXT_ENTITY) {
render_entity(text, size, fn_append);
continue;
}

std::invoke(fn_append, this, text, size);
}
}

@@ -416,7 +413,7 @@ void md_html::render_open_td_block(

case MD_ALIGN_RIGHT:
render_verbatim(" align=\"right\">");
break;
default:
case MD_ALIGN_DEFAULT:
render_verbatim(">");
break;
}

@@ -724,7 +721,9 @@ int text_callback(

case MD_TEXT_ENTITY:
data->render_entity(text, size, &md_html::render_html_escaped);
break;
default:
case MD_TEXT_NORMAL:
case MD_TEXT_CODE:
case MD_TEXT_LATEXMATH:
data->render_html_escaped(text, size);
break;
}

diff --git a/ source/startgit-index.cpp b/ source/startgit-index.cpp

@@ -10,6 +10,66 @@

#include "arguments.hpp"
#include "document.hpp"
#include "repository.hpp"
namespace startgit
{

hemplate::element write_table_row(const std::filesystem::path& repo_path)
{
using namespace hemplate::html; // NOLINT

try {
const repository repo(repo_path);

for (const auto& branch : repo.get_branches()) {
if (branch.get_name() != "master") {
continue;
}

const auto url = repo.get_name() + "/master/log.html";
return tr {
td {a {{{"href", url}}, repo.get_name()}},
td {repo.get_description()},
td {repo.get_owner()},
td {branch.get_commits()[0].get_time()},
};
}

std::cerr << std::format(
"Warning: {} doesn't have master branch\n", repo.get_path().string()
);
} catch (const git2wrap::error<git2wrap::error_code_t::ENOTFOUND>& err) {
std::cerr << std::format(
"Warning: {} is not a repository\n", repo_path.string()
);
}

return element {};
}

hemplate::element write_table()
{
using namespace hemplate::html; // NOLINT

return element {
h1 {args.title},
p {args.description},
table {
thead {
tr {
td {"Name"},
td {"Description"},
td {"Owner"},
td {"Last commit"},
},
},
tbody {
transform(args.repos, write_table_row),
},
}
};
}

} // namespace startgit

namespace
{

@@ -91,67 +151,6 @@ static const poafloc::arg_t arg {


} // namespace

namespace startgit
{

hemplate::element write_table_row(const std::filesystem::path& repo_path)
{
using namespace hemplate::html; // NOLINT

try {
const repository repo(repo_path);

for (const auto& branch : repo.get_branches()) {
if (branch.get_name() != "master") {
continue;
}

const auto url = repo.get_name() + "/master/log.html";
return tr {
td {a {{{"href", url}}, repo.get_name()}},
td {repo.get_description()},
td {repo.get_owner()},
td {branch.get_commits()[0].get_time()},
};
}

std::cerr << std::format(
"Warning: {} doesn't have master branch\n", repo.get_path().string()
);
} catch (const git2wrap::error<git2wrap::error_code_t::ENOTFOUND>& err) {
std::cerr << std::format(
"Warning: {} is not a repository\n", repo_path.string()
);
}

return element {};
}

hemplate::element write_table()
{
using namespace hemplate::html; // NOLINT

return element {
h1 {args.title},
p {args.description},
table {
thead {
tr {
td {"Name"},
td {"Description"},
td {"Owner"},
td {"Last commit"},
},
},
tbody {
transform(args.repos, write_table_row),
},
}
};
}

} // namespace startgit

int main(int argc, char* argv[])
{
using namespace hemplate::html; // NOLINT

diff --git a/ source/startgit.cpp b/ source/startgit.cpp

@@ -19,14 +19,15 @@

#include "repository.hpp"
#include "utils.hpp"

using hemplate::element;
namespace
{

template<std::ranges::forward_range R>
hemplate::element wtable(
element wtable(
std::initializer_list<std::string_view> head_content,
const R& range,
based::Procedure<std::ranges::range_value_t<R>> auto proc
based::Procedure<element, std::ranges::range_value_t<R>> auto proc
)
{
using namespace hemplate::html; // NOLINT

@@ -56,7 +57,7 @@ hemplate::element wtable(

namespace startgit
{

hemplate::element page_title(
element page_title(
const repository& repo,
const branch& branch,
const std::string& relpath = "./"

@@ -107,7 +108,7 @@ hemplate::element page_title(

};
}

hemplate::element commit_table(const branch& branch)
element commit_table(const branch& branch)
{
using namespace hemplate::html; // NOLINT

@@ -131,7 +132,7 @@ hemplate::element commit_table(const branch& branch)

);
}

hemplate::element files_table(const branch& branch)
element files_table(const branch& branch)
{
using namespace hemplate::html; // NOLINT

@@ -155,9 +156,7 @@ hemplate::element files_table(const branch& branch)

);
}

hemplate::element branch_table(
const repository& repo, const std::string& branch_name
)
element branch_table(const repository& repo, const std::string& branch_name)
{
using namespace hemplate::html; // NOLINT

@@ -185,7 +184,7 @@ hemplate::element branch_table(

};
}

hemplate::element tag_table(const repository& repo)
element tag_table(const repository& repo)
{
using namespace hemplate::html; // NOLINT

@@ -207,7 +206,7 @@ hemplate::element tag_table(const repository& repo)

};
}

hemplate::element file_changes(const diff& diff)
element file_changes(const diff& diff)
{
using namespace hemplate::html; // NOLINT

@@ -261,7 +260,7 @@ hemplate::element file_changes(const diff& diff)

};
}

hemplate::element diff_hunk(const hunk& hunk)
element diff_hunk(const hunk& hunk)
{
using namespace hemplate::html; // NOLINT

@@ -280,7 +279,7 @@ hemplate::element diff_hunk(const hunk& hunk)

{{"style", "white-space: pre"}},
transform(
hunk.get_lines(),
[](const auto& line) -> hemplate::element
[](const auto& line) -> element
{
using hemplate::html::div;

@@ -306,7 +305,7 @@ hemplate::element diff_hunk(const hunk& hunk)

};
}

hemplate::element file_diffs(const diff& diff)
element file_diffs(const diff& diff)
{
using namespace hemplate::html; // NOLINT

@@ -334,7 +333,7 @@ hemplate::element file_diffs(const diff& diff)

);
}

hemplate::element commit_diff(const commit& commit)
element commit_diff(const commit& commit)
{
using namespace hemplate::html; // NOLINT

@@ -348,8 +347,7 @@ hemplate::element commit_diff(const commit& commit)

td {b {"commit"}},
td {a {{{"href", url}}, commit.get_id()}},
},
commit.get_parentcount() == 0 ? element {}
: [&]() -> hemplate::element
commit.get_parentcount() == 0 ? element {} : [&]() -> element
{
const auto purl =
std::format("../commit/{}.html", commit.get_parent_id());

@@ -363,10 +361,12 @@ hemplate::element commit_diff(const commit& commit)

td {b {"author"}},
td {
commit.get_author_name(),
"&lt;",
a {
{{"href", mailto}},
"&lt;" + commit.get_author_email() + "&gt;",
commit.get_author_email(),
},
"&gt;",
},
},
tr {

@@ -386,7 +386,7 @@ hemplate::element commit_diff(const commit& commit)

};
}

hemplate::element write_file_title(const file& file)
element write_file_title(const file& file)
{
using namespace hemplate::html; // NOLINT

@@ -398,7 +398,7 @@ hemplate::element write_file_title(const file& file)

};
}

hemplate::element write_file_content(const file& file)
element write_file_content(const file& file)
{
using namespace hemplate::html; // NOLINT

@@ -433,25 +433,6 @@ hemplate::element write_file_content(const file& file)

};
}

void write_html(std::ostream& ost, const file& file)
{
static const auto process_output =
+[](const MD_CHAR* str, MD_SIZE size, void* data)
{
std::ofstream& ofs = *static_cast<std::ofstream*>(data);
ofs << std::string(str, size);
};

md_html(
file.get_content(),
static_cast<MD_SIZE>(file.get_size()),
process_output,
&ost,
MD_DIALECT_GITHUB,
0
);
}

void write_log(
const std::filesystem::path& base,
const repository& repo,

@@ -464,7 +445,7 @@ void write_log(

ofs,
[&]()
{
return hemplate::element {
return element {
page_title(repo, branch),
commit_table(branch),
};

@@ -483,7 +464,7 @@ void write_file(

ofs,
[&]()
{
return hemplate::element {
return element {
page_title(repo, branch),
files_table(branch),
};

@@ -502,7 +483,7 @@ void write_refs(

ofs,
[&]()
{
return hemplate::element {
return element {
page_title(repo, branch),
branch_table(repo, branch.get_name()),
tag_table(repo),

@@ -530,7 +511,7 @@ bool write_commits(

ofs,
[&]()
{
return hemplate::element {
return element {
page_title(repo, branch, "../"),
commit_diff(commit),
};

@@ -565,7 +546,7 @@ void write_files(

ofs,
[&]()
{
return hemplate::element {
return element {
page_title(repo, branch, relpath),
write_file_title(file),
write_file_content(file),

@@ -583,23 +564,33 @@ void write_readme_licence(

{
for (const auto& file : branch.get_special()) {
std::ofstream ofs(base / file.get_path().replace_extension("html"));
std::stringstream sstr;
document {repo, branch, file.get_path().string()}.render(
sstr,
ofs,
[&]()
{
return hemplate::element {
std::string html;

static const auto process_output =
+[](const MD_CHAR* str, MD_SIZE size, void* data)
{
auto buffer = *static_cast<std::string*>(data);
buffer += std::string(str, size);
};

md_html(
file.get_content(),
static_cast<MD_SIZE>(file.get_size()),
process_output,
&html,
MD_DIALECT_GITHUB,
0
);
return element {
page_title(repo, branch),
html,
};
}
);

const std::string data = sstr.str();
const auto pos = data.find("hr /") + 6;

ofs << data.substr(0, pos);
write_html(ofs, file);
ofs << data.substr(pos);
}
}

@@ -610,25 +601,21 @@ void write_atom(

using namespace hemplate::atom; // NOLINT
using hemplate::atom::link;

const hemplate::attribute_list self = {
{"href", base_url + "/atom.xml"},
{"rel", "self"},
};

const hemplate::attribute_list alter = {
{"href", args.resource_url},
{"rel", "alternate"},
{"type", "text/html"},
};

ost << feed {
title {args.title},
subtitle {args.description},
id {base_url + '/'},
updated {format_time_now()},
author {name {args.author}},
link {self, " "},
link {alter, " "},
link {{
{"href", base_url + "/atom.xml"},
{"rel", "self"},
}},
link {{
{"href", args.resource_url},
{"rel", "alternate"},
{"type", "text/html"},
}},
transform(
branch.get_commits(),
[&](const auto& commit)

@@ -640,7 +627,7 @@ void write_atom(

id {url},
updated {format_time(commit.get_time_raw())},
title {commit.get_summary()},
link {{{"href", url}}, " "},
link {{{"href", url}}},
author {
name {commit.get_author_name()},
email {commit.get_author_email()},

@@ -668,7 +655,7 @@ void write_rss(

link {base_url + '/'},
generator {"startgit"},
language {"en-us"},
atomLink {{{"href", base_url + "/atom.xml"}}},
atomLink {base_url + "/atom.xml"},
transform(
branch.get_commits(),
[&](const auto& commit)

diff --git a/ source/utils.cpp b/ source/utils.cpp

@@ -38,13 +38,13 @@ void xmlencode(std::ostream& ost, const std::string& str)

{
for (const char c: str) {
switch(c) {
case '<': ost << "&lt;"; break;
case '>': ost << "&gt;"; break;
case '\'': ost << "&#39;"; break;
case '&': ost << "&amp;"; break;
case '"': ost << "&quot;"; break;
default: ost << c;
case '<': ost << "&lt;"; continue;
case '>': ost << "&gt;"; continue;
case '\'': ost << "&#39;"; continue;
case '&': ost << "&amp;"; continue;
case '"': ost << "&quot;"; continue;
}
ost << c;
}
}

@@ -55,13 +55,13 @@ std::string xmlencode(const std::string& str)

res.reserve(str.size());
for (const char c: str) {
switch(c) {
case '<': res += "&lt;"; break;
case '>': res += "&gt;"; break;
case '\'': res += "&#39;"; break;
case '&': res += "&amp;"; break;
case '"': res += "&quot;"; break;
default: res += c;
case '<': res += "&lt;"; continue;
case '>': res += "&gt;"; continue;
case '\'': res += "&#39;"; continue;
case '&': res += "&amp;"; continue;
case '"': res += "&quot;"; continue;
}
res += c;
}

return res;