int CmdGHistoryAnnual::execute (std::string& output) { int rc = 0; std::map <time_t, int> groups; // Represents any month with data std::map <time_t, int> addedGroup; // Additions by month std::map <time_t, int> completedGroup; // Completions by month std::map <time_t, int> deletedGroup; // Deletions by month // Scan the pending tasks. handleRecurrence (); std::vector <Task> filtered; filter (filtered); context.tdb2.commit (); std::vector <Task>::iterator task; for (task = filtered.begin (); task != filtered.end (); ++task) { Date entry (task->get_date ("entry")); Date end; if (task->has ("end")) end = Date (task->get_date ("end")); time_t epoch = entry.startOfYear ().toEpoch (); groups[epoch] = 0; // Every task has an entry date. ++addedGroup[epoch]; // All deleted tasks have an end date. if (task->getStatus () == Task::deleted) { epoch = end.startOfYear ().toEpoch (); groups[epoch] = 0; ++deletedGroup[epoch]; } // All completed tasks have an end date. else if (task->getStatus () == Task::completed) { epoch = end.startOfYear ().toEpoch (); groups[epoch] = 0; ++completedGroup[epoch]; } } int widthOfBar = context.getWidth () - 5; // 5 == strlen ("YYYY ") // Now build the view. ViewText view; view.width (context.getWidth ()); view.add (Column::factory ("string", STRING_CMD_GHISTORY_YEAR)); view.add (Column::factory ("string.left_fixed", STRING_CMD_GHISTORY_NUMBER)); Color color_add (context.config.get ("color.history.add")); Color color_done (context.config.get ("color.history.done")); Color color_delete (context.config.get ("color.history.delete")); // Determine the longest line, and the longest "added" line. int maxAddedLine = 0; int maxRemovedLine = 0; std::map <time_t, int>::iterator i; for (i = groups.begin (); i != groups.end (); ++i) { if (completedGroup[i->first] + deletedGroup[i->first] > maxRemovedLine) maxRemovedLine = completedGroup[i->first] + deletedGroup[i->first]; if (addedGroup[i->first] > maxAddedLine) maxAddedLine = addedGroup[i->first]; } int maxLine = maxAddedLine + maxRemovedLine; if (maxLine > 0) { unsigned int leftOffset = (widthOfBar * maxAddedLine) / maxLine; int totalAdded = 0; int totalCompleted = 0; int totalDeleted = 0; int priorYear = 0; int row = 0; std::map <time_t, int>::iterator i; for (i = groups.begin (); i != groups.end (); ++i) { row = view.addRow (); totalAdded += addedGroup[i->first]; totalCompleted += completedGroup[i->first]; totalDeleted += deletedGroup[i->first]; Date dt (i->first); int m, d, y; dt.toMDY (m, d, y); if (y != priorYear) { view.set (row, 0, y); priorYear = y; } unsigned int addedBar = (widthOfBar * addedGroup[i->first]) / maxLine; unsigned int completedBar = (widthOfBar * completedGroup[i->first]) / maxLine; unsigned int deletedBar = (widthOfBar * deletedGroup[i->first]) / maxLine; std::string bar = ""; if (context.color ()) { std::string aBar = ""; if (addedGroup[i->first]) { aBar = format (addedGroup[i->first]); while (aBar.length () < addedBar) aBar = " " + aBar; } std::string cBar = ""; if (completedGroup[i->first]) { cBar = format (completedGroup[i->first]); while (cBar.length () < completedBar) cBar = " " + cBar; } std::string dBar = ""; if (deletedGroup[i->first]) { dBar = format (deletedGroup[i->first]); while (dBar.length () < deletedBar) dBar = " " + dBar; } bar += std::string (leftOffset - aBar.length (), ' '); bar += color_add.colorize (aBar); bar += color_done.colorize (cBar); bar += color_delete.colorize (dBar); } else { std::string aBar = ""; while (aBar.length () < addedBar) aBar += "+"; std::string cBar = ""; while (cBar.length () < completedBar) cBar += "X"; std::string dBar = ""; while (dBar.length () < deletedBar) dBar += "-"; bar += std::string (leftOffset - aBar.length (), ' '); bar += aBar + cBar + dBar; } view.set (row, 1, bar); } } std::stringstream out; if (view.rows ()) { out << optionalBlankLine () << view.render () << "\n"; if (context.color ()) out << format (STRING_CMD_HISTORY_LEGEND, color_add.colorize (STRING_CMD_HISTORY_ADDED), color_done.colorize (STRING_CMD_HISTORY_COMP), color_delete.colorize (STRING_CMD_HISTORY_DEL)) << optionalBlankLine () << "\n"; else out << STRING_CMD_HISTORY_LEGEND_A << "\n"; } else { context.footnote (STRING_FEEDBACK_NO_TASKS); rc = 1; } output = out.str (); return rc; }
int CmdHistoryMonthly::execute (std::string& output) { int rc = 0; std::map <time_t, int> groups; // Represents any month with data std::map <time_t, int> addedGroup; // Additions by month std::map <time_t, int> completedGroup; // Completions by month std::map <time_t, int> deletedGroup; // Deletions by month // Scan the pending tasks. handleRecurrence (); std::vector <Task> filtered; filter (filtered); context.tdb2.commit (); std::vector <Task>::iterator task; for (task = filtered.begin (); task != filtered.end (); ++task) { Date entry (task->get_date ("entry")); Date end; if (task->has ("end")) end = Date (task->get_date ("end")); time_t epoch = entry.startOfMonth ().toEpoch (); groups[epoch] = 0; // Every task has an entry date. ++addedGroup[epoch]; // All deleted tasks have an end date. if (task->getStatus () == Task::deleted) { epoch = end.startOfMonth ().toEpoch (); groups[epoch] = 0; ++deletedGroup[epoch]; } // All completed tasks have an end date. else if (task->getStatus () == Task::completed) { epoch = end.startOfMonth ().toEpoch (); groups[epoch] = 0; ++completedGroup[epoch]; } } // Now build the view. ViewText view; view.width (context.getWidth ()); view.add (Column::factory ("string", STRING_CMD_HISTORY_YEAR)); view.add (Column::factory ("string", STRING_CMD_HISTORY_MONTH)); view.add (Column::factory ("string.right", STRING_CMD_HISTORY_ADDED)); view.add (Column::factory ("string.right", STRING_CMD_HISTORY_COMP)); view.add (Column::factory ("string.right", STRING_CMD_HISTORY_DEL)); view.add (Column::factory ("string.right", STRING_CMD_HISTORY_NET)); int totalAdded = 0; int totalCompleted = 0; int totalDeleted = 0; int priorYear = 0; int row = 0; std::map <time_t, int>::iterator i; for (i = groups.begin (); i != groups.end (); ++i) { row = view.addRow (); totalAdded += addedGroup [i->first]; totalCompleted += completedGroup [i->first]; totalDeleted += deletedGroup [i->first]; Date dt (i->first); int m, d, y; dt.toMDY (m, d, y); if (y != priorYear) { view.set (row, 0, y); priorYear = y; } view.set (row, 1, Date::monthName(m)); int net = 0; if (addedGroup.find (i->first) != addedGroup.end ()) { view.set (row, 2, addedGroup[i->first]); net +=addedGroup[i->first]; } if (completedGroup.find (i->first) != completedGroup.end ()) { view.set (row, 3, completedGroup[i->first]); net -= completedGroup[i->first]; } if (deletedGroup.find (i->first) != deletedGroup.end ()) { view.set (row, 4, deletedGroup[i->first]); net -= deletedGroup[i->first]; } Color net_color; if (context.color () && net) net_color = net > 0 ? Color (Color::red) : Color (Color::green); view.set (row, 5, net, net_color); } if (view.rows ()) { row = view.addRow (); view.set (row, 0, " "); row = view.addRow (); Color row_color; if (context.color ()) row_color = Color (Color::nocolor, Color::nocolor, false, true, false); view.set (row, 1, STRING_CMD_HISTORY_AVERAGE, row_color); view.set (row, 2, totalAdded / (view.rows () - 2), row_color); view.set (row, 3, totalCompleted / (view.rows () - 2), row_color); view.set (row, 4, totalDeleted / (view.rows () - 2), row_color); view.set (row, 5, (totalAdded - totalCompleted - totalDeleted) / (view.rows () - 2), row_color); } std::stringstream out; if (view.rows ()) out << optionalBlankLine () << view.render () << "\n"; else { context.footnote (STRING_FEEDBACK_NO_TASKS); rc = 1; } output = out.str (); return rc; }
//////////////////////////////////////////////////////////////////////////////// // 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; // Scan the pending tasks. handleRecurrence (); // Apply filter. std::vector <Task> filtered; filter (filtered); context.tdb2.commit (); // Generate unique list of project names from all pending tasks. std::map <std::string, bool> allProjects; std::vector <Task>::iterator task; for (task = filtered.begin (); task != filtered.end (); ++task) if (task->getStatus () == Task::pending) allProjects[task->get ("project")] = false; // Initialize counts, sum. std::map <std::string, int> countPending; std::map <std::string, int> countCompleted; std::map <std::string, double> sumEntry; std::map <std::string, int> counter; time_t now = time (NULL); // Initialize counters. std::map <std::string, bool>::iterator project; for (project = allProjects.begin (); project != allProjects.end (); ++project) { countPending [project->first] = 0; countCompleted [project->first] = 0; sumEntry [project->first] = 0.0; counter [project->first] = 0; } // Count the various tasks. for (task = filtered.begin (); task != filtered.end (); ++task) { std::string project = task->get ("project"); ++counter[project]; if (task->getStatus () == Task::pending || task->getStatus () == Task::waiting) { ++countPending[project]; time_t entry = strtol (task->get ("entry").c_str (), NULL, 10); if (entry) sumEntry[project] = sumEntry[project] + (double) (now - entry); } else if (task->getStatus () == Task::completed) { ++countCompleted[project]; 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[project] = sumEntry[project] + (double) (end - entry); } } // Create a table for output. ViewText view; view.width (context.getWidth ()); view.add (Column::factory ("string", STRING_CMD_SUMMARY_PROJECT)); view.add (Column::factory ("string.right", STRING_CMD_SUMMARY_REMAINING)); view.add (Column::factory ("string.right", STRING_CMD_SUMMARY_AVG_AGE)); view.add (Column::factory ("string.right", STRING_CMD_SUMMARY_COMPLETE)); view.add (Column::factory ("string.left_fixed", "0% 100%")); Color bar_color (context.config.get ("color.summary.bar")); Color bg_color (context.config.get ("color.summary.background")); int barWidth = 30; std::vector <std::string> processed; std::map <std::string, bool>::iterator i; for (i = allProjects.begin (); i != allProjects.end (); ++i) { if (countPending[i->first] > 0) { const std::vector <std::string> parents = extractParents (i->first); std::vector <std::string>::const_iterator parent; for (parent = parents.begin (); parent != parents.end (); parent++) { 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 == "" ? STRING_CMD_SUMMARY_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])).format ()); int c = countCompleted[i->first]; int p = countPending[i->first]; int 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]; sprintf (percent, "%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 (); if (view.rows ()) out << format (STRING_CMD_PROJECTS_SUMMARY2, view.rows ()); else out << STRING_CMD_PROJECTS_SUMMARY; out << "\n"; } else { out << STRING_CMD_PROJECTS_NO << "\n"; rc = 1; } output = out.str (); return rc; }
int CmdTimesheet::execute (std::string& output) { int rc = 0; // Scan the pending tasks. handleRecurrence (); std::vector <Task> all = context.tdb2.all_tasks (); context.tdb2.commit (); // What day of the week does the user consider the first? int weekStart = Date::dayOfWeek (context.config.get ("weekstart")); if (weekStart != 0 && weekStart != 1) throw std::string (STRING_DATE_BAD_WEEKSTART); // Determine the date of the first day of the most recent report. Date today; Date start; start -= (((today.dayOfWeek () - weekStart) + 7) % 7) * 86400; // Roll back to midnight. start = Date (start.month (), start.day (), start.year ()); Date end = start + (7 * 86400); // Determine how many reports to run. int quantity = 1; std::vector <std::string> words = context.a3.extract_words (); if (words.size () == 1) quantity = strtol (words[0].c_str (), NULL, 10);; std::stringstream out; for (int week = 0; week < quantity; ++week) { Date endString (end); endString -= 86400; std::string title = start.toString (context.config.get ("dateformat")) + " - " + endString.toString (context.config.get ("dateformat")); Color bold (Color::nocolor, Color::nocolor, false, true, false); out << "\n" << (context.color () ? bold.colorize (title) : title) << "\n"; // Render the completed table. ViewText completed; completed.width (context.getWidth ()); completed.add (Column::factory ("string", " ")); completed.add (Column::factory ("string", STRING_COLUMN_LABEL_PROJECT)); completed.add (Column::factory ("string.right", STRING_COLUMN_LABEL_DUE)); completed.add (Column::factory ("string", STRING_COLUMN_LABEL_DESC)); std::vector <Task>::iterator task; for (task = all.begin (); task != all.end (); ++task) { // If task completed within range. if (task->getStatus () == Task::completed) { Date compDate (task->get_date ("end")); if (compDate >= start && compDate < end) { Color c (task->get ("fg") + " " + task->get ("bg")); if (context.color ()) autoColorize (*task, c); int row = completed.addRow (); std::string format = context.config.get ("dateformat.report"); if (format == "") format = context.config.get ("dateformat"); completed.set (row, 1, task->get ("project"), c); if(task->has ("due")) { Date dt (task->get_date ("due")); completed.set (row, 2, dt.toString (format)); } std::string description = task->get ("description"); int indent = context.config.getInteger ("indent.annotation"); std::map <std::string, std::string> annotations; task->getAnnotations (annotations); std::map <std::string, std::string>::iterator ann; for (ann = annotations.begin (); ann != annotations.end (); ++ann) description += "\n" + std::string (indent, ' ') + Date (ann->first.substr (11)).toString (context.config.get ("dateformat")) + " " + ann->second; completed.set (row, 3, description, c); } } } out << " " << format (STRING_CMD_TIMESHEET_DONE, completed.rows ()) << "\n"; if (completed.rows ()) out << completed.render () << "\n"; // Now render the started table. ViewText started; started.width (context.getWidth ()); started.add (Column::factory ("string", " ")); started.add (Column::factory ("string", STRING_COLUMN_LABEL_PROJECT)); started.add (Column::factory ("string.right", STRING_COLUMN_LABEL_DUE)); started.add (Column::factory ("string", STRING_COLUMN_LABEL_DESC)); for (task = all.begin (); task != all.end (); ++task) { // If task started within range, but not completed withing range. if (task->getStatus () == Task::pending && task->has ("start")) { Date startDate (task->get_date ("start")); if (startDate >= start && startDate < end) { Color c (task->get ("fg") + " " + task->get ("bg")); if (context.color ()) autoColorize (*task, c); int row = started.addRow (); std::string format = context.config.get ("dateformat.report"); if (format == "") format = context.config.get ("dateformat"); started.set (row, 1, task->get ("project"), c); if(task->has ("due")) { Date dt (task->get_date ("due")); started.set (row, 2, dt.toString (format)); } std::string description = task->get ("description"); int indent = context.config.getInteger ("indent.annotation"); std::map <std::string, std::string> annotations; task->getAnnotations (annotations); std::map <std::string, std::string>::iterator ann; for (ann = annotations.begin (); ann != annotations.end (); ++ann) description += "\n" + std::string (indent, ' ') + Date (ann->first.substr (11)).toString (context.config.get ("dateformat")) + " " + ann->second; started.set (row, 3, description, c); } } } out << " " << format (STRING_CMD_TIMESHEET_STARTED, started.rows ()) << "\n"; if (started.rows ()) out << started.render () << "\n\n"; // Prior week. start -= 7 * 86400; end -= 7 * 86400; } output = out.str (); return rc; }
int CmdShow::execute (std::string& output) { int rc = 0; std::stringstream out; // Obtain the arguments from the description. That way, things like '--' // have already been handled. std::vector <std::string> words = context.a3.extract_words (); if (words.size () > 2) throw std::string (STRING_CMD_SHOW_ARGS); int width = context.getWidth (); // Complain about configuration variables that are not recognized. // These are the regular configuration variables. // Note that there is a leading and trailing space, to make it easier to // search for whole words. std::string recognized = " abbreviation.minimum" " active.indicator" " avoidlastcolumn" " bulk" " burndown.bias" " calendar.details" " calendar.details.report" " calendar.holidays" " calendar.legend" " calendar.offset" " calendar.offset.value" " color" " color.active" " color.alternate" " color.blocked" " color.burndown.done" " color.burndown.pending" " color.burndown.started" " color.calendar.due" " color.calendar.due.today" " color.calendar.holiday" " color.calendar.overdue" " color.calendar.today" " color.calendar.weekend" " color.calendar.weeknumber" " color.completed" " color.debug" " color.deleted" " color.due" " color.due.today" " color.footnote" " color.header" " color.history.add" " color.history.delete" " color.history.done" " color.label" " color.overdue" " color.pri.H" " color.pri.L" " color.pri.M" " color.pri.none" " color.recurring" " color.summary.background" " color.summary.bar" " color.sync.added" " color.sync.changed" " color.sync.rejected" " color.tagged" " color.undo.after" " color.undo.before" " column.padding" " complete.all.projects" " complete.all.tags" " confirmation" " data.location" " dateformat" " dateformat.annotation" " dateformat.holiday" " dateformat.report" " debug" " default.command" " default.due" " default.priority" " default.project" " defaultheight" " defaultwidth" " dependency.confirmation" " dependency.indicator" " dependency.reminder" " detection" " displayweeknumber" " dom" " due" " echo.command" // Deprecated 2.0 " edit.verbose" // Deprecated 2.0 " editor" " exit.on.missing.db" " expressions" " extensions" " fontunderline" " gc" " hyphenate" " indent.annotation" " indent.report" " journal.info" " journal.time" " journal.time.start.annotation" " journal.time.stop.annotation" " json.array" " list.all.projects" " list.all.tags" " locale" " locking" " merge.autopush" " merge.default.uri" " monthsperline" " nag" " patterns" " pull.default.uri" " push.default.uri" " recurrence.indicator" " recurrence.limit" " regex" " row.padding" " rule.precedence.color" " search.case.sensitive" " shadow.command" " shadow.file" " shadow.notify" " shell.prompt" " tag.indicator" " taskd.server" " taskd.credentials" " undo.style" " urgency.active.coefficient" " urgency.annotations.coefficient" " urgency.blocked.coefficient" " urgency.blocking.coefficient" " urgency.due.coefficient" " urgency.next.coefficient" " urgency.priority.coefficient" " urgency.project.coefficient" " urgency.tags.coefficient" " urgency.waiting.coefficient" " urgency.age.coefficient" " urgency.age.max" " verbose" " weekstart" " xterm.title" " "; // This configuration variable is supported, but not documented. It exists // so that unit tests can force color to be on even when the output from task // is redirected to a file, or stdout is not a tty. recognized += "_forcecolor "; std::vector <std::string> all; context.config.all (all); std::vector <std::string> unrecognized; std::vector <std::string>::iterator i; for (i = all.begin (); i != all.end (); ++i) { // Disallow partial matches by tacking a leading and trailing space on each // variable name. std::string pattern = " " + *i + " "; if (recognized.find (pattern) == std::string::npos) { // These are special configuration variables, because their name is // dynamic. if (i->substr (0, 14) != "color.keyword." && i->substr (0, 14) != "color.project." && i->substr (0, 10) != "color.tag." && i->substr (0, 8) != "holiday." && i->substr (0, 7) != "report." && i->substr (0, 6) != "alias." && i->substr (0, 5) != "hook." && i->substr (0, 5) != "push." && i->substr (0, 5) != "pull." && i->substr (0, 6) != "merge." && i->substr (0, 4) != "uda." && i->substr (0, 21) != "urgency.user.project." && i->substr (0, 17) != "urgency.user.tag.") { unrecognized.push_back (*i); } } } // Find all the values that match the defaults, for highlighting. std::vector <std::string> default_values; Config default_config; default_config.setDefaults (); for (i = all.begin (); i != all.end (); ++i) if (context.config.get (*i) != default_config.get (*i)) default_values.push_back (*i); // Create output view. ViewText view; view.width (width); view.add (Column::factory ("string", STRING_CMD_SHOW_CONF_VAR)); view.add (Column::factory ("string", STRING_CMD_SHOW_CONF_VALUE)); Color error ("bold white on red"); Color warning ("black on yellow"); std::string section; // Look for the first plausible argument which could be a pattern if (words.size ()) section = words[0]; if (section == "all") section = ""; for (i = all.begin (); i != all.end (); ++i) { std::string::size_type loc = i->find (section, 0); if (loc != std::string::npos) { // Look for unrecognized. Color color; if (std::find (unrecognized.begin (), unrecognized.end (), *i) != unrecognized.end ()) color = error; else if (std::find (default_values.begin (), default_values.end (), *i) != default_values.end ()) color = warning; std::string value = context.config.get (*i); // hide sensible information if ( (i->substr (0, 5) == "push." || i->substr (0, 5) == "pull." || i->substr (0, 6) == "merge.") && (i->find (".uri") != std::string::npos) ) { Uri uri (value); uri.parse (); value = uri.ToString (); } int row = view.addRow (); view.set (row, 0, *i, color); view.set (row, 1, value, color); } } out << "\n" << view.render () << (view.rows () == 0 ? STRING_CMD_SHOW_NONE : "") << (view.rows () == 0 ? "\n\n" : "\n"); if (default_values.size ()) { out << STRING_CMD_SHOW_DIFFER; if (context.color ()) out << " " << format (STRING_CMD_SHOW_DIFFER_COLOR, warning.colorize ("color")) << "\n\n"; } // Display the unrecognized variables. if (unrecognized.size ()) { out << STRING_CMD_SHOW_UNREC << "\n"; for (i = unrecognized.begin (); i != unrecognized.end (); ++i) out << " " << *i << "\n"; if (context.color ()) out << "\n " << format (STRING_CMD_SHOW_DIFFER_COLOR, error.colorize ("color")); out << "\n\n"; } out << legacyCheckForDeprecatedVariables (); out << legacyCheckForDeprecatedColor (); out << legacyCheckForDeprecatedColumns (); // TODO Check for referenced but missing theme files. // TODO Check for referenced but missing string files. // TODO Check for referenced but missing tips files. // Check for referenced but missing hook scripts. #ifdef HAVE_LIBLUA std::vector <std::string> missing_scripts; for (i = all.begin (); i != all.end (); ++i) { if (i->substr (0, 5) == "hook.") { std::string value = context.config.get (*i); Nibbler n (value); // <path>:<function> [, ...] while (!n.depleted ()) { std::string file; std::string function; if (n.getUntil (':', file) && n.skip (':') && n.getUntil (',', function)) { Path script (file); if (!script.exists () || !script.readable ()) missing_scripts.push_back (file); (void) n.skip (','); } } } } if (missing_scripts.size ()) { out << STRING_CMD_SHOW_HOOKS << "\n"; for (i = missing_scripts.begin (); i != missing_scripts.end (); ++i) out << " " << *i << "\n"; out << "\n"; } #endif // Check for bad values in rc.annotations. // TODO Deprecated. std::string annotations = context.config.get ("annotations"); if (annotations != "full" && annotations != "sparse" && annotations != "none") out << format (STRING_CMD_SHOW_CONFIG_ERROR, "annotations", annotations) << "\n"; // Check for bad values in rc.calendar.details. std::string calendardetails = context.config.get ("calendar.details"); if (calendardetails != "full" && calendardetails != "sparse" && calendardetails != "none") out << format (STRING_CMD_SHOW_CONFIG_ERROR, "calendar.details", calendardetails) << "\n"; // Check for bad values in rc.calendar.holidays. std::string calendarholidays = context.config.get ("calendar.holidays"); if (calendarholidays != "full" && calendarholidays != "sparse" && calendarholidays != "none") out << format (STRING_CMD_SHOW_CONFIG_ERROR, "calendar.holidays", calendarholidays) << "\n"; // Check for bad values in rc.default.priority. std::string defaultPriority = context.config.get ("default.priority"); if (defaultPriority != "H" && defaultPriority != "M" && defaultPriority != "L" && defaultPriority != "") out << format (STRING_CMD_SHOW_CONFIG_ERROR, "default.priority", defaultPriority) << "\n"; // Verify installation. This is mentioned in the documentation as the way // to ensure everything is properly installed. if (all.size () == 0) { out << STRING_CMD_SHOW_EMPTY << "\n"; rc = 1; } else { Directory location (context.config.get ("data.location")); if (location._data == "") out << STRING_CMD_SHOW_NO_LOCATION << "\n"; if (! location.exists ()) out << STRING_CMD_SHOW_LOC_EXIST << "\n"; } output = out.str (); return rc; }
int CmdInfo::execute (std::string& output) { int rc = 0; // Apply filter. std::vector <Task> filtered; filter (filtered); if (! filtered.size ()) { context.footnote (STRING_FEEDBACK_NO_MATCH); rc = 1; } // Get the undo data. std::vector <std::string> undo; if (context.config.getBoolean ("journal.info")) undo = context.tdb2.undo.get_lines (); // Determine the output date format, which uses a hierarchy of definitions. // rc.dateformat.info // rc.dateformat std::string dateformat = context.config.get ("dateformat.info"); if (dateformat == "") dateformat = context.config.get ("dateformat"); std::string dateformatanno = context.config.get ("dateformat.annotation"); if (dateformatanno == "") dateformatanno = dateformat; // Render each task. std::stringstream out; std::vector <Task>::iterator task; for (task = filtered.begin (); task != filtered.end (); ++task) { ViewText view; view.width (context.getWidth ()); view.add (Column::factory ("string", STRING_COLUMN_LABEL_NAME)); view.add (Column::factory ("string", STRING_COLUMN_LABEL_VALUE)); // If an alternating row color is specified, notify the table. if (context.color ()) { Color alternate (context.config.get ("color.alternate")); view.colorOdd (alternate); view.intraColorOdd (alternate); } Date now; // id int row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_ID); view.set (row, 1, (task->id ? format (task->id) : "-")); std::string status = ucFirst (Task::statusToText (task->getStatus ())); // description Color c; autoColorize (*task, c); std::string description = task->get ("description"); int indent = context.config.getInteger ("indent.annotation"); std::map <std::string, std::string> annotations; task->getAnnotations (annotations); std::map <std::string, std::string>::iterator ann; for (ann = annotations.begin (); ann != annotations.end (); ++ann) description += "\n" + std::string (indent, ' ') + Date (ann->first.substr (11)).toString (dateformatanno) + " " + ann->second; row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_DESC); view.set (row, 1, description, c); // status row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_STATUS); view.set (row, 1, status); // project if (task->has ("project")) { row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_PROJECT); view.set (row, 1, task->get ("project")); } // priority if (task->has ("priority")) { row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_PRIORITY); view.set (row, 1, task->get ("priority")); } // dependencies: blocked { std::vector <Task> blocked; dependencyGetBlocking (*task, blocked); if (blocked.size ()) { std::stringstream message; std::vector <Task>::const_iterator it; for (it = blocked.begin (); it != blocked.end (); ++it) message << it->id << " " << it->get ("description") << "\n"; row = view.addRow (); view.set (row, 0, STRING_CMD_INFO_BLOCKED); view.set (row, 1, message.str ()); } } // dependencies: blocking { std::vector <Task> blocking; dependencyGetBlocked (*task, blocking); if (blocking.size ()) { std::stringstream message; std::vector <Task>::const_iterator it; for (it = blocking.begin (); it != blocking.end (); ++it) message << it->id << " " << it->get ("description") << "\n"; row = view.addRow (); view.set (row, 0, STRING_CMD_INFO_BLOCKING); view.set (row, 1, message.str ()); } } // recur if (task->has ("recur")) { row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_RECUR_L); view.set (row, 1, task->get ("recur")); } // until if (task->has ("until")) { row = view.addRow (); view.set (row, 0, STRING_CMD_INFO_UNTIL); view.set (row, 1, Date (task->get_date ("until")).toString (dateformat)); } // mask if (task->getStatus () == Task::recurring) { row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_MASK); view.set (row, 1, task->get ("mask")); } if (task->has ("parent")) { // parent row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_PARENT); view.set (row, 1, task->get ("parent")); // imask row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_MASK_IDX); view.set (row, 1, task->get ("imask")); } // due (colored) if (task->has ("due")) { row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_DUE); view.set (row, 1, Date (task->get_date ("due")).toString (dateformat)); } // wait if (task->has ("wait")) { row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_WAITING); view.set (row, 1, Date (task->get_date ("wait")).toString (dateformat)); } // scheduled if (task->has ("scheduled")) { row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_SCHED); view.set (row, 1, Date (task->get_date ("scheduled")).toString (dateformat)); } // start if (task->has ("start")) { row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_START); view.set (row, 1, Date (task->get_date ("start")).toString (dateformat)); } // end if (task->has ("end")) { row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_END); view.set (row, 1, Date (task->get_date ("end")).toString (dateformat)); } // tags ... std::vector <std::string> tags; task->getTags (tags); if (tags.size ()) { std::string allTags; join (allTags, " ", tags); row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_TAGS); view.set (row, 1, allTags); } // uuid row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_UUID); std::string uuid = task->get ("uuid"); view.set (row, 1, uuid); // entry row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_ENTERED); Date dt (task->get_date ("entry")); std::string entry = dt.toString (dateformat); std::string age; std::string created = task->get ("entry"); if (created.length ()) { Date dt (strtol (created.c_str (), NULL, 10)); age = Duration (now - dt).format (); } view.set (row, 1, entry + " (" + age + ")"); // fg TODO deprecated 2.0 std::string color = task->get ("fg"); if (color != "") { row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_FG); view.set (row, 1, color); } // bg TODO deprecated 2.0 color = task->get ("bg"); if (color != "") { row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_BG); view.set (row, 1, color); } // Task::urgency row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_URGENCY); view.set (row, 1, trimLeft (format (task->urgency (), 4, 4))); // Show any UDAs std::vector <std::string> all = task->all (); std::vector <std::string>::iterator att; std::string type; for (att = all.begin (); att != all.end (); ++att) { type = context.config.get ("uda." + *att + ".type"); if (type != "") { Column* col = context.columns[*att]; if (col) { std::string value = task->get (*att); if (value != "") { row = view.addRow (); view.set (row, 0, col->label ()); if (type == "date") value = Date (value).toString (dateformat); else if (type == "duration") value = Duration (value).formatCompact (); view.set (row, 1, value); } } } } // Show any orphaned UDAs, which are identified by not being represented in // the context.columns map. for (att = all.begin (); att != all.end (); ++att) if (att->substr (0, 11) != "annotation_" && context.columns.find (*att) == context.columns.end ()) { row = view.addRow (); view.set (row, 0, "[" + *att); view.set (row, 1, task->get (*att) + "]"); } // Create a second table, containing undo log change details. ViewText journal; // If an alternating row color is specified, notify the table. if (context.color ()) { Color alternate (context.config.get ("color.alternate")); journal.colorOdd (alternate); journal.intraColorOdd (alternate); } journal.width (context.getWidth ()); journal.add (Column::factory ("string", STRING_COLUMN_LABEL_DATE)); journal.add (Column::factory ("string", STRING_CMD_INFO_MODIFICATION)); if (context.config.getBoolean ("journal.info") && undo.size () > 3) { // Scan the undo data for entries matching this task. std::string when; std::string previous; std::string current; unsigned int i = 0; long total_time = 0; while (i < undo.size ()) { when = undo[i++]; previous = ""; if (undo[i].substr (0, 3) == "old") previous = undo[i++]; current = undo[i++]; i++; // Separator if (current.find ("uuid:\"" + uuid) != std::string::npos) { if (previous != "") { int row = journal.addRow (); Date timestamp (strtol (when.substr (5).c_str (), NULL, 10)); journal.set (row, 0, timestamp.toString (dateformat)); Task before (previous.substr (4)); Task after (current.substr (4)); journal.set (row, 1, taskInfoDifferences (before, after, dateformat)); // calculate the total active time if (before.get ("start") == "" && after.get ("start") != "") { // task started total_time -= timestamp.toEpoch (); } else if (((before.get ("start") != "" && after.get ("start") == "") || (before.get ("status") != "completed" && after.get ("status") == "completed")) && total_time < 0) { // task stopped or done total_time += timestamp.toEpoch (); } } } } // add now() if task is still active if (total_time < 0) total_time += Date ().toEpoch (); // print total active time if (total_time > 0) { row = journal.addRow (); journal.set (row, 0, STRING_CMD_INFO_TOTAL_ACTIVE); journal.set (row, 1, Duration (total_time).formatPrecise (), (context.color () ? Color ("bold") : Color ())); } } out << optionalBlankLine () << view.render () << "\n"; if (journal.rows () > 0) out << journal.render () << "\n"; } output = out.str (); return rc; }