startgitStatic 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
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("&");
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("&");
} 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(),
"<",
a {
{{"href", mailto}},
"<" + commit.get_author_email() + ">",
commit.get_author_email(),
},
">",
},
},
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 << "<"; break;
case '>': ost << ">"; break;
case '\'': ost << "'"; break;
case '&': ost << "&"; break;
case '"': ost << """; break;
default: ost << c;
case '<': ost << "<"; continue;
case '>': ost << ">"; continue;
case '\'': ost << "'"; continue;
case '&': ost << "&"; continue;
case '"': ost << """; 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 += "<"; break;
case '>': res += ">"; break;
case '\'': res += "'"; break;
case '&': res += "&"; break;
case '"': res += """; break;
default: res += c;
case '<': res += "<"; continue;
case '>': res += ">"; continue;
case '\'': res += "'"; continue;
case '&': res += "&"; continue;
case '"': res += """; continue;
}
res += c;
}
return res;