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 | 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