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 8f1333f0167b066b50aad6151e81e05fbde8088d
parent c3279e6f297d1987f0a8224df3e9b0337bd0a0f9
author Dimitrije Dobrota <mail@dimitrijedobrota.com>
date Sun, 19 Jan 2025 20:31:21 +0100

Write Atom and RSS feeds for each branch

Diffstat:
M CMakeLists.txt | ++ --
M source/commit.cpp | ++++++ -
M source/commit.hpp | +
M source/main.cpp | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --------
M source/utils.cpp | +++++++++++++++ ----------

5 files changed, 132 insertions(+), 24 deletions(-)


diff --git a/ CMakeLists.txt b/ CMakeLists.txt

@@ -4,7 +4,7 @@ include(cmake/prelude.cmake) project( startgit
VERSION 0.1.23
VERSION 0.1.24
DESCRIPTION "Static page generator for git repositories" HOMEPAGE_URL "https://git.dimitrijedobrota.com/stargit.git" LANGUAGES CXX

@@ -16,7 +16,7 @@ include(cmake/variables.cmake) # ---- Declare dependencies ---- find_package(git2wrap CONFIG REQUIRED)
find_package(hemplate 0.2 CONFIG REQUIRED)
find_package(hemplate 0.2.2 CONFIG REQUIRED)
find_package(md4c CONFIG REQUIRED) find_package(poafloc 1 CONFIG REQUIRED)

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

@@ -54,6 +54,11 @@ std::string commit::get_time_long() const return time_long(m_commit.get_author().get_time()); }
int64_t commit::get_time_raw() const
{
return m_commit.get_author().get_time().time;
}
std::string commit::get_author_name() const { return m_commit.get_author().get_name();

@@ -61,7 +66,7 @@ std::string commit::get_author_name() const std::string commit::get_author_email() const {
return m_commit.get_author().get_name();
return m_commit.get_author().get_email();
} git2wrap::tree commit::get_tree() const

diff --git a/ source/commit.hpp b/ source/commit.hpp

@@ -22,6 +22,7 @@ public: std::string get_summary() const; std::string get_time() const; std::string get_time_long() const;
int64_t get_time_raw() const;
std::string get_author_name() const; std::string get_author_email() const; git2wrap::tree get_tree() const;

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

@@ -18,7 +18,8 @@ struct arguments_t { std::filesystem::path output_dir = "."; std::vector<std::filesystem::path> repos;
std::string url = "https://dimitrijedobrota.com";
std::string resource_url = "https://dimitrijedobrota.com";
std::string base_url = "https://git.dimitrijedobrota.com";
std::string author = "Dimitrije Dobrota"; std::string title = "Collection of git repositories"; std::string description = "Publicly available personal projects";

@@ -47,16 +48,18 @@ void write_header(std::ostream& ost, {"name", "viewport"}})) // Stylesheets .add(html::link({{"rel", "stylesheet"}, {"type", "text/css"}})
.set("href", args.url + "/css/index.css"))
.set("href", args.resource_url + "/css/index.css"))
.add(html::link({{"rel", "stylesheet"}, {"type", "text/css"}})
.set("href", args.url + "/css/colors.css"))
.set("href", args.resource_url + "/css/colors.css"))
// Icons
.add(html::link({{"rel", "icon"}, {"type", "image/png"}})
.set("sizes", "32x32")
.set("href", args.url + "/img/favicon-32x32.png"))
.add(
html::link({{"rel", "icon"}, {"type", "image/png"}})
.set("sizes", "32x32")
.set("href", args.resource_url + "/img/favicon-32x32.png"))
.add(html::link({{"rel", "icon"}, {"type", "image/png"}}) .set("sizes", "16x16")
.set("href", args.url + "/img/favicon-16x16.png"));
.set("href",
args.resource_url + "/img/favicon-16x16.png"));
ost << html::body(); ost << html::input() .set("type", "checkbox")

@@ -478,7 +481,7 @@ void write_footer(std::ostream& ost) html::div().tgl_state(); ost << html::div();
ost << html::script(" ").set("src", args.url + "/scripts/main.js");
ost << html::script(" ").set("src", args.resource_url + "/scripts/main.js");
ost << html::script( "function switchPage(value) {" " let arr = window.location.href.split('/');"

@@ -604,6 +607,78 @@ void write_readme_licence(const std::filesystem::path& base, } }
void write_atom(std::ostream& ost,
const startgit::branch& branch,
const std::string& base_url)
{
using namespace hemplate; // NOLINT
ost << atom::feed();
ost << atom::title(args.title);
ost << atom::subtitle(args.description);
ost << atom::id(base_url + '/');
ost << atom::updated(atom::format_time_now());
ost << atom::author().add(atom::name(args.author));
ost << atom::link(" ", {{"rel", "self"}, {"href", base_url + "/atom.xml"}});
ost << atom::link(" ",
{{"href", args.resource_url},
{"rel", "alternate"},
{"type", "text/html"}});
for (const auto& commit : branch.get_commits()) {
const auto url =
std::format("{}/commit/{}.html", base_url, commit.get_id());
ost << atom::entry()
.add(atom::id(url))
.add(atom::updated(atom::format_time(commit.get_time_raw())))
.add(atom::title(commit.get_summary()))
.add(atom::link(" ").set("href", url))
.add(atom::author()
.add(atom::name(commit.get_author_name()))
.add(atom::email(commit.get_author_email())))
.add(atom::content(commit.get_message()));
}
ost << atom::feed();
}
void write_rss(std::ostream& ost,
const startgit::branch& branch,
const std::string& base_url)
{
using namespace hemplate; // NOLINT
ost << xml();
ost << rss::rss();
ost << rss::channel();
ost << rss::title(args.title);
ost << rss::description(args.description);
ost << rss::link(base_url + '/');
ost << rss::generator("startgit");
ost << rss::language("en-us");
ost << rss::atomLink().set("href", base_url + "/atom.xml");
for (const auto& commit : branch.get_commits()) {
const auto url =
std::format("{}/commit/{}.html", base_url, commit.get_id());
ost << rss::item()
.add(rss::title(commit.get_summary()))
.add(rss::link(url))
.add(rss::guid(url))
.add(rss::pubDate(rss::format_time(commit.get_time_raw())))
.add(rss::author(std::format("{} ({})",
commit.get_author_email(),
commit.get_author_name())));
}
ost << rss::channel();
ost << rss::rss();
}
int parse_opt(int key, const char* arg, poafloc::Parser* parser) { auto* l_args = static_cast<arguments_t*>(parser->input());

@@ -611,8 +686,17 @@ int parse_opt(int key, const char* arg, poafloc::Parser* parser) case 'o': l_args->output_dir = arg; break;
case 'u':
l_args->url = arg;
case 'b':
l_args->base_url = arg;
if (l_args->base_url.back() == '/') {
l_args->base_url.pop_back();
}
break;
case 'r':
l_args->resource_url = arg;
if (l_args->resource_url.back() == '/') {
l_args->resource_url.pop_back();
}
break; case 'a': l_args->author = arg;

@@ -635,7 +719,8 @@ static const poafloc::option_t options[] = { {0, 0, 0, 0, "Output mode", 1}, {"output", 'o', "DIR", 0, "Output directory"}, {0, 0, 0, 0, "General information", 2},
{"url", 'u', "BASEURL", 0, "Base URL to make links in the Atom feeds absolute"},
{"base", 'b', "URL", 0, "Absolute destination URL"},
{"resource", 'r', "URL", 0, "URL that houses styles and scripts"},
{"author", 'a', "NAME", 0, "Owner of the repository"}, {"title", 't', "TITLE", 0, "Title for the index page"}, {"description", 'd', "DESC", 0, "Description for the index page"},

@@ -663,6 +748,7 @@ int main(int argc, char* argv[]) const git2wrap::libgit2 libgit; std::stringstream index;
std::filesystem::create_directories(args.output_dir);
for (const auto& repo_path : args.repos) { try { const startgit::repository repo(repo_path);

@@ -687,6 +773,17 @@ int main(int argc, char* argv[]) std::filesystem::create_directory(file); write_files(file, repo, branch);
const auto relative =
std::filesystem::relative(base_branch, args.output_dir).string();
std::ofstream atom(base_branch / "atom.xml");
write_atom(
atom, branch, "https://git.dimitrijedobrota.com/" + relative);
std::ofstream rss(base_branch / "rss.xml");
write_rss(
rss, branch, "https://git.dimitrijedobrota.com/" + relative);
} write_repo_table_entry(index, repo);

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

@@ -1,3 +1,4 @@
#include <chrono>
#include <format> #include <iomanip> #include <sstream>

@@ -9,21 +10,25 @@ namespace startgit {
std::string time_short(int64_t date)
auto sec_since_epoch(std::int64_t sec)
{
std::stringstream strs;
strs << std::put_time(std::gmtime(&date), "%Y-%m-%d %H:%M"); // NOLINT
return strs.str();
return std::chrono::time_point_cast<std::chrono::seconds>(
std::chrono::system_clock::from_time_t(time_t {0})
+ std::chrono::seconds(sec));
}
std::string time_short(std::int64_t date)
{
return std::format("{:%Y-%m-%d %H:%M}", sec_since_epoch(date));
} std::string time_long(const git2wrap::time& time) {
std::stringstream strs;
strs << std::put_time(std::gmtime(&time.time), // NOLINT
"%a, %e %b %Y %H:%M:%S ");
strs << (time.offset < 0 ? '-' : '+');
strs << std::format("{:02}{:02}", time.offset / 60, time.offset % 60);
return strs.str();
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