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;