//////////////////////////////////////////////////////////////////////////////// // // Copyright 2006 - 2018, 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 #include #include #include #include #include #include #include extern Context context; //////////////////////////////////////////////////////////////////////////////// CmdSummary::CmdSummary () { _keyword = "summary"; _usage = "task summary"; _description = "Shows a report of task status by project"; _read_only = true; _displays_id = false; _needs_gc = true; _uses_context = true; _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; _category = Command::Category::graphs; } //////////////////////////////////////////////////////////////////////////////// // Project Remaining Avg Age Complete 0% 100% // A 12 13d 55% XXXXXXXXXXXXX----------- // B 109 3d 12h 10% XXX--------------------- int CmdSummary::execute (std::string& output) { int rc = 0; bool showAllProjects = context.config.getBoolean ("summary.all.projects"); // Apply filter. handleRecurrence (); Filter filter; std::vector filtered; filter.subset (filtered); // Generate unique list of project names from all pending tasks. std::map allProjects; for (auto& task : filtered) if (showAllProjects || task.getStatus () == Task::pending) allProjects[task.get ("project")] = false; // Initialize counts, sum. std::map countPending; std::map countCompleted; std::map sumEntry; std::map counter; time_t now = time (NULL); // Initialize counters. for (auto& project : allProjects) { countPending [project.first] = 0; countCompleted [project.first] = 0; sumEntry [project.first] = 0.0; counter [project.first] = 0; } // Count the various tasks. for (auto& task : filtered) { std::string project = task.get ("project"); std::vector projects = extractParents (project); projects.push_back (project); for (auto& parent : projects) ++counter[parent]; if (task.getStatus () == Task::pending || task.getStatus () == Task::waiting) { for (auto& parent : projects) { ++countPending[parent]; time_t entry = strtol (task.get ("entry").c_str (), NULL, 10); if (entry) sumEntry[parent] = sumEntry[parent] + (double) (now - entry); } } else if (task.getStatus () == Task::completed) { for (auto& parent : projects) { ++countCompleted[parent]; time_t entry = strtol (task.get ("entry").c_str (), NULL, 10); time_t end = strtol (task.get ("end").c_str (), NULL, 10); if (entry && end) sumEntry[parent] = sumEntry[parent] + (double) (end - entry); } } } // Create a table for output. Table view; view.width (context.getWidth ()); view.add ("Project"); view.add ("Remaining", false); view.add ("Avg age", false); view.add ("Complete", false); view.add ("0% 100%", true, false); setHeaderUnderline (view); Color bar_color; Color bg_color; if (context.color ()) { bar_color = Color (context.config.get ("color.summary.bar")); bg_color = Color (context.config.get ("color.summary.background")); } int barWidth = 30; std::vector processed; for (auto& i : allProjects) { if (showAllProjects || countPending[i.first] > 0) { const std::vector parents = extractParents (i.first); for (auto& parent : parents) { if (std::find (processed.begin (), processed.end (), parent) == processed.end ()) { int row = view.addRow (); view.set (row, 0, indentProject (parent)); processed.push_back (parent); } } int row = view.addRow (); view.set (row, 0, (i.first == "" ? "(none)" : indentProject (i.first, " ", '.'))); view.set (row, 1, countPending[i.first]); if (counter[i.first]) view.set (row, 2, Duration ((int) (sumEntry[i.first] / (double)counter[i.first])).formatVague ()); int c = countCompleted[i.first]; int p = countPending[i.first]; int completedBar = 0; if (c + p) completedBar = (c * barWidth) / (c + p); std::string bar; std::string subbar; if (context.color ()) { bar += bar_color.colorize (std::string ( completedBar, ' ')); bar += bg_color.colorize (std::string (barWidth - completedBar, ' ')); } else { bar += std::string ( completedBar, '=') + std::string (barWidth - completedBar, ' '); } view.set (row, 4, bar); char percent[12] = "0%"; if (c + p) snprintf (percent, 12, "%d%%", 100 * c / (c + p)); view.set (row, 3, percent); processed.push_back (i.first); } } std::stringstream out; if (view.rows ()) { out << optionalBlankLine () << view.render () << optionalBlankLine (); out << format ("{1} projects\n", view.rows ()); } else { out << "No projects.\n"; rc = 1; } output = out.str (); return rc; } ////////////////////////////////////////////////////////////////////////////////