Refactor program into TodoApp class
This commit is contained in:
@@ -4,4 +4,4 @@ project(todo)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_executable(todo main.cpp colors.h json.h)
|
||||
add_executable(todo main.cpp TodoApp.h colors.h json.h)
|
||||
|
263
TodoApp.h
Normal file
263
TodoApp.h
Normal file
@@ -0,0 +1,263 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include "colors.h"
|
||||
#include "json.h"
|
||||
|
||||
#define VERSION "1.2"
|
||||
|
||||
using std::chrono_literals::operator""h;
|
||||
using timestamp = std::chrono::time_point<std::chrono::system_clock>;
|
||||
|
||||
|
||||
std::string timestamp_to_string(const timestamp time)
|
||||
{
|
||||
auto in_time_t = std::chrono::system_clock::to_time_t(time);
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %H:%M:%S");
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
const timestamp string_to_timestamp(std::string time_str)
|
||||
{
|
||||
std::tm tm = {};
|
||||
strptime(time_str.c_str(), "%Y-%m-%d %H:%M:%S", &tm);
|
||||
// the -1h is a lazy hack to hotfix timezones incrementing the hour?
|
||||
return std::chrono::system_clock::from_time_t(std::mktime(&tm))-1h;
|
||||
}
|
||||
|
||||
timestamp get_current_timestamp() { return std::chrono::system_clock::now(); }
|
||||
|
||||
std::string get_current_timestamp_str() {
|
||||
return timestamp_to_string(std::chrono::system_clock::now());
|
||||
}
|
||||
|
||||
int get_timestamp_age(const std::string timestamp)
|
||||
{
|
||||
auto tp = string_to_timestamp(timestamp);
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
const auto difference = std::chrono::duration_cast<std::chrono::days>(now-tp).count();
|
||||
return difference;
|
||||
}
|
||||
|
||||
bool matches(const std::string needle, std::vector<std::string> haystack)
|
||||
{
|
||||
return std::find(haystack.begin(), haystack.end(), needle) != haystack.end();
|
||||
}
|
||||
|
||||
std::string rebuild_args_to_sentence(std::vector<std::string> args)
|
||||
{
|
||||
std::string sentence;
|
||||
for (int i = 0; i < args.size(); i++) {
|
||||
sentence += args[i];
|
||||
if (i < args.size()-1) // Append space between each token except for last
|
||||
sentence += " ";
|
||||
}
|
||||
}
|
||||
|
||||
#define FILE_NAME ".todo.txt"
|
||||
#define CONFIG_NAME ".todo.conf"
|
||||
#define MAGENTA_LIMIT 21
|
||||
#define RED_LIMIT 14
|
||||
#define YELLOW_LIMIT 7
|
||||
#define GREEN_LIMIT 1
|
||||
|
||||
// TODO: Create TodoEntry class
|
||||
class TodoEntry
|
||||
{
|
||||
public:
|
||||
protected:
|
||||
private:
|
||||
};
|
||||
|
||||
class ConsoleApp
|
||||
{
|
||||
public:
|
||||
protected:
|
||||
private:
|
||||
};
|
||||
|
||||
|
||||
class TodoApp : ConsoleApp
|
||||
{
|
||||
public:
|
||||
std::string working_dir;
|
||||
std::string home_dir;
|
||||
std::string file_path;
|
||||
std::string config_path;
|
||||
std::vector<std::string> args;
|
||||
std::string current_timestamp;
|
||||
std::string tag = "";
|
||||
TodoApp(int argc, char ** argv);
|
||||
void Run();
|
||||
void ShowVersionInfo();
|
||||
void ShowLicenseInfo();
|
||||
void ShowHelpInfo();
|
||||
void AddTodo();
|
||||
void OutputTodoList();
|
||||
void OutputLine(const char*, std::string, std::string, std::string, std::string);
|
||||
protected:
|
||||
private:
|
||||
bool ParseOptions();
|
||||
};
|
||||
|
||||
TodoApp::TodoApp(int argc, char ** argv)
|
||||
{
|
||||
|
||||
home_dir = getenv("HOME");
|
||||
config_path = home_dir + "/" + CONFIG_NAME;
|
||||
file_path = home_dir + "/" + FILE_NAME;
|
||||
if (argc > 1) {
|
||||
// TODO: Fix this fumble here lolol
|
||||
args = std::vector<std::string>(argv+1, argv+argc);
|
||||
//args.erase(args.begin());
|
||||
}
|
||||
current_timestamp = get_current_timestamp_str();
|
||||
}
|
||||
|
||||
// Returns whether to terminate early or not;
|
||||
bool TodoApp::ParseOptions()
|
||||
{
|
||||
// Check for option flags to parse out (--<flag>)
|
||||
// flags are removed from args_list at this point
|
||||
// runs in reverse so we can remove vector elements safely
|
||||
for (int i = args.size()-1; i>=0; i--) {
|
||||
if (matches(args[i], {"--version", "-v", "--v"})) {
|
||||
this->ShowVersionInfo();
|
||||
return true;
|
||||
}
|
||||
if (matches(args[i], {"--help", "-h", "--h"})) {
|
||||
this->ShowHelpInfo();
|
||||
return true;
|
||||
}
|
||||
if (matches(args[i], {"--license", "-l"})) {
|
||||
this->ShowLicenseInfo();
|
||||
return true;
|
||||
}
|
||||
if (matches(args[i], {"--here"})) {
|
||||
this->file_path = FILE_NAME;
|
||||
args.erase(args.begin()+i);
|
||||
continue;
|
||||
}
|
||||
if (matches(args[i], {"--urgent"})) { }
|
||||
if (matches(args[i], {"--important"})) { }
|
||||
if (matches(args[i], {"--tag"})) { }
|
||||
if (matches(args[i], {"--file", "-f"})) {
|
||||
this->file_path = args[i+1];
|
||||
args.erase(args.begin()+i);
|
||||
args.erase(args.begin()+i);
|
||||
continue;
|
||||
}
|
||||
if (matches(args[i], {"--config"})) {
|
||||
this->config_path = args[i+1]; // TODO: Bounds check
|
||||
args.erase(args.begin()+i);
|
||||
args.erase(args.begin()+i); // remove 2 indices
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return false; // No special conditions met, so we continue the routine;
|
||||
}
|
||||
|
||||
void TodoApp::Run()
|
||||
{
|
||||
if (this->ParseOptions())
|
||||
return;
|
||||
|
||||
if (this->args.size() == 0)
|
||||
this->OutputTodoList();
|
||||
else
|
||||
this->AddTodo();
|
||||
}
|
||||
|
||||
|
||||
void TodoApp::AddTodo()
|
||||
{
|
||||
// Take whatever args we have left
|
||||
// & Append into a sentence
|
||||
std::string text = rebuild_args_to_sentence(this->args);
|
||||
|
||||
nlohmann::json token = {
|
||||
{"text", text},
|
||||
{"timestamp", this->current_timestamp},
|
||||
{"tag", this->tag},
|
||||
};
|
||||
std::ofstream file(this->file_path, std::ios_base::app);
|
||||
file << token.dump() << std::endl;
|
||||
file.close();
|
||||
std::cout << "Added to todo list: " << text << std::endl;
|
||||
}
|
||||
|
||||
|
||||
void TodoApp::OutputLine(const char* colorcode, std::string timestamp, std::string age, std::string text, std::string tag)
|
||||
{
|
||||
std::cout << BOLD << colorcode;
|
||||
std::cout << "[" << timestamp << "] [" << age << "] ";
|
||||
if (tag.size() > 0) {
|
||||
if (tag == "URGENT")
|
||||
std::cout << FG_RED;
|
||||
if (tag == "IMPORTANT")
|
||||
std::cout << FG_YELLOW;
|
||||
std::cout << "(" << tag << ")";
|
||||
}
|
||||
std::cout << FG_WHITE << text << std::endl;
|
||||
}
|
||||
|
||||
void TodoApp::OutputTodoList()
|
||||
{
|
||||
std::ifstream file(this->file_path);
|
||||
|
||||
if (file.peek() == std::ifstream::traits_type::eof())
|
||||
{
|
||||
std::cout << "No tasks on the TODO list!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string str;
|
||||
// Read each line from the file and format for output
|
||||
while (std::getline(file, str)) {
|
||||
// Parses 1 line of the file as a json object;
|
||||
// NOT the entire file
|
||||
// Grab relevant strings;
|
||||
nlohmann::json token = nlohmann::json::parse(str);
|
||||
std::string timestamp = token["timestamp"].get<std::string>();
|
||||
std::string text = token["text" ].get<std::string>();
|
||||
std::string tag = token["tag" ].get<std::string>();
|
||||
// Calculate age of todo
|
||||
// TODO: Configurable time (days, hours)
|
||||
int age = get_timestamp_age(timestamp);
|
||||
std::string age_as_string = std::to_string(age) + " days ago";
|
||||
|
||||
auto colorcode = FG_BLUE;
|
||||
if (age > MAGENTA_LIMIT) { colorcode = FG_MAGENTA; }
|
||||
else if (age > RED_LIMIT) { colorcode = FG_RED; }
|
||||
else if (age > YELLOW_LIMIT) { colorcode = FG_YELLOW; }
|
||||
else if (age > GREEN_LIMIT) { colorcode = FG_GREEN; }
|
||||
else { colorcode = FG_BLUE; }
|
||||
|
||||
this->OutputLine(colorcode, timestamp, age_as_string, text, tag);
|
||||
}
|
||||
file.close();
|
||||
std::cout << RESET_ALL;
|
||||
}
|
||||
|
||||
void TodoApp::ShowVersionInfo()
|
||||
{
|
||||
std::cout << "Todo List Version " << VERSION << std::endl;
|
||||
}
|
||||
|
||||
void TodoApp::ShowLicenseInfo()
|
||||
{
|
||||
std::cout << "GNU GPL-3" << std::endl;
|
||||
}
|
||||
|
||||
void TodoApp::ShowHelpInfo()
|
||||
{
|
||||
std::cout << "Todo List Program" << std::endl;
|
||||
std::cout << "Usage: todo [-flags] <message>" << std::endl;
|
||||
}
|
213
main.cpp
213
main.cpp
@@ -10,222 +10,21 @@
|
||||
// Usage //
|
||||
|
||||
// Changelog //
|
||||
// April 11, 2023
|
||||
// Refactored program logic into TodoApp class
|
||||
// April 10, 2023
|
||||
// added --urgent and --important flags
|
||||
// added --tag <tag> flag
|
||||
// implement tag display
|
||||
|
||||
// Source //
|
||||
// See TodoApp.h for app implementation;
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include "colors.h"
|
||||
#include "json.h"
|
||||
|
||||
using std::chrono_literals::operator""h;
|
||||
using string = std::string;
|
||||
using timestamp = std::chrono::time_point<std::chrono::system_clock>;
|
||||
string timestamp_to_string(const timestamp time)
|
||||
{
|
||||
auto in_time_t = std::chrono::system_clock::to_time_t(time);
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %H:%M:%S");
|
||||
return ss.str();
|
||||
}
|
||||
const timestamp string_to_timestamp(std::string time_str)
|
||||
{
|
||||
std::tm tm = {};
|
||||
strptime(time_str.c_str(), "%Y-%m-%d %H:%M:%S", &tm);
|
||||
// The -1h is a lazy hack to hotfix timezones incrementing the hour
|
||||
return std::chrono::system_clock::from_time_t(std::mktime(&tm))-1h;
|
||||
}
|
||||
timestamp get_current_timestamp() {
|
||||
return std::chrono::system_clock::now();
|
||||
}
|
||||
string get_current_timestamp_str() {
|
||||
return timestamp_to_string(get_current_timestamp());
|
||||
}
|
||||
int get_timestamp_age(const std::string timestamp)
|
||||
{
|
||||
auto tp = string_to_timestamp(timestamp);
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
const auto difference = std::chrono::duration_cast<std::chrono::days>(now-tp).count();
|
||||
return difference;
|
||||
}
|
||||
|
||||
bool matches(const std::string needle, std::vector<std::string> haystack)
|
||||
{
|
||||
return std::find(haystack.begin(), haystack.end(), needle) != haystack.end();
|
||||
}
|
||||
|
||||
void show_help()
|
||||
{
|
||||
std::cout << "To Do List Program by Conarium Software" << std::endl;
|
||||
std::cout << "Usage: todo " << std::endl;
|
||||
}
|
||||
|
||||
std::string rebuild_args_to_sentence(std::vector<std::string> args)
|
||||
{
|
||||
std::string sentence;
|
||||
for(int i = 0; i < args.size(); i++) {
|
||||
sentence += args[i];
|
||||
if (i < args.size()-1) // Append a space between each token, without appending to the final token's end
|
||||
sentence += " ";
|
||||
}
|
||||
return sentence;
|
||||
}
|
||||
|
||||
#define FILENAME ".todo.txt"
|
||||
#define MAGENTA_LIMIT 21
|
||||
#define RED_LIMIT 14
|
||||
#define YELLOW_LIMIT 7
|
||||
#define GREEN_LIMIT 1
|
||||
|
||||
void output_line(const char* colorcode, std::string timestamp, std::string age, std::string text, std::string tag)
|
||||
{
|
||||
std::cout << BOLD << colorcode << "[" << timestamp << "] [" << age << "] ";
|
||||
if (tag.size() > 0)
|
||||
{
|
||||
if (tag == "URGENT")
|
||||
std::cout << FG_RED;
|
||||
if (tag == "IMPORTANT")
|
||||
std::cout << FG_YELLOW;
|
||||
|
||||
std::cout << "(" << tag << ") ";
|
||||
}
|
||||
std::cout << FG_WHITE << text << std::endl;
|
||||
}
|
||||
|
||||
void output_list(std::string file_path)
|
||||
{
|
||||
// Find, Parse, Output the todo list
|
||||
std::ifstream file(file_path);
|
||||
|
||||
// is the file empty?
|
||||
if (file.peek() == std::ifstream::traits_type::eof())
|
||||
{
|
||||
std::cout << "No tasks on the TODO list!" << std::endl;
|
||||
return;
|
||||
}
|
||||
// Read each line from the file and parse it into a TODO.
|
||||
std::string str;
|
||||
while (std::getline(file, str)) {
|
||||
// Parses 1 line of the file (str) as a json object; (NOT the entire file!)
|
||||
// Grab relevant strings; timestamp, text, tag
|
||||
nlohmann::json token = nlohmann::json::parse(str);
|
||||
std::string timestamp = token["timestamp"].get<std::string>();
|
||||
std::string text = token["text"].get<std::string>();
|
||||
std::string tag = token["tag"].get<std::string>();
|
||||
// Calculate age (in days) of the TODO
|
||||
int age = get_timestamp_age(timestamp);
|
||||
// TODO: Make timeline configurable.
|
||||
// Create timestamp string
|
||||
std::string days_counter = std::to_string(age) + " days ago";
|
||||
|
||||
auto colorcode = FG_BLUE;
|
||||
if (age > MAGENTA_LIMIT) { colorcode = FG_MAGENTA; }
|
||||
else if (age > RED_LIMIT) { colorcode = FG_RED; }
|
||||
else if (age > YELLOW_LIMIT) { colorcode = FG_YELLOW; }
|
||||
else if (age > GREEN_LIMIT) { colorcode = FG_GREEN; }
|
||||
else { colorcode = FG_BLUE; }
|
||||
|
||||
output_line(colorcode, timestamp, days_counter, text, tag);
|
||||
}
|
||||
file.close();
|
||||
std::cout << RESET_ALL;
|
||||
}
|
||||
|
||||
void append_to_list(std::string file_id, std::string current_timestamp, std::string content, std::string tag){
|
||||
nlohmann::json token = {
|
||||
{"text", content},
|
||||
{"timestamp", current_timestamp},
|
||||
{"tag", tag},
|
||||
};
|
||||
std::ofstream file(file_id, std::ios_base::app);
|
||||
file << token.dump() << std::endl;
|
||||
file.close();
|
||||
std::cout << "Added to TODO list!" << std::endl;
|
||||
}
|
||||
|
||||
// TODO: Refactor into class
|
||||
#include "TodoApp.h"
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
// System Variables
|
||||
std::string current_timestamp = get_current_timestamp_str();
|
||||
std::string home_dir = getenv("HOME");
|
||||
|
||||
// Look for config
|
||||
std::string config_path = home_dir + "/" + ".todo.cfg.json";
|
||||
std::ifstream config(config_path);
|
||||
|
||||
if (config.peek() == std::ifstream::traits_type::eof())
|
||||
{
|
||||
// No Config Yet, so we need to generate one
|
||||
// Presumably the first time the software is used
|
||||
// also show some help info?
|
||||
}
|
||||
|
||||
std::string file_id = home_dir + "/" + FILENAME;
|
||||
std::vector<std::string> args_list(argv + 1, argv + argc);
|
||||
|
||||
std::string tag = "";
|
||||
|
||||
// Check for option flags to parse out. (--<flag>)
|
||||
// Flags are removed from args_list at this point.
|
||||
// Runs in reverse so we can remove vector elements safely.
|
||||
for(int i = args_list.size()- 1; i >= 0 ; i--) {
|
||||
if (matches(args_list[i], {"--version", "-v", "--v"})) {
|
||||
args_list.erase(args_list.begin() + i);
|
||||
std::cout << "Version 1.1" << std::endl;
|
||||
return 0; // Certain Flags cause the program to terminate.
|
||||
}
|
||||
if (matches(args_list[i], {"--help", "-h", "--h"})) {
|
||||
args_list.erase(args_list.begin() + i);
|
||||
show_help();
|
||||
return 0; // Certain Flags cause the program to terminate.
|
||||
}
|
||||
if (matches(args_list[i], {"--here"})) {
|
||||
args_list.erase(args_list.begin() + i);
|
||||
|
||||
file_id = FILENAME;
|
||||
continue;
|
||||
}
|
||||
if (matches(args_list[i], {"--urgent"}))
|
||||
{
|
||||
args_list.erase(args_list.begin()+i);
|
||||
tag = "URGENT";
|
||||
continue;
|
||||
}
|
||||
if (matches(args_list[i], {"--important"}))
|
||||
{
|
||||
args_list.erase(args_list.begin()+i);
|
||||
tag = "IMPORTANT";
|
||||
continue;
|
||||
}
|
||||
if (matches(args_list[i], {"--tag"}))
|
||||
{
|
||||
tag = args_list[i+1];
|
||||
args_list.erase(args_list.begin()+i);
|
||||
args_list.erase(args_list.begin()+i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Run the program with cherry-picked args_list
|
||||
if (args_list.size() < 1) {
|
||||
output_list(file_id);
|
||||
} else {
|
||||
std::string item = rebuild_args_to_sentence(args_list);
|
||||
append_to_list(file_id, get_current_timestamp_str(), item, tag);
|
||||
return 0;
|
||||
}
|
||||
TodoApp app(argc, argv);
|
||||
app.Run();
|
||||
return 0;
|
||||
}
|
||||
|
Reference in New Issue
Block a user