From 8ebf25cb019a6dbb4b736131b42b83ac3cdb1e1d Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Thu, 28 Apr 2016 20:02:31 -0400 Subject: [PATCH] data: Broke out inclusion/exclusion code from helper.cpp --- src/CMakeLists.txt | 2 +- src/Timeline.cpp | 136 -------------- src/Timeline.h | 55 ------ src/data.cpp | 435 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 436 insertions(+), 192 deletions(-) delete mode 100644 src/Timeline.cpp delete mode 100644 src/Timeline.h create mode 100644 src/data.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 01056413..4dd12c8c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,7 +15,7 @@ set (timew_SRCS CLI.cpp CLI.h Palette.cpp Palette.h Range.cpp Range.h Rules.cpp Rules.h - Timeline.cpp Timeline.h + data.cpp init.cpp helper.cpp util.cpp) diff --git a/src/Timeline.cpp b/src/Timeline.cpp deleted file mode 100644 index 44d4ebf4..00000000 --- a/src/Timeline.cpp +++ /dev/null @@ -1,136 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2015 - 2016, Paul Beckingham, Federico Hernandez. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -// http://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////// -// The Timeline object represents a continuum with a defined start and end -// point. Between these two points there may be incluѕions (tracked time), and -// exclusions (untrackable time). When all known incluѕions and exclusions are -// added to a timeline, it is possible to extract (a) untracked gaps, and (b) -// blocks of tracked time. For example: -// -// Inputs: -// -// [------------------------------------------------------) Timeline -// [---------------------------------------) Inclusion -// [--------) [--------) [--------) Exclusion -// -// Derived: -// -// [-------------) [----------------) Tracked -// [-----) [-----) Gaps -// -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -void Timeline::include (const Interval& interval) -{ - _inclusions.push_back (interval); -} - -//////////////////////////////////////////////////////////////////////////////// -void Timeline::exclude (const Exclusion& exclusion) -{ - _exclusions.push_back (exclusion); -} - -//////////////////////////////////////////////////////////////////////////////// -std::vector Timeline::tracked (Rules& rules) const -{ - // Get the set of expanded exclusions that overlap the range defined by the - // timeline. If no range is defined, derive it from the set of all data. - auto exclusions = excluded (rules); - - std::vector all; - for (auto& interval : _inclusions) - { - // Closed intervals are returned as-is. - if (interval.range.ended ()) - { - all.push_back (interval); - } - - // Open intervals need to be split according to the exclusions. - else - { - for (auto& fragment : splitInterval (interval, exclusions)) - all.push_back (fragment); - } - } - - return all; -} - -//////////////////////////////////////////////////////////////////////////////// -// Untracked time is that which is not excluded, and not filled. Gaps. -std::vector Timeline::untracked (Rules& rules) const -{ - std::vector gaps; - - // Get the set of expanded exclusions that overlap the range defined by the - // timeline. If no range is defined, derive it from the set of all data. - auto exclusions = excluded (rules); - - // TODO subtract all exclusions - // TODO subtract all inclusions - - return gaps; -} - -//////////////////////////////////////////////////////////////////////////////// -// Excluded time is that which is not available for work. -std::vector Timeline::excluded (Rules& rules) const -{ - // Create a range representing the whole timeline. - // If no range is defined, then assume the full range of all the inclusions. - Range overallRange {range}; - if (! overallRange.started () && - ! overallRange.ended ()) - overallRange = overallRangeFromIntervals (_inclusions); - - // Cobmine all the non-trackable time. - return combineHolidaysAndExclusions (overallRange, rules, _exclusions); -} - -//////////////////////////////////////////////////////////////////////////////// -std::string Timeline::dump () const -{ - std::stringstream out; - - out << "Timeline range " << range.dump () << '\n'; - for (auto& i : _inclusions) - out << " " << i.json (); - for (auto& e : _exclusions) - out << " " << e.dump (); - - return out.str (); -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/src/Timeline.h b/src/Timeline.h deleted file mode 100644 index 5b7b4338..00000000 --- a/src/Timeline.h +++ /dev/null @@ -1,55 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2015 - 2016, Paul Beckingham, Federico Hernandez. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -// http://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef INCLUDED_TIMELINE -#define INCLUDED_TIMELINE - -#include -#include -#include -#include -#include - -class Timeline -{ -public: - Timeline () = default; - void include (const Interval&); - void exclude (const Exclusion&); - std::vector tracked (Rules&) const; - std::vector untracked (Rules&) const; - std::vector excluded (Rules&) const; - std::string dump () const; - -public: - Range range {}; - -private: - std::vector _inclusions {}; - std::vector _exclusions {}; -}; - -#endif diff --git a/src/data.cpp b/src/data.cpp new file mode 100644 index 00000000..f8c1165f --- /dev/null +++ b/src/data.cpp @@ -0,0 +1,435 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 - 2016, Paul Beckingham, Federico Hernandez. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// http://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +// A filter is just another interval, containing start, end and tags. +// +// Supported interval forms: +// ["from"] ["to"|"-" ] +// ["from"] "for" +// ["before"|"after" ] +// +Interval getFilter (const CLI& cli) +{ + Interval filter; + std::string start; + std::string end; + std::string duration; + + std::vector args; + for (auto& arg : cli._args) + { + if (arg.hasTag ("BINARY") || + arg.hasTag ("CMD") || + arg.hasTag ("EXT")) + continue; + + if (arg.hasTag ("FILTER")) + { + auto canonical = arg.attribute ("canonical"); + auto raw = arg.attribute ("raw"); + + if (arg.hasTag ("HINT")) + { + if (expandIntervalHint (canonical, start, end)) + { + args.push_back (""); + args.push_back ("-"); + args.push_back (""); + } + + // Hints that are not expandable to a date range are ignored. + } + else if (arg._lextype == Lexer::Type::date) + { + if (start == "") + start = raw; + else if (end == "") + end = raw; + + args.push_back (""); + } + else if (arg._lextype == Lexer::Type::duration) + { + if (duration == "") + duration = raw; + + args.push_back (""); + } + else if (arg.hasTag ("KEYWORD")) + { + args.push_back (raw); + } + else + { + filter.tag (raw); + } + } + } + + Range range; + + // + if (args.size () == 1 && + args[0] == "") + { + range.start = Datetime (start); + range.end = Datetime ("now"); + } + + // from + else if (args.size () == 2 && + args[0] == "from" && + args[1] == "") + { + range.start = Datetime (start); + range.end = Datetime ("now"); + } + + // to/- + else if (args.size () == 3 && + args[0] == "" && + (args[1] == "to" || args[1] == "-") && + args[2] == "") + { + range.start = Datetime (start); + range.end = Datetime (end); + } + + // from/since to/- + else if (args.size () == 4 && + args[0] == "from" && + args[1] == "" && + (args[2] == "to" || args[2] == "-") && + args[3] == "") + { + range.start = Datetime (start); + range.end = Datetime (end); + } + + // for + else if (args.size () == 3 && + args[0] == "" && + args[1] == "for" && + args[2] == "") + { + range.start = Datetime (start); + range.end = Datetime (start) + Duration (duration).toTime_t (); + } + + // from/since for + else if (args.size () == 4 && + (args[0] == "from" || args[0] == "since") && + args[1] == "" && + args[2] == "for" && + args[3] == "") + { + range.start = Datetime (start); + range.end = Datetime (start) + Duration (duration).toTime_t (); + } + + // before + else if (args.size () == 3 && + args[0] == "" && + args[1] == "before" && + args[2] == "") + { + range.start = Datetime (start) - Duration (duration).toTime_t (); + range.end = Datetime (start); + } + + // after + else if (args.size () == 3 && + args[0] == "" && + args[1] == "after" && + args[2] == "") + { + range.start = Datetime (start); + range.end = Datetime (start) + Duration (duration).toTime_t (); + } + + // + else if (args.size () == 1 && + args[0] == "") + { + range.start = Datetime ("now") - Duration (duration).toTime_t (); + range.end = Datetime ("now"); + } + + // Unrecognized date range construct. + else if (args.size ()) + { + throw std::string ("Unrecognized date range: '") + join (" ", args) + "'."; + } + + filter.range = range; + return filter; +} + +//////////////////////////////////////////////////////////////////////////////// +// Read rules and extract all holiday definitions. Create a Range for each +// one that spans from midnight to midnight. +std::vector getHolidays (const Rules& rules) +{ + std::vector results; + for (auto& holiday : rules.all ("holidays.")) + { + auto lastDot = holiday.rfind ('.'); + if (lastDot != std::string::npos) + { + Range r; + Datetime d (holiday.substr (lastDot + 1), "Y_M_D"); + r.start = d; + ++d; + r.end = d; + results.push_back (r); + } + } + + return results; +} + +//////////////////////////////////////////////////////////////////////////////// +// [1] Read holiday definitions from the rules, extract their dates and create +// a set of Range from them. +// [2] For 'exc day ...' exclusions, separate into daysOn and daysOff sets, +// based on whether the exclusion is additive. +// [3] Treat daysOff as additional holidays. +// [4] Subtract daysOn from the set of holidays. +// [5] Take all the 'exc ...' exclusions and expand them into +// concrete ranges within the overall range, adding them to the results. +// +// The result is the complete set of untrackable time that lies within the +// input range. This will be a set of nights, weekends, holidays and lunchtimes. +std::vector getAllExclusions ( + const Range& range, + const Rules& rules) +{ + // Start with the set of all holidays, intersected with range. + std::vector results; + results = addRanges (range, results, getHolidays (rules)); + + auto exclusions = getExclusions (rules); + + // Find exclusions 'exc day on ' and remove from holidays. + // Find exlcusions 'exc day off ' and add to holidays. + std::vector daysOn; + std::vector daysOff; + for (auto& exclusion : exclusions) + { + if (exclusion.tokens ()[1] == "day") + { + if (exclusion.additive ()) + for (auto& r : exclusion.ranges (range)) + daysOn.push_back (r); + else + for (auto& r : exclusion.ranges (range)) + daysOff.push_back (r); + } + } + + // daysOff are combined with existing holidays. + results = addRanges (range, results, daysOff); + + // daysOn are subtracted from the existing holidays. + results = subtractRanges (range, results, daysOn); + + // Expand all exclusions that are not 'exc day ...' into excluded ranges that + // overlage with range. + std::vector exclusionRanges; + for (auto& exclusion : exclusions) + if (exclusion.tokens ()[1] != "day") + for (auto& r : exclusion.ranges (range)) + exclusionRanges.push_back (r); + + return addRanges (range, results, exclusionRanges); +} + +//////////////////////////////////////////////////////////////////////////////// +std::vector getExclusions (const Rules& rules) +{ + // Add exclusions from configuration. + std::vector all; + for (auto& name : rules.all ("exclusions.")) + all.push_back (Exclusion (lowerCase (name), rules.get (name))); + + return all; +} + +//////////////////////////////////////////////////////////////////////////////// +std::vector getInclusions (Database& database) +{ + std::vector all; + for (auto& line : database.allLines ()) + { + Interval i; + i.initialize (line); + all.push_back (i); + } + + return all; +} + +//////////////////////////////////////////////////////////////////////////////// +std::vector subset ( + const Interval& filter, + const std::vector & intervals) +{ + std::vector all; + for (auto& interval : intervals) + if (intervalMatchesFilter (interval, filter)) + all.push_back (interval); + + return all; +} + +//////////////////////////////////////////////////////////////////////////////// +std::vector subset ( + const Range& range, + const std::vector & ranges) +{ + std::vector all; + for (auto& r : ranges) + if (range.overlap (r)) + all.push_back (r); + + return all; +} + +//////////////////////////////////////////////////////////////////////////////// +std::vector subset ( + const Range& range, + const std::vector & intervals) +{ + std::vector all; + for (auto& interval : intervals) + if (range.overlap (interval.range)) + all.push_back (interval); + + return all; +} + +//////////////////////////////////////////////////////////////////////////////// +std::vector realize ( + const Interval& interval, + const std::vector & exclusions) +{ + std::vector all; + + // Start with a single range from the interval, from which to subtract. + std::vector pieces {interval.range}; + for (auto& exclusion : exclusions) + { + std::vector split_pieces; + for (auto& piece : pieces) + for (auto& smaller_piece : piece.subtract (exclusion)) + split_pieces.push_back (smaller_piece); + + pieces = split_pieces; + } + + // Return all the fragments as intervals. + for (auto& piece : pieces) + { + // Clone the interval, override the range. + Interval clipped {interval}; + clipped.range = piece; + all.push_back (clipped); + } + + return all; +} + +//////////////////////////////////////////////////////////////////////////////// +// Subset both ranges and additions by limits, and combine. +std::vector addRanges ( + const Range& limits, + const std::vector & ranges, + const std::vector & additions) +{ + std::vector results; + + for (auto& range : ranges) + if (limits.overlap (range)) + results.push_back (range); + + for (auto& addition : additions) + if (limits.overlap (addition)) + results.push_back (addition); + + return results; +} + +//////////////////////////////////////////////////////////////////////////////// +// Subtract a set of Range from another set of Range, all within a defined +// range. +std::vector subtractRanges ( + const Range& limits, + const std::vector & ranges, + const std::vector & subtractions) +{ + if (! subtractions.size ()) + return ranges; + + std::vector results; + for (auto& r1 : ranges) + for (auto& r2 : subtractions) + for (auto& r3 : r1.subtract (r2)) + results.push_back (limits.intersect (r3)); + + return results; +} + +//////////////////////////////////////////////////////////////////////////////// +// From a set of intervals, find the earliest start and the latest end, and +// return these in a Range. +Range outerRange (const std::vector & intervals) +{ + Range overall; + + for (auto& interval : intervals) + { + if (interval.range.start < overall.start || overall.start.toEpoch () == 0) + overall.start = interval.range.start; + + // Deliberately mixed start/end. + if (interval.range.start > overall.end) + overall.end = interval.range.start; + + if (interval.range.end > overall.end) + overall.end = interval.range.end; + } + + return overall; +} + +////////////////////////////////////////////////////////////////////////////////