Extract creation of summary table into SummaryTableBuilder

Signed-off-by: Thomas Lauf <thomas.lauf@tngtech.com>
This commit is contained in:
Thomas Lauf 2018-01-11 21:23:33 +01:00 committed by Thomas Lauf
parent 65985bcd37
commit d74184f46b
6 changed files with 412 additions and 163 deletions

View file

@ -27,6 +27,7 @@ set (timew_SRCS AtomicFile.cpp AtomicFile.h
Journal.cpp Journal.h
Range.cpp Range.h
Rules.cpp Rules.h
SummaryTable.cpp SummaryTable.h
TagDescription.cpp TagDescription.h
TagInfo.cpp TagInfo.h
TagInfoDatabase.cpp TagInfoDatabase.h

276
src/SummaryTable.cpp Normal file
View file

@ -0,0 +1,276 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2023, Gothenburg Bit Factory.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// https://www.opensource.org/licenses/mit-license.php
//
////////////////////////////////////////////////////////////////////////////////
#include <Datetime.h>
#include <SummaryTable.h>
#include <Table.h>
#include <format.h>
#include <timew.h>
#include <utf8.h>
#include <utility>
////////////////////////////////////////////////////////////////////////////////
SummaryTable::Builder SummaryTable::builder ()
{
return {};
}
////////////////////////////////////////////////////////////////////////////////
SummaryTable::Builder& SummaryTable::Builder::withWeekFormat (const std::string& format)
{
_week_fmt = format;
return *this;
}
////////////////////////////////////////////////////////////////////////////////
SummaryTable::Builder& SummaryTable::Builder::withDateFormat (const std::string& format)
{
_date_fmt = format;
return *this;
}
////////////////////////////////////////////////////////////////////////////////
SummaryTable::Builder& SummaryTable::Builder::withTimeFormat (const std::string& format)
{
_time_fmt = format;
return *this;
}
////////////////////////////////////////////////////////////////////////////////
SummaryTable::Builder& SummaryTable::Builder::withAnnotations (const bool show)
{
_show_annotations = show;
return *this;
}
////////////////////////////////////////////////////////////////////////////////
SummaryTable::Builder& SummaryTable::Builder::withIds (bool show, Color color)
{
_show_ids = show;
_color_id = color;
return *this;
}
////////////////////////////////////////////////////////////////////////////////
SummaryTable::Builder & SummaryTable::Builder::withTags (bool show, std::map <std::string, Color>& colors)
{
_show_tags = show;
_color_tags = std::move (colors);
return *this;
}
////////////////////////////////////////////////////////////////////////////////
SummaryTable::Builder& SummaryTable::Builder::withWeekdays (const bool show)
{
_show_weekdays = show;
return *this;
}
////////////////////////////////////////////////////////////////////////////////
SummaryTable::Builder& SummaryTable::Builder::withWeeks (const bool show)
{
_show_weeks = show;
return *this;
}
////////////////////////////////////////////////////////////////////////////////
SummaryTable::Builder& SummaryTable::Builder::withRange (const Range& range)
{
_range = range;
return *this;
}
////////////////////////////////////////////////////////////////////////////////
SummaryTable::Builder & SummaryTable::Builder::withIntervals (const std::vector<Interval>& tracked)
{
_tracked = tracked;
return *this;
}
////////////////////////////////////////////////////////////////////////////////
Table SummaryTable::Builder::build ()
{
const auto dates_col_offset = _show_weeks ? 1 : 0;
const auto weekdays_col_offset = dates_col_offset;
const auto ids_col_offset = weekdays_col_offset + (_show_weekdays ? 1: 0);
const auto tags_col_offset = ids_col_offset + (_show_ids ? 1 : 0);
const auto annotation_col_offset = tags_col_offset + (_show_tags ? 1 : 0);
const auto start_col_offset = annotation_col_offset + (_show_annotations ? 1 : 0);
const auto weeks_col_index = 0;
const auto dates_col_index = 0 + dates_col_offset;
const auto weekdays_col_index = 1 + weekdays_col_offset;
const auto ids_col_index = 1 + ids_col_offset;
const auto tags_col_index = 1 + tags_col_offset;
const auto annotation_col_index = 1 + annotation_col_offset;
const auto start_col_index = 1 + start_col_offset;
const auto end_col_index = 2 + start_col_offset;
const auto duration_col_index = 3 + start_col_offset;
const auto total_col_index = 4 + start_col_offset;
Table table;
table.width (1024);
table.colorHeader (Color ("underline"));
if (_show_weeks)
{
table.add ("Wk");
}
table.add ("Date");
if (_show_weekdays)
{
table.add ("Day");
}
if (_show_ids)
{
table.add ("ID");
}
if (_show_tags)
{
table.add ("Tags");
}
if (_show_annotations)
{
table.add ("Annotation");
}
table.add ("Start", false);
table.add ("End", false);
table.add ("Time", false);
table.add ("Total", false);
// Each day is rendered separately.
time_t grand_total = 0;
Datetime previous;
auto days_start = _range.is_started () ? _range.start : _tracked.front ().start;
auto days_end = _range.is_ended () ? _range.end : _tracked.back ().end;
const auto now = Datetime ();
if (days_end == 0)
{
days_end = now;
}
for (Datetime day = days_start.startOfDay (); day < days_end; ++day)
{
auto day_range = getFullDay (day);
time_t daily_total = 0;
int row = -1;
for (auto& track : subset (day_range, _tracked))
{
// Make sure the track only represents one day.
if ((track.is_open () && day > now))
{
continue;
}
row = table.addRow ();
if (day != previous)
{
if (_show_weeks)
{
table.set (row, weeks_col_index, format (_week_fmt, day.week ()));
}
table.set (row, dates_col_index, day.toString (_date_fmt));
if (_show_weekdays)
{
table.set (row, weekdays_col_index, Datetime::dayNameShort (day.dayOfWeek ()));
}
previous = day;
}
// Intersect track with day.
auto today = day_range.intersect (track);
if (track.is_open () && track.start > now)
{
today.end = track.start;
}
else if (track.is_open () && day <= now && today.end > now)
{
today.end = now;
}
if (_show_ids)
{
table.set (row, ids_col_index, format ("@{1}", track.id), _color_id);
}
if (_show_tags)
{
auto tags_string = join (", ", track.tags ());
table.set (row, tags_col_index, tags_string, summaryIntervalColor (_color_tags, track.tags ()));
}
if (_show_annotations)
{
auto annotation = track.getAnnotation ();
if (utf8_length (annotation) > 15)
{
annotation = utf8_substr (annotation, 0, 12) + "...";
}
table.set (row, annotation_col_index, annotation);
}
const auto total = today.total ();
table.set (row, start_col_index, today.start.toString (_time_fmt));
table.set (row, end_col_index, (track.is_open () ? "-" : today.end.toString (_time_fmt)));
table.set (row, duration_col_index, Duration (total).formatHours ());
daily_total += total;
}
if (row != -1)
{
table.set (row, total_col_index, Duration (daily_total).formatHours ());
}
grand_total += daily_total;
}
// Add the total.
table.set (table.addRow (), total_col_index, " ", Color ("underline"));
table.set (table.addRow (), total_col_index, Duration (grand_total).formatHours ());
return table;
}
////////////////////////////////////////////////////////////////////////////////

77
src/SummaryTable.h Normal file
View file

@ -0,0 +1,77 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright 2023, Gothenburg Bit Factory.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// https://www.opensource.org/licenses/mit-license.php
//
//////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_SUMMARYTABLE
#define INCLUDED_SUMMARYTABLE
#include <Color.h>
#include <Interval.h>
#include <Range.h>
#include <Table.h>
#include <map>
class SummaryTable
{
class Builder
{
public:
Builder& withWeekFormat (const std::string &);
Builder& withDateFormat (const std::string &);
Builder& withTimeFormat (const std::string &);
Builder& withAnnotations (bool);
Builder& withIds (bool, Color);
Builder& withTags (bool, std::map <std::string, Color>&);
Builder& withWeeks (bool);
Builder& withWeekdays (bool);
Builder& withRange (const Range &);
Builder& withIntervals (const std::vector <Interval>&);
Table build ();
private:
std::string _week_fmt;
std::string _date_fmt;
std::string _time_fmt;
bool _show_annotations;
bool _show_ids;
bool _show_tags;
bool _show_weekdays;
bool _show_weeks;
Range _range;
std::vector <Interval> _tracked;
Color _color_id;
std::map <std::string, Color> _color_tags;
};
public:
static Builder builder ();
};
#endif // INCLUDED_SUMMARYTABLE

View file

@ -24,10 +24,10 @@
//
////////////////////////////////////////////////////////////////////////////////
#include <Duration.h>
#include <IntervalFilterAllInRange.h>
#include <IntervalFilterAllWithTags.h>
#include <IntervalFilterAndGroup.h>
#include <SummaryTable.h>
#include <Table.h>
#include <commands.h>
#include <format.h>
@ -92,10 +92,7 @@ int CmdSummary (
// Map tags to colors.
Color colorID (rules.getBoolean ("color") ? rules.get ("theme.colors.ids") : "");
const auto week_fmt = "W{1}";
const auto date_fmt = "Y-M-D";
const auto time_fmt = "h:N:S";
auto tagColorMap = createTagColorMap (rules, tracked);
const auto show_weeks = rules.getBoolean ("reports.summary.weeks", true);
const auto show_weekdays = rules.getBoolean ("reports.summary.weekdays", true);
@ -104,162 +101,18 @@ int CmdSummary (
const auto show_annotations = cli.getComplementaryHint ("annotations", rules.getBoolean ("reports.summary.annotations"));
const auto show_holidays = cli.getComplementaryHint ("holidays", rules.getBoolean ("reports.summary.holidays"));
const auto dates_col_offset = show_weeks ? 1 : 0;
const auto weekdays_col_offset = dates_col_offset;
const auto ids_col_offset = weekdays_col_offset + (show_weekdays ? 1: 0);
const auto tags_col_offset = ids_col_offset + (show_ids ? 1 : 0);
const auto annotation_col_offset = tags_col_offset + (show_tags ? 1 : 0);
const auto start_col_offset = annotation_col_offset + (show_annotations ? 1 : 0);
const auto weeks_col_index = 0;
const auto dates_col_index = 0 + dates_col_offset;
const auto weekdays_col_index = 1 + weekdays_col_offset;
const auto ids_col_index = 1 + ids_col_offset;
const auto tags_col_index = 1 + tags_col_offset;
const auto annotation_col_index = 1 + annotation_col_offset;
const auto start_col_index = 1 + start_col_offset;
const auto end_col_index = 2 + start_col_offset;
const auto duration_col_index = 3 + start_col_offset;
const auto total_col_index = 4 + start_col_offset;
Table table;
table.width (1024);
table.colorHeader (Color ("underline"));
if (show_weeks)
{
table.add ("Wk");
}
table.add ("Date");
if (show_weekdays)
{
table.add ("Day");
}
if (show_ids)
{
table.add ("ID");
}
if (show_tags)
{
table.add ("Tags");
}
if (show_annotations)
{
table.add ("Annotation");
}
table.add ("Start", false);
table.add ("End", false);
table.add ("Time", false);
table.add ("Total", false);
// Each day is rendered separately.
time_t grand_total = 0;
Datetime previous;
auto days_start = range.is_started() ? range.start : tracked.front ().start;
auto days_end = range.is_ended() ? range.end : tracked.back ().end;
const auto now = Datetime ();
if (days_end == 0)
{
days_end = now;
}
for (Datetime day = days_start.startOfDay (); day < days_end; ++day)
{
auto day_range = getFullDay (day);
time_t daily_total = 0;
int row = -1;
for (auto& track : subset (day_range, tracked))
{
// Make sure the track only represents one day.
if ((track.is_open () && day > now))
{
continue;
}
row = table.addRow ();
if (day != previous)
{
if (show_weeks)
{
table.set (row, weeks_col_index, format (week_fmt, day.week ()));
}
table.set (row, dates_col_index, day.toString (date_fmt));
if (show_weekdays)
{
table.set (row, weekdays_col_index, Datetime::dayNameShort (day.dayOfWeek ()));
}
previous = day;
}
// Intersect track with day.
auto today = day_range.intersect (track);
if (track.is_open() && track.start > now)
{
today.end = track.start;
}
else if (track.is_open () && day <= now && today.end > now)
{
today.end = now;
}
if (show_ids)
{
table.set (row, ids_col_index, format ("@{1}", track.id), colorID);
}
if (show_tags)
{
std::string tags_string = join (", ", track.tags ());
table.set (row, tags_col_index, tags_string, summaryIntervalColor (rules, track.tags ()));
}
if (show_annotations)
{
auto annotation = track.getAnnotation ();
if (utf8_length (annotation) > 15)
{
annotation = utf8_substr (annotation, 0, 12) + "...";
}
table.set (row, annotation_col_index, annotation);
}
const auto total = today.total ();
table.set (row, start_col_index, today.start.toString (time_fmt));
table.set (row, end_col_index, (track.is_open () ? "-" : today.end.toString (time_fmt)));
table.set (row, duration_col_index, Duration (total).formatHours ());
daily_total += total;
}
if (row != -1)
{
table.set (row, total_col_index, Duration (daily_total).formatHours ());
}
grand_total += daily_total;
}
// Add the total.
table.set (table.addRow (), total_col_index, " ", Color ("underline"));
table.set (table.addRow (), total_col_index, Duration (grand_total).formatHours ());
auto table = SummaryTable::builder ()
.withWeekFormat ("W{1}")
.withDateFormat ("Y-M-D")
.withTimeFormat ("h:N:S")
.withWeeks (show_weeks)
.withWeekdays (show_weekdays)
.withIds (show_ids, colorID)
.withTags (show_tags, tagColorMap)
.withAnnotations (show_annotations)
.withRange (range)
.withIntervals (tracked)
.build ();
std::cout << '\n'
<< table.render ()
@ -270,11 +123,11 @@ int CmdSummary (
}
////////////////////////////////////////////////////////////////////////////////
std::string renderHolidays (const std::map<Datetime, std::string> &holidays)
std::string renderHolidays (const std::map<Datetime, std::string>& holidays)
{
std::stringstream out;
for (auto &entry : holidays)
for (auto& entry : holidays)
{
out << entry.first.toString ("Y-M-D")
<< " "

View file

@ -31,6 +31,7 @@
#include <iomanip>
#include <map>
#include <sstream>
#include <string>
#include <timew.h>
#include <vector>
@ -50,6 +51,21 @@ Color summaryIntervalColor (
return c;
}
////////////////////////////////////////////////////////////////////////////////
Color summaryIntervalColor (
std::map <std::string, Color>& tagColors,
const std::set <std::string>& tags)
{
Color c;
for (const auto& tag : tags)
{
c.blend (tagColors[tag]);
}
return c;
}
////////////////////////////////////////////////////////////////////////////////
// Select a color to represent the interval on a chart.
Color chartIntervalColor (
@ -410,6 +426,30 @@ std::map <std::string, Color> createTagColorMap (
return mapping;
}
////////////////////////////////////////////////////////////////////////////////
std::map <std::string, Color> createTagColorMap (const Rules& rules, const std::vector <Interval>& intervals)
{
std::set <std::string> tags;
for (const auto& interval : intervals)
{
tags.insert (interval.tags ().begin (), interval.tags ().end ());
}
std::map <std::string, Color> mapping;
for (const auto& tag : tags)
{
std::string key = "tags." + tag + ".color";
if (rules.has (key))
{
mapping[tag] = Color (rules.get (key));
}
}
return mapping;
}
////////////////////////////////////////////////////////////////////////////////
int quantizeToNMinutes (const int minutes, const int N)
{

View file

@ -69,6 +69,7 @@ int dispatchCommand (const CLI&, Database&, Journal&, Rules&, const Extensions&)
// helper.cpp
Color summaryIntervalColor (const Rules&, const std::set <std::string>&);
Color summaryIntervalColor (std::map <std::string, Color>&, const std::set <std::string>&);
Color chartIntervalColor (const std::set <std::string>&, const std::map <std::string, Color>&);
Color tagColor (const Rules&, const std::string&);
std::string intervalSummarize (const Rules&, const Interval&);
@ -76,6 +77,7 @@ bool expandIntervalHint (const std::string&, Range&);
std::string jsonFromIntervals (const std::vector <Interval>&);
Palette createPalette (const Rules&);
std::map <std::string, Color> createTagColorMap (const Rules&, Palette&, const std::vector <Interval>&);
std::map <std::string, Color> createTagColorMap (const Rules& rules, const std::vector <Interval>& intervals);
int quantizeToNMinutes (int, int);
bool findHint (const CLI&, const std::string&);