XGCRPWKLJYWGVCMHDIR2K3PVT2R22JALVV5K2IXTHU3JBAEYANGAC
#ifndef NIX_PLUGIN_PIJUL_REPO_H
#define NIX_PLUGIN_PIJUL_REPO_H
#include <optional>
#include <date/date.h>
#include <types.hh>
namespace nixpluginpijul
{
struct RepoStatus {
std::string channel;
std::string state;
uint64_t lastModified;
};
std::string runPijul(nix::Strings args, std::optional<nix::Path> chdir = {}, std::optional<std::string> input = {}, bool isInteractive = false);
RepoStatus getRepoStatus(const nix::PathView &repoPath);
std::pair<std::string, uint64_t> getState(const nix::PathView &repoPath);
std::string getRepoChannel(const nix::PathView &repoPath);
date::sys_time<std::chrono::milliseconds> parseRFC3339(const std::string &spec);
}
#endif // NIX_PLUGIN_PIJUL_REPO_H
#include "repo.h"
#include <types.hh>
#include <util.hh>
#if NIX_VERSION >= 0x021900
#include <processes.hh>
#endif
#include <nlohmann/json.hpp>
using nlohmann::json;
using namespace nix;
using namespace std::string_literals;
using namespace std::string_view_literals;
namespace nixpluginpijul
{
std::string runPijul(Strings args, std::optional<Path> chdir, std::optional<std::string> input, bool isInteractive)
{
auto program = "pijul"sv;
auto res = runProgram(RunOptions{
.program = std::string(program),
.searchPath = true,
.args = std::move(args),
.chdir = std::move(chdir),
.input = std::move(input),
.isInteractive = isInteractive,
});
if (!statusOk(res.first)) {
throw ExecError(res.first, "program '%1%' %2%", program, statusToString(res.first));
}
return res.second;
}
RepoStatus getRepoStatus(const PathView &repoPath)
{
auto [state, lastModified] = getState(repoPath);
auto channel = getRepoChannel(repoPath);
return RepoStatus{
.channel = std::move(channel),
.state = std::move(state),
.lastModified = lastModified,
};
}
std::pair<std::string, uint64_t> getState(const PathView &repoPath)
{
const auto &output = runPijul({"log", "--output-format", "json", "--state", "--limit", "1"}, Path(repoPath));
const auto &json = json::parse(output);
const auto &commitInfo = json.at(0);
const auto ×tampSpec = commitInfo.at("timestamp").get<std::string>();
const uint64_t timestamp = std::chrono::duration_cast<std::chrono::seconds>(parseRFC3339(timestampSpec).time_since_epoch()).count();
const std::string &state = commitInfo.at("state");
return {state, timestamp};
}
std::string getRepoChannel(const PathView &repoPath)
{
const auto &output = runPijul({"channel"}, Path(repoPath));
std::string::size_type pos = 0;
do {
const auto nl = output.find('\n', pos);
const auto line = std::string_view(output).substr(pos, nl - pos);
if (line.empty()) {
continue;
}
if (line.at(0) == '*') {
return std::string(line.substr(2));
}
pos = nl;
} while (pos != std::string::npos);
throw Error("could not parse current channel"s);
}
date::sys_time<std::chrono::milliseconds> parseRFC3339(const std::string &spec)
{
std::istringstream in{spec};
date::sys_time<std::chrono::milliseconds> pt;
in >> date::parse("%FT%TZ", pt);
if (in.fail()) {
in.clear();
in.exceptions(std::ios::failbit);
in.str(spec);
in >> date::parse("%FT%T%Ez", pt);
}
return pt;
}
}
std::string runPijul(Strings args, std::optional<Path> chdir = {}, std::optional<std::string> input = {}, bool isInteractive = false)
{
auto program = "pijul"sv;
auto res = runProgram(RunOptions{
.program = std::string(program),
.searchPath = true,
.args = std::move(args),
.chdir = std::move(chdir),
.input = std::move(input),
.isInteractive = isInteractive,
});
if (!statusOk(res.first)) {
throw ExecError(res.first, "program '%1%' %2%", program, statusToString(res.first));
}
return res.second;
}
static RepoStatus getRepoStatus(const PathView &repoPath)
{
auto [state, lastModified] = getState(repoPath);
auto channel = getRepoChannel(repoPath);
return RepoStatus{
.channel = std::move(channel),
.state = std::move(state),
.lastModified = lastModified,
};
}
static std::pair<std::string, uint64_t> getState(const PathView &repoPath)
{
const auto &output = runPijul({"log", "--output-format", "json", "--state", "--limit", "1"}, Path(repoPath));
const auto &json = nlohmann::json::parse(output);
const auto &commitInfo = json.at(0);
const auto ×tampSpec = commitInfo.at("timestamp").get<std::string>();
const uint64_t timestamp = std::chrono::duration_cast<std::chrono::seconds>(parseRFC3339(timestampSpec).time_since_epoch()).count();
const std::string &state = commitInfo.at("state");
return {state, timestamp};
}
static date::sys_seconds parseRFC3339(const std::string &spec)
{
std::istringstream in;
date::sys_seconds pt;
in >> date::parse("%FT%TZ", pt);
if (in.fail()) {
in.clear();
in.exceptions(std::ios::failbit);
in >> date::parse("%FT%T%Ez", pt);
}
return pt;
}
static std::string getRepoChannel(const PathView &repoPath)
{
const auto &output = runPijul({"channel"}, Path(repoPath));
std::string::size_type pos = 0;
do {
const auto nl = output.find('\n', pos);
const auto line = std::string_view(output).substr(pos, nl - pos);
if (line.empty()) {
continue;
}
if (line.at(0) == '*') {
return std::string(line.substr(2));
}
pos = nl;
} while (pos != std::string::npos);
throw Error("could not parse current channel"s);
}