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 | 242ae076ddb665b60394f51a158ebba169250c9e |
parent | 709e26144f2bd17fda81e65ef982857a18f5b681 |
author | Dimitrije Dobrota < mail@dimitrijedobrota.com > |
date | Thu, 24 Apr 2025 09:02:09 +0200 |
Improve formatting
M | .clang-format | | | + - |
M | source/common.cpp | | | +++++++++++++++++++++++++++++++++ ----------------------- |
M | source/common.hpp | | | ++++++++++++++++ ------------ |
M | source/diff.cpp | | | +++++++++++++++++ ------------ |
M | source/diff.hpp | | | ++++++++++++++ ------------ |
M | source/file.cpp | | | ++ -- |
M | source/html.cpp | | | ++++++++++++++++++++++++++++++++++++++++++++++++ ---------------------------------- |
M | source/html.hpp | | | ++++++++ ------ |
M | source/repository.cpp | | | +++++ --- |
M | source/repository.hpp | | | +++ -- |
M | source/startgit-index.cpp | | | +++++++++++++++++++++++++++++++++++++++++++ --------------------------------------- |
M | source/startgit.cpp | | | +++++++++++++++++++++++++++++++++++++++++++++ ------------------------------------- |
M | source/utils.cpp | | | +++++++++ ------ |
13 files changed, 521 insertions(+), 412 deletions(-)
diff --git a/ .clang-format b/ .clang-format
@@ -2,7 +2,7 @@
Language: Cpp
# BasedOnStyle: Chromium
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignAfterOpenBracket: BlockIndent
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveBitFields: false
diff --git a/ source/common.cpp b/ source/common.cpp
@@ -9,12 +9,14 @@
namespace startgit
{
void write_header(std::ostream& ost,
const std::string& title_txt,
const std::string& description,
const std::string& author,
const std::string& relpath,
bool has_feed)
void write_header(
std::ostream& ost,
const std::string& title_txt,
const std::string& description,
const std::string& author,
const std::string& relpath,
bool has_feed
)
{
using namespace hemplate::html; // NOLINT
using hemplate::html::div;
@@ -97,22 +99,28 @@
void write_header(std::ostream& ost,
});
}
void write_header(std::ostream& ost,
const repository& repo,
const branch& branch,
const std::string& description,
const std::string& relpath,
bool has_feed)
void write_header(
std::ostream& ost,
const repository& repo,
const branch& branch,
const std::string& description,
const std::string& relpath,
bool has_feed
)
{
write_header(ost,
std::format("{} ({}) - {}",
repo.get_name(),
branch.get_name(),
repo.get_description()),
description,
repo.get_owner(),
relpath,
has_feed);
write_header(
ost,
std::format(
"{} ({}) - {}",
repo.get_name(),
branch.get_name(),
repo.get_description()
),
description,
repo.get_owner(),
relpath,
has_feed
);
}
void write_footer(std::ostream& ost)
@@ -132,7 +140,8 @@
void write_footer(std::ostream& ost)
" arr[4] = value;"
" history.replaceState(history.state, '', arr.join('/'));"
" location.reload();"
"}");
"}"
);
ost << style(
" table { "
" margin-left: 0;"
@@ -149,7 +158,8 @@
void write_footer(std::ostream& ost)
" color: var(--theme_green);"
"} .del {"
" color: var(--theme_red);"
"}");
"}"
);
ost << body();
ost << html();
}
diff --git a/ source/common.hpp b/ source/common.hpp
@@ -9,19 +9,23 @@
namespace startgit
{
void write_header(std::ostream& ost,
const std::string& title_txt,
const std::string& description,
const std::string& author,
const std::string& relpath = "./",
bool has_feed = true);
void write_header(
std::ostream& ost,
const std::string& title_txt,
const std::string& description,
const std::string& author,
const std::string& relpath = "./",
bool has_feed = true
);
void write_header(std::ostream& ost,
const repository& repo,
const branch& branch,
const std::string& description,
const std::string& relpath = "./",
bool has_feed = true);
void write_header(
std::ostream& ost,
const repository& repo,
const branch& branch,
const std::string& description,
const std::string& relpath = "./",
bool has_feed = true
);
void write_footer(std::ostream& ost);
diff --git a/ source/diff.cpp b/ source/diff.cpp
@@ -13,8 +13,11 @@
diff::diff(const git2wrap::commit& cmmt)
git2wrap::diff_options opts;
git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION);
// NOLINTBEGIN hicpp-signed-bitwise
opts.flags = GIT_DIFF_DISABLE_PATHSPEC_MATCH | GIT_DIFF_IGNORE_SUBMODULES
| GIT_DIFF_INCLUDE_TYPECHANGE;
// NOLINTEND hicpp-signed-bitwise
m_diff = git2wrap::diff::tree_to_tree(ptree, cmmt.get_tree(), &opts);
m_stats = m_diff.get_stats();
@@ -27,7 +30,8 @@
const std::vector<delta>& diff::get_deltas() const
}
m_diff.foreach(
file_cb, nullptr, hunk_cb, line_cb, const_cast<diff*>(this)); // NOLINT
file_cb, nullptr, hunk_cb, line_cb, const_cast<diff*>(this) // NOLINT
);
for (auto& delta : m_deltas) {
for (const auto& hunk : delta.get_hunks()) {
@@ -37,32 +41,33 @@
const std::vector<delta>& diff::get_deltas() const
}
}
}
return m_deltas;
}
int diff::file_cb(const git_diff_delta* delta,
float /* progress */,
void* payload)
int diff::file_cb(
const git_diff_delta* delta, float /* progress */, void* payload
)
{
diff& crnt = *reinterpret_cast<diff*>(payload); // NOLINT
crnt.m_deltas.emplace_back(delta);
return 0;
}
int diff::hunk_cb(const git_diff_delta* /* delta */,
const git_diff_hunk* hunk,
void* payload)
int diff::hunk_cb(
const git_diff_delta* /* delta */, const git_diff_hunk* hunk, void* payload
)
{
diff& crnt = *reinterpret_cast<diff*>(payload); // NOLINT
crnt.m_deltas.back().m_hunks.emplace_back(hunk);
return 0;
}
int diff::line_cb(const git_diff_delta* /* delta */,
const git_diff_hunk* /* hunk */,
const git_diff_line* line,
void* payload)
int diff::line_cb(
const git_diff_delta* /* delta */,
const git_diff_hunk* /* hunk */,
const git_diff_line* line,
void* payload
)
{
diff& crnt = *reinterpret_cast<diff*>(payload); // NOLINT
crnt.m_deltas.back().m_hunks.back().m_lines.emplace_back(line);
diff --git a/ source/diff.hpp b/ source/diff.hpp
@@ -73,18 +73,20 @@
public:
const std::vector<delta>& get_deltas() const;
private:
static int file_cb(const git_diff_delta* delta,
float progress,
void* payload);
static int hunk_cb(const git_diff_delta* delta,
const git_diff_hunk* hunk,
void* payload);
static int line_cb(const git_diff_delta* delta,
const git_diff_hunk* hunk,
const git_diff_line* line,
void* payload);
static int file_cb(
const git_diff_delta* delta, float progress, void* payload
);
static int hunk_cb(
const git_diff_delta* delta, const git_diff_hunk* hunk, void* payload
);
static int line_cb(
const git_diff_delta* delta,
const git_diff_hunk* hunk,
const git_diff_line* line,
void* payload
);
git2wrap::diff m_diff;
git2wrap::diff_stats m_stats;
diff --git a/ source/file.cpp b/ source/file.cpp
@@ -13,8 +13,8 @@
namespace startgit
file::file(const git2wrap::tree_entry& entry, std::filesystem::path path)
: m_filemode(filemode(entry.get_filemode()))
, m_path(std::move(path))
, m_blob(
git2wrap::repository(entry.get_owner()).blob_lookup(entry.get_id()))
, m_blob(git2wrap::repository(entry.get_owner()).blob_lookup(entry.get_id())
)
{
}
diff --git a/ source/html.cpp b/ source/html.cpp
@@ -53,8 +53,9 @@
public:
void render_open_ol_block(const MD_BLOCK_OL_DETAIL* det);
void render_open_li_block(const MD_BLOCK_LI_DETAIL* det);
void render_open_code_block(const MD_BLOCK_CODE_DETAIL* det);
void render_open_td_block(const MD_CHAR* cell_type,
const MD_BLOCK_TD_DETAIL* det);
void render_open_td_block(
const MD_CHAR* cell_type, const MD_BLOCK_TD_DETAIL* det
);
void render_open_a_span(const MD_SPAN_A_DETAIL* det);
void render_open_img_span(const MD_SPAN_IMG_DETAIL* det);
void render_close_img_span(const MD_SPAN_IMG_DETAIL* det);
@@ -175,9 +176,11 @@
std::string translate_url(const MD_CHAR* data, MD_SIZE size)
auto itr = startgit::args.special.find(url.substr(rslash + 1));
if (itr != startgit::args.special.end()) {
auto cpy = *itr;
url = std::format("{}/{}.html",
url.substr(0, rslash),
cpy.replace_extension().string());
url = std::format(
"{}/{}.html",
url.substr(0, rslash),
cpy.replace_extension().string()
);
} else {
const std::size_t slash = url.find('/', bpos + 1);
url.replace(slash, 1, "/file/");
@@ -206,9 +209,9 @@
void md_html::render_url_escaped(const MD_CHAR* data, MD_SIZE size)
MD_OFFSET beg = 0;
MD_OFFSET off = 0;
const auto url = translate_url(data, size);
size = static_cast<unsigned>(url.size());
data = url.data();
const auto urll = translate_url(data, size);
size = static_cast<unsigned>(urll.size());
data = urll.data();
while (true) {
while (off < size && !md_html::need_url_esc(data[off])) { // NOLINT
@@ -228,10 +231,12 @@
void md_html::render_url_escaped(const MD_CHAR* data, MD_SIZE size)
break;
default:
hex[0] = '%';
hex[1] = hex_chars[(static_cast<unsigned>(data[off]) >> 4) // NOLINT
& 0xf]; // NOLINT
hex[2] = hex_chars[(static_cast<unsigned>(data[off]) >> 0) // NOLINT
& 0xf]; // NOLINT
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;
}
@@ -261,7 +266,8 @@
unsigned hex_val(char chr)
void md_html::render_utf8_codepoint(unsigned codepoint, append_fn fn_append)
{
static const MD_CHAR utf8_replacement_char[] = {
char(0xef), char(0xbf), char(0xbd)};
char(0xef), char(0xbf), char(0xbd)
};
unsigned char utf8[4];
size_t n;
@@ -287,10 +293,12 @@
void md_html::render_utf8_codepoint(unsigned codepoint, append_fn fn_append)
}
if (0 < codepoint && codepoint <= 0x10ffff) {
std::invoke(fn_append,
this,
reinterpret_cast<char*>(utf8),
static_cast<MD_SIZE>(n)); // NOLINT
std::invoke(
fn_append,
this,
reinterpret_cast<char*>(utf8),
static_cast<MD_SIZE>(n)
); // NOLINT
} else {
std::invoke(fn_append, this, utf8_replacement_char, 3);
}
@@ -299,9 +307,9 @@
void md_html::render_utf8_codepoint(unsigned codepoint, append_fn fn_append)
/* Translate entity to its UTF-8 equivalent, or output the verbatim one
* if such entity is unknown (or if the translation is disabled). */
void md_html::render_entity(const MD_CHAR* text,
MD_SIZE size,
append_fn fn_append)
void md_html::render_entity(
const MD_CHAR* text, MD_SIZE size, append_fn fn_append
)
{
/* We assume UTF-8 output is what is desired. */
if (size > 3 && text[1] == '#') { // NOLINT
@@ -366,7 +374,8 @@
void md_html::render_open_li_block(const MD_BLOCK_LI_DETAIL* det)
render_verbatim(
"<li class=\"task-list-item\">"
"<input type=\"checkbox\" "
"class=\"task-list-item-checkbox\" disabled");
"class=\"task-list-item-checkbox\" disabled"
);
if (det->task_mark == 'x' || det->task_mark == 'X') {
render_verbatim(" checked");
}
@@ -390,8 +399,9 @@
void md_html::render_open_code_block(const MD_BLOCK_CODE_DETAIL* det)
render_verbatim(">");
}
void md_html::render_open_td_block(const MD_CHAR* cell_type,
const MD_BLOCK_TD_DETAIL* det)
void md_html::render_open_td_block(
const MD_CHAR* cell_type, const MD_BLOCK_TD_DETAIL* det
)
{
render_verbatim("<");
render_verbatim(cell_type);
@@ -463,7 +473,8 @@
int enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
"<h3>",
"<h4>",
"<h5>",
"<h6>"};
"<h6>"
};
auto* data = static_cast<class md_html*>(userdata);
switch (type) {
@@ -476,23 +487,25 @@
int enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
data->render_verbatim("<ul>\n");
break;
case MD_BLOCK_OL:
data->render_open_ol_block(
static_cast<const MD_BLOCK_OL_DETAIL*>(detail));
data->render_open_ol_block(static_cast<const MD_BLOCK_OL_DETAIL*>(detail)
);
break;
case MD_BLOCK_LI:
data->render_open_li_block(
static_cast<const MD_BLOCK_LI_DETAIL*>(detail));
data->render_open_li_block(static_cast<const MD_BLOCK_LI_DETAIL*>(detail)
);
break;
case MD_BLOCK_HR:
data->render_verbatim("<hr>\n");
break;
case MD_BLOCK_H:
data->render_verbatim(
head[static_cast<MD_BLOCK_H_DETAIL*>(detail)->level - 1]); // NOLINT
head[static_cast<MD_BLOCK_H_DETAIL*>(detail)->level - 1] // NOLINT
);
break;
case MD_BLOCK_CODE:
data->render_open_code_block(
static_cast<const MD_BLOCK_CODE_DETAIL*>(detail));
static_cast<const MD_BLOCK_CODE_DETAIL*>(detail)
);
break;
case MD_BLOCK_HTML: /* noop */
break;
@@ -512,12 +525,14 @@
int enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
data->render_verbatim("<tr>\n");
break;
case MD_BLOCK_TH:
data->render_open_td_block("th",
static_cast<MD_BLOCK_TD_DETAIL*>(detail));
data->render_open_td_block(
"th", static_cast<MD_BLOCK_TD_DETAIL*>(detail)
);
break;
case MD_BLOCK_TD:
data->render_open_td_block("td",
static_cast<MD_BLOCK_TD_DETAIL*>(detail));
data->render_open_td_block(
"td", static_cast<MD_BLOCK_TD_DETAIL*>(detail)
);
break;
}
@@ -532,7 +547,8 @@
int leave_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
"</h3>\n",
"</h4>\n",
"</h5>\n",
"</h6>\n"};
"</h6>\n"
};
auto* data = static_cast<class md_html*>(userdata);
switch (type) {
@@ -554,7 +570,8 @@
int leave_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
break;
case MD_BLOCK_H:
data->render_verbatim(
head[static_cast<MD_BLOCK_H_DETAIL*>(detail)->level - 1]); // NOLINT
head[static_cast<MD_BLOCK_H_DETAIL*>(detail)->level - 1] // NOLINT
);
break;
case MD_BLOCK_CODE:
data->render_verbatim("</code></pre>\n");
@@ -630,7 +647,8 @@
int enter_span_callback(MD_SPANTYPE type, void* detail, void* userdata)
break;
case MD_SPAN_WIKILINK:
data->render_open_wikilink_span(
static_cast<MD_SPAN_WIKILINK_DETAIL*>(detail));
static_cast<MD_SPAN_WIKILINK_DETAIL*>(detail)
);
break;
}
@@ -683,10 +701,9 @@
int leave_span_callback(MD_SPANTYPE type, void* detail, void* userdata)
return 0;
}
int text_callback(MD_TEXTTYPE type,
const MD_CHAR* text,
MD_SIZE size,
void* userdata)
int text_callback(
MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdata
)
{
auto* data = static_cast<class md_html*>(userdata);
@@ -695,8 +712,8 @@
int text_callback(MD_TEXTTYPE type,
data->render_utf8_codepoint(0x0000, &md_html::render_verbatim);
break;
case MD_TEXT_BR:
data->render_verbatim(
(data->image_nesting_level == 0 ? ("<br>\n") : " "));
data->render_verbatim((data->image_nesting_level == 0 ? ("<br>\n") : " ")
);
break;
case MD_TEXT_SOFTBR:
data->render_verbatim((data->image_nesting_level == 0 ? "\n" : " "));
@@ -718,24 +735,33 @@
int text_callback(MD_TEXTTYPE type,
namespace startgit
{
int md_html(const MD_CHAR* input,
MD_SIZE input_size,
void (*process_output)(const MD_CHAR*, MD_SIZE, void*),
void* userdata,
unsigned parser_flags,
unsigned renderer_flags)
int md_html(
const MD_CHAR* input,
MD_SIZE input_size,
void (*process_output)(const MD_CHAR*, MD_SIZE, void*),
void* userdata,
unsigned parser_flags,
unsigned renderer_flags
)
{
class md_html render = {process_output, userdata, renderer_flags, 0};
const MD_PARSER parser = {0,
parser_flags,
enter_block_callback,
leave_block_callback,
enter_span_callback,
leave_span_callback,
text_callback,
nullptr,
nullptr};
class md_html render = {
.process_output = process_output,
.userdata = userdata,
.flags = renderer_flags,
.image_nesting_level = 0
};
const MD_PARSER parser = {
0,
parser_flags,
enter_block_callback,
leave_block_callback,
enter_span_callback,
leave_span_callback,
text_callback,
nullptr,
nullptr
};
return md_parse(input, input_size, &parser, &render);
}
diff --git a/ source/html.hpp b/ source/html.hpp
@@ -5,11 +5,13 @@
namespace startgit
{
int md_html(const MD_CHAR* input,
MD_SIZE input_size,
void (*process_output)(const MD_CHAR*, MD_SIZE, void*),
void* userdata,
unsigned parser_flags,
unsigned renderer_flags);
int md_html(
const MD_CHAR* input,
MD_SIZE input_size,
void (*process_output)(const MD_CHAR*, MD_SIZE, void*),
void* userdata,
unsigned parser_flags,
unsigned renderer_flags
);
} // namespace startgit
diff --git a/ source/repository.cpp b/ source/repository.cpp
@@ -8,7 +8,8 @@
namespace startgit
repository::repository(const std::filesystem::path& path)
: m_path(path)
, m_repo(git2wrap::repository::open(
path.c_str(), GIT_REPOSITORY_OPEN_NO_SEARCH, nullptr))
path.c_str(), GIT_REPOSITORY_OPEN_NO_SEARCH, nullptr
))
, m_name(path.stem().string())
, m_url(read_file(path, "url"))
, m_owner(read_file(path, "owner"))
@@ -33,8 +34,9 @@
repository::repository(const std::filesystem::path& path)
m_repo.tag_foreach(callback, this);
}
std::string repository::read_file(const std::filesystem::path& base,
const char* file)
std::string repository::read_file(
const std::filesystem::path& base, const char* file
)
{
std::ifstream ifs(base / file);
diff --git a/ source/repository.hpp b/ source/repository.hpp
@@ -34,8 +34,9 @@
public:
const auto& get_tags() const { return m_tags; }
private:
static std::string read_file(const std::filesystem::path& base,
const char* file);
static std::string read_file(
const std::filesystem::path& base, const char* file
);
std::filesystem::path m_path;
git2wrap::repository m_repo;
diff --git a/ source/startgit-index.cpp b/ source/startgit-index.cpp
@@ -109,12 +109,14 @@
int main(int argc, char* argv[])
output_dir = std::filesystem::canonical(output_dir);
std::ofstream ofs(args.output_dir / "index.html");
write_header(ofs,
args.title,
args.description,
args.author,
"./",
/*has_feed=*/false);
write_header(
ofs,
args.title,
args.description,
args.author,
"./",
/*has_feed=*/false
);
ofs << h1(args.title);
ofs << p(args.description);
@@ -129,39 +131,41 @@
int main(int argc, char* argv[])
},
},
tbody {
transform(args.repos,
[](const auto& repo_path) -> element
{
using git2wrap::error_code_t::ENOTFOUND;
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<ENOTFOUND>& err) {
std::cerr << std::format(
"Warning: {} is not a repository\n",
repo_path.string());
}
return text();
}),
transform(
args.repos,
[](const auto& repo_path) -> element
{
using git2wrap::error_code_t::ENOTFOUND;
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<ENOTFOUND>& err) {
std::cerr << std::format(
"Warning: {} is not a repository\n", repo_path.string()
);
}
return text();
}
),
},
};
diff --git a/ source/startgit.cpp b/ source/startgit.cpp
@@ -23,18 +23,21 @@
namespace
{
template<std::ranges::forward_range R>
void wtable(std::ostream& ost,
std::initializer_list<std::string_view> head,
const R& range,
based::Procedure<std::ranges::range_value_t<R>> auto proc)
void wtable(
std::ostream& ost,
std::initializer_list<std::string_view> head,
const R& range,
based::Procedure<std::ranges::range_value_t<R>> auto proc
)
{
using namespace hemplate::html; // NOLINT
ost << table {
thead {
tr {
transform(head,
[](const auto& elem) { return td {text {elem}}; }),
transform(
head, [](const auto& elem) { return td {text {elem}}; }
),
},
},
tbody {
@@ -48,10 +51,12 @@
void wtable(std::ostream& ost,
namespace startgit
{
void write_title(std::ostream& ost,
const repository& repo,
const branch& branch,
const std::string& relpath = "./")
void write_title(
std::ostream& ost,
const repository& repo,
const branch& branch,
const std::string& relpath = "./"
)
{
using namespace hemplate::html; // NOLINT
@@ -77,18 +82,20 @@
void write_title(std::ostream& ost,
a {{{"href", relpath + "refs.html"}}, "Refs"},
},
},
transform(branch.get_special(),
[&](const auto& file)
{
auto path = file.get_path();
const auto filename = path.replace_extension("html").string();
const auto name = path.replace_extension().string();
return transparent {
text {" | "},
a {{{"href", relpath + filename}}, name},
};
}),
transform(
branch.get_special(),
[&](const auto& file)
{
auto path = file.get_path();
const auto filename = path.replace_extension("html").string();
const auto name = path.replace_extension().string();
return transparent {
text {" | "},
a {{{"href", relpath + filename}}, name},
};
}
),
} << hr {};
}
@@ -96,73 +103,79 @@
void write_commit_table(std::ostream& ost, const branch& branch)
{
using namespace hemplate::html; // NOLINT
wtable(ost,
{"Date", "Commit message", "Author", "Files", "+", "-"},
branch.get_commits(),
[&](const auto& commit)
{
const auto idd = commit.get_id();
const auto url = std::format("./commit/{}.html", idd);
return tr {
td {commit.get_time()},
td {a {{{"href", url}}, commit.get_summary()}},
td {commit.get_author_name()},
td {commit.get_diff().get_files_changed()},
td {commit.get_diff().get_insertions()},
td {commit.get_diff().get_deletions()},
};
});
wtable(
ost,
{"Date", "Commit message", "Author", "Files", "+", "-"},
branch.get_commits(),
[&](const auto& commit)
{
const auto idd = commit.get_id();
const auto url = std::format("./commit/{}.html", idd);
return tr {
td {commit.get_time()},
td {a {{{"href", url}}, commit.get_summary()}},
td {commit.get_author_name()},
td {commit.get_diff().get_files_changed()},
td {commit.get_diff().get_insertions()},
td {commit.get_diff().get_deletions()},
};
}
);
}
void write_files_table(std::ostream& ost, const branch& branch)
{
using namespace hemplate::html; // NOLINT
wtable(ost,
{"Mode", "Name", "Size"},
branch.get_files(),
[&](const auto& file)
{
const auto path = file.get_path().string();
const auto url = std::format("./file/{}.html", path);
const auto size = file.is_binary()
? std::format("{}B", file.get_size())
: std::format("{}L", file.get_lines());
return tr {
td {file.get_filemode()},
td {a {{{"href", url}}, path}},
td {size},
};
});
wtable(
ost,
{"Mode", "Name", "Size"},
branch.get_files(),
[&](const auto& file)
{
const auto path = file.get_path().string();
const auto url = std::format("./file/{}.html", path);
const auto size = file.is_binary()
? std::format("{}B", file.get_size())
: std::format("{}L", file.get_lines());
return tr {
td {file.get_filemode()},
td {a {{{"href", url}}, path}},
td {size},
};
}
);
}
void write_branch_table(std::ostream& ost,
const repository& repo,
const std::string& branch_name)
void write_branch_table(
std::ostream& ost, const repository& repo, const std::string& branch_name
)
{
using namespace hemplate::html; // NOLINT
ost << h2("Branches");
wtable(ost,
{" ", "Name", "Last commit date", "Author"},
repo.get_branches(),
[&](const auto& branch)
{
const auto& last = branch.get_last_commit();
const auto url = branch.get_name() != branch_name
? std::format("../{}/refs.html", branch.get_name())
: "";
const auto name = branch.get_name() == branch_name ? "*" : " ";
return tr {
td {name},
td {a {{{"href", url}}, branch.get_name()}},
td {last.get_time()},
td {last.get_author_name()},
};
});
wtable(
ost,
{" ", "Name", "Last commit date", "Author"},
repo.get_branches(),
[&](const auto& branch)
{
const auto& last = branch.get_last_commit();
const auto url = branch.get_name() != branch_name
? std::format("../{}/refs.html", branch.get_name())
: "";
const auto name = branch.get_name() == branch_name ? "*" : " ";
return tr {
td {name},
td {a {{{"href", url}}, branch.get_name()}},
td {last.get_time()},
td {last.get_author_name()},
};
}
);
}
void write_tag_table(std::ostream& ost, const repository& repo)
@@ -170,18 +183,20 @@
void write_tag_table(std::ostream& ost, const repository& repo)
using namespace hemplate::html; // NOLINT
ost << h2("Tags");
wtable(ost,
{" ", "Name", "Last commit date", "Author"},
repo.get_tags(),
[&](const auto& tag)
{
return tr {
td {" "},
td {tag.get_name()},
td {tag.get_time()},
td {tag.get_author()},
};
});
wtable(
ost,
{" ", "Name", "Last commit date", "Author"},
repo.get_tags(),
[&](const auto& tag)
{
return tr {
td {" "},
td {tag.get_name()},
td {tag.get_time()},
td {tag.get_author()},
};
}
);
}
void write_file_changes(std::ostream& ost, const diff& diff)
@@ -189,47 +204,51 @@
void write_file_changes(std::ostream& ost, const diff& diff)
using namespace hemplate::html; // NOLINT
ost << b("Diffstat:");
wtable(ost,
{},
diff.get_deltas(),
[&](const auto& delta)
{
static const char* marker = " ADMRC T ";
const std::string link = std::format("#{}", delta->new_file.path);
uint32_t add = delta.get_adds();
uint32_t del = delta.get_dels();
const uint32_t changed = add + del;
const uint32_t total = 80;
if (changed > total) {
const double percent = 1.0 * total / changed;
if (add > 0) {
add = static_cast<uint32_t>(std::lround(percent * add) + 1);
}
if (del > 0) {
del = static_cast<uint32_t>(std::lround(percent * del) + 1);
}
}
return tr {
td {std::string(1, marker[delta->status])}, // NOLINT
td {a {{{"href", link}}, delta->new_file.path}},
td {"|"},
td {
span {{{"class", "add"}}, std::string(add, '+')},
span {{{"class", "del"}}, std::string(del, '-')},
},
};
});
wtable(
ost,
{},
diff.get_deltas(),
[&](const auto& delta)
{
static const char* marker = " ADMRC T ";
const std::string link = std::format("#{}", delta->new_file.path);
uint32_t add = delta.get_adds();
uint32_t del = delta.get_dels();
const uint32_t changed = add + del;
const uint32_t total = 80;
if (changed > total) {
const double percent = 1.0 * total / changed;
if (add > 0) {
add = static_cast<uint32_t>(std::lround(percent * add) + 1);
}
if (del > 0) {
del = static_cast<uint32_t>(std::lround(percent * del) + 1);
}
}
return tr {
td {std::string(1, marker[delta->status])}, // NOLINT
td {a {{{"href", link}}, delta->new_file.path}},
td {"|"},
td {
span {{{"class", "add"}}, std::string(add, '+')},
span {{{"class", "del"}}, std::string(del, '-')},
},
};
}
);
ost << p {
std::format("{} files changed, {} insertions(+), {} deletions(-)",
diff.get_files_changed(),
diff.get_insertions(),
diff.get_deletions()),
std::format(
"{} files changed, {} insertions(+), {} deletions(-)",
diff.get_files_changed(),
diff.get_insertions(),
diff.get_deletions()
),
};
}
@@ -262,46 +281,52 @@
void write_file_diffs(std::ostream& ost, const diff& diff)
return transparent {
h4 {
text {
std::format("@@ -{},{} +{},{} @@ ",
hunk->old_start,
hunk->old_lines,
hunk->new_start,
hunk->new_lines),
std::format(
"@@ -{},{} +{},{} @@ ",
hunk->old_start,
hunk->old_lines,
hunk->new_start,
hunk->new_lines
),
},
text {
xmlencode(header.substr(header.rfind('@') + 2)),
},
span {
{{"style", "white-space: pre"}},
transform(hunk.get_lines(),
[](const auto& line) -> element
{
using hemplate::html::div;
if (line.is_add()) {
return div {
{{"class", "add"}},
xmlencode(line.get_content()),
};
}
if (line.is_del()) {
return div {
{{"class", "del"}},
xmlencode(line.get_content()),
};
}
return text {
xmlencode(line.get_content()),
};
}),
transform(
hunk.get_lines(),
[](const auto& line) -> element
{
using hemplate::html::div;
if (line.is_add()) {
return div {
{{"class", "add"}},
xmlencode(line.get_content()),
};
}
if (line.is_del()) {
return div {
{{"class", "del"}},
xmlencode(line.get_content()),
};
}
return text {
xmlencode(line.get_content()),
};
}
),
},
},
};
}),
}
),
};
});
}
);
}
void write_commit_diff(std::ostream& ost, const commit& commit)
@@ -380,16 +405,20 @@
void write_file_content(std::ostream& ost, const file& file)
int count = 0;
ost << span {
{{"style", "white-space: pre;"}},
transform(lines,
[&](const auto& line)
{
return text {
std::format(R"(<a id="{0}" href="#{0}">{0:5}</a> {1}\n)",
count++,
xmlencode(line)),
};
}),
transform(
lines,
[&](const auto& line)
{
return text {
std::format(
R"(<a id="{0}" href="#{0}">{0:5}</a> {1}\n)",
count++,
xmlencode(line)
),
};
}
),
};
}
@@ -402,17 +431,21 @@
void write_html(std::ostream& ost, const file& file)
ofs << std::string(str, size);
};
md_html(file.get_content(),
static_cast<MD_SIZE>(file.get_size()),
process_output,
&ost,
MD_DIALECT_GITHUB,
0);
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,
const branch& branch)
void write_log(
const std::filesystem::path& base,
const repository& repo,
const branch& branch
)
{
std::ofstream ofs(base / "log.html");
@@ -422,9 +455,11 @@
void write_log(const std::filesystem::path& base,
write_footer(ofs);
}
void write_file(const std::filesystem::path& base,
const repository& repo,
const branch& branch)
void write_file(
const std::filesystem::path& base,
const repository& repo,
const branch& branch
)
{
std::ofstream ofs(base / "files.html");
@@ -434,9 +469,11 @@
void write_file(const std::filesystem::path& base,
write_footer(ofs);
}
void write_refs(const std::filesystem::path& base,
const repository& repo,
const branch& branch)
void write_refs(
const std::filesystem::path& base,
const repository& repo,
const branch& branch
)
{
std::ofstream ofs(base / "refs.html");
@@ -447,9 +484,11 @@
void write_refs(const std::filesystem::path& base,
write_footer(ofs);
}
bool write_commits(const std::filesystem::path& base,
const repository& repo,
const branch& branch)
bool write_commits(
const std::filesystem::path& base,
const repository& repo,
const branch& branch
)
{
bool changed = false;
@@ -470,9 +509,11 @@
bool write_commits(const std::filesystem::path& base,
return changed;
}
void write_files(const std::filesystem::path& base,
const repository& repo,
const branch& branch)
void write_files(
const std::filesystem::path& base,
const repository& repo,
const branch& branch
)
{
for (const auto& file : branch.get_files()) {
const std::filesystem::path path =
@@ -495,9 +536,11 @@
void write_files(const std::filesystem::path& base,
}
}
void write_readme_licence(const std::filesystem::path& base,
const repository& repo,
const branch& branch)
void write_readme_licence(
const std::filesystem::path& base,
const repository& repo,
const branch& branch
)
{
for (const auto& file : branch.get_special()) {
std::ofstream ofs(base / file.get_path().replace_extension("html"));
@@ -508,9 +551,9 @@
void write_readme_licence(const std::filesystem::path& base,
}
}
void write_atom(std::ostream& ost,
const branch& branch,
const std::string& base_url)
void write_atom(
std::ostream& ost, const branch& branch, const std::string& base_url
)
{
using namespace hemplate::atom; // NOLINT
using hemplate::atom::link;
@@ -534,30 +577,32 @@
void write_atom(std::ostream& ost,
author {name {args.author}},
link {self, " "},
link {alter, " "},
transform(branch.get_commits(),
[&](const auto& commit)
{
const auto url = std::format(
"{}/commit/{}.html", base_url, commit.get_id());
return entry {
id {url},
updated {format_time(commit.get_time_raw())},
title {commit.get_summary()},
link {{{"href", url}}, " "},
author {
name {commit.get_author_name()},
email {commit.get_author_email()},
},
content {commit.get_message()},
};
}),
transform(
branch.get_commits(),
[&](const auto& commit)
{
const auto url =
std::format("{}/commit/{}.html", base_url, commit.get_id());
return entry {
id {url},
updated {format_time(commit.get_time_raw())},
title {commit.get_summary()},
link {{{"href", url}}, " "},
author {
name {commit.get_author_name()},
email {commit.get_author_email()},
},
content {commit.get_message()},
};
}
),
};
}
void write_rss(std::ostream& ost,
const branch& branch,
const std::string& base_url)
void write_rss(
std::ostream& ost, const branch& branch, const std::string& base_url
)
{
using namespace hemplate::rss; // NOLINT
using hemplate::rss::link;
@@ -572,22 +617,26 @@
void write_rss(std::ostream& ost,
generator {"startgit"},
language {"en-us"},
atomLink {{{"href", base_url + "/atom.xml"}}},
transform(branch.get_commits(),
[&](const auto& commit)
{
const auto url = std::format(
"{}/commit/{}.html", base_url, commit.get_id());
return item {
title {commit.get_summary()},
link {url},
guid {url},
pubDate {format_time(commit.get_time_raw())},
author {std::format("{} ({})",
commit.get_author_email(),
commit.get_author_name())},
};
}),
transform(
branch.get_commits(),
[&](const auto& commit)
{
const auto url =
std::format("{}/commit/{}.html", base_url, commit.get_id());
return item {
title {commit.get_summary()},
link {url},
guid {url},
pubDate {format_time(commit.get_time_raw())},
author {std::format(
"{} ({})",
commit.get_author_email(),
commit.get_author_name()
)},
};
}
),
},
};
}
@@ -748,8 +797,9 @@
int main(int argc, char* argv[])
write_rss(rss, branch, absolute);
}
} catch (const git2wrap::error<git2wrap::error_code_t::ENOTFOUND>& err) {
std::cerr << std::format("Warning: {} is not a repository\n",
args.repos[0].string());
std::cerr << std::format(
"Warning: {} is not a repository\n", args.repos[0].string()
);
} catch (const git2wrap::runtime_error& err) {
std::cerr << std::format("Error (git2wrap): {}\n", err.what());
} catch (const std::runtime_error& err) {
diff --git a/ source/utils.cpp b/ source/utils.cpp
@@ -12,7 +12,8 @@
auto sec_since_epoch(std::int64_t sec)
{
return std::chrono::time_point_cast<std::chrono::seconds>(
std::chrono::system_clock::from_time_t(time_t {0})
+ std::chrono::seconds(sec));
+ std::chrono::seconds(sec)
);
}
std::string time_short(std::int64_t date)
@@ -22,11 +23,13 @@
std::string time_short(std::int64_t date)
std::string time_long(const git2wrap::time& time)
{
return std::format("{:%a, %e %b %Y %H:%M:%S} {}{:02}{:02}",
sec_since_epoch(time.time),
time.offset < 0 ? '-' : '+',
time.offset / 60, // NOLINT
time.offset % 60); // NOLINT
return std::format(
"{:%a, %e %b %Y %H:%M:%S} {}{:02}{:02}",
sec_since_epoch(time.time),
time.offset < 0 ? '-' : '+',
time.offset / 60, // NOLINT
time.offset % 60 // NOLINT
);
}
// NOLINTBEGIN
// clang-format off