better config options, mvp for notifying based on current tasks

This commit is contained in:
Andy Pack 2025-01-28 00:17:47 +00:00
parent 79d21bc3cb
commit 9c01297069
Signed by: sarsoo
GPG Key ID: A55BA3536A5E0ED7
8 changed files with 205 additions and 94 deletions

1
.gitignore vendored

@ -55,6 +55,7 @@ bld/
[Bb]uild/
[Oo]bj/
[Ll]og/
cmake-build-debug
# Visual Studio 2015 cache/options directory
.vs/

@ -6,28 +6,59 @@
#include <boost/program_options.hpp>
#include "const.hpp"
po::options_description get_current_tasks_options() {
po::options_description options("Current Tasks");
options.add_options()
(CONFIG_NOTIFY.c_str(), po::bool_switch(), "send notification to notify")
(CONFIG_HOST.c_str(), po::value<std::string>(), "ntfy hostname")
(CONFIG_TOPIC.c_str(), po::value<std::string>(), "ntfy topic name")
(CONFIG_TITLE.c_str(), po::value<std::string>(), "title for notifications")
;
return options;
}
std::shared_ptr<po::variables_map> init_config(int argc, const char *argv[])
{
try {
po::options_description desc("Config");
desc.add_options()
("help", "produce help message")
po::options_description visible_general("KC Config");
visible_general.add_options()
("help,h", "produce help message")
("path,p", po::value<std::vector<std::string>>(), "set root path of knowledge base")
("config", po::value<std::string>()->default_value("kc.ini"), "config file location")
("out,o", po::value<std::string>()->default_value("."), "output file location")
("command", po::value<std::string>(), "command to execute")
("subargs", po::value<std::vector<std::string> >(), "Arguments for command")
("index", po::value<int>()->default_value(1), "index")
;
po::positional_options_description pos;
pos.add("command", 1).add("subargs", -1);
po::options_description hidden_general("Hidden");
hidden_general.add_options()
("command", po::value<std::string>(), "command to execute")
;
po::positional_options_description positional;
positional.add("command", 1);
auto current_tasks_options = get_current_tasks_options();
/////////
// CMD
/////////
po::options_description cmdline_options;
cmdline_options.add(desc);
cmdline_options
.add(visible_general)
.add(hidden_general)
.add(current_tasks_options)
;
/////////
// FILE
/////////
po::options_description config_file_options;
config_file_options.add(desc);
config_file_options
.add(visible_general)
.add(hidden_general)
.add(current_tasks_options)
;
////////////
// PARSE
@ -35,10 +66,10 @@ std::shared_ptr<po::variables_map> init_config(int argc, const char *argv[])
auto vm = std::make_shared<po::variables_map>();
po::store(po::command_line_parser(argc, argv)
.positional(positional)
.options(cmdline_options)
.positional(pos)
// .allow_unregistered()
.run(),
.run(),
*vm);
if (vm->contains("config"))
@ -54,7 +85,19 @@ std::shared_ptr<po::variables_map> init_config(int argc, const char *argv[])
po::notify(*vm);
if (vm->contains("help")) {
std::cout << desc;
if (vm->count("command") == 1)
{
auto command = (*vm)["command"].as<std::string>();
if (command == CMD_CURRENT_TASKS) {
std::cout << visible_general << std::endl << current_tasks_options;
}
}
else
{
std::cout << visible_general;
}
return nullptr;
}

@ -2,6 +2,31 @@
#include <string>
///////////////
/// COMMANDS
///////////////
static const std::string CMD_VALIDATE_TASKS = "validate";
static const std::string CMD_IMG_TASKS = "img";
static const std::string CMD_PRINT_TASKS = "print";
static const std::string CMD_CURRENT_TASKS = "current";
static const std::string CMD_NET_TASKS = "net";
///////////////
/// CONFIG
///////////////
static const std::string CONFIG_NOTIFY = "notify";
static const std::string CONFIG_HOST = "host";
static const std::string CONFIG_TOPIC = "topic";
static const std::string CONFIG_TITLE = "title";
///////////////
/// REGEXES
///////////////
static const std::string MD_LINK_REGEX = R"(\[.*?\]\(.*?\))";
static const std::string MD_MD_LINK_REGEX = R"(\[.*?\]\(.*?\.md(#.*?)*\))";
static const std::string MD_IMAGE_LINK_REGEX = R"(!\[.*?\]\(.*?\.png\))";

@ -21,7 +21,7 @@
void run_validate(const kc::AppContext &app_context);
void run_img(const kc::AppContext &app_context);
void run_print(const kc::AppContext &app_context);
void run_current_tasks(const kc::AppContext &app_context);
int run_current_tasks(const kc::AppContext &app_context);
void run_test_net(const kc::AppContext &app_context);
@ -42,27 +42,27 @@ int main(int argc, const char *argv[]) {
const auto command = app_context.command();
if (!command.empty())
{
if (command == "validate")
if (command == CMD_VALIDATE_TASKS)
{
app_context.load_and_parse_cache(kc::LINKS);
run_validate(app_context);
}
else if (command == "img")
else if (command == CMD_IMG_TASKS)
{
app_context.load_and_parse_cache(kc::IMAGES);
run_img(app_context);
}
else if (command == "print")
else if (command == CMD_PRINT_TASKS)
{
app_context.load_and_parse_cache();
run_print(app_context);
}
else if (command == "current")
else if (command == CMD_CURRENT_TASKS)
{
app_context.load_and_parse_cache(kc::TASKS);
run_current_tasks(app_context);
return run_current_tasks(app_context);
}
else if (command == "net")
else if (command == CMD_NET_TASKS)
{
// app_context.load_and_parse_cache(kc::TASKS);
run_test_net(app_context);
@ -106,13 +106,10 @@ void run_print(const kc::AppContext &app_context)
}
}
void run_current_tasks(const kc::AppContext &app_context)
int run_current_tasks(const kc::AppContext &app_context)
{
BOOST_LOG_TRIVIAL(info) << "Running \"Current Tasks\" command";
for(const auto& file_cache : app_context.file_caches) {
BOOST_LOG_TRIVIAL(info) << "Finding current tasks in " << file_cache->get_root_path();
kc::current_tasks(file_cache->get());
}
return kc::current_tasks(app_context);
}
void run_test_net(const kc::AppContext &app_context)

@ -1,9 +1,54 @@
#include "http.hpp"
#include <boost/beast/version.hpp>
#include <boost/log/trivial.hpp>
// https://www.boost.org/doc/libs/1_87_0/libs/beast/example/http/client/sync-ssl/http_client_sync_ssl.cpp
namespace kc {
std::shared_ptr<ssl::stream<beast::tcp_stream>> connect_stream(const std::string &host, int port) {
http::response<http::dynamic_body> read_response(const std::shared_ptr<ssl::stream<beast::tcp_stream>> &stream) {
// This buffer is used for reading and must be persisted
beast::flat_buffer buffer;
// Declare a container to hold the response
http::response<http::dynamic_body> res;
// Receive the HTTP response
BOOST_LOG_TRIVIAL(debug) << "Reading HTTP response";
http::read(*stream, buffer, res);
BOOST_LOG_TRIVIAL(debug) << "Received response from (" << res.result_int() << ")";
return res;
}
void shutdown_stream(const std::shared_ptr<ssl::stream<beast::tcp_stream>> &stream) {
// Gracefully close the socket
beast::error_code ec;
BOOST_LOG_TRIVIAL(debug) << "Gracefully shutting down stream";
stream->shutdown(ec);
if(ec == net::error::eof)
{
// Rationale:
// http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error
ec = {};
}
if(ec)
throw beast::system_error{ec};
}
void request(http::verb method, const std::string &host, std::string target, std::string body, std::unique_ptr<std::unordered_map<std::string, std::string>> headers) {
request(method, host, target, 443, body, std::move(headers));
}
void request(http::verb method, const std::string &host, std::string target, std::string body) {
request(method, host, target, 443, body, nullptr);
}
void request(http::verb method, const std::string &host, std::string target, int port, std::string body, std::unique_ptr<std::unordered_map<std::string, std::string>> headers) {
BOOST_LOG_TRIVIAL(info) << "Opening connection to [" << host << ":" << port << "]";
// The io_context is required for all I/O
net::io_context ioc;
@ -28,55 +73,17 @@ std::shared_ptr<ssl::stream<beast::tcp_stream>> connect_stream(const std::string
}
// Look up the domain name
BOOST_LOG_TRIVIAL(debug) << "Resolving IP for [" << host << "]";
auto const results = resolver.resolve(host, std::to_string(port));
// Make the connection on the IP address we get from a lookup
BOOST_LOG_TRIVIAL(debug) << "Connecting stream [" << host << ":" << port << "]";
beast::get_lowest_layer(*stream).connect(results);
// Perform the SSL handshake
BOOST_LOG_TRIVIAL(debug) << "Performing TLS handshake [" << host << ":" << port << "]";
stream->handshake(ssl::stream_base::client);
return stream;
}
http::response<http::dynamic_body> read_response(const std::shared_ptr<ssl::stream<beast::tcp_stream>> &stream) {
// This buffer is used for reading and must be persisted
beast::flat_buffer buffer;
// Declare a container to hold the response
http::response<http::dynamic_body> res;
// Receive the HTTP response
http::read(*stream, buffer, res);
return res;
}
void shutdown_stream(const std::shared_ptr<ssl::stream<beast::tcp_stream>> &stream) {
// Gracefully close the socket
beast::error_code ec;
stream->shutdown(ec);
if(ec == net::error::eof)
{
// Rationale:
// http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error
ec = {};
}
if(ec)
throw beast::system_error{ec};
}
http::response<http::dynamic_body> request(http::verb method, const std::string &host, std::string target, std::string body, std::unique_ptr<std::unordered_map<std::string, std::string>> headers) {
return request(method, host, target, 443, body, std::move(headers));
}
http::response<http::dynamic_body> request(http::verb method, const std::string &host, std::string target, std::string body) {
return request(method, host, target, 443, body, nullptr);
}
http::response<http::dynamic_body> request(http::verb method, const std::string &host, std::string target, int port, std::string body, std::unique_ptr<std::unordered_map<std::string, std::string>> headers) {
auto stream = connect_stream(host, port);
http::request<http::string_body> req{method, target, 11};
req.set(http::field::host, host);
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
@ -92,14 +99,11 @@ http::response<http::dynamic_body> request(http::verb method, const std::string
req.prepare_payload(); // set content-length based on the body
// Send the HTTP request to the remote host
BOOST_LOG_TRIVIAL(debug) << "Writing HTTP request to stream [" << host << ":" << port << "]";
http::write(*stream, req);
auto response = read_response(stream);
shutdown_stream(stream);
// Write the message to standard out
std::cout << response << std::endl;
return response;
}
}

@ -3,12 +3,8 @@
#include <unordered_map>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl.hpp>
#include <cstdlib>
#include <iostream>
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
@ -17,8 +13,8 @@ namespace ssl = net::ssl; // from <boost/asio/ssl.hpp>
using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace kc {
http::response<http::dynamic_body> request(http::verb method, const std::string &host, std::string target, std::string body);
http::response<http::dynamic_body> request(http::verb method, const std::string &host, std::string target, std::string body, std::unique_ptr<std::unordered_map<std::string, std::string>> headers);
http::response<http::dynamic_body> request(http::verb method, const std::string &host, std::string target, int port, std::string body, std::unique_ptr<std::unordered_map<std::string, std::string>> headers);
void request(http::verb method, const std::string &host, std::string target, std::string body);
void request(http::verb method, const std::string &host, std::string target, std::string body, std::unique_ptr<std::unordered_map<std::string, std::string>> headers);
void request(http::verb method, const std::string &host, std::string target, int port, std::string body, std::unique_ptr<std::unordered_map<std::string, std::string>> headers);
}

@ -2,21 +2,67 @@
#include "task.hpp"
#include <iostream>
#include <algorithm>
#include <boost/algorithm/string/join.hpp>
#include "../logging.hpp"
namespace kc {
void current_tasks(const std::vector<std::shared_ptr<kc::FileContext>> &contexts)
{
auto date = day_clock::local_day();
std::string get_notification_content(const std::vector<Task>& tasks) {
std::vector<std::string> contents(tasks.size());
for (const auto &entry : contexts) {
std::ranges::transform(tasks, contents.rbegin(), [](const Task &t) -> std::string { return t.get_content(); });
for (const auto &task : entry->tasks) {
if (is_current(task, date))
std::cout << "(" << task.get_content() << ") : " << task.get_due_date() << " " << entry->file_entry->file_entry.path() << std::endl;
}
}
return boost::join(contents, "\n");
}
}
int current_tasks(const kc::AppContext &app_context)
{
auto date = day_clock::local_day();
std::vector<Task> tasks;
for(const auto& file_cache : app_context.file_caches) {
for (const auto &entry : file_cache->get()) {
for (const auto &task : entry->tasks) {
if (is_current(task, date)) {
tasks.emplace_back(task);
}
}
}
}
std::ranges::sort(tasks, [](Task a, Task b)
{
return a.get_due_date() < b.get_due_date();
});
for (const auto& task : tasks) {
std::cout << task.get_content() << " (" << task.get_due_date() << ")" << std::endl;
}
if (app_context.config->contains(CONFIG_NOTIFY)) {
if (!app_context.config->contains(CONFIG_HOST)) {
print_and_log_error("No NTFY host provided");
return 1;
}
if (!app_context.config->contains(CONFIG_TOPIC)) {
print_and_log_error("No NTFY topic name provided");
return 1;
}
auto host_name = (*app_context.config)[CONFIG_HOST].as<std::string>();
auto topic_name = (*app_context.config)[CONFIG_TOPIC].as<std::string>();
auto payload = get_notification_content(tasks);
auto notif = kc::Notification(topic_name, payload);
kc::notify(host_name, notif);
}
return 0;
}
}

@ -1,11 +1,10 @@
#pragma once
#include <filesystem>
#include <vector>
#include "../parse/FileContext.hpp"
#include "../appcontext.hpp"
#include "../net/ntfy.hpp"
namespace kc {
void current_tasks(const std::vector<std::shared_ptr<kc::FileContext>> &contexts);
int current_tasks(const kc::AppContext &app_context);
}