diff --git a/src/CLI.cpp b/src/CLI.cpp index 0f61768e..551df928 100644 --- a/src/CLI.cpp +++ b/src/CLI.cpp @@ -564,14 +564,14 @@ std::set CLI::getIds() const } //////////////////////////////////////////////////////////////////////////////// -std::vector CLI::getTags () const +std::set CLI::getTags () const { - std::vector tags; + std::set 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 CLI::getDomReferences () const } //////////////////////////////////////////////////////////////////////////////// -// A filter is just another interval, containing start, end and tags. // -// Supported interval forms: +// Supported range forms: // ["from"] ["to"|"-" ] // ["from"] "for" // ["before"|"after" ] // "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 (""); + args.emplace_back(""); } else { start = range.start.toISO (); end = range.end.toISO (); - args.emplace_back (""); - args.emplace_back ("-"); - args.emplace_back (""); + args.emplace_back(""); + args.emplace_back("-"); + args.emplace_back(""); } } @@ -688,14 +686,14 @@ Interval CLI::getFilter (const Range& default_range) const else if (end.empty ()) end = raw; - args.emplace_back (""); + args.emplace_back(""); } else if (arg._lextype == Lexer::Type::duration) { if (duration.empty ()) duration = raw; - args.emplace_back (""); + args.emplace_back(""); } 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; } - // else if (args.size () == 1 && - args[0] == "") + args[0] == "") { DatetimeParser dtp; Range range = dtp.parse_range(start); - filter.setRange (range); + the_range = Range (range); } - // from else if (args.size () == 2 && args[0] == "from" && args[1] == "") { - filter.setRange ({Datetime (start), 0}); + the_range = Range ({Datetime (start), 0}); } // to/- else if (args.size () == 3 && @@ -742,9 +730,8 @@ Interval CLI::getFilter (const Range& default_range) const (args[1] == "to" || args[1] == "-") && args[2] == "") { - filter.setRange ({Datetime (start), Datetime (end)}); + the_range = Range ({Datetime (start), Datetime (end)}); } - // from to/- 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] == "") { - filter.setRange ({Datetime (start), Datetime (end)}); + the_range = Range ({Datetime (start), Datetime (end)}); } - // for else if (args.size () == 3 && args[0] == "" && args[1] == "for" && args[2] == "") { - filter.setRange ({Datetime (start), Datetime (start) + Duration (duration).toTime_t ()}); + the_range = Range ({Datetime (start), Datetime (start) + Duration (duration).toTime_t ()}); } - // from for 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] == "") { - filter.setRange ({Datetime (start), Datetime (start) + Duration (duration).toTime_t ()}); + the_range = Range ({Datetime (start), Datetime (start) + Duration (duration).toTime_t ()}); } - // before else if (args.size () == 3 && args[0] == "" && args[1] == "before" && args[2] == "") { - filter.setRange ({Datetime (start) - Duration (duration).toTime_t (), Datetime (start)}); + the_range = Range ({Datetime (start) - Duration (duration).toTime_t (), Datetime (start)}); } - // after else if (args.size () == 3 && args[0] == "" && args[1] == "after" && args[2] == "") { - filter.setRange ({Datetime (start), Datetime (start) + Duration (duration).toTime_t ()}); + the_range = Range ({Datetime (start), Datetime (start) + Duration (duration).toTime_t ()}); } - // ago else if (args.size () == 2 && args[0] == "" && args[1] == "ago") { - filter.setRange ({now - Duration (duration).toTime_t (), 0}); + the_range = Range ({now - Duration (duration).toTime_t (), 0}); } - // for else if (args.size () == 2 && args[0] == "for" && args[1] == "") { - filter.setRange ({now - Duration (duration).toTime_t (), now}); + the_range = Range ({now - Duration (duration).toTime_t (), now}); } - // else if (args.size () == 1 && args[0] == "") { - 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] == "") { - 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; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/CLI.h b/src/CLI.h index aef544e6..0b69addd 100644 --- a/src/CLI.h +++ b/src/CLI.h @@ -70,11 +70,11 @@ public: bool getComplementaryHint (const std::string&, bool) const; bool getHint(const std::string&, bool) const; std::set getIds () const; - std::vector getTags () const; + std::set getTags () const; std::string getAnnotation() const; Duration getDuration() const; std::vector 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: diff --git a/src/Chart.cpp b/src/Chart.cpp index 06b5c9f2..dc351b4c 100644 --- a/src/Chart.cpp +++ b/src/Chart.cpp @@ -61,14 +61,14 @@ Chart::Chart (const ChartConfig& configuration) : { } std::string Chart::render ( - const Interval &filter, + const Range& range, const std::vector &tracked, const std::vector &exclusions, const std::map &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 Chart::determineHourRange ( - const Interval &filter, + const Range& range, const std::vector &tracked) { // If there is no data, show the whole day. @@ -185,11 +185,11 @@ std::pair 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 &holida //////////////////////////////////////////////////////////////////////////////// std::string Chart::renderSummary ( const std::string &indent, - const Interval &filter, + const Range& range, const std::vector &exclusions, const std::vector &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; diff --git a/src/Chart.h b/src/Chart.h index 5f6ceff3..8d36b716 100644 --- a/src/Chart.h +++ b/src/Chart.h @@ -37,7 +37,7 @@ class Chart public: explicit Chart (const ChartConfig& configuration); - std::string render (const Interval&, const std::vector &, const std::vector &, const std::map &); + std::string render (const Range&, const std::vector &, const std::vector &, const std::map &); private: std::string renderAxis (int, int); @@ -45,7 +45,7 @@ private: std::string renderHolidays (const std::map &); 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 &, const std::vector &); + std::string renderSummary (const std::string&, const Range&, const std::vector &, const std::vector &); 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 determineHourRange (const Interval&, const std::vector &); + std::pair determineHourRange (const Range&, const std::vector &); Color getDayColor (const Datetime&, const std::map &); Color getHourColor (int) const; diff --git a/src/DatetimeParser.cpp b/src/DatetimeParser.cpp index 129460a3..d34ceb27 100644 --- a/src/DatetimeParser.cpp +++ b/src/DatetimeParser.cpp @@ -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); diff --git a/src/Interval.h b/src/Interval.h index 929b1c0c..60a54f83 100644 --- a/src/Interval.h +++ b/src/Interval.h @@ -30,12 +30,15 @@ #include #include #include +#include class Interval : public Range { public: Interval () = default; Interval (const Datetime& start, const Datetime& end) : Range (start, end) {} + Interval (const Range& range, std::set tags) : Range (range), _tags(std::move(tags)) {} + bool operator== (const Interval&) const; bool operator!= (const Interval&) const; diff --git a/src/commands/CmdAnnotate.cpp b/src/commands/CmdAnnotate.cpp index 85f0f429..00d40ba4 100644 --- a/src/commands/CmdAnnotate.cpp +++ b/src/commands/CmdAnnotate.cpp @@ -41,8 +41,8 @@ int CmdAnnotate ( { const bool verbose = rules.getBoolean ("verbose"); - std::set ids = cli.getIds (); - std::string annotation = cli.getAnnotation (); + auto ids = cli.getIds (); + auto annotation = cli.getAnnotation (); journal.startTransaction (); flattenDatabase (database, rules); diff --git a/src/commands/CmdChart.cpp b/src/commands/CmdChart.cpp index 9c5cbc73..fec8a574 100644 --- a/src/commands/CmdChart.cpp +++ b/src/commands/CmdChart.cpp @@ -36,9 +36,9 @@ #include #include -int renderChart (const CLI&, const std::string&, Interval&, Rules&, Database&); +int renderChart (const std::string&, const CLI&, Rules&, Database&); -std::map createHolidayMap (Rules&, Interval&); +std::map 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 ( Range { filter.start, filter.end }), - std::make_shared (filter.tags()) + std::make_shared (range), + std::make_shared (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 createHolidayMap (Rules &rules, Interval &filter) +std::map createHolidayMap (Rules& rules, Range& range) { std::map mapping; auto holidays = rules.all ("holidays."); @@ -199,7 +186,7 @@ std::map 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 << " [" diff --git a/src/commands/CmdContinue.cpp b/src/commands/CmdContinue.cpp index fb0fab0f..8bcedcc8 100644 --- a/src/commands/CmdContinue.cpp +++ b/src/commands/CmdContinue.cpp @@ -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 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 (filter.tags ())}; + IntervalFilterFirstOf filtering {std::make_shared (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 { diff --git a/src/commands/CmdDelete.cpp b/src/commands/CmdDelete.cpp index df5d4c02..1e99b611 100644 --- a/src/commands/CmdDelete.cpp +++ b/src/commands/CmdDelete.cpp @@ -39,8 +39,7 @@ int CmdDelete ( { const bool verbose = rules.getBoolean ("verbose"); - // Gather IDs. - std::set 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) diff --git a/src/commands/CmdExport.cpp b/src/commands/CmdExport.cpp index 5f68fc32..87af4789 100644 --- a/src/commands/CmdExport.cpp +++ b/src/commands/CmdExport.cpp @@ -38,16 +38,19 @@ int CmdExport ( Rules& rules, Database& database) { - auto filter = cli.getFilter (); - std::set ids = cli.getIds (); + auto ids = cli.getIds (); + auto range = cli.getRange (); + auto tags = cli.getTags (); + std::shared_ptr 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 (ids); } else @@ -55,14 +58,15 @@ int CmdExport ( filtering = std::make_shared ( std::vector > ( { - std::make_shared (Range{filter.start, filter.end}), - std::make_shared (filter.tags ()), + std::make_shared (range), + std::make_shared (tags), } ) ); } auto intervals = getTracked (database, rules, *filtering); + std::cout << jsonFromIntervals (intervals); return 0; diff --git a/src/commands/CmdFill.cpp b/src/commands/CmdFill.cpp index 65bef79b..33958c02 100644 --- a/src/commands/CmdFill.cpp +++ b/src/commands/CmdFill.cpp @@ -40,7 +40,7 @@ int CmdFill ( { const bool verbose = rules.getBoolean ("verbose"); - std::set 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 (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"; diff --git a/src/commands/CmdGaps.cpp b/src/commands/CmdGaps.cpp index 330a0099..65b2c8dd 100644 --- a/src/commands/CmdGaps.cpp +++ b/src/commands/CmdGaps.cpp @@ -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 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; diff --git a/src/commands/CmdGet.cpp b/src/commands/CmdGet.cpp index 79027163..ef922455 100644 --- a/src/commands/CmdGet.cpp +++ b/src/commands/CmdGet.cpp @@ -37,9 +37,10 @@ int CmdGet ( Rules& rules, Database& database) { + auto references = cli.getDomReferences (); + auto filter = Interval {cli.getRange (), cli.getTags()}; + std::vector results; - std::vector references = cli.getDomReferences (); - Interval filter = cli.getFilter (); for (auto& reference : references) { diff --git a/src/commands/CmdJoin.cpp b/src/commands/CmdJoin.cpp index 6c8164aa..79f26b64 100644 --- a/src/commands/CmdJoin.cpp +++ b/src/commands/CmdJoin.cpp @@ -41,7 +41,7 @@ int CmdJoin ( const bool verbose = rules.getBoolean ("verbose"); // Gather IDs and TAGs. - std::set ids = cli.getIds (); + auto ids = cli.getIds (); // Only 2 IDs allowed in a join. if (ids.size () != 2) diff --git a/src/commands/CmdLengthen.cpp b/src/commands/CmdLengthen.cpp index 3ccb7cfc..30a5cd0c 100644 --- a/src/commands/CmdLengthen.cpp +++ b/src/commands/CmdLengthen.cpp @@ -40,15 +40,14 @@ int CmdLengthen ( { const bool verbose = rules.getBoolean ("verbose"); - // Gather IDs and TAGs. - std::set 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 (); diff --git a/src/commands/CmdModify.cpp b/src/commands/CmdModify.cpp index 9bfe7e62..fd17a427 100644 --- a/src/commands/CmdModify.cpp +++ b/src/commands/CmdModify.cpp @@ -39,10 +39,9 @@ int CmdModify ( { const bool verbose = rules.getBoolean ("verbose"); - auto filter = cli.getFilter (); - std::set ids = cli.getIds (); - std::vector 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; } diff --git a/src/commands/CmdReport.cpp b/src/commands/CmdReport.cpp index 30003717..2870b9f5 100644 --- a/src/commands/CmdReport.cpp +++ b/src/commands/CmdReport.cpp @@ -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 ( Range { filter.start, filter.end }), - std::make_shared (filter.tags ()) + std::make_shared (range), + std::make_shared (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; } diff --git a/src/commands/CmdStart.cpp b/src/commands/CmdStart.cpp index a56d57d6..ca197ce8 100644 --- a/src/commands/CmdStart.cpp +++ b/src/commands/CmdStart.cpp @@ -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); diff --git a/src/commands/CmdStop.cpp b/src/commands/CmdStop.cpp index 8fe2948e..a4a5c7e5 100644 --- a/src/commands/CmdStop.cpp +++ b/src/commands/CmdStop.cpp @@ -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 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); diff --git a/src/commands/CmdSummary.cpp b/src/commands/CmdSummary.cpp index c4335c34..14296e03 100644 --- a/src/commands/CmdSummary.cpp +++ b/src/commands/CmdSummary.cpp @@ -36,7 +36,7 @@ #include // Implemented in CmdChart.cpp. -std::map createHolidayMap (Rules&, Interval&); +std::map createHolidayMap (Rules&, Range&); std::string renderHolidays (const std::map &); //////////////////////////////////////////////////////////////////////////////// @@ -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 ( Range { filter.start, filter.end }), - std::make_shared (filter.tags()) + std::make_shared (range), + std::make_shared (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; diff --git a/src/commands/CmdTag.cpp b/src/commands/CmdTag.cpp index f774bea7..d03e473e 100644 --- a/src/commands/CmdTag.cpp +++ b/src/commands/CmdTag.cpp @@ -43,7 +43,7 @@ int CmdTag ( // Gather IDs and TAGs. std::set ids = cli.getIds (); - std::vector tags = cli.getTags (); + std::set tags = cli.getTags (); if (tags.empty ()) { diff --git a/src/commands/CmdTags.cpp b/src/commands/CmdTags.cpp index 25882c07..d36e46b4 100644 --- a/src/commands/CmdTags.cpp +++ b/src/commands/CmdTags.cpp @@ -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 ( Range { filter.start, filter.end }), - std::make_shared (filter.tags ()) + std::make_shared (cli.getRange ()), + std::make_shared (cli.getTags ()) }); // Generate a unique, ordered list of tags. std::set tags; + for (const auto& interval : getTracked (database, rules, filtering)) for (auto& tag : interval.tags ()) tags.insert (tag); diff --git a/src/commands/CmdTrack.cpp b/src/commands/CmdTrack.cpp index de125c8a..e1b6cbcd 100644 --- a/src/commands/CmdTrack.cpp +++ b/src/commands/CmdTrack.cpp @@ -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 (); diff --git a/src/commands/CmdUntag.cpp b/src/commands/CmdUntag.cpp index 1e88c99b..d8fb6cbe 100644 --- a/src/commands/CmdUntag.cpp +++ b/src/commands/CmdUntag.cpp @@ -43,7 +43,7 @@ int CmdUntag ( // Gather IDs and TAGs. std::set ids = cli.getIds (); - std::vector tags = cli.getTags (); + std::set tags = cli.getTags (); if (tags.empty ()) { diff --git a/src/dom.cpp b/src/dom.cpp index 051d8217..22521a07 100644 --- a/src/dom.cpp +++ b/src/dom.cpp @@ -119,8 +119,8 @@ bool domGet ( else if (pig.skipLiteral ("tracked.")) { IntervalFilterAndGroup filtering ({ - std::make_shared ( Range { filter.start, filter.end }), - std::make_shared (filter.tags()) + std::make_shared (Range {filter.start, filter.end}), + std::make_shared (filter.tags ()) }); auto tracked = getTracked (database, rules, filtering); diff --git a/test/tag.t b/test/tag.t index dfa833ba..9b5ae503 100755 --- a/test/tag.t +++ b/test/tag.t @@ -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"]) diff --git a/test/untag.t b/test/untag.t index f15c388a..ccba1f7b 100755 --- a/test/untag.t +++ b/test/untag.t @@ -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"])