Replace CLI::getFilter by CLI::getRange and CLI::getTags

- Add CLI::getRange
- Make CLI::getTags return a set instead of a vector. This has the side effect that the tags are sorted now...
- Replace variable 'filter' by 'range' and 'tags'. Variable 'filter' was just a wrapper, better use the components directly
This commit is contained in:
Thomas Lauf 2023-04-14 19:10:14 +02:00
parent 0c84dfd42e
commit 34bba44d38
28 changed files with 221 additions and 219 deletions

View file

@ -564,14 +564,14 @@ std::set<int> CLI::getIds() const
}
////////////////////////////////////////////////////////////////////////////////
std::vector<std::string> CLI::getTags () const
std::set <std::string> CLI::getTags () const
{
std::vector <std::string> tags;
std::set <std::string> tags;
for (auto& arg : _args)
{
if (arg.hasTag ("TAG"))
tags.push_back (arg.attribute ("raw"));
tags.insert (arg.attribute ("raw"));
}
return tags;
@ -628,20 +628,18 @@ std::vector <std::string> CLI::getDomReferences () const
}
////////////////////////////////////////////////////////////////////////////////
// A filter is just another interval, containing start, end and tags.
//
// Supported interval forms:
// Supported range forms:
// ["from"] <date> ["to"|"-" <date>]
// ["from"] <date> "for" <duration>
// <duration> ["before"|"after" <date>]
// <duration> "ago"
//
Interval CLI::getFilter (const Range& default_range) const
Range CLI::getRange(const Range& default_range) const
{
// One instance, so we can directly compare.
Datetime now;
Interval filter;
Range the_range;
std::string start;
std::string end;
std::string duration;
@ -666,16 +664,16 @@ Interval CLI::getFilter (const Range& default_range) const
{
if (range.is_empty ())
{
args.emplace_back ("<all>");
args.emplace_back("<all>");
}
else
{
start = range.start.toISO ();
end = range.end.toISO ();
args.emplace_back ("<date>");
args.emplace_back ("-");
args.emplace_back ("<date>");
args.emplace_back("<date>");
args.emplace_back("-");
args.emplace_back("<date>");
}
}
@ -688,14 +686,14 @@ Interval CLI::getFilter (const Range& default_range) const
else if (end.empty ())
end = raw;
args.emplace_back ("<date>");
args.emplace_back("<date>");
}
else if (arg._lextype == Lexer::Type::duration)
{
if (duration.empty ())
duration = raw;
args.emplace_back ("<duration>");
args.emplace_back("<duration>");
}
else if (arg.hasTag ("KEYWORD"))
{
@ -704,37 +702,27 @@ Interval CLI::getFilter (const Range& default_range) const
// function.
args.push_back (raw);
}
else if (arg.hasTag ("ID"))
{
// Not part of a filter.
}
else
{
filter.tag (raw);
}
}
}
if (args.empty ())
{
filter.setRange(default_range);
the_range = default_range;
}
// <date>
else if (args.size () == 1 &&
args[0] == "<date>")
args[0] == "<date>")
{
DatetimeParser dtp;
Range range = dtp.parse_range(start);
filter.setRange (range);
the_range = Range (range);
}
// from <date>
else if (args.size () == 2 &&
args[0] == "from" &&
args[1] == "<date>")
{
filter.setRange ({Datetime (start), 0});
the_range = Range ({Datetime (start), 0});
}
// <date> to/- <date>
else if (args.size () == 3 &&
@ -742,9 +730,8 @@ Interval CLI::getFilter (const Range& default_range) const
(args[1] == "to" || args[1] == "-") &&
args[2] == "<date>")
{
filter.setRange ({Datetime (start), Datetime (end)});
the_range = Range ({Datetime (start), Datetime (end)});
}
// from <date> to/- <date>
else if (args.size () == 4 &&
args[0] == "from" &&
@ -752,18 +739,16 @@ Interval CLI::getFilter (const Range& default_range) const
(args[2] == "to" || args[2] == "-") &&
args[3] == "<date>")
{
filter.setRange ({Datetime (start), Datetime (end)});
the_range = Range ({Datetime (start), Datetime (end)});
}
// <date> for <duration>
else if (args.size () == 3 &&
args[0] == "<date>" &&
args[1] == "for" &&
args[2] == "<duration>")
{
filter.setRange ({Datetime (start), Datetime (start) + Duration (duration).toTime_t ()});
the_range = Range ({Datetime (start), Datetime (start) + Duration (duration).toTime_t ()});
}
// from <date> for <duration>
else if (args.size () == 4 &&
args[0] == "from" &&
@ -771,66 +756,61 @@ Interval CLI::getFilter (const Range& default_range) const
args[2] == "for" &&
args[3] == "<duration>")
{
filter.setRange ({Datetime (start), Datetime (start) + Duration (duration).toTime_t ()});
the_range = Range ({Datetime (start), Datetime (start) + Duration (duration).toTime_t ()});
}
// <duration> before <date>
else if (args.size () == 3 &&
args[0] == "<duration>" &&
args[1] == "before" &&
args[2] == "<date>")
{
filter.setRange ({Datetime (start) - Duration (duration).toTime_t (), Datetime (start)});
the_range = Range ({Datetime (start) - Duration (duration).toTime_t (), Datetime (start)});
}
// <duration> after <date>
else if (args.size () == 3 &&
args[0] == "<duration>" &&
args[1] == "after" &&
args[2] == "<date>")
{
filter.setRange ({Datetime (start), Datetime (start) + Duration (duration).toTime_t ()});
the_range = Range ({Datetime (start), Datetime (start) + Duration (duration).toTime_t ()});
}
// <duration> ago
else if (args.size () == 2 &&
args[0] == "<duration>" &&
args[1] == "ago")
{
filter.setRange ({now - Duration (duration).toTime_t (), 0});
the_range = Range ({now - Duration (duration).toTime_t (), 0});
}
// for <duration>
else if (args.size () == 2 &&
args[0] == "for" &&
args[1] == "<duration>")
{
filter.setRange ({now - Duration (duration).toTime_t (), now});
the_range = Range ({now - Duration (duration).toTime_t (), now});
}
// <duration>
else if (args.size () == 1 &&
args[0] == "<duration>")
{
filter.setRange ({now - Duration (duration).toTime_t (), now});
the_range = Range ({now - Duration (duration).toTime_t (), now});
}
// :all
else if (args.size () == 1 && args[0] == "<all>")
{
filter.setRange (0, 0);
the_range = Range (0, 0);
}
// Unrecognized date range construct.
// Unrecognized date range construct.
else if (! args.empty ())
{
throw std::string ("Unrecognized date range: '") + join (" ", args) + "'.";
}
if (filter.end != 0 && filter.start > filter.end)
if (the_range.end != 0 && the_range.start > the_range.end)
{
throw std::string ("The end of a date range must be after the start.");
}
return filter;
return the_range;
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -70,11 +70,11 @@ public:
bool getComplementaryHint (const std::string&, bool) const;
bool getHint(const std::string&, bool) const;
std::set <int> getIds () const;
std::vector<std::string> getTags () const;
std::set<std::string> getTags () const;
std::string getAnnotation() const;
Duration getDuration() const;
std::vector<std::string> getDomReferences () const;
Interval getFilter (const Range& = {}) const;
Range getRange (const Range& default_range = {0, 0}) const;
std::string dump (const std::string& title = "CLI Parser") const;
private:

View file

@ -61,14 +61,14 @@ Chart::Chart (const ChartConfig& configuration) :
{ }
std::string Chart::render (
const Interval &filter,
const Range& range,
const std::vector<Interval> &tracked,
const std::vector<Range> &exclusions,
const std::map<Datetime, std::string> &holidays)
{
// Determine hours shown.
auto hour_range = determine_hour_range
? determineHourRange (filter, tracked)
? determineHourRange (range, tracked)
: std::make_pair (0, 23);
int first_hour = hour_range.first;
@ -97,7 +97,7 @@ std::string Chart::render (
// Each day is rendered separately.
time_t total_work = 0;
for (Datetime day = filter.start; day < filter.end; day++)
for (Datetime day = range.start; day < range.end; day++)
{
// Render the exclusion blocks.
@ -154,7 +154,7 @@ std::string Chart::render (
out << (with_totals ? renderSubTotal (total_work, std::string (padding_size, ' ')) : "")
<< (with_holidays ? renderHolidays (holidays) : "")
<< (with_summary ? renderSummary (indent, filter, exclusions, tracked) : "");
<< (with_summary ? renderSummary (indent, range, exclusions, tracked) : "");
return out.str ();
}
@ -172,7 +172,7 @@ unsigned long Chart::getIndentSize ()
// Scan all tracked intervals, looking for the earliest and latest hour into
// which an interval extends.
std::pair<int, int> Chart::determineHourRange (
const Interval &filter,
const Range& range,
const std::vector<Interval> &tracked)
{
// If there is no data, show the whole day.
@ -185,11 +185,11 @@ std::pair<int, int> Chart::determineHourRange (
auto first_hour = 23;
auto last_hour = 0;
for (Datetime day = filter.start; day < filter.end; day++)
for (Datetime day = range.start; day < range.end; day++)
{
auto day_range = getFullDay (day);
for (auto &track : tracked)
for (auto& track : tracked)
{
Interval test {track};
@ -549,7 +549,7 @@ std::string Chart::renderHolidays (const std::map<Datetime, std::string> &holida
////////////////////////////////////////////////////////////////////////////////
std::string Chart::renderSummary (
const std::string &indent,
const Interval &filter,
const Range& range,
const std::vector<Range> &exclusions,
const std::vector<Interval> &tracked)
{
@ -558,9 +558,9 @@ std::string Chart::renderSummary (
for (auto &exclusion : exclusions)
{
if (filter.overlaps (exclusion))
if (range.overlaps (exclusion))
{
total_unavailable += filter.intersect (exclusion).total ();
total_unavailable += range.intersect (exclusion).total ();
}
}
@ -570,9 +570,9 @@ std::string Chart::renderSummary (
{
for (auto &interval : tracked)
{
if (filter.overlaps (interval))
if (range.overlaps (interval))
{
Interval clipped = clip (interval, filter);
Interval clipped = clip (interval, range);
if (interval.is_open ())
{
clipped.end = reference_datetime;
@ -583,7 +583,7 @@ std::string Chart::renderSummary (
}
}
auto total_available = filter.total () - total_unavailable;
auto total_available = range.total () - total_unavailable;
assert (total_available >= 0);
auto total_remaining = total_available - total_worked;

View file

@ -37,7 +37,7 @@ class Chart
public:
explicit Chart (const ChartConfig& configuration);
std::string render (const Interval&, const std::vector <Interval>&, const std::vector <Range>&, const std::map <Datetime, std::string>&);
std::string render (const Range&, const std::vector <Interval>&, const std::vector <Range>&, const std::map <Datetime, std::string>&);
private:
std::string renderAxis (int, int);
@ -45,7 +45,7 @@ private:
std::string renderHolidays (const std::map <Datetime, std::string>&);
std::string renderMonth (const Datetime&, const Datetime&);
std::string renderSubTotal (time_t, const std::string&);
std::string renderSummary (const std::string&, const Interval&, const std::vector <Range>&, const std::vector <Interval>&);
std::string renderSummary (const std::string&, const Range&, const std::vector <Range>&, const std::vector <Interval>&);
std::string renderTotal (time_t);
std::string renderWeek (const Datetime&, const Datetime&);
std::string renderWeekday (Datetime&, const Color&);
@ -55,7 +55,7 @@ private:
unsigned long getIndentSize ();
std::pair <int, int> determineHourRange (const Interval&, const std::vector <Interval>&);
std::pair <int, int> determineHourRange (const Range&, const std::vector <Interval>&);
Color getDayColor (const Datetime&, const std::map <Datetime, std::string>&);
Color getHourColor (int) const;

View file

@ -86,7 +86,7 @@ Range DatetimeParser::parse_range (const std::string& input)
{
start = pig.cursor ();
resolve ();
return Range { Datetime {_date}, 0 };
return Range {Datetime {_date}, 0};
}
}
@ -115,13 +115,13 @@ Range DatetimeParser::parse_range (const std::string& input)
{
auto start_date = Datetime (_date);
auto end_date = Datetime(start_date.year(), start_date.month()+1, 1);
return Range { start_date, end_date };
return Range {start_date, end_date};
}
else if (_year != 0)
{
auto start_date = Datetime (_date);
auto end_date = Datetime(start_date.year()+1, 1, 1);
return Range { start_date, end_date };
return Range {start_date, end_date};
}
return Range {};
}
@ -145,7 +145,7 @@ Range DatetimeParser::parse_range (const std::string& input)
{
start = pig.cursor ();
resolve ();
return Range { Datetime (_date), 0 };
return Range {Datetime (_date), 0};
}
}
@ -153,14 +153,14 @@ Range DatetimeParser::parse_range (const std::string& input)
if (parse_informal_time (pig))
{
return Range { Datetime {_date}, 0 };
return Range {Datetime {_date}, 0};
}
if (parse_named_day (pig))
{
// ::validate and ::resolve are not needed in this case.
start = pig.cursor ();
return Range { Datetime (_date), Datetime (_date) + Duration ("1d").toTime_t () };
return Range {Datetime (_date), Datetime (_date) + Duration ("1d").toTime_t ()};
}
if (parse_named_month (pig))
@ -171,14 +171,14 @@ Range DatetimeParser::parse_range (const std::string& input)
auto month = (begin.month() + 1) % 13 + (begin.month() == 12);
auto year = (begin.year() + (begin.month() == 12));
auto end = Datetime (year, month, 1);
return Range { begin, end };
return Range {begin, end};
}
if (parse_named (pig))
{
// ::validate and ::resolve are not needed in this case.
start = pig.cursor ();
return Range { Datetime (_date), 0 };
return Range {Datetime (_date), 0};
}
throw format ("'{1}' is not a valid range.", input);

View file

@ -30,12 +30,15 @@
#include <Range.h>
#include <set>
#include <string>
#include <utility>
class Interval : public Range
{
public:
Interval () = default;
Interval (const Datetime& start, const Datetime& end) : Range (start, end) {}
Interval (const Range& range, std::set <std::string> tags) : Range (range), _tags(std::move(tags)) {}
bool operator== (const Interval&) const;
bool operator!= (const Interval&) const;

View file

@ -41,8 +41,8 @@ int CmdAnnotate (
{
const bool verbose = rules.getBoolean ("verbose");
std::set <int> ids = cli.getIds ();
std::string annotation = cli.getAnnotation ();
auto ids = cli.getIds ();
auto annotation = cli.getAnnotation ();
journal.startTransaction ();
flattenDatabase (database, rules);

View file

@ -36,9 +36,9 @@
#include <iostream>
#include <timew.h>
int renderChart (const CLI&, const std::string&, Interval&, Rules&, Database&);
int renderChart (const std::string&, const CLI&, Rules&, Database&);
std::map <Datetime, std::string> createHolidayMap (Rules&, Interval&);
std::map <Datetime, std::string> createHolidayMap (Rules&, Range&);
////////////////////////////////////////////////////////////////////////////////
int CmdChartDay (
@ -46,16 +46,7 @@ int CmdChartDay (
Rules& rules,
Database& database)
{
auto default_hint = rules.get ("reports.range", "day");
auto report_hint = rules.get ("reports.day.range", default_hint);
Range default_range = {};
expandIntervalHint (":" + report_hint, default_range);
// Create a filter, and if empty, choose the current day.
auto filter = cli.getFilter (default_range);
return renderChart (cli, "day", filter, rules, database);
return renderChart ("day", cli, rules, database);
}
////////////////////////////////////////////////////////////////////////////////
@ -64,16 +55,7 @@ int CmdChartWeek (
Rules& rules,
Database& database)
{
auto default_hint = rules.get ("reports.range", "week");
auto report_hint = rules.get ("reports.week.range", default_hint);
Range default_range = {};
expandIntervalHint (":" + report_hint, default_range);
// Create a filter, and if empty, choose the current week.
auto filter = cli.getFilter (default_range);
return renderChart (cli, "week", filter, rules, database);
return renderChart ("week", cli, rules, database);
}
////////////////////////////////////////////////////////////////////////////////
@ -82,32 +64,31 @@ int CmdChartMonth (
Rules& rules,
Database& database)
{
auto default_hint = rules.get ("reports.range", "month");
auto report_hint = rules.get ("reports.month.range", default_hint);
Range default_range = {};
expandIntervalHint (":" + report_hint, default_range);
// Create a filter, and if empty, choose the current month.
auto filter = cli.getFilter (default_range);
return renderChart (cli, "month", filter, rules, database);
return renderChart ("month", cli, rules, database);
}
////////////////////////////////////////////////////////////////////////////////
int renderChart (
const CLI& cli,
const std::string& type,
Interval& filter,
const CLI& cli,
Rules& rules,
Database& database)
{
const bool verbose = rules.getBoolean ("verbose");
auto default_hint = rules.get ("reports.range", type);
auto report_hint = rules.get ("reports." + type + ".range", default_hint);
Range default_range = {};
expandIntervalHint (":" + report_hint, default_range);
auto range = cli.getRange (default_range);
auto tags = cli.getTags ();
// Load the data.
IntervalFilterAndGroup filtering ({
std::make_shared <IntervalFilterAllInRange> ( Range { filter.start, filter.end }),
std::make_shared <IntervalFilterAllWithTags> (filter.tags())
std::make_shared <IntervalFilterAllInRange> (range),
std::make_shared <IntervalFilterAllWithTags> (tags)
});
auto tracked = getTracked (database, rules, filtering);
@ -118,16 +99,18 @@ int renderChart (
{
std::cout << "No filtered data found";
if (filter.is_started ())
if (range.is_started ())
{
std::cout << " in the range " << filter.start.toISOLocalExtended ();
if (filter.is_ended ())
std::cout << " - " << filter.end.toISOLocalExtended ();
std::cout << " in the range " << range.start.toISOLocalExtended ();
if (range.is_ended ())
{
std::cout << " - " << range.end.toISOLocalExtended ();
}
}
if (! filter.tags ().empty ())
if (! tags.empty ())
{
std::cout << " tagged with " << joinQuotedIfNeeded (", ", filter.tags ());
std::cout << " tagged with " << joinQuotedIfNeeded (", ", tags);
}
std::cout << ".\n";
@ -136,8 +119,8 @@ int renderChart (
return 0;
}
const auto exclusions = getAllExclusions (rules, filter);
const auto holidays = createHolidayMap (rules, filter);
const auto exclusions = getAllExclusions (rules, range);
const auto holidays = createHolidayMap (rules, range);
// Map tags to colors.
auto palette = createPalette (rules);
@ -146,12 +129,16 @@ int renderChart (
const auto minutes_per_char = rules.getInteger ("reports." + type + ".cell");
if (minutes_per_char < 1)
{
throw format ("The value for 'reports.{1}.cell' must be at least 1.", type);
}
const auto num_lines = rules.getInteger ("reports." + type + ".lines", 1);
if (num_lines < 1)
{
throw format ("Invalid value for 'reports.{1}.lines': '{2}'", type, num_lines);
}
ChartConfig configuration {};
configuration.reference_datetime = Datetime ();
@ -177,13 +164,13 @@ int renderChart (
Chart chart (configuration);
std::cout << chart.render (filter, tracked, exclusions, holidays);
std::cout << chart.render (range, tracked, exclusions, holidays);
return 0;
}
////////////////////////////////////////////////////////////////////////////////
std::map <Datetime, std::string> createHolidayMap (Rules &rules, Interval &filter)
std::map <Datetime, std::string> createHolidayMap (Rules& rules, Range& range)
{
std::map <Datetime, std::string> mapping;
auto holidays = rules.all ("holidays.");
@ -199,7 +186,7 @@ std::map <Datetime, std::string> createHolidayMap (Rules &rules, Interval &filte
std::replace (date.begin (), date.end (), '_', '-');
Datetime holiday (date);
if (holiday >= filter.start && holiday <= filter.end)
if (holiday >= range.start && holiday <= range.end)
{
std::stringstream out;
out << " ["

View file

@ -44,16 +44,17 @@ int CmdContinue (
const bool verbose = rules.getBoolean ("verbose");
const Datetime now {};
auto filter = cli.getFilter ({ now, 0 });
auto range = cli.getRange ({now, 0});
if (filter.start > now)
if (range.start > now)
{
throw std::string ("Time tracking cannot be set in the future.");
}
std::set <int> ids = cli.getIds ();
auto tags = cli.getTags ();
auto ids = cli.getIds ();
if (!ids.empty () && !filter.tags().empty ())
if (! ids.empty () && ! tags.empty ())
{
throw std::string ("You cannot specify both id and tags to continue an interval.");
}
@ -74,14 +75,14 @@ int CmdContinue (
throw format ("ID '@{1}' does not correspond to any tracking.", *ids.begin ());
}
}
else if (!filter.tags ().empty ())
else if (!tags.empty ())
{
IntervalFilterFirstOf filtering {std::make_shared <IntervalFilterAllWithTags> (filter.tags ())};
IntervalFilterFirstOf filtering {std::make_shared <IntervalFilterAllWithTags> (tags)};
intervals = getTracked (database, rules, filtering);
if (intervals.empty ())
{
throw format ("Tags '{1}' do not correspond to any tracking.", joinQuotedIfNeeded (", ", filter.tags ()));
throw format ("Tags '{1}' do not correspond to any tracking.", joinQuotedIfNeeded (", ", tags));
}
}
else
@ -106,10 +107,10 @@ int CmdContinue (
Datetime start_time;
Datetime end_time;
if (filter.is_started ())
if (range.is_started ())
{
start_time = filter.start;
end_time = filter.end;
start_time = range.start;
end_time = range.end;
}
else
{

View file

@ -39,8 +39,7 @@ int CmdDelete (
{
const bool verbose = rules.getBoolean ("verbose");
// Gather IDs.
std::set <int> ids = cli.getIds ();
auto ids = cli.getIds ();
if (ids.empty ())
{
@ -53,7 +52,6 @@ int CmdDelete (
auto filtering = IntervalFilterAllWithIds (ids);
auto intervals = getTracked (database, rules, filtering);
if (intervals.size () != ids.size ())
{
for (auto& id: ids)

View file

@ -38,16 +38,19 @@ int CmdExport (
Rules& rules,
Database& database)
{
auto filter = cli.getFilter ();
std::set <int> ids = cli.getIds ();
auto ids = cli.getIds ();
auto range = cli.getRange ();
auto tags = cli.getTags ();
std::shared_ptr <IntervalFilter> filtering;
if (!ids.empty ())
if (! ids.empty ())
{
if (!filter.empty ())
if (! range.is_empty ())
{
throw std::string ("You cannot specify both id and tags/range to export intervals.");
}
filtering = std::make_shared <IntervalFilterAllWithIds> (ids);
}
else
@ -55,14 +58,15 @@ int CmdExport (
filtering = std::make_shared <IntervalFilterAndGroup> (
std::vector <std::shared_ptr <IntervalFilter>> (
{
std::make_shared <IntervalFilterAllInRange> (Range{filter.start, filter.end}),
std::make_shared <IntervalFilterAllWithTags> (filter.tags ()),
std::make_shared <IntervalFilterAllInRange> (range),
std::make_shared <IntervalFilterAllWithTags> (tags),
}
)
);
}
auto intervals = getTracked (database, rules, *filtering);
std::cout << jsonFromIntervals (intervals);
return 0;

View file

@ -40,7 +40,7 @@ int CmdFill (
{
const bool verbose = rules.getBoolean ("verbose");
std::set <int> ids = cli.getIds ();
auto ids = cli.getIds ();
if (ids.empty ())
{
@ -48,10 +48,7 @@ int CmdFill (
}
// Load the data.
// Note: There is no filter.
Interval filter;
auto filtering = IntervalFilterAllInRange ({ 0, 0 });
auto filtering = IntervalFilterAllInRange ({0, 0});
auto tracked = getTracked (database, rules, filtering);
journal.startTransaction ();
@ -60,7 +57,9 @@ int CmdFill (
for (auto& id : ids)
{
if (id > static_cast <int> (tracked.size ()))
{
throw format ("ID '@{1}' does not correspond to any tracking.", id);
}
Interval from = tracked[tracked.size () - id];
std::cout << "# from " << from.dump () << "\n";

View file

@ -45,16 +45,22 @@ int CmdGaps (
Range default_range = {};
expandIntervalHint (":" + report_hint, default_range);
auto filter = cli.getFilter (default_range);
auto range = cli.getRange (default_range);
auto tags = cli.getTags ();
auto filter = Interval {range, tags};
// Is the :blank hint being used?
bool blank = cli.getHint ("blank", false);
std::vector <Range> untracked;
if (blank)
untracked = subtractRanges ({filter}, getAllExclusions (rules, filter));
{
untracked = subtractRanges ({range}, getAllExclusions (rules, range));
}
else
{
untracked = getUntracked (database, rules, filter);
}
Table table;
table.width (1024);
@ -70,7 +76,7 @@ int CmdGaps (
// Each day is rendered separately.
time_t grand_total = 0;
Datetime previous;
for (Datetime day = filter.start; day < filter.end; day++)
for (Datetime day = range.start; day < range.end; day++)
{
auto day_range = getFullDay (day);
time_t daily_total = 0;

View file

@ -37,9 +37,10 @@ int CmdGet (
Rules& rules,
Database& database)
{
auto references = cli.getDomReferences ();
auto filter = Interval {cli.getRange (), cli.getTags()};
std::vector <std::string> results;
std::vector <std::string> references = cli.getDomReferences ();
Interval filter = cli.getFilter ();
for (auto& reference : references)
{

View file

@ -41,7 +41,7 @@ int CmdJoin (
const bool verbose = rules.getBoolean ("verbose");
// Gather IDs and TAGs.
std::set <int> ids = cli.getIds ();
auto ids = cli.getIds ();
// Only 2 IDs allowed in a join.
if (ids.size () != 2)

View file

@ -40,15 +40,14 @@ int CmdLengthen (
{
const bool verbose = rules.getBoolean ("verbose");
// Gather IDs and TAGs.
std::set <int> ids = cli.getIds ();
auto ids = cli.getIds ();
if (ids.empty ())
{
throw std::string ("IDs must be specified. See 'timew help lengthen'.");
}
Duration dur = cli.getDuration ();
auto dur = cli.getDuration ();
journal.startTransaction ();

View file

@ -39,10 +39,9 @@ int CmdModify (
{
const bool verbose = rules.getBoolean ("verbose");
auto filter = cli.getFilter ();
std::set <int> ids = cli.getIds ();
std::vector <std::string> words = cli.getWords ();
enum { MODIFY_START, MODIFY_END } op = MODIFY_START;
auto words = cli.getWords ();
enum {MODIFY_START, MODIFY_END} op = MODIFY_START;
if (words.empty())
{
@ -62,6 +61,8 @@ int CmdModify (
throw format ("Must specify start|end command to modify. See 'timew help modify'.", words.at (0));
}
auto ids = cli.getIds ();
if (ids.empty ())
{
throw std::string ("ID must be specified. See 'timew help modify'.");
@ -72,6 +73,8 @@ int CmdModify (
throw std::string ("Only one ID may be specified. See 'timew help modify'.");
}
auto range = cli.getRange ({0, 0});
int id = *ids.begin();
flattenDatabase (database, rules);
@ -84,7 +87,7 @@ int CmdModify (
}
assert (intervals.size () == 1);
if (! filter.is_started ())
if (! range.is_started ())
{
throw std::string ("No updated time specified. See 'timew help modify'.");
}
@ -94,7 +97,7 @@ int CmdModify (
switch (op)
{
case MODIFY_START:
modified.start = filter.start;
modified.start = range.start;
break;
case MODIFY_END:
@ -102,7 +105,7 @@ int CmdModify (
{
throw format ("Cannot modify end of open interval @{1}.", id);
}
modified.end = filter.start;
modified.end = range.start;
break;
}

View file

@ -134,24 +134,28 @@ int CmdReport (
expandIntervalHint (":" + report_hint, default_range);
// Create a filter, and if empty, choose the current week.
auto filter = cli.getFilter (default_range);
auto tags = cli.getTags ();
auto range = cli.getRange (default_range);
IntervalFilterAndGroup filtering ({
std::make_shared <IntervalFilterAllInRange> ( Range { filter.start, filter.end }),
std::make_shared <IntervalFilterAllWithTags> (filter.tags ())
std::make_shared <IntervalFilterAllInRange> (range),
std::make_shared <IntervalFilterAllWithTags> (tags)
});
auto tracked = getTracked (database, rules, filtering);
// Compose Header info.
rules.set ("temp.report.start", filter.is_started () ? filter.start.toISO () : "");
rules.set ("temp.report.end", filter.is_ended () ? filter.end.toISO () : "");
rules.set ("temp.report.tags", joinQuotedIfNeeded (",", filter.tags ()));
rules.set ("temp.report.start", range.is_started () ? range.start.toISO () : "");
rules.set ("temp.report.end", range.is_ended () ? range.end.toISO () : "");
rules.set ("temp.report.tags", joinQuotedIfNeeded (",", tags));
rules.set ("temp.version", VERSION);
std::stringstream header;
for (auto& name : rules.all ())
{
header << name << ": " << rules.get (name) << '\n';
}
// Get the data.
auto input = header.str ()
@ -168,7 +172,9 @@ int CmdReport (
// Display the output.
for (auto& line : output)
{
std::cout << line << '\n';
}
return rc;
}

View file

@ -38,13 +38,14 @@ int CmdStart (
const bool verbose = rules.getBoolean ("verbose");
const Datetime now {};
auto interval = cli.getFilter ({ now, 0 });
auto range = cli.getRange ({now, 0});
auto tags = cli.getTags ();
if (interval.start > now)
if (range.start > now)
{
throw std::string ("Time tracking cannot be set in the future.");
}
else if (!interval.is_started () || interval.is_ended ())
else if (! range.is_started () || range.is_ended ())
{
throw std::string ("The start command does not accept ranges but only a single datetime. "
"Perhaps you want the track command?");
@ -58,6 +59,8 @@ int CmdStart (
}
journal.startTransaction ();
auto interval = Interval {range, tags};
if (validate (cli, rules, database, interval))
{
database.addInterval (interval, verbose);

View file

@ -50,7 +50,6 @@ int CmdStop (
const bool verbose = rules.getBoolean ("verbose");
const Datetime now {};
auto filter = cli.getFilter ({ now, 0 });
// Load the most recent interval.
auto latest = getLatestInterval (database);
@ -67,30 +66,34 @@ int CmdStop (
"Perhaps you want the modify command?.");
}
if (! filter.is_started())
auto range = cli.getRange ({now, 0});
if (! range.is_started ())
{
throw std::string ("No datetime specified.");
}
else if (filter.start <= latest.start)
else if (range.start <= latest.start)
{
throw std::string ("The end of a date range must be after the start.");
}
auto tags = cli.getTags ();
std::set <std::string> diff = {};
if(! std::includes(latest.tags ().begin (), latest.tags ().end (),
filter.tags ().begin (), filter.tags ().end ()))
tags.begin (), tags.end ()))
{
std::set_difference(filter.tags ().begin (), filter.tags ().end (),
std::set_difference(tags.begin (), tags.end (),
latest.tags ().begin (), latest.tags ().end (),
std::inserter(diff, diff.begin ()));
throw format ("The current interval does not have the '{1}' tag.", *diff.begin ());
}
else if (!filter.tags ().empty ())
else if (! tags.empty ())
{
std::set_difference(latest.tags ().begin (), latest.tags ().end (),
filter.tags ().begin (), filter.tags ().end (),
tags.begin (), tags.end (),
std::inserter(diff, diff.begin()));
}
@ -98,8 +101,8 @@ int CmdStop (
if (diff.empty ())
{
Interval modified { latest };
modified.end = filter.start;
Interval modified {latest};
modified.end = range.start;
database.deleteInterval (latest);
validate (cli, rules, database, modified);
@ -116,7 +119,7 @@ int CmdStop (
}
else
{
Interval next { filter.start, 0 };
Interval next {range.start, 0};
for (auto& tag : diff)
{
next.tag (tag);

View file

@ -36,7 +36,7 @@
#include <utf8.h>
// Implemented in CmdChart.cpp.
std::map <Datetime, std::string> createHolidayMap (Rules&, Interval&);
std::map <Datetime, std::string> createHolidayMap (Rules&, Range&);
std::string renderHolidays (const std::map <Datetime, std::string>&);
////////////////////////////////////////////////////////////////////////////////
@ -53,12 +53,13 @@ int CmdSummary (
Range default_range = {};
expandIntervalHint (":" + report_hint, default_range);
auto filter = cli.getFilter (default_range);
auto range = cli.getRange (default_range);
auto tags = cli.getTags ();
// Load the data.
IntervalFilterAndGroup filtering ({
std::make_shared <IntervalFilterAllInRange> ( Range { filter.start, filter.end }),
std::make_shared <IntervalFilterAllWithTags> (filter.tags())
std::make_shared <IntervalFilterAllInRange> (range),
std::make_shared <IntervalFilterAllWithTags> (tags)
});
auto tracked = getTracked (database, rules, filtering);
@ -69,16 +70,18 @@ int CmdSummary (
{
std::cout << "No filtered data found";
if (filter.is_started ())
if (range.is_started ())
{
std::cout << " in the range " << filter.start.toISOLocalExtended ();
if (filter.is_ended ())
std::cout << " - " << filter.end.toISOLocalExtended ();
std::cout << " in the range " << range.start.toISOLocalExtended ();
if (range.is_ended ())
{
std::cout << " - " << range.end.toISOLocalExtended ();
}
}
if (! filter.tags ().empty ())
if (! tags.empty ())
{
std::cout << " tagged with " << joinQuotedIfNeeded (", ", filter.tags ());
std::cout << " tagged with " << joinQuotedIfNeeded (", ", tags);
}
std::cout << ".\n";
@ -159,8 +162,8 @@ int CmdSummary (
time_t grand_total = 0;
Datetime previous;
auto days_start = filter.is_started() ? filter.start : tracked.front ().start;
auto days_end = filter.is_ended() ? filter.end : tracked.back ().end;
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 ();
@ -221,8 +224,8 @@ int CmdSummary (
if (show_tags)
{
std::string tags = join (", ", track.tags ());
table.set (row, tags_col_index, tags, summaryIntervalColor (rules, track.tags ()));
std::string tags_string = join (", ", track.tags ());
table.set (row, tags_col_index, tags_string, summaryIntervalColor (rules, track.tags ()));
}
if (show_annotations)
@ -260,7 +263,7 @@ int CmdSummary (
std::cout << '\n'
<< table.render ()
<< (show_holidays ? renderHolidays (createHolidayMap (rules, filter)) : "")
<< (show_holidays ? renderHolidays (createHolidayMap (rules, range)) : "")
<< '\n';
return 0;

View file

@ -43,7 +43,7 @@ int CmdTag (
// Gather IDs and TAGs.
std::set <int> ids = cli.getIds ();
std::vector<std::string> tags = cli.getTags ();
std::set<std::string> tags = cli.getTags ();
if (tags.empty ())
{

View file

@ -42,15 +42,14 @@ int CmdTags (
{
const bool verbose = rules.getBoolean ("verbose");
// Create a filter, with no default range.
auto filter = cli.getFilter ();
IntervalFilterAndGroup filtering ({
std::make_shared <IntervalFilterAllInRange> ( Range { filter.start, filter.end }),
std::make_shared <IntervalFilterAllWithTags> (filter.tags ())
std::make_shared <IntervalFilterAllInRange> (cli.getRange ()),
std::make_shared <IntervalFilterAllWithTags> (cli.getTags ())
});
// Generate a unique, ordered list of tags.
std::set <std::string> tags;
for (const auto& interval : getTracked (database, rules, filtering))
for (auto& tag : interval.tags ())
tags.insert (tag);

View file

@ -37,8 +37,6 @@ int CmdTrack (
{
const bool verbose = rules.getBoolean ("verbose");
auto filter = cli.getFilter ();
// We expect no ids
if (! cli.getIds ().empty ())
{
@ -46,23 +44,32 @@ int CmdTrack (
"Perhaps you want the continue command?");
}
auto range = cli.getRange ();
// If this is not a proper closed interval, then the user is trying to make
// the 'track' command behave like 'start', so delegate to CmdStart.
if (! filter.is_started () ||
! filter.is_ended ())
if (! range.is_started () || ! range.is_ended ())
{
return CmdStart (cli, rules, database, journal);
}
auto tags = cli.getTags ();
journal.startTransaction ();
auto filter = Interval {range, tags};
// Validation must occur before flattening.
validate (cli, rules, database, filter);
for (auto& interval : flatten (filter, getAllExclusions (rules, filter)))
for (auto& interval : flatten (filter, getAllExclusions (rules, range)))
{
database.addInterval (interval, verbose);
if (verbose)
{
std::cout << intervalSummarize (rules, interval);
}
}
journal.endTransaction ();

View file

@ -43,7 +43,7 @@ int CmdUntag (
// Gather IDs and TAGs.
std::set <int> ids = cli.getIds ();
std::vector<std::string> tags = cli.getTags ();
std::set<std::string> tags = cli.getTags ();
if (tags.empty ())
{

View file

@ -119,8 +119,8 @@ bool domGet (
else if (pig.skipLiteral ("tracked."))
{
IntervalFilterAndGroup filtering ({
std::make_shared <IntervalFilterAllInRange> ( Range { filter.start, filter.end }),
std::make_shared <IntervalFilterAllWithTags> (filter.tags())
std::make_shared <IntervalFilterAllInRange> (Range {filter.start, filter.end}),
std::make_shared <IntervalFilterAllWithTags> (filter.tags ())
});
auto tracked = getTracked (database, rules, filtering);

View file

@ -124,7 +124,7 @@ class TestTag(TestCase):
code, out, err = self.t("tag @1 foo bar")
self.assertIn("Added foo bar to @1", out)
self.assertIn("Added bar foo to @1", out)
j = self.t.export()
self.assertOpenInterval(j[0], expectedTags=["bar", "foo"])
@ -138,7 +138,7 @@ class TestTag(TestCase):
code, out, err = self.t("tag @1 foo bar")
self.assertIn("Added foo bar to @1", out)
self.assertIn("Added bar foo to @1", out)
j = self.t.export()
self.assertClosedInterval(j[0], expectedTags=["bar", "foo"])
@ -171,7 +171,7 @@ class TestTag(TestCase):
code, out, err = self.t("tag @1 @2 foo bar")
self.assertIn("Added foo bar to @2\nAdded foo bar to @1", out)
self.assertIn("Added bar foo to @2\nAdded bar foo to @1", out)
j = self.t.export()
self.assertClosedInterval(j[0], expectedTags=["bar", "foo", "one"])

View file

@ -124,7 +124,7 @@ class TestUntag(TestCase):
code, out, err = self.t("untag @1 foo bar")
self.assertIn('Removed foo bar from @1', out)
self.assertIn('Removed bar foo from @1', out)
j = self.t.export()
self.assertOpenInterval(j[0], expectedTags=["baz"])
@ -138,7 +138,7 @@ class TestUntag(TestCase):
code, out, err = self.t("untag @1 foo bar")
self.assertIn('Removed foo bar from @1', out)
self.assertIn('Removed bar foo from @1', out)
j = self.t.export()
self.assertClosedInterval(j[0], expectedTags=["baz"])
@ -171,7 +171,7 @@ class TestUntag(TestCase):
code, out, err = self.t("untag @1 @2 foo bar")
self.assertIn('Removed foo bar from @2\nRemoved foo bar from @1', out)
self.assertIn('Removed bar foo from @2\nRemoved bar foo from @1', out)
j = self.t.export()
self.assertClosedInterval(j[0], expectedTags=["one"])