mirror of
https://github.com/GothenburgBitFactory/timewarrior.git
synced 2025-06-26 10:54:28 +02:00
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:
parent
0c84dfd42e
commit
34bba44d38
28 changed files with 221 additions and 219 deletions
82
src/CLI.cpp
82
src/CLI.cpp
|
@ -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)
|
for (auto& arg : _args)
|
||||||
{
|
{
|
||||||
if (arg.hasTag ("TAG"))
|
if (arg.hasTag ("TAG"))
|
||||||
tags.push_back (arg.attribute ("raw"));
|
tags.insert (arg.attribute ("raw"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return tags;
|
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> ["to"|"-" <date>]
|
||||||
// ["from"] <date> "for" <duration>
|
// ["from"] <date> "for" <duration>
|
||||||
// <duration> ["before"|"after" <date>]
|
// <duration> ["before"|"after" <date>]
|
||||||
// <duration> "ago"
|
// <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;
|
Datetime now;
|
||||||
|
|
||||||
Interval filter;
|
Range the_range;
|
||||||
std::string start;
|
std::string start;
|
||||||
std::string end;
|
std::string end;
|
||||||
std::string duration;
|
std::string duration;
|
||||||
|
@ -666,16 +664,16 @@ Interval CLI::getFilter (const Range& default_range) const
|
||||||
{
|
{
|
||||||
if (range.is_empty ())
|
if (range.is_empty ())
|
||||||
{
|
{
|
||||||
args.emplace_back ("<all>");
|
args.emplace_back("<all>");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
start = range.start.toISO ();
|
start = range.start.toISO ();
|
||||||
end = range.end.toISO ();
|
end = range.end.toISO ();
|
||||||
|
|
||||||
args.emplace_back ("<date>");
|
args.emplace_back("<date>");
|
||||||
args.emplace_back ("-");
|
args.emplace_back("-");
|
||||||
args.emplace_back ("<date>");
|
args.emplace_back("<date>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -688,14 +686,14 @@ Interval CLI::getFilter (const Range& default_range) const
|
||||||
else if (end.empty ())
|
else if (end.empty ())
|
||||||
end = raw;
|
end = raw;
|
||||||
|
|
||||||
args.emplace_back ("<date>");
|
args.emplace_back("<date>");
|
||||||
}
|
}
|
||||||
else if (arg._lextype == Lexer::Type::duration)
|
else if (arg._lextype == Lexer::Type::duration)
|
||||||
{
|
{
|
||||||
if (duration.empty ())
|
if (duration.empty ())
|
||||||
duration = raw;
|
duration = raw;
|
||||||
|
|
||||||
args.emplace_back ("<duration>");
|
args.emplace_back("<duration>");
|
||||||
}
|
}
|
||||||
else if (arg.hasTag ("KEYWORD"))
|
else if (arg.hasTag ("KEYWORD"))
|
||||||
{
|
{
|
||||||
|
@ -704,37 +702,27 @@ Interval CLI::getFilter (const Range& default_range) const
|
||||||
// function.
|
// function.
|
||||||
args.push_back (raw);
|
args.push_back (raw);
|
||||||
}
|
}
|
||||||
else if (arg.hasTag ("ID"))
|
|
||||||
{
|
|
||||||
// Not part of a filter.
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
filter.tag (raw);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.empty ())
|
if (args.empty ())
|
||||||
{
|
{
|
||||||
filter.setRange(default_range);
|
the_range = default_range;
|
||||||
}
|
}
|
||||||
|
|
||||||
// <date>
|
// <date>
|
||||||
else if (args.size () == 1 &&
|
else if (args.size () == 1 &&
|
||||||
args[0] == "<date>")
|
args[0] == "<date>")
|
||||||
{
|
{
|
||||||
DatetimeParser dtp;
|
DatetimeParser dtp;
|
||||||
Range range = dtp.parse_range(start);
|
Range range = dtp.parse_range(start);
|
||||||
filter.setRange (range);
|
the_range = Range (range);
|
||||||
}
|
}
|
||||||
|
|
||||||
// from <date>
|
// from <date>
|
||||||
else if (args.size () == 2 &&
|
else if (args.size () == 2 &&
|
||||||
args[0] == "from" &&
|
args[0] == "from" &&
|
||||||
args[1] == "<date>")
|
args[1] == "<date>")
|
||||||
{
|
{
|
||||||
filter.setRange ({Datetime (start), 0});
|
the_range = Range ({Datetime (start), 0});
|
||||||
}
|
}
|
||||||
// <date> to/- <date>
|
// <date> to/- <date>
|
||||||
else if (args.size () == 3 &&
|
else if (args.size () == 3 &&
|
||||||
|
@ -742,9 +730,8 @@ Interval CLI::getFilter (const Range& default_range) const
|
||||||
(args[1] == "to" || args[1] == "-") &&
|
(args[1] == "to" || args[1] == "-") &&
|
||||||
args[2] == "<date>")
|
args[2] == "<date>")
|
||||||
{
|
{
|
||||||
filter.setRange ({Datetime (start), Datetime (end)});
|
the_range = Range ({Datetime (start), Datetime (end)});
|
||||||
}
|
}
|
||||||
|
|
||||||
// from <date> to/- <date>
|
// from <date> to/- <date>
|
||||||
else if (args.size () == 4 &&
|
else if (args.size () == 4 &&
|
||||||
args[0] == "from" &&
|
args[0] == "from" &&
|
||||||
|
@ -752,18 +739,16 @@ Interval CLI::getFilter (const Range& default_range) const
|
||||||
(args[2] == "to" || args[2] == "-") &&
|
(args[2] == "to" || args[2] == "-") &&
|
||||||
args[3] == "<date>")
|
args[3] == "<date>")
|
||||||
{
|
{
|
||||||
filter.setRange ({Datetime (start), Datetime (end)});
|
the_range = Range ({Datetime (start), Datetime (end)});
|
||||||
}
|
}
|
||||||
|
|
||||||
// <date> for <duration>
|
// <date> for <duration>
|
||||||
else if (args.size () == 3 &&
|
else if (args.size () == 3 &&
|
||||||
args[0] == "<date>" &&
|
args[0] == "<date>" &&
|
||||||
args[1] == "for" &&
|
args[1] == "for" &&
|
||||||
args[2] == "<duration>")
|
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>
|
// from <date> for <duration>
|
||||||
else if (args.size () == 4 &&
|
else if (args.size () == 4 &&
|
||||||
args[0] == "from" &&
|
args[0] == "from" &&
|
||||||
|
@ -771,66 +756,61 @@ Interval CLI::getFilter (const Range& default_range) const
|
||||||
args[2] == "for" &&
|
args[2] == "for" &&
|
||||||
args[3] == "<duration>")
|
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>
|
// <duration> before <date>
|
||||||
else if (args.size () == 3 &&
|
else if (args.size () == 3 &&
|
||||||
args[0] == "<duration>" &&
|
args[0] == "<duration>" &&
|
||||||
args[1] == "before" &&
|
args[1] == "before" &&
|
||||||
args[2] == "<date>")
|
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>
|
// <duration> after <date>
|
||||||
else if (args.size () == 3 &&
|
else if (args.size () == 3 &&
|
||||||
args[0] == "<duration>" &&
|
args[0] == "<duration>" &&
|
||||||
args[1] == "after" &&
|
args[1] == "after" &&
|
||||||
args[2] == "<date>")
|
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
|
// <duration> ago
|
||||||
else if (args.size () == 2 &&
|
else if (args.size () == 2 &&
|
||||||
args[0] == "<duration>" &&
|
args[0] == "<duration>" &&
|
||||||
args[1] == "ago")
|
args[1] == "ago")
|
||||||
{
|
{
|
||||||
filter.setRange ({now - Duration (duration).toTime_t (), 0});
|
the_range = Range ({now - Duration (duration).toTime_t (), 0});
|
||||||
}
|
}
|
||||||
|
|
||||||
// for <duration>
|
// for <duration>
|
||||||
else if (args.size () == 2 &&
|
else if (args.size () == 2 &&
|
||||||
args[0] == "for" &&
|
args[0] == "for" &&
|
||||||
args[1] == "<duration>")
|
args[1] == "<duration>")
|
||||||
{
|
{
|
||||||
filter.setRange ({now - Duration (duration).toTime_t (), now});
|
the_range = Range ({now - Duration (duration).toTime_t (), now});
|
||||||
}
|
}
|
||||||
|
|
||||||
// <duration>
|
// <duration>
|
||||||
else if (args.size () == 1 &&
|
else if (args.size () == 1 &&
|
||||||
args[0] == "<duration>")
|
args[0] == "<duration>")
|
||||||
{
|
{
|
||||||
filter.setRange ({now - Duration (duration).toTime_t (), now});
|
the_range = Range ({now - Duration (duration).toTime_t (), now});
|
||||||
}
|
}
|
||||||
|
|
||||||
// :all
|
// :all
|
||||||
else if (args.size () == 1 && args[0] == "<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 ())
|
else if (! args.empty ())
|
||||||
{
|
{
|
||||||
throw std::string ("Unrecognized date range: '") + join (" ", args) + "'.";
|
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.");
|
throw std::string ("The end of a date range must be after the start.");
|
||||||
|
}
|
||||||
|
|
||||||
return filter;
|
return the_range;
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -70,11 +70,11 @@ public:
|
||||||
bool getComplementaryHint (const std::string&, bool) const;
|
bool getComplementaryHint (const std::string&, bool) const;
|
||||||
bool getHint(const std::string&, bool) const;
|
bool getHint(const std::string&, bool) const;
|
||||||
std::set <int> getIds () const;
|
std::set <int> getIds () const;
|
||||||
std::vector<std::string> getTags () const;
|
std::set<std::string> getTags () const;
|
||||||
std::string getAnnotation() const;
|
std::string getAnnotation() const;
|
||||||
Duration getDuration() const;
|
Duration getDuration() const;
|
||||||
std::vector<std::string> getDomReferences () 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;
|
std::string dump (const std::string& title = "CLI Parser") const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -61,14 +61,14 @@ Chart::Chart (const ChartConfig& configuration) :
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
std::string Chart::render (
|
std::string Chart::render (
|
||||||
const Interval &filter,
|
const Range& range,
|
||||||
const std::vector<Interval> &tracked,
|
const std::vector<Interval> &tracked,
|
||||||
const std::vector<Range> &exclusions,
|
const std::vector<Range> &exclusions,
|
||||||
const std::map<Datetime, std::string> &holidays)
|
const std::map<Datetime, std::string> &holidays)
|
||||||
{
|
{
|
||||||
// Determine hours shown.
|
// Determine hours shown.
|
||||||
auto hour_range = determine_hour_range
|
auto hour_range = determine_hour_range
|
||||||
? determineHourRange (filter, tracked)
|
? determineHourRange (range, tracked)
|
||||||
: std::make_pair (0, 23);
|
: std::make_pair (0, 23);
|
||||||
|
|
||||||
int first_hour = hour_range.first;
|
int first_hour = hour_range.first;
|
||||||
|
@ -97,7 +97,7 @@ std::string Chart::render (
|
||||||
// Each day is rendered separately.
|
// Each day is rendered separately.
|
||||||
time_t total_work = 0;
|
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.
|
// Render the exclusion blocks.
|
||||||
|
|
||||||
|
@ -154,7 +154,7 @@ std::string Chart::render (
|
||||||
|
|
||||||
out << (with_totals ? renderSubTotal (total_work, std::string (padding_size, ' ')) : "")
|
out << (with_totals ? renderSubTotal (total_work, std::string (padding_size, ' ')) : "")
|
||||||
<< (with_holidays ? renderHolidays (holidays) : "")
|
<< (with_holidays ? renderHolidays (holidays) : "")
|
||||||
<< (with_summary ? renderSummary (indent, filter, exclusions, tracked) : "");
|
<< (with_summary ? renderSummary (indent, range, exclusions, tracked) : "");
|
||||||
|
|
||||||
return out.str ();
|
return out.str ();
|
||||||
}
|
}
|
||||||
|
@ -172,7 +172,7 @@ unsigned long Chart::getIndentSize ()
|
||||||
// Scan all tracked intervals, looking for the earliest and latest hour into
|
// Scan all tracked intervals, looking for the earliest and latest hour into
|
||||||
// which an interval extends.
|
// which an interval extends.
|
||||||
std::pair<int, int> Chart::determineHourRange (
|
std::pair<int, int> Chart::determineHourRange (
|
||||||
const Interval &filter,
|
const Range& range,
|
||||||
const std::vector<Interval> &tracked)
|
const std::vector<Interval> &tracked)
|
||||||
{
|
{
|
||||||
// If there is no data, show the whole day.
|
// If there is no data, show the whole day.
|
||||||
|
@ -185,11 +185,11 @@ std::pair<int, int> Chart::determineHourRange (
|
||||||
auto first_hour = 23;
|
auto first_hour = 23;
|
||||||
auto last_hour = 0;
|
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);
|
auto day_range = getFullDay (day);
|
||||||
|
|
||||||
for (auto &track : tracked)
|
for (auto& track : tracked)
|
||||||
{
|
{
|
||||||
Interval test {track};
|
Interval test {track};
|
||||||
|
|
||||||
|
@ -549,7 +549,7 @@ std::string Chart::renderHolidays (const std::map<Datetime, std::string> &holida
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
std::string Chart::renderSummary (
|
std::string Chart::renderSummary (
|
||||||
const std::string &indent,
|
const std::string &indent,
|
||||||
const Interval &filter,
|
const Range& range,
|
||||||
const std::vector<Range> &exclusions,
|
const std::vector<Range> &exclusions,
|
||||||
const std::vector<Interval> &tracked)
|
const std::vector<Interval> &tracked)
|
||||||
{
|
{
|
||||||
|
@ -558,9 +558,9 @@ std::string Chart::renderSummary (
|
||||||
|
|
||||||
for (auto &exclusion : exclusions)
|
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)
|
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 ())
|
if (interval.is_open ())
|
||||||
{
|
{
|
||||||
clipped.end = reference_datetime;
|
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);
|
assert (total_available >= 0);
|
||||||
auto total_remaining = total_available - total_worked;
|
auto total_remaining = total_available - total_worked;
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ class Chart
|
||||||
public:
|
public:
|
||||||
explicit Chart (const ChartConfig& configuration);
|
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:
|
private:
|
||||||
std::string renderAxis (int, int);
|
std::string renderAxis (int, int);
|
||||||
|
@ -45,7 +45,7 @@ private:
|
||||||
std::string renderHolidays (const std::map <Datetime, std::string>&);
|
std::string renderHolidays (const std::map <Datetime, std::string>&);
|
||||||
std::string renderMonth (const Datetime&, const Datetime&);
|
std::string renderMonth (const Datetime&, const Datetime&);
|
||||||
std::string renderSubTotal (time_t, const std::string&);
|
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 renderTotal (time_t);
|
||||||
std::string renderWeek (const Datetime&, const Datetime&);
|
std::string renderWeek (const Datetime&, const Datetime&);
|
||||||
std::string renderWeekday (Datetime&, const Color&);
|
std::string renderWeekday (Datetime&, const Color&);
|
||||||
|
@ -55,7 +55,7 @@ private:
|
||||||
|
|
||||||
unsigned long getIndentSize ();
|
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 getDayColor (const Datetime&, const std::map <Datetime, std::string>&);
|
||||||
Color getHourColor (int) const;
|
Color getHourColor (int) const;
|
||||||
|
|
|
@ -86,7 +86,7 @@ Range DatetimeParser::parse_range (const std::string& input)
|
||||||
{
|
{
|
||||||
start = pig.cursor ();
|
start = pig.cursor ();
|
||||||
resolve ();
|
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 start_date = Datetime (_date);
|
||||||
auto end_date = Datetime(start_date.year(), start_date.month()+1, 1);
|
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)
|
else if (_year != 0)
|
||||||
{
|
{
|
||||||
auto start_date = Datetime (_date);
|
auto start_date = Datetime (_date);
|
||||||
auto end_date = Datetime(start_date.year()+1, 1, 1);
|
auto end_date = Datetime(start_date.year()+1, 1, 1);
|
||||||
return Range { start_date, end_date };
|
return Range {start_date, end_date};
|
||||||
}
|
}
|
||||||
return Range {};
|
return Range {};
|
||||||
}
|
}
|
||||||
|
@ -145,7 +145,7 @@ Range DatetimeParser::parse_range (const std::string& input)
|
||||||
{
|
{
|
||||||
start = pig.cursor ();
|
start = pig.cursor ();
|
||||||
resolve ();
|
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))
|
if (parse_informal_time (pig))
|
||||||
{
|
{
|
||||||
return Range { Datetime {_date}, 0 };
|
return Range {Datetime {_date}, 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parse_named_day (pig))
|
if (parse_named_day (pig))
|
||||||
{
|
{
|
||||||
// ::validate and ::resolve are not needed in this case.
|
// ::validate and ::resolve are not needed in this case.
|
||||||
start = pig.cursor ();
|
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))
|
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 month = (begin.month() + 1) % 13 + (begin.month() == 12);
|
||||||
auto year = (begin.year() + (begin.month() == 12));
|
auto year = (begin.year() + (begin.month() == 12));
|
||||||
auto end = Datetime (year, month, 1);
|
auto end = Datetime (year, month, 1);
|
||||||
return Range { begin, end };
|
return Range {begin, end};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parse_named (pig))
|
if (parse_named (pig))
|
||||||
{
|
{
|
||||||
// ::validate and ::resolve are not needed in this case.
|
// ::validate and ::resolve are not needed in this case.
|
||||||
start = pig.cursor ();
|
start = pig.cursor ();
|
||||||
return Range { Datetime (_date), 0 };
|
return Range {Datetime (_date), 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw format ("'{1}' is not a valid range.", input);
|
throw format ("'{1}' is not a valid range.", input);
|
||||||
|
|
|
@ -30,12 +30,15 @@
|
||||||
#include <Range.h>
|
#include <Range.h>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
class Interval : public Range
|
class Interval : public Range
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Interval () = default;
|
Interval () = default;
|
||||||
Interval (const Datetime& start, const Datetime& end) : Range (start, end) {}
|
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;
|
||||||
bool operator!= (const Interval&) const;
|
bool operator!= (const Interval&) const;
|
||||||
|
|
||||||
|
|
|
@ -41,8 +41,8 @@ int CmdAnnotate (
|
||||||
{
|
{
|
||||||
const bool verbose = rules.getBoolean ("verbose");
|
const bool verbose = rules.getBoolean ("verbose");
|
||||||
|
|
||||||
std::set <int> ids = cli.getIds ();
|
auto ids = cli.getIds ();
|
||||||
std::string annotation = cli.getAnnotation ();
|
auto annotation = cli.getAnnotation ();
|
||||||
|
|
||||||
journal.startTransaction ();
|
journal.startTransaction ();
|
||||||
flattenDatabase (database, rules);
|
flattenDatabase (database, rules);
|
||||||
|
|
|
@ -36,9 +36,9 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <timew.h>
|
#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 (
|
int CmdChartDay (
|
||||||
|
@ -46,16 +46,7 @@ int CmdChartDay (
|
||||||
Rules& rules,
|
Rules& rules,
|
||||||
Database& database)
|
Database& database)
|
||||||
{
|
{
|
||||||
auto default_hint = rules.get ("reports.range", "day");
|
return renderChart ("day", cli, rules, database);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -64,16 +55,7 @@ int CmdChartWeek (
|
||||||
Rules& rules,
|
Rules& rules,
|
||||||
Database& database)
|
Database& database)
|
||||||
{
|
{
|
||||||
auto default_hint = rules.get ("reports.range", "week");
|
return renderChart ("week", cli, rules, database);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -82,32 +64,31 @@ int CmdChartMonth (
|
||||||
Rules& rules,
|
Rules& rules,
|
||||||
Database& database)
|
Database& database)
|
||||||
{
|
{
|
||||||
auto default_hint = rules.get ("reports.range", "month");
|
return renderChart ("month", cli, rules, database);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
int renderChart (
|
int renderChart (
|
||||||
const CLI& cli,
|
|
||||||
const std::string& type,
|
const std::string& type,
|
||||||
Interval& filter,
|
const CLI& cli,
|
||||||
Rules& rules,
|
Rules& rules,
|
||||||
Database& database)
|
Database& database)
|
||||||
{
|
{
|
||||||
const bool verbose = rules.getBoolean ("verbose");
|
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.
|
// Load the data.
|
||||||
IntervalFilterAndGroup filtering ({
|
IntervalFilterAndGroup filtering ({
|
||||||
std::make_shared <IntervalFilterAllInRange> ( Range { filter.start, filter.end }),
|
std::make_shared <IntervalFilterAllInRange> (range),
|
||||||
std::make_shared <IntervalFilterAllWithTags> (filter.tags())
|
std::make_shared <IntervalFilterAllWithTags> (tags)
|
||||||
});
|
});
|
||||||
|
|
||||||
auto tracked = getTracked (database, rules, filtering);
|
auto tracked = getTracked (database, rules, filtering);
|
||||||
|
@ -118,16 +99,18 @@ int renderChart (
|
||||||
{
|
{
|
||||||
std::cout << "No filtered data found";
|
std::cout << "No filtered data found";
|
||||||
|
|
||||||
if (filter.is_started ())
|
if (range.is_started ())
|
||||||
{
|
{
|
||||||
std::cout << " in the range " << filter.start.toISOLocalExtended ();
|
std::cout << " in the range " << range.start.toISOLocalExtended ();
|
||||||
if (filter.is_ended ())
|
if (range.is_ended ())
|
||||||
std::cout << " - " << filter.end.toISOLocalExtended ();
|
{
|
||||||
|
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";
|
std::cout << ".\n";
|
||||||
|
@ -136,8 +119,8 @@ int renderChart (
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto exclusions = getAllExclusions (rules, filter);
|
const auto exclusions = getAllExclusions (rules, range);
|
||||||
const auto holidays = createHolidayMap (rules, filter);
|
const auto holidays = createHolidayMap (rules, range);
|
||||||
|
|
||||||
// Map tags to colors.
|
// Map tags to colors.
|
||||||
auto palette = createPalette (rules);
|
auto palette = createPalette (rules);
|
||||||
|
@ -146,12 +129,16 @@ int renderChart (
|
||||||
const auto minutes_per_char = rules.getInteger ("reports." + type + ".cell");
|
const auto minutes_per_char = rules.getInteger ("reports." + type + ".cell");
|
||||||
|
|
||||||
if (minutes_per_char < 1)
|
if (minutes_per_char < 1)
|
||||||
|
{
|
||||||
throw format ("The value for 'reports.{1}.cell' must be at least 1.", type);
|
throw format ("The value for 'reports.{1}.cell' must be at least 1.", type);
|
||||||
|
}
|
||||||
|
|
||||||
const auto num_lines = rules.getInteger ("reports." + type + ".lines", 1);
|
const auto num_lines = rules.getInteger ("reports." + type + ".lines", 1);
|
||||||
|
|
||||||
if (num_lines < 1)
|
if (num_lines < 1)
|
||||||
|
{
|
||||||
throw format ("Invalid value for 'reports.{1}.lines': '{2}'", type, num_lines);
|
throw format ("Invalid value for 'reports.{1}.lines': '{2}'", type, num_lines);
|
||||||
|
}
|
||||||
|
|
||||||
ChartConfig configuration {};
|
ChartConfig configuration {};
|
||||||
configuration.reference_datetime = Datetime ();
|
configuration.reference_datetime = Datetime ();
|
||||||
|
@ -177,13 +164,13 @@ int renderChart (
|
||||||
|
|
||||||
Chart chart (configuration);
|
Chart chart (configuration);
|
||||||
|
|
||||||
std::cout << chart.render (filter, tracked, exclusions, holidays);
|
std::cout << chart.render (range, tracked, exclusions, holidays);
|
||||||
|
|
||||||
return 0;
|
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;
|
std::map <Datetime, std::string> mapping;
|
||||||
auto holidays = rules.all ("holidays.");
|
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 (), '_', '-');
|
std::replace (date.begin (), date.end (), '_', '-');
|
||||||
Datetime holiday (date);
|
Datetime holiday (date);
|
||||||
|
|
||||||
if (holiday >= filter.start && holiday <= filter.end)
|
if (holiday >= range.start && holiday <= range.end)
|
||||||
{
|
{
|
||||||
std::stringstream out;
|
std::stringstream out;
|
||||||
out << " ["
|
out << " ["
|
||||||
|
|
|
@ -44,16 +44,17 @@ int CmdContinue (
|
||||||
const bool verbose = rules.getBoolean ("verbose");
|
const bool verbose = rules.getBoolean ("verbose");
|
||||||
const Datetime now {};
|
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.");
|
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.");
|
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 ());
|
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);
|
intervals = getTracked (database, rules, filtering);
|
||||||
|
|
||||||
if (intervals.empty ())
|
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
|
else
|
||||||
|
@ -106,10 +107,10 @@ int CmdContinue (
|
||||||
Datetime start_time;
|
Datetime start_time;
|
||||||
Datetime end_time;
|
Datetime end_time;
|
||||||
|
|
||||||
if (filter.is_started ())
|
if (range.is_started ())
|
||||||
{
|
{
|
||||||
start_time = filter.start;
|
start_time = range.start;
|
||||||
end_time = filter.end;
|
end_time = range.end;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -39,8 +39,7 @@ int CmdDelete (
|
||||||
{
|
{
|
||||||
const bool verbose = rules.getBoolean ("verbose");
|
const bool verbose = rules.getBoolean ("verbose");
|
||||||
|
|
||||||
// Gather IDs.
|
auto ids = cli.getIds ();
|
||||||
std::set <int> ids = cli.getIds ();
|
|
||||||
|
|
||||||
if (ids.empty ())
|
if (ids.empty ())
|
||||||
{
|
{
|
||||||
|
@ -53,7 +52,6 @@ int CmdDelete (
|
||||||
auto filtering = IntervalFilterAllWithIds (ids);
|
auto filtering = IntervalFilterAllWithIds (ids);
|
||||||
auto intervals = getTracked (database, rules, filtering);
|
auto intervals = getTracked (database, rules, filtering);
|
||||||
|
|
||||||
|
|
||||||
if (intervals.size () != ids.size ())
|
if (intervals.size () != ids.size ())
|
||||||
{
|
{
|
||||||
for (auto& id: ids)
|
for (auto& id: ids)
|
||||||
|
|
|
@ -38,16 +38,19 @@ int CmdExport (
|
||||||
Rules& rules,
|
Rules& rules,
|
||||||
Database& database)
|
Database& database)
|
||||||
{
|
{
|
||||||
auto filter = cli.getFilter ();
|
auto ids = cli.getIds ();
|
||||||
std::set <int> ids = cli.getIds ();
|
auto range = cli.getRange ();
|
||||||
|
auto tags = cli.getTags ();
|
||||||
|
|
||||||
std::shared_ptr <IntervalFilter> filtering;
|
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.");
|
throw std::string ("You cannot specify both id and tags/range to export intervals.");
|
||||||
}
|
}
|
||||||
|
|
||||||
filtering = std::make_shared <IntervalFilterAllWithIds> (ids);
|
filtering = std::make_shared <IntervalFilterAllWithIds> (ids);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -55,14 +58,15 @@ int CmdExport (
|
||||||
filtering = std::make_shared <IntervalFilterAndGroup> (
|
filtering = std::make_shared <IntervalFilterAndGroup> (
|
||||||
std::vector <std::shared_ptr <IntervalFilter>> (
|
std::vector <std::shared_ptr <IntervalFilter>> (
|
||||||
{
|
{
|
||||||
std::make_shared <IntervalFilterAllInRange> (Range{filter.start, filter.end}),
|
std::make_shared <IntervalFilterAllInRange> (range),
|
||||||
std::make_shared <IntervalFilterAllWithTags> (filter.tags ()),
|
std::make_shared <IntervalFilterAllWithTags> (tags),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto intervals = getTracked (database, rules, *filtering);
|
auto intervals = getTracked (database, rules, *filtering);
|
||||||
|
|
||||||
std::cout << jsonFromIntervals (intervals);
|
std::cout << jsonFromIntervals (intervals);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -40,7 +40,7 @@ int CmdFill (
|
||||||
{
|
{
|
||||||
const bool verbose = rules.getBoolean ("verbose");
|
const bool verbose = rules.getBoolean ("verbose");
|
||||||
|
|
||||||
std::set <int> ids = cli.getIds ();
|
auto ids = cli.getIds ();
|
||||||
|
|
||||||
if (ids.empty ())
|
if (ids.empty ())
|
||||||
{
|
{
|
||||||
|
@ -48,10 +48,7 @@ int CmdFill (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the data.
|
// Load the data.
|
||||||
// Note: There is no filter.
|
auto filtering = IntervalFilterAllInRange ({0, 0});
|
||||||
Interval filter;
|
|
||||||
|
|
||||||
auto filtering = IntervalFilterAllInRange ({ 0, 0 });
|
|
||||||
auto tracked = getTracked (database, rules, filtering);
|
auto tracked = getTracked (database, rules, filtering);
|
||||||
|
|
||||||
journal.startTransaction ();
|
journal.startTransaction ();
|
||||||
|
@ -60,7 +57,9 @@ int CmdFill (
|
||||||
for (auto& id : ids)
|
for (auto& id : ids)
|
||||||
{
|
{
|
||||||
if (id > static_cast <int> (tracked.size ()))
|
if (id > static_cast <int> (tracked.size ()))
|
||||||
|
{
|
||||||
throw format ("ID '@{1}' does not correspond to any tracking.", id);
|
throw format ("ID '@{1}' does not correspond to any tracking.", id);
|
||||||
|
}
|
||||||
|
|
||||||
Interval from = tracked[tracked.size () - id];
|
Interval from = tracked[tracked.size () - id];
|
||||||
std::cout << "# from " << from.dump () << "\n";
|
std::cout << "# from " << from.dump () << "\n";
|
||||||
|
|
|
@ -45,16 +45,22 @@ int CmdGaps (
|
||||||
Range default_range = {};
|
Range default_range = {};
|
||||||
expandIntervalHint (":" + report_hint, 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?
|
// Is the :blank hint being used?
|
||||||
bool blank = cli.getHint ("blank", false);
|
bool blank = cli.getHint ("blank", false);
|
||||||
|
|
||||||
std::vector <Range> untracked;
|
std::vector <Range> untracked;
|
||||||
if (blank)
|
if (blank)
|
||||||
untracked = subtractRanges ({filter}, getAllExclusions (rules, filter));
|
{
|
||||||
|
untracked = subtractRanges ({range}, getAllExclusions (rules, range));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
untracked = getUntracked (database, rules, filter);
|
untracked = getUntracked (database, rules, filter);
|
||||||
|
}
|
||||||
|
|
||||||
Table table;
|
Table table;
|
||||||
table.width (1024);
|
table.width (1024);
|
||||||
|
@ -70,7 +76,7 @@ int CmdGaps (
|
||||||
// Each day is rendered separately.
|
// Each day is rendered separately.
|
||||||
time_t grand_total = 0;
|
time_t grand_total = 0;
|
||||||
Datetime previous;
|
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);
|
auto day_range = getFullDay (day);
|
||||||
time_t daily_total = 0;
|
time_t daily_total = 0;
|
||||||
|
|
|
@ -37,9 +37,10 @@ int CmdGet (
|
||||||
Rules& rules,
|
Rules& rules,
|
||||||
Database& database)
|
Database& database)
|
||||||
{
|
{
|
||||||
|
auto references = cli.getDomReferences ();
|
||||||
|
auto filter = Interval {cli.getRange (), cli.getTags()};
|
||||||
|
|
||||||
std::vector <std::string> results;
|
std::vector <std::string> results;
|
||||||
std::vector <std::string> references = cli.getDomReferences ();
|
|
||||||
Interval filter = cli.getFilter ();
|
|
||||||
|
|
||||||
for (auto& reference : references)
|
for (auto& reference : references)
|
||||||
{
|
{
|
||||||
|
|
|
@ -41,7 +41,7 @@ int CmdJoin (
|
||||||
const bool verbose = rules.getBoolean ("verbose");
|
const bool verbose = rules.getBoolean ("verbose");
|
||||||
|
|
||||||
// Gather IDs and TAGs.
|
// Gather IDs and TAGs.
|
||||||
std::set <int> ids = cli.getIds ();
|
auto ids = cli.getIds ();
|
||||||
|
|
||||||
// Only 2 IDs allowed in a join.
|
// Only 2 IDs allowed in a join.
|
||||||
if (ids.size () != 2)
|
if (ids.size () != 2)
|
||||||
|
|
|
@ -40,15 +40,14 @@ int CmdLengthen (
|
||||||
{
|
{
|
||||||
const bool verbose = rules.getBoolean ("verbose");
|
const bool verbose = rules.getBoolean ("verbose");
|
||||||
|
|
||||||
// Gather IDs and TAGs.
|
auto ids = cli.getIds ();
|
||||||
std::set <int> ids = cli.getIds ();
|
|
||||||
|
|
||||||
if (ids.empty ())
|
if (ids.empty ())
|
||||||
{
|
{
|
||||||
throw std::string ("IDs must be specified. See 'timew help lengthen'.");
|
throw std::string ("IDs must be specified. See 'timew help lengthen'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Duration dur = cli.getDuration ();
|
auto dur = cli.getDuration ();
|
||||||
|
|
||||||
journal.startTransaction ();
|
journal.startTransaction ();
|
||||||
|
|
||||||
|
|
|
@ -39,10 +39,9 @@ int CmdModify (
|
||||||
{
|
{
|
||||||
const bool verbose = rules.getBoolean ("verbose");
|
const bool verbose = rules.getBoolean ("verbose");
|
||||||
|
|
||||||
auto filter = cli.getFilter ();
|
auto words = cli.getWords ();
|
||||||
std::set <int> ids = cli.getIds ();
|
|
||||||
std::vector <std::string> words = cli.getWords ();
|
enum {MODIFY_START, MODIFY_END} op = MODIFY_START;
|
||||||
enum { MODIFY_START, MODIFY_END } op = MODIFY_START;
|
|
||||||
|
|
||||||
if (words.empty())
|
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));
|
throw format ("Must specify start|end command to modify. See 'timew help modify'.", words.at (0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto ids = cli.getIds ();
|
||||||
|
|
||||||
if (ids.empty ())
|
if (ids.empty ())
|
||||||
{
|
{
|
||||||
throw std::string ("ID must be specified. See 'timew help modify'.");
|
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'.");
|
throw std::string ("Only one ID may be specified. See 'timew help modify'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto range = cli.getRange ({0, 0});
|
||||||
|
|
||||||
int id = *ids.begin();
|
int id = *ids.begin();
|
||||||
|
|
||||||
flattenDatabase (database, rules);
|
flattenDatabase (database, rules);
|
||||||
|
@ -84,7 +87,7 @@ int CmdModify (
|
||||||
}
|
}
|
||||||
|
|
||||||
assert (intervals.size () == 1);
|
assert (intervals.size () == 1);
|
||||||
if (! filter.is_started ())
|
if (! range.is_started ())
|
||||||
{
|
{
|
||||||
throw std::string ("No updated time specified. See 'timew help modify'.");
|
throw std::string ("No updated time specified. See 'timew help modify'.");
|
||||||
}
|
}
|
||||||
|
@ -94,7 +97,7 @@ int CmdModify (
|
||||||
switch (op)
|
switch (op)
|
||||||
{
|
{
|
||||||
case MODIFY_START:
|
case MODIFY_START:
|
||||||
modified.start = filter.start;
|
modified.start = range.start;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MODIFY_END:
|
case MODIFY_END:
|
||||||
|
@ -102,7 +105,7 @@ int CmdModify (
|
||||||
{
|
{
|
||||||
throw format ("Cannot modify end of open interval @{1}.", id);
|
throw format ("Cannot modify end of open interval @{1}.", id);
|
||||||
}
|
}
|
||||||
modified.end = filter.start;
|
modified.end = range.start;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -134,24 +134,28 @@ int CmdReport (
|
||||||
expandIntervalHint (":" + report_hint, default_range);
|
expandIntervalHint (":" + report_hint, default_range);
|
||||||
|
|
||||||
// Create a filter, and if empty, choose the current week.
|
// 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 ({
|
IntervalFilterAndGroup filtering ({
|
||||||
std::make_shared <IntervalFilterAllInRange> ( Range { filter.start, filter.end }),
|
std::make_shared <IntervalFilterAllInRange> (range),
|
||||||
std::make_shared <IntervalFilterAllWithTags> (filter.tags ())
|
std::make_shared <IntervalFilterAllWithTags> (tags)
|
||||||
});
|
});
|
||||||
|
|
||||||
auto tracked = getTracked (database, rules, filtering);
|
auto tracked = getTracked (database, rules, filtering);
|
||||||
|
|
||||||
// Compose Header info.
|
// Compose Header info.
|
||||||
rules.set ("temp.report.start", filter.is_started () ? filter.start.toISO () : "");
|
rules.set ("temp.report.start", range.is_started () ? range.start.toISO () : "");
|
||||||
rules.set ("temp.report.end", filter.is_ended () ? filter.end.toISO () : "");
|
rules.set ("temp.report.end", range.is_ended () ? range.end.toISO () : "");
|
||||||
rules.set ("temp.report.tags", joinQuotedIfNeeded (",", filter.tags ()));
|
rules.set ("temp.report.tags", joinQuotedIfNeeded (",", tags));
|
||||||
rules.set ("temp.version", VERSION);
|
rules.set ("temp.version", VERSION);
|
||||||
|
|
||||||
std::stringstream header;
|
std::stringstream header;
|
||||||
|
|
||||||
for (auto& name : rules.all ())
|
for (auto& name : rules.all ())
|
||||||
|
{
|
||||||
header << name << ": " << rules.get (name) << '\n';
|
header << name << ": " << rules.get (name) << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
// Get the data.
|
// Get the data.
|
||||||
auto input = header.str ()
|
auto input = header.str ()
|
||||||
|
@ -168,7 +172,9 @@ int CmdReport (
|
||||||
|
|
||||||
// Display the output.
|
// Display the output.
|
||||||
for (auto& line : output)
|
for (auto& line : output)
|
||||||
|
{
|
||||||
std::cout << line << '\n';
|
std::cout << line << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,13 +38,14 @@ int CmdStart (
|
||||||
const bool verbose = rules.getBoolean ("verbose");
|
const bool verbose = rules.getBoolean ("verbose");
|
||||||
const Datetime now {};
|
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.");
|
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. "
|
throw std::string ("The start command does not accept ranges but only a single datetime. "
|
||||||
"Perhaps you want the track command?");
|
"Perhaps you want the track command?");
|
||||||
|
@ -58,6 +59,8 @@ int CmdStart (
|
||||||
}
|
}
|
||||||
|
|
||||||
journal.startTransaction ();
|
journal.startTransaction ();
|
||||||
|
auto interval = Interval {range, tags};
|
||||||
|
|
||||||
if (validate (cli, rules, database, interval))
|
if (validate (cli, rules, database, interval))
|
||||||
{
|
{
|
||||||
database.addInterval (interval, verbose);
|
database.addInterval (interval, verbose);
|
||||||
|
|
|
@ -50,7 +50,6 @@ int CmdStop (
|
||||||
const bool verbose = rules.getBoolean ("verbose");
|
const bool verbose = rules.getBoolean ("verbose");
|
||||||
const Datetime now {};
|
const Datetime now {};
|
||||||
|
|
||||||
auto filter = cli.getFilter ({ now, 0 });
|
|
||||||
// Load the most recent interval.
|
// Load the most recent interval.
|
||||||
auto latest = getLatestInterval (database);
|
auto latest = getLatestInterval (database);
|
||||||
|
|
||||||
|
@ -67,30 +66,34 @@ int CmdStop (
|
||||||
"Perhaps you want the modify command?.");
|
"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.");
|
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.");
|
throw std::string ("The end of a date range must be after the start.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto tags = cli.getTags ();
|
||||||
|
|
||||||
std::set <std::string> diff = {};
|
std::set <std::string> diff = {};
|
||||||
|
|
||||||
if(! std::includes(latest.tags ().begin (), latest.tags ().end (),
|
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 (),
|
latest.tags ().begin (), latest.tags ().end (),
|
||||||
std::inserter(diff, diff.begin ()));
|
std::inserter(diff, diff.begin ()));
|
||||||
|
|
||||||
throw format ("The current interval does not have the '{1}' tag.", *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 (),
|
std::set_difference(latest.tags ().begin (), latest.tags ().end (),
|
||||||
filter.tags ().begin (), filter.tags ().end (),
|
tags.begin (), tags.end (),
|
||||||
std::inserter(diff, diff.begin()));
|
std::inserter(diff, diff.begin()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,8 +101,8 @@ int CmdStop (
|
||||||
|
|
||||||
if (diff.empty ())
|
if (diff.empty ())
|
||||||
{
|
{
|
||||||
Interval modified { latest };
|
Interval modified {latest};
|
||||||
modified.end = filter.start;
|
modified.end = range.start;
|
||||||
|
|
||||||
database.deleteInterval (latest);
|
database.deleteInterval (latest);
|
||||||
validate (cli, rules, database, modified);
|
validate (cli, rules, database, modified);
|
||||||
|
@ -116,7 +119,7 @@ int CmdStop (
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Interval next { filter.start, 0 };
|
Interval next {range.start, 0};
|
||||||
for (auto& tag : diff)
|
for (auto& tag : diff)
|
||||||
{
|
{
|
||||||
next.tag (tag);
|
next.tag (tag);
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
#include <utf8.h>
|
#include <utf8.h>
|
||||||
|
|
||||||
// Implemented in CmdChart.cpp.
|
// 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>&);
|
std::string renderHolidays (const std::map <Datetime, std::string>&);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -53,12 +53,13 @@ int CmdSummary (
|
||||||
Range default_range = {};
|
Range default_range = {};
|
||||||
expandIntervalHint (":" + report_hint, 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.
|
// Load the data.
|
||||||
IntervalFilterAndGroup filtering ({
|
IntervalFilterAndGroup filtering ({
|
||||||
std::make_shared <IntervalFilterAllInRange> ( Range { filter.start, filter.end }),
|
std::make_shared <IntervalFilterAllInRange> (range),
|
||||||
std::make_shared <IntervalFilterAllWithTags> (filter.tags())
|
std::make_shared <IntervalFilterAllWithTags> (tags)
|
||||||
});
|
});
|
||||||
|
|
||||||
auto tracked = getTracked (database, rules, filtering);
|
auto tracked = getTracked (database, rules, filtering);
|
||||||
|
@ -69,16 +70,18 @@ int CmdSummary (
|
||||||
{
|
{
|
||||||
std::cout << "No filtered data found";
|
std::cout << "No filtered data found";
|
||||||
|
|
||||||
if (filter.is_started ())
|
if (range.is_started ())
|
||||||
{
|
{
|
||||||
std::cout << " in the range " << filter.start.toISOLocalExtended ();
|
std::cout << " in the range " << range.start.toISOLocalExtended ();
|
||||||
if (filter.is_ended ())
|
if (range.is_ended ())
|
||||||
std::cout << " - " << filter.end.toISOLocalExtended ();
|
{
|
||||||
|
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";
|
std::cout << ".\n";
|
||||||
|
@ -159,8 +162,8 @@ int CmdSummary (
|
||||||
time_t grand_total = 0;
|
time_t grand_total = 0;
|
||||||
Datetime previous;
|
Datetime previous;
|
||||||
|
|
||||||
auto days_start = filter.is_started() ? filter.start : tracked.front ().start;
|
auto days_start = range.is_started() ? range.start : tracked.front ().start;
|
||||||
auto days_end = filter.is_ended() ? filter.end : tracked.back ().end;
|
auto days_end = range.is_ended() ? range.end : tracked.back ().end;
|
||||||
|
|
||||||
const auto now = Datetime ();
|
const auto now = Datetime ();
|
||||||
|
|
||||||
|
@ -221,8 +224,8 @@ int CmdSummary (
|
||||||
|
|
||||||
if (show_tags)
|
if (show_tags)
|
||||||
{
|
{
|
||||||
std::string tags = join (", ", track.tags ());
|
std::string tags_string = join (", ", track.tags ());
|
||||||
table.set (row, tags_col_index, tags, summaryIntervalColor (rules, track.tags ()));
|
table.set (row, tags_col_index, tags_string, summaryIntervalColor (rules, track.tags ()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (show_annotations)
|
if (show_annotations)
|
||||||
|
@ -260,7 +263,7 @@ int CmdSummary (
|
||||||
|
|
||||||
std::cout << '\n'
|
std::cout << '\n'
|
||||||
<< table.render ()
|
<< table.render ()
|
||||||
<< (show_holidays ? renderHolidays (createHolidayMap (rules, filter)) : "")
|
<< (show_holidays ? renderHolidays (createHolidayMap (rules, range)) : "")
|
||||||
<< '\n';
|
<< '\n';
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -43,7 +43,7 @@ int CmdTag (
|
||||||
|
|
||||||
// Gather IDs and TAGs.
|
// Gather IDs and TAGs.
|
||||||
std::set <int> ids = cli.getIds ();
|
std::set <int> ids = cli.getIds ();
|
||||||
std::vector<std::string> tags = cli.getTags ();
|
std::set<std::string> tags = cli.getTags ();
|
||||||
|
|
||||||
if (tags.empty ())
|
if (tags.empty ())
|
||||||
{
|
{
|
||||||
|
|
|
@ -42,15 +42,14 @@ int CmdTags (
|
||||||
{
|
{
|
||||||
const bool verbose = rules.getBoolean ("verbose");
|
const bool verbose = rules.getBoolean ("verbose");
|
||||||
|
|
||||||
// Create a filter, with no default range.
|
|
||||||
auto filter = cli.getFilter ();
|
|
||||||
IntervalFilterAndGroup filtering ({
|
IntervalFilterAndGroup filtering ({
|
||||||
std::make_shared <IntervalFilterAllInRange> ( Range { filter.start, filter.end }),
|
std::make_shared <IntervalFilterAllInRange> (cli.getRange ()),
|
||||||
std::make_shared <IntervalFilterAllWithTags> (filter.tags ())
|
std::make_shared <IntervalFilterAllWithTags> (cli.getTags ())
|
||||||
});
|
});
|
||||||
|
|
||||||
// Generate a unique, ordered list of tags.
|
// Generate a unique, ordered list of tags.
|
||||||
std::set <std::string> tags;
|
std::set <std::string> tags;
|
||||||
|
|
||||||
for (const auto& interval : getTracked (database, rules, filtering))
|
for (const auto& interval : getTracked (database, rules, filtering))
|
||||||
for (auto& tag : interval.tags ())
|
for (auto& tag : interval.tags ())
|
||||||
tags.insert (tag);
|
tags.insert (tag);
|
||||||
|
|
|
@ -37,8 +37,6 @@ int CmdTrack (
|
||||||
{
|
{
|
||||||
const bool verbose = rules.getBoolean ("verbose");
|
const bool verbose = rules.getBoolean ("verbose");
|
||||||
|
|
||||||
auto filter = cli.getFilter ();
|
|
||||||
|
|
||||||
// We expect no ids
|
// We expect no ids
|
||||||
if (! cli.getIds ().empty ())
|
if (! cli.getIds ().empty ())
|
||||||
{
|
{
|
||||||
|
@ -46,23 +44,32 @@ int CmdTrack (
|
||||||
"Perhaps you want the continue command?");
|
"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
|
// 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.
|
// the 'track' command behave like 'start', so delegate to CmdStart.
|
||||||
if (! filter.is_started () ||
|
if (! range.is_started () || ! range.is_ended ())
|
||||||
! filter.is_ended ())
|
{
|
||||||
return CmdStart (cli, rules, database, journal);
|
return CmdStart (cli, rules, database, journal);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tags = cli.getTags ();
|
||||||
|
|
||||||
journal.startTransaction ();
|
journal.startTransaction ();
|
||||||
|
|
||||||
|
auto filter = Interval {range, tags};
|
||||||
|
|
||||||
// Validation must occur before flattening.
|
// Validation must occur before flattening.
|
||||||
validate (cli, rules, database, filter);
|
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);
|
database.addInterval (interval, verbose);
|
||||||
|
|
||||||
if (verbose)
|
if (verbose)
|
||||||
|
{
|
||||||
std::cout << intervalSummarize (rules, interval);
|
std::cout << intervalSummarize (rules, interval);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
journal.endTransaction ();
|
journal.endTransaction ();
|
||||||
|
|
|
@ -43,7 +43,7 @@ int CmdUntag (
|
||||||
|
|
||||||
// Gather IDs and TAGs.
|
// Gather IDs and TAGs.
|
||||||
std::set <int> ids = cli.getIds ();
|
std::set <int> ids = cli.getIds ();
|
||||||
std::vector<std::string> tags = cli.getTags ();
|
std::set<std::string> tags = cli.getTags ();
|
||||||
|
|
||||||
if (tags.empty ())
|
if (tags.empty ())
|
||||||
{
|
{
|
||||||
|
|
|
@ -119,8 +119,8 @@ bool domGet (
|
||||||
else if (pig.skipLiteral ("tracked."))
|
else if (pig.skipLiteral ("tracked."))
|
||||||
{
|
{
|
||||||
IntervalFilterAndGroup filtering ({
|
IntervalFilterAndGroup filtering ({
|
||||||
std::make_shared <IntervalFilterAllInRange> ( Range { filter.start, filter.end }),
|
std::make_shared <IntervalFilterAllInRange> (Range {filter.start, filter.end}),
|
||||||
std::make_shared <IntervalFilterAllWithTags> (filter.tags())
|
std::make_shared <IntervalFilterAllWithTags> (filter.tags ())
|
||||||
});
|
});
|
||||||
|
|
||||||
auto tracked = getTracked (database, rules, filtering);
|
auto tracked = getTracked (database, rules, filtering);
|
||||||
|
|
|
@ -124,7 +124,7 @@ class TestTag(TestCase):
|
||||||
|
|
||||||
code, out, err = self.t("tag @1 foo bar")
|
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()
|
j = self.t.export()
|
||||||
self.assertOpenInterval(j[0], expectedTags=["bar", "foo"])
|
self.assertOpenInterval(j[0], expectedTags=["bar", "foo"])
|
||||||
|
@ -138,7 +138,7 @@ class TestTag(TestCase):
|
||||||
|
|
||||||
code, out, err = self.t("tag @1 foo bar")
|
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()
|
j = self.t.export()
|
||||||
self.assertClosedInterval(j[0], expectedTags=["bar", "foo"])
|
self.assertClosedInterval(j[0], expectedTags=["bar", "foo"])
|
||||||
|
@ -171,7 +171,7 @@ class TestTag(TestCase):
|
||||||
|
|
||||||
code, out, err = self.t("tag @1 @2 foo bar")
|
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()
|
j = self.t.export()
|
||||||
self.assertClosedInterval(j[0], expectedTags=["bar", "foo", "one"])
|
self.assertClosedInterval(j[0], expectedTags=["bar", "foo", "one"])
|
||||||
|
|
|
@ -124,7 +124,7 @@ class TestUntag(TestCase):
|
||||||
|
|
||||||
code, out, err = self.t("untag @1 foo bar")
|
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()
|
j = self.t.export()
|
||||||
self.assertOpenInterval(j[0], expectedTags=["baz"])
|
self.assertOpenInterval(j[0], expectedTags=["baz"])
|
||||||
|
@ -138,7 +138,7 @@ class TestUntag(TestCase):
|
||||||
|
|
||||||
code, out, err = self.t("untag @1 foo bar")
|
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()
|
j = self.t.export()
|
||||||
self.assertClosedInterval(j[0], expectedTags=["baz"])
|
self.assertClosedInterval(j[0], expectedTags=["baz"])
|
||||||
|
@ -171,7 +171,7 @@ class TestUntag(TestCase):
|
||||||
|
|
||||||
code, out, err = self.t("untag @1 @2 foo bar")
|
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()
|
j = self.t.export()
|
||||||
self.assertClosedInterval(j[0], expectedTags=["one"])
|
self.assertClosedInterval(j[0], expectedTags=["one"])
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue