int CmdStatistics::execute (std::string& output) { int rc = 0; std::stringstream out; std::string dateformat = context.config.get ("dateformat"); // Go get the file sizes. size_t dataSize = context.tdb2.pending._file.size () + context.tdb2.completed._file.size () + context.tdb2.undo._file.size () /* // TODO Re-enable this once 2.1 has taskd support. + context.tdb2.backlog._file.size () + context.tdb2.synch_key._file.size () */ ; // Count the undo transactions. std::vector <std::string> undoTxns = context.tdb2.undo.get_lines (); int undoCount = 0; std::vector <std::string>::iterator tx; for (tx = undoTxns.begin (); tx != undoTxns.end (); ++tx) if (*tx == "---") ++undoCount; // Get all the tasks. std::vector <Task> all = context.tdb2.all_tasks (); std::vector <Task> filtered; filter (all, filtered); Date now; time_t earliest = time (NULL); time_t latest = 1; int totalT = 0; int deletedT = 0; int pendingT = 0; int completedT = 0; int waitingT = 0; int taggedT = 0; int annotationsT = 0; int recurringT = 0; float daysPending = 0.0; int descLength = 0; std::map <std::string, int> allTags; std::map <std::string, int> allProjects; std::vector <Task>::iterator task; for (task = filtered.begin (); task != filtered.end (); ++task) { ++totalT; Task::status status = task->getStatus (); switch (status) { case Task::deleted: ++deletedT; break; case Task::pending: ++pendingT; break; case Task::completed: ++completedT; break; case Task::recurring: ++recurringT; break; case Task::waiting: ++waitingT; break; } time_t entry = strtol (task->get ("entry").c_str (), NULL, 10); if (entry < earliest) earliest = entry; if (entry > latest) latest = entry; if (status == Task::completed) { time_t end = strtol (task->get ("end").c_str (), NULL, 10); daysPending += (end - entry) / 86400.0; } if (status == Task::pending) daysPending += (now.toEpoch () - entry) / 86400.0; descLength += task->get ("description").length (); std::map <std::string, std::string> annotations; task->getAnnotations (annotations); annotationsT += annotations.size (); std::vector <std::string> tags; task->getTags (tags); if (tags.size ()) ++taggedT; std::vector <std::string>::iterator t; for (t = tags.begin (); t != tags.end (); ++t) allTags[*t] = 0; std::string project = task->get ("project"); if (project != "") allProjects[project] = 0; } // Create a table for output. ViewText view; view.width (context.getWidth ()); view.intraPadding (2); view.add (Column::factory ("string", STRING_CMD_STATS_CATEGORY)); view.add (Column::factory ("string", STRING_CMD_STATS_DATA)); int row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_STAT_PE); view.set (row, 1, pendingT); row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_STAT_WA); view.set (row, 1, waitingT); row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_STAT_RE); view.set (row, 1, recurringT); row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_STAT_CO); view.set (row, 1, completedT); row = view.addRow (); view.set (row, 0, STRING_COLUMN_LABEL_STAT_DE); view.set (row, 1, deletedT); row = view.addRow (); view.set (row, 0, STRING_CMD_STATS_TOTAL); view.set (row, 1, totalT); row = view.addRow (); view.set (row, 0, STRING_CMD_STATS_ANNOTATIONS); view.set (row, 1, annotationsT); row = view.addRow (); view.set (row, 0, STRING_CMD_STATS_UNIQUE_TAGS); view.set (row, 1, (int)allTags.size ()); row = view.addRow (); view.set (row, 0, STRING_CMD_STATS_PROJECTS); view.set (row, 1, (int)allProjects.size ()); row = view.addRow (); view.set (row, 0, STRING_CMD_STATS_DATA_SIZE); view.set (row, 1, formatBytes (dataSize)); row = view.addRow (); view.set (row, 0, STRING_CMD_STATS_UNDO_TXNS); view.set (row, 1, undoCount); if (totalT) { row = view.addRow (); view.set (row, 0, STRING_CMD_STATS_TAGGED); std::stringstream value; value << std::setprecision (3) << (100.0 * taggedT / totalT) << "%"; view.set (row, 1, value.str ()); } if (filtered.size ()) { Date e (earliest); row = view.addRow (); view.set (row, 0, STRING_CMD_STATS_OLDEST); view.set (row, 1, e.toString (dateformat)); Date l (latest); row = view.addRow (); view.set (row, 0, STRING_CMD_STATS_NEWEST); view.set (row, 1, l.toString (dateformat)); row = view.addRow (); view.set (row, 0, STRING_CMD_STATS_USED_FOR); view.set (row, 1, Duration (latest - earliest).format ()); } if (totalT) { row = view.addRow (); view.set (row, 0, STRING_CMD_STATS_ADD_EVERY); view.set (row, 1, Duration (((latest - earliest) / totalT)).format ()); } if (completedT) { row = view.addRow (); view.set (row, 0, STRING_CMD_STATS_COMP_EVERY); view.set (row, 1, Duration ((latest - earliest) / completedT).format ()); } if (deletedT) { row = view.addRow (); view.set (row, 0, STRING_CMD_STATS_DEL_EVERY); view.set (row, 1, Duration ((latest - earliest) / deletedT).format ()); } if (pendingT || completedT) { row = view.addRow (); view.set (row, 0, STRING_CMD_STATS_AVG_PEND); view.set (row, 1, Duration ((int) ((daysPending / (pendingT + completedT)) * 86400)).format ()); } if (totalT) { row = view.addRow (); view.set (row, 0, STRING_CMD_STATS_DESC_LEN); view.set (row, 1, format (STRING_CMD_STATS_CHARS, (int) (descLength / totalT))); } /* // TODO Re-enable this when 2.1 has taskd support. Until then, it makes no // sense to include this. row = view.addRow (); view.set (row, 0, STRING_CMD_STATS_LAST_SYNCH); if (context.tdb2.synch_key._file.exists ()) view.set (row, 1, Date (context.tdb2.synch_key._file.mtime ()).toISO ()); else view.set (row, 1, "-"); */ // If an alternating row color is specified, notify the table. if (context.color ()) { Color alternate (context.config.get ("color.alternate")); if (alternate.nontrivial ()) { view.colorOdd (alternate); view.intraColorOdd (alternate); view.extraColorOdd (alternate); } } out << optionalBlankLine () << view.render () << optionalBlankLine (); output = out.str (); return rc; }
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 CmdProjects::execute (std::string& output) { int rc = 0; // Get all the tasks. handleRecurrence (); std::vector <Task> tasks = context.tdb2.pending.get_tasks (); if (context.config.getBoolean ("list.all.projects")) { std::vector <Task> extra = context.tdb2.completed.get_tasks (); std::vector <Task>::iterator task; for (task = extra.begin (); task != extra.end (); ++task) tasks.push_back (*task); } int quantity = tasks.size (); context.tdb2.commit (); // Apply filter. std::vector <Task> filtered; filter (tasks, filtered); std::stringstream out; // Scan all the tasks for their project name, building a map using project // names as keys. std::map <std::string, int> unique; std::map <std::string, int> high; std::map <std::string, int> medium; std::map <std::string, int> low; std::map <std::string, int> none; bool no_project = false; std::string project; std::string priority; std::vector <Task>::iterator task; for (task = filtered.begin (); task != filtered.end (); ++task) { project = task->get ("project"); priority = task->get ("priority"); unique[project] += 1; if (project == "") no_project = true; if (priority == "H") high[project] += 1; else if (priority == "M") medium[project] += 1; else if (priority == "L") low[project] += 1; else none[project] += 1; } if (unique.size ()) { // Render a list of project names from the map. ViewText view; view.width (context.getWidth ()); view.add (Column::factory ("string", STRING_COLUMN_LABEL_PROJECT)); view.add (Column::factory ("string.right", STRING_COLUMN_LABEL_TASKS)); view.add (Column::factory ("string.right", STRING_CMD_PROJECTS_PRI_N)); view.add (Column::factory ("string.right", STRING_CMD_PROJECTS_PRI_H)); view.add (Column::factory ("string.right", STRING_CMD_PROJECTS_PRI_M)); view.add (Column::factory ("string.right", STRING_CMD_PROJECTS_PRI_L)); std::map <std::string, int>::iterator project; for (project = unique.begin (); project != unique.end (); ++project) { int row = view.addRow (); view.set (row, 0, (project->first == "" ? STRING_CMD_PROJECTS_NONE : project->first)); view.set (row, 1, project->second); view.set (row, 2, none[project->first]); view.set (row, 3, low[project->first]); view.set (row, 4, medium[project->first]); view.set (row, 5, high[project->first]); } int number_projects = unique.size (); if (no_project) --number_projects; out << optionalBlankLine () << view.render () << optionalBlankLine () << (number_projects == 1 ? format (STRING_CMD_PROJECTS_SUMMARY, number_projects) : format (STRING_CMD_PROJECTS_SUMMARY2, number_projects)) << " " << (quantity == 1 ? format (STRING_CMD_PROJECTS_TASK, quantity) : format (STRING_CMD_PROJECTS_TASKS, quantity)) << "\n"; } else { out << STRING_CMD_PROJECTS_NO << "\n"; rc = 1; } output = out.str (); return rc; }
int CmdCustom::execute (std::string& output) { int rc = 0; // Load report configuration. std::string reportColumns = context.config.get ("report." + _keyword + ".columns"); std::string reportLabels = context.config.get ("report." + _keyword + ".labels"); std::string reportSort = context.config.get ("report." + _keyword + ".sort"); std::string reportFilter = context.config.get ("report." + _keyword + ".filter"); if (reportFilter != "") reportFilter = "( " + reportFilter + " )"; std::vector <std::string> columns; split (columns, reportColumns, ','); validateReportColumns (columns); std::vector <std::string> labels; split (labels, reportLabels, ','); if (columns.size () != labels.size () && labels.size () != 0) throw format (STRING_CMD_CUSTOM_MISMATCH, _keyword); std::vector <std::string> sortOrder; split (sortOrder, reportSort, ','); validateSortColumns (sortOrder); // Prepend the argument list with those from the report filter. context.cli.addRawFilter (reportFilter); // Apply filter. handleRecurrence (); Filter filter; std::vector <Task> filtered; filter.subset (filtered); // Sort the tasks. std::vector <int> sequence; for (unsigned int i = 0; i < filtered.size (); ++i) sequence.push_back (i); sort_tasks (filtered, sequence, reportSort); // Configure the view. ViewTask view; view.width (context.getWidth ()); view.leftMargin (context.config.getInteger ("indent.report")); view.extraPadding (context.config.getInteger ("row.padding")); view.intraPadding (context.config.getInteger ("column.padding")); Color label (context.config.get ("color.label")); view.colorHeader (label); Color label_sort (context.config.get ("color.label.sort")); view.colorSortHeader (label_sort); Color alternate (context.config.get ("color.alternate")); view.colorOdd (alternate); view.intraColorOdd (alternate); // Capture columns that are sorted. std::vector <std::string> sortColumns; // Add the break columns, if any. std::vector <std::string>::iterator so; for (so = sortOrder.begin (); so != sortOrder.end (); ++so) { std::string name; bool ascending; bool breakIndicator; context.decomposeSortField (*so, name, ascending, breakIndicator); if (breakIndicator) view.addBreak (name); sortColumns.push_back (name); } // Add the columns and labels. for (unsigned int i = 0; i < columns.size (); ++i) { Column* c = Column::factory (columns[i], _keyword); if (i < labels.size ()) c->setLabel (labels[i]); bool sort = std::find (sortColumns.begin (), sortColumns.end (), c->name ()) != sortColumns.end () ? true : false; view.add (c, sort); } // How many lines taken up by table header? int table_header = 0; if (context.verbose ("label")) { if (context.color () && context.config.getBoolean ("fontunderline")) table_header = 1; // Underlining doesn't use extra line. else table_header = 2; // Dashes use an extra line. } // Report output can be limited by rows or lines. int maxrows = 0; int maxlines = 0; context.getLimits (maxrows, maxlines); // Adjust for fluff in the output. if (maxlines) maxlines -= table_header + (context.verbose ("blank") ? 1 : 0) + (context.verbose ("footnote") ? context.footnotes.size () : 0) + (context.verbose ("affected") ? 1 : 0) + context.config.getInteger ("reserved.lines"); // For prompt, etc. // Render. std::stringstream out; if (filtered.size ()) { view.truncateRows (maxrows); view.truncateLines (maxlines); out << optionalBlankLine () << view.render (filtered, sequence) << optionalBlankLine (); // Print the number of rendered tasks if (context.verbose ("affected")) { out << (filtered.size () == 1 ? STRING_CMD_CUSTOM_COUNT : format (STRING_CMD_CUSTOM_COUNTN, filtered.size ())); if (maxrows && maxrows < (int)filtered.size ()) out << ", " << format (STRING_CMD_CUSTOM_SHOWN, maxrows); if (maxlines && maxlines < (int)filtered.size ()) out << ", " << format (STRING_CMD_CUSTOM_TRUNCATED, maxlines - table_header); out << "\n"; } } else { context.footnote (STRING_FEEDBACK_NO_MATCH); rc = 1; } feedback_backlog (); 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; }
int CmdCustom::execute (std::string& output) { int rc = 0; // Load report configuration. std::string reportColumns = context.config.get ("report." + _keyword + ".columns"); std::string reportLabels = context.config.get ("report." + _keyword + ".labels"); std::string reportSort = context.config.get ("report." + _keyword + ".sort"); std::string reportFilter = context.config.get ("report." + _keyword + ".filter"); std::vector <std::string> columns; split (columns, reportColumns, ','); validateReportColumns (columns); std::vector <std::string> labels; split (labels, reportLabels, ','); if (columns.size () != labels.size () && labels.size () != 0) throw format (STRING_CMD_CUSTOM_MISMATCH, _keyword); std::vector <std::string> sortOrder; split (sortOrder, reportSort, ','); if (sortOrder.size () != 0 && sortOrder[0] != "none") validateSortColumns (sortOrder); // Add the report filter to any existing filter. if (reportFilter != "") context.cli2.addFilter (reportFilter); // Apply filter. handleRecurrence (); Filter filter; std::vector <Task> filtered; filter.subset (filtered); std::vector <int> sequence; if (sortOrder.size () && sortOrder[0] == "none") { // Assemble a sequence vector that represents the tasks listed in // context.cli2._uuid_ranges, in the order in which they appear. This // equates to no sorting, just a specified order. sortOrder.clear (); for (auto& i : context.cli2._uuid_list) for (unsigned int t = 0; t < filtered.size (); ++t) if (filtered[t].get ("uuid") == i) sequence.push_back (t); } else { // There is a sortOrder, so sorting will take place, which means the initial // order of sequence is ascending. for (unsigned int i = 0; i < filtered.size (); ++i) sequence.push_back (i); // Sort the tasks. if (sortOrder.size ()) sort_tasks (filtered, sequence, reportSort); } // Configure the view. ViewTask view; view.width (context.getWidth ()); view.leftMargin (context.config.getInteger ("indent.report")); view.extraPadding (context.config.getInteger ("row.padding")); view.intraPadding (context.config.getInteger ("column.padding")); if (context.color ()) { Color label (context.config.get ("color.label")); view.colorHeader (label); Color label_sort (context.config.get ("color.label.sort")); view.colorSortHeader (label_sort); // If an alternating row color is specified, notify the table. Color alternate (context.config.get ("color.alternate")); if (alternate.nontrivial ()) { view.colorOdd (alternate); view.intraColorOdd (alternate); } } // Capture columns that are sorted. std::vector <std::string> sortColumns; // Add the break columns, if any. for (auto& so : sortOrder) { std::string name; bool ascending; bool breakIndicator; context.decomposeSortField (so, name, ascending, breakIndicator); if (breakIndicator) view.addBreak (name); sortColumns.push_back (name); } // Add the columns and labels. for (unsigned int i = 0; i < columns.size (); ++i) { Column* c = Column::factory (columns[i], _keyword); if (i < labels.size ()) c->setLabel (labels[i]); bool sort = std::find (sortColumns.begin (), sortColumns.end (), c->name ()) != sortColumns.end () ? true : false; view.add (c, sort); } // How many lines taken up by table header? int table_header = 0; if (context.verbose ("label")) { if (context.color () && context.config.getBoolean ("fontunderline")) table_header = 1; // Underlining doesn't use extra line. else table_header = 2; // Dashes use an extra line. } // Report output can be limited by rows or lines. int maxrows = 0; int maxlines = 0; context.getLimits (maxrows, maxlines); // Adjust for fluff in the output. if (maxlines) maxlines -= table_header + (context.verbose ("blank") ? 1 : 0) + (context.verbose ("footnote") ? context.footnotes.size () : 0) + (context.verbose ("affected") ? 1 : 0) + context.config.getInteger ("reserved.lines"); // For prompt, etc. // Render. std::stringstream out; if (filtered.size ()) { view.truncateRows (maxrows); view.truncateLines (maxlines); out << optionalBlankLine () << view.render (filtered, sequence) << optionalBlankLine (); // Print the number of rendered tasks if (context.verbose ("affected")) { out << (filtered.size () == 1 ? STRING_CMD_CUSTOM_COUNT : format (STRING_CMD_CUSTOM_COUNTN, filtered.size ())); if (maxrows && maxrows < (int)filtered.size ()) out << ", " << format (STRING_CMD_CUSTOM_SHOWN, maxrows); if (maxlines && maxlines < (int)filtered.size ()) out << ", " << format (STRING_CMD_CUSTOM_TRUNCATED, maxlines - table_header); out << "\n"; } } else { context.footnote (STRING_FEEDBACK_NO_MATCH); rc = 1; } feedback_backlog (); output = out.str (); return rc; }
int CmdCustom::execute (std::string& output) { int rc = 0; // Load report configuration. std::string reportColumns = context.config.get ("report." + _keyword + ".columns"); std::string reportLabels = context.config.get ("report." + _keyword + ".labels"); std::string reportSort = context.config.get ("report." + _keyword + ".sort"); std::string reportFilter = context.config.get ("report." + _keyword + ".filter"); std::vector <std::string> columns; split (columns, reportColumns, ','); validateReportColumns (columns); std::vector <std::string> labels; split (labels, reportLabels, ','); if (columns.size () != labels.size () && labels.size () != 0) throw format (STRING_CMD_CUSTOM_MISMATCH, _keyword); std::map <std::string, std::string> columnLabels; if (labels.size ()) for (unsigned int i = 0; i < columns.size (); ++i) columnLabels[columns[i]] = labels[i]; std::vector <std::string> sortOrder; split (sortOrder, reportSort, ','); validateSortColumns (sortOrder); // Prepend the argument list with those from the report filter. std::vector <std::string> filterArgs; split (filterArgs, reportFilter, ' '); std::vector <std::string>::iterator arg; for (arg = filterArgs.begin (); arg != filterArgs.end (); ++ arg) context.a3.capture_first (*arg); context.a3.categorize (); // Load the data. handleRecurrence (); std::vector <Task> filtered; filter (filtered); // Sort the tasks. std::vector <int> sequence; for (unsigned int i = 0; i < filtered.size (); ++i) sequence.push_back (i); sort_tasks (filtered, sequence, reportSort); // Configure the view. ViewTask view; view.width (context.getWidth ()); view.leftMargin (context.config.getInteger ("indent.report")); view.extraPadding (context.config.getInteger ("row.padding")); view.intraPadding (context.config.getInteger ("column.padding")); Color label (context.config.get ("color.label")); view.colorHeader (label); Color alternate (context.config.get ("color.alternate")); view.colorOdd (alternate); view.intraColorOdd (alternate); // Add the columns and labels. for (unsigned int i = 0; i < columns.size (); ++i) { Column* c = Column::factory (columns[i], _keyword); if (i < labels.size ()) c->setLabel (labels[i]); view.add (c); } // How many lines taken up by table header? // TODO Consider rc.verbose int table_header = 0; if (context.verbose ("label")) { if (context.color () && context.config.getBoolean ("fontunderline")) table_header = 1; // Underlining doesn't use extra line. else table_header = 2; // Dashes use an extra line. } // Report output can be limited by rows or lines. int maxrows = 0; int maxlines = 0; getLimits (_keyword, maxrows, maxlines); // Adjust for fluff in the output. if (maxlines) maxlines -= (context.verbose ("blank") ? 1 : 0) + table_header + (context.verbose ("footnote") ? context.footnotes.size () : 0) + 1; // "X tasks shown ..." // Render. std::stringstream out; if (filtered.size ()) { view.truncateRows (maxrows); view.truncateLines (maxlines); out << optionalBlankLine () << view.render (filtered, sequence) << optionalBlankLine (); if (context.verbose ("affected")) out << (filtered.size () == 1 ? STRING_CMD_CUSTOM_COUNT : format (STRING_CMD_CUSTOM_COUNTN, filtered.size ())); // TODO Conditional if (maxrows && maxrows < (int)filtered.size ()) out << ", " << format (STRING_CMD_CUSTOM_SHOWN, maxrows); // TODO Conditional if (maxlines && maxlines < (int)filtered.size ()) out << ", " << format (STRING_CMD_CUSTOM_TRUNCATED, maxlines - table_header); if (context.verbose ("affected")) out << "\n"; } else { context.footnote (STRING_FEEDBACK_NO_MATCH); rc = 1; } context.tdb2.commit (); output = out.str (); return rc; }
//////////////////////////////////////////////////////////////////////////////// // Algorithm: // Copy file rc.data.location/extensions // Generate UUID // Call the "install" function once, store results in rc: // extension.<uuid>=<JSON> int CmdLogo::execute (std::string& output) { static const char* data[] = { ".........ABDEF", ".......BDILNMM", ".....ACJNLHFEE", "....AFMMFDEEEE", "...AFOIDEEEEDD", "..AFOHDEEEDIOR", "..DMIDEEEEOXXX", ".BJMDEEEDMXXXX", ".ENFEEEEFVXXXX", "BILDEEEEJXXXXX", "DLHEEEEDLXXXXX", "GMFEEEEDKXXXXX", "GMEEEEEEGTIKOR", "GMEEEEEEETODDD", "GMEEEEEEETXRGE", "GMEEEEEEFVXXSE", "ENFEEEEEHWXXUE", "CLHEEEEDLXXXUE", "AILDEEEDRXXXUE", ".DNFEEEEPXXXUE", ".BJMDEEEEPXXUE", "..DMIDEEEDLXUE", "..AFOHDEEEDHME", "...AFOIDEEEEDE", "....AFMMFDEEEE", ".....ACJNLHFEE", ".......BDILNMM", ".........ABDEF", "" }; if (!context.color ()) throw std::string (STRING_CMD_LOGO_COLOR_REQ); std::string indent (context.config.getInteger ("indent.report"), ' '); output += optionalBlankLine (); for (int line = 0; data[line][0]; ++line) { output += indent; for (int c = 0; c < 14; ++c) { int value = (int) data[line][c]; if (value == '.') output += " "; else { value += 167; char block [24]; sprintf (block, "\033[48;5;%dm \033[0m", value); output += block; } } for (int c = 13; c >= 0; --c) { int value = data[line][c]; if (value == '.') output += " "; else { value += 167; char block [24]; sprintf (block, "\033[48;5;%dm \033[0m", value); output += block; } } output += "\n"; } output += optionalBlankLine (); return 0; }
//////////////////////////////////////////////////////////////////////////////// // 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 CmdCalendar::execute (std::string& output) { int rc = 0; // Each month requires 28 text columns width. See how many will actually // fit. But if a preference is specified, and it fits, use it. int width = context.getWidth (); int preferredMonthsPerLine = (context.config.getInteger ("monthsperline")); int monthsThatFit = width / 26; int monthsPerLine = monthsThatFit; if (preferredMonthsPerLine != 0 && preferredMonthsPerLine < monthsThatFit) monthsPerLine = preferredMonthsPerLine; // Load the pending tasks. handleRecurrence (); std::vector <Task> tasks = context.tdb2.pending.get_tasks (); Date today; bool getpendingdate = false; int monthsToDisplay = 1; int mFrom = today.month (); int yFrom = today.year (); int mTo = mFrom; int yTo = yFrom; // Defaults. monthsToDisplay = monthsPerLine; mFrom = today.month (); yFrom = today.year (); // Set up a vector of commands (1), for autoComplete. std::vector <std::string> commandNames; commandNames.push_back ("calendar"); // Set up a vector of keywords, for autoComplete. std::vector <std::string> keywordNames; keywordNames.push_back ("due"); // Set up a vector of months, for autoComplete. std::vector <std::string> monthNames; for (int i = 1; i <= 12; ++i) monthNames.push_back (lowerCase (Date::monthName (i))); // For autoComplete results. std::vector <std::string> matches; // Look at all args, regardless of sequence. int argMonth = 0; int argYear = 0; bool argWholeYear = false; std::vector <std::string> words = context.cli.getWords (); std::vector <std::string>::iterator arg; for (arg = words.begin (); arg != words.end (); ++arg) { // Some version of "calendar". if (autoComplete (lowerCase (*arg), commandNames, matches, context.config.getInteger ("abbreviation.minimum")) == 1) continue; // "due". else if (autoComplete (lowerCase (*arg), keywordNames, matches, context.config.getInteger ("abbreviation.minimum")) == 1) getpendingdate = true; // "y". else if (lowerCase (*arg) == "y") argWholeYear = true; // YYYY. else if (Lexer::isAllDigits (*arg) && arg->length () == 4) argYear = strtol (arg->c_str (), NULL, 10); // MM. else if (Lexer::isAllDigits (*arg) && arg->length () <= 2) { argMonth = strtol (arg->c_str (), NULL, 10); if (argMonth < 1 || argMonth > 12) throw format (STRING_CMD_CAL_BAD_MONTH, *arg); } // "January" etc. else if (autoComplete (lowerCase (*arg), monthNames, matches, context.config.getInteger ("abbreviation.minimum")) == 1) { argMonth = Date::monthOfYear (matches[0]); if (argMonth == -1) throw format (STRING_CMD_CAL_BAD_MONTH, *arg); } else throw format (STRING_CMD_CAL_BAD_ARG, *arg); } // Supported combinations: // // Command line monthsToDisplay mFrom yFrom getpendingdate // ------------ --------------- ----- ----- -------------- // cal monthsPerLine today today false // cal y 12 today today false // cal due monthsPerLine today today true // cal YYYY 12 1 arg false // cal due y 12 today today true // cal MM YYYY monthsPerLine arg arg false // cal MM YYYY y 12 arg arg false if (argWholeYear || (argYear && !argMonth && !argWholeYear)) monthsToDisplay = 12; if (!argMonth && argYear) mFrom = 1; else if (argMonth && argYear) mFrom = argMonth; if (argYear) yFrom = argYear; // Now begin the data subset and rendering. int countDueDates = 0; if (getpendingdate == true) { // Find the oldest pending due date. Date oldest (12, 31, 2037); std::vector <Task>::iterator task; for (task = tasks.begin (); task != tasks.end (); ++task) { if (task->getStatus () == Task::pending) { if (task->has ("due") && !task->hasTag ("nocal")) { ++countDueDates; Date d (task->get ("due")); if (d < oldest) oldest = d; } } } mFrom = oldest.month(); yFrom = oldest.year(); } if (context.config.getBoolean ("calendar.offset")) { int moffset = context.config.getInteger ("calendar.offset.value") % 12; int yoffset = context.config.getInteger ("calendar.offset.value") / 12; mFrom += moffset; yFrom += yoffset; if (mFrom < 1) { mFrom += 12; yFrom--; } else if (mFrom > 12) { mFrom -= 12; yFrom++; } } mTo = mFrom + monthsToDisplay - 1; yTo = yFrom; if (mTo > 12) { mTo -= 12; yTo++; } int details_yFrom = yFrom; int details_mFrom = mFrom; std::stringstream out; out << "\n"; while (yFrom < yTo || (yFrom == yTo && mFrom <= mTo)) { int nextM = mFrom; int nextY = yFrom; // Print month headers (cheating on the width settings, yes) for (int i = 0 ; i < monthsPerLine ; i++) { std::string month = Date::monthName (nextM); // 12345678901234567890123456 = 26 chars wide // ^^ = center // <-------> = 13 - (month.length / 2) + 1 // <------> = 26 - above // +--------------------------+ // | July 2009 | // | Mo Tu We Th Fr Sa Su | // | 27 1 2 3 4 5 | // | 28 6 7 8 9 10 11 12 | // | 29 13 14 15 16 17 18 19 | // | 30 20 21 22 23 24 25 26 | // | 31 27 28 29 30 31 | // +--------------------------+ int totalWidth = 26; int labelWidth = month.length () + 5; // 5 = " 2009" int leftGap = (totalWidth / 2) - (labelWidth / 2); int rightGap = totalWidth - leftGap - labelWidth; out << std::setw (leftGap) << ' ' << month << ' ' << nextY << std::setw (rightGap) << ' '; if (++nextM > 12) { nextM = 1; nextY++; } } out << "\n" << optionalBlankLine () << renderMonths (mFrom, yFrom, today, tasks, monthsPerLine) << "\n"; mFrom += monthsPerLine; if (mFrom > 12) { mFrom -= 12; ++yFrom; } } Color color_today (context.config.get ("color.calendar.today")); Color color_due (context.config.get ("color.calendar.due")); Color color_duetoday (context.config.get ("color.calendar.due.today")); Color color_overdue (context.config.get ("color.calendar.overdue")); Color color_weekend (context.config.get ("color.calendar.weekend")); Color color_holiday (context.config.get ("color.calendar.holiday")); Color color_weeknumber (context.config.get ("color.calendar.weeknumber")); Color color_label (context.config.get ("color.label")); if (context.color () && context.config.getBoolean ("calendar.legend")) out << "Legend: " << color_today.colorize ("today") << ", " << color_due.colorize ("due") << ", " << color_duetoday.colorize ("due-today") << ", " << color_overdue.colorize ("overdue") << ", " << color_weekend.colorize ("weekend") << ", " << color_holiday.colorize ("holiday") << ", " << color_weeknumber.colorize ("weeknumber") << "." << optionalBlankLine () << "\n"; if (context.config.get ("calendar.details") == "full" || context.config.get ("calendar.holidays") == "full") { --details_mFrom; if (details_mFrom == 0) { details_mFrom = 12; --details_yFrom; } int details_dFrom = Date::daysInMonth (details_mFrom, details_yFrom); ++mTo; if (mTo == 13) { mTo = 1; ++yTo; } Date date_after (details_mFrom, details_dFrom, details_yFrom); std::string after = date_after.toString (context.config.get ("dateformat")); Date date_before (mTo, 1, yTo); std::string before = date_before.toString (context.config.get ("dateformat")); // Table with due date information if (context.config.get ("calendar.details") == "full") { // Assert that 'report' is a valid report. std::string report = context.config.get ("calendar.details.report"); if (context.commands.find (report) == context.commands.end ()) throw std::string (STRING_ERROR_DETAILS); // If the executable was "cal" or equivalent, replace it with "task". std::string executable = context.cli._args[0].attribute ("raw"); std::string::size_type cal = executable.find ("cal"); if (cal != std::string::npos) executable = executable.substr (0, cal) + PACKAGE; std::vector <std::string> args; args.push_back ("rc:" + context.rc_file._data); args.push_back ("rc.due:0"); args.push_back ("rc.verbose:label,affected,blank"); args.push_back ("due.after:" + after); args.push_back ("due.before:" + before); args.push_back ("-nocal"); args.push_back (report); std::string output; ::execute (executable, args, "", output); out << output; } // Table with holiday information if (context.config.get ("calendar.holidays") == "full") { ViewText holTable; holTable.width (context.getWidth ()); holTable.add (Column::factory ("string", STRING_CMD_CAL_LABEL_DATE)); holTable.add (Column::factory ("string", STRING_CMD_CAL_LABEL_HOL)); holTable.colorHeader (color_label); Config::const_iterator it; std::map <time_t, std::vector<std::string>> hm; // we need to store multiple holidays per day for (it = context.config.begin (); it != context.config.end (); ++it) if (it->first.substr (0, 8) == "holiday.") if (it->first.substr (it->first.size () - 4) == "name") { std::string holName = context.config.get ("holiday." + it->first.substr (8, it->first.size () - 13) + ".name"); std::string holDate = context.config.get ("holiday." + it->first.substr (8, it->first.size () - 13) + ".date"); Date hDate (holDate.c_str (), context.config.get ("dateformat.holiday")); if (date_after < hDate && hDate < date_before) { hm[hDate.toEpoch()].push_back(holName); } } std::string format = context.config.get ("report." + context.config.get ("calendar.details.report") + ".dateformat"); if (format == "") format = context.config.get ("dateformat.report"); if (format == "") format = context.config.get ("dateformat"); std::map <time_t, std::vector<std::string>>::iterator hm_it; for (hm_it = hm.begin(); hm_it != hm.end(); ++hm_it) { std::vector <std::string> v = hm_it->second; Date hDate (hm_it->first); std::string d = hDate.toString (format); for (size_t i = 0; i < v.size(); i++) { int row = holTable.addRow (); holTable.set (row, 0, d); holTable.set (row, 1, v[i]); } } out << optionalBlankLine () << holTable.render () << "\n"; } } output = out.str (); return rc; }
int CmdReports::execute (std::string& output) { std::vector <std::string> reports; // Add custom reports. std::vector <std::string> vars; context.config.all (vars); std::vector <std::string>::iterator i; for (i = vars.begin (); i != vars.end (); ++i) { if (i->substr (0, 7) == "report.") { std::string report = i->substr (7); std::string::size_type columns = report.find (".columns"); if (columns != std::string::npos) reports.push_back (report.substr (0, columns)); } } // Add known reports. reports.push_back ("burndown.daily"); reports.push_back ("burndown.monthly"); reports.push_back ("burndown.weekly"); reports.push_back ("ghistory.annual"); reports.push_back ("ghistory.monthly"); reports.push_back ("history.annual"); reports.push_back ("history.monthly"); reports.push_back ("information"); reports.push_back ("projects"); reports.push_back ("summary"); reports.push_back ("tags"); std::sort (reports.begin (), reports.end ()); // Compose the output. std::stringstream out; ViewText view; view.width (context.getWidth ()); view.add (Column::factory ("string", STRING_CMD_REPORTS_REPORT)); view.add (Column::factory ("string", STRING_CMD_REPORTS_DESC)); // 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); } std::vector <std::string>::iterator report; for (report = reports.begin (); report != reports.end (); ++report) { int row = view.addRow (); view.set (row, 0, *report); view.set (row, 1, context.commands[*report]->description ()); } out << optionalBlankLine () << view.render () << optionalBlankLine () << format (STRING_CMD_REPORTS_SUMMARY, reports.size ()) << "\n"; output = out.str (); return 0; }
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; }