int CmdCompletionProjects::execute (std::string& output) { // 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); } context.tdb2.commit (); // Apply filter. std::vector <Task> filtered; filter (tasks, filtered); // Scan all the tasks for their project name, building a map using project // names as keys. std::map <std::string, int> unique; std::vector <Task>::iterator task; for (task = filtered.begin (); task != filtered.end (); ++task) unique[task->get ("project")] = 0; std::map <std::string, int>::iterator project; for (project = unique.begin (); project != unique.end (); ++project) if (project->first.length ()) output += project->first + "\n"; return 0; }
int CmdBurndownMonthly::execute (std::string& output) { int rc = 0; // Scan the pending tasks, applying any filter. handleRecurrence (); std::vector <Task> filtered; filter (filtered); context.tdb2.commit (); // Create a chart, scan the tasks, then render. Chart chart ('M'); chart.scan (filtered); output = chart.render (); return rc; }
//////////////////////////////////////////////////////////////////////////////// // Introducing the Silver Bullet. This feature is the catch-all fixative for // various other ills. This is like opening up the hood and going in with a // wrench. To be used sparingly. int CmdEdit::execute (std::string& output) { // Filter the tasks. handleRecurrence (); Filter filter; std::vector <Task> filtered; filter.subset (filtered); // Find number of matching tasks. std::vector <Task>::iterator task; for (task = filtered.begin (); task != filtered.end (); ++task) if (editFile (*task)) context.tdb2.modify (*task); return 0; }
int CmdCount::execute (std::string& output) { // Apply filter. handleRecurrence (); Filter filter; std::vector <Task> filtered; filter.subset (filtered); // Find number of matching tasks. Skip recurring parent tasks. int count = 0; for (auto& task : filtered) if (task.getStatus () != Task::recurring) ++count; output = format (count) + "\n"; return 0; }
int CmdCount::execute (std::string& output) { // Apply filter. handleRecurrence (); std::vector <Task> filtered; filter (filtered); context.tdb2.commit (); // Find number of matching tasks. Skip recurring parent tasks. int count = 0; std::vector <Task>::iterator it; for (it = filtered.begin (); it != filtered.end (); ++it) if (it->getStatus () != Task::recurring) ++count; output = format (count) + "\n"; return 0; }
//////////////////////////////////////////////////////////////////////////////// // Introducing the Silver Bullet. This feature is the catch-all fixative for // various other ills. This is like opening up the hood and going in with a // wrench. To be used sparingly. int CmdEdit::execute (std::string& output) { int rc = 0; // Filter the tasks. handleRecurrence (); std::vector <Task> filtered; filter (filtered); // Find number of matching tasks. Skip recurring parent tasks. std::vector <Task>::iterator task; for (task = filtered.begin (); task != filtered.end (); ++task) if (editFile (*task)) context.tdb2.modify (*task); context.tdb2.commit (); return rc; }
int CmdCompletionUuids::execute (std::string& output) { // Apply filter. handleRecurrence (); std::vector <Task> filtered; filter (filtered); context.tdb2.commit (); std::vector <std::string> uuids; std::vector <Task>::iterator task; for (task = filtered.begin (); task != filtered.end (); ++task) uuids.push_back (task->get ("uuid")); std::sort (uuids.begin (), uuids.end ()); join (output, "\n", uuids); output += "\n"; context.headers.clear (); return 0; }
//////////////////////////////////////////////////////////////////////////////// // Introducing the Silver Bullet. This feature is the catch-all fixative for // various other ills. This is like opening up the hood and going in with a // wrench. To be used sparingly. int CmdEdit::execute (std::string&) { // Filter the tasks. handleRecurrence (); Filter filter; std::vector <Task> filtered; filter.subset (filtered); // Find number of matching tasks. for (auto& task : filtered) { CmdEdit::editResult result = editFile (task); if (result == CmdEdit::editResult::error) break; else if (result == CmdEdit::editResult::changes) context.tdb2.modify (task); } return 0; }
int CmdZshCompletionUuids::execute (std::string& output) { // Apply filter. handleRecurrence (); std::vector <Task> filtered; filter (filtered); context.tdb2.commit (); std::stringstream out; std::vector <Task>::iterator task; for (task = filtered.begin (); task != filtered.end (); ++task) out << task->get ("uuid") << ":" << str_replace (task->get ("description"), ":", zshColonReplacement) << "\n"; output = out.str (); context.headers.clear (); return 0; }
int CmdIDs::execute (std::string& output) { // Apply filter. handleRecurrence (); std::vector <Task> filtered; filter (filtered); context.tdb2.commit (); // Find number of matching tasks. std::vector <int> ids; std::vector <Task>::iterator task; for (task = filtered.begin (); task != filtered.end (); ++task) if (task->id) ids.push_back (task->id); std::sort (ids.begin (), ids.end ()); output = compressIds (ids) + "\n"; context.headers.clear (); return 0; }
int CmdCompletionIds::execute (std::string& output) { // Apply filter. handleRecurrence (); std::vector <Task> filtered; filter (filtered); context.tdb2.commit (); std::vector <int> ids; std::vector <Task>::iterator task; for (task = filtered.begin (); task != filtered.end (); ++task) if (task->getStatus () != Task::deleted && task->getStatus () != Task::completed) ids.push_back (task->id); std::sort (ids.begin (), ids.end ()); join (output, "\n", ids); output += "\n"; context.headers.clear (); return 0; }
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; }
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 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; }
//////////////////////////////////////////////////////////////////////////////// // 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 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; }
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 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 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; }