int CmdStats::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 () + context.tdb2.backlog._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; // Count the backlog transactions. std::vector <std::string> backlogTxns = context.tdb2.backlog.get_lines (); int backlogCount = 0; for (tx = backlogTxns.begin (); tx != backlogTxns.end (); ++tx) if ((*tx)[0] == '{') ++backlogCount; // Get all the tasks. Filter filter; std::vector <Task> all = context.tdb2.all_tasks (); std::vector <Task> filtered; filter.subset (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; int blockingT = 0; int blockedT = 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; } if (task->is_blocked) ++blockedT; if (task->is_blocking) ++blockingT; 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)); Color label (context.config.get ("color.label")); view.colorHeader (label); 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_BLOCKED); view.set (row, 1, blockedT); row = view.addRow (); view.set (row, 0, STRING_CMD_STATS_BLOCKING); view.set (row, 1, blockingT); 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); row = view.addRow (); view.set (row, 0, STRING_CMD_STATS_BACKLOG); view.set (row, 1, backlogCount); 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.3 has taskd support. Until then, it makes no // sense to include this. row = view.addRow (); view.set (row, 0, STRING_CMD_STATS_LAST_SYNC); 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; }
void TDB2::show_diff ( const std::string& current, const std::string& prior, const std::string& when) { ISO8601d lastChange (strtol (when.c_str (), NULL, 10)); // Set the colors. Color color_red (context.color () ? context.config.get ("color.undo.before") : ""); Color color_green (context.color () ? context.config.get ("color.undo.after") : ""); if (context.config.get ("undo.style") == "side") { std::cout << "\n" << format (STRING_TDB2_LAST_MOD, lastChange.toString ()) << "\n"; // Attributes are all there is, so figure the different attribute names // between before and after. ViewText view; view.width (context.getWidth ()); view.intraPadding (2); view.add (Column::factory ("string", "")); view.add (Column::factory ("string", STRING_TDB2_UNDO_PRIOR)); view.add (Column::factory ("string", STRING_TDB2_UNDO_CURRENT)); Color label (context.config.get ("color.label")); view.colorHeader (label); Task after (current); if (prior != "") { Task before (prior); std::vector <std::string> beforeAtts; for (auto& att : before) beforeAtts.push_back (att.first); std::vector <std::string> afterAtts; for (auto& att : after) afterAtts.push_back (att.first); std::vector <std::string> beforeOnly; std::vector <std::string> afterOnly; listDiff (beforeAtts, afterAtts, beforeOnly, afterOnly); int row; for (auto& name : beforeOnly) { row = view.addRow (); view.set (row, 0, name); view.set (row, 1, renderAttribute (name, before.get (name)), color_red); } for (auto& att : before) { std::string priorValue = before.get (att.first); std::string currentValue = after.get (att.first); if (currentValue != "") { row = view.addRow (); view.set (row, 0, att.first); view.set (row, 1, renderAttribute (att.first, priorValue), (priorValue != currentValue ? color_red : Color ())); view.set (row, 2, renderAttribute (att.first, currentValue), (priorValue != currentValue ? color_green : Color ())); } } for (auto& name : afterOnly) { row = view.addRow (); view.set (row, 0, name); view.set (row, 2, renderAttribute (name, after.get (name)), color_green); } } else { int row; for (auto& att : after) { row = view.addRow (); view.set (row, 0, att.first); view.set (row, 2, renderAttribute (att.first, after.get (att.first)), color_green); } } std::cout << "\n" << view.render () << "\n"; } // This style looks like this: // --- before 2009-07-04 00:00:25.000000000 +0200 // +++ after 2009-07-04 00:00:45.000000000 +0200 // // - name: old // att deleted // + name: // // - name: old // att changed // + name: new // // - name: // + name: new // att added // else if (context.config.get ("undo.style") == "diff") { // Create reference tasks. Task before; if (prior != "") before.parse (prior); Task after (current); // Generate table header. ViewText view; view.width (context.getWidth ()); view.intraPadding (2); view.add (Column::factory ("string", "")); view.add (Column::factory ("string", "")); int row = view.addRow (); view.set (row, 0, STRING_TDB2_DIFF_PREV, color_red); view.set (row, 1, STRING_TDB2_DIFF_PREV_DESC, color_red); row = view.addRow (); view.set (row, 0, STRING_TDB2_DIFF_CURR, color_green); // Note trailing space. view.set (row, 1, format (STRING_TDB2_DIFF_CURR_DESC, lastChange.toString (context.config.get ("dateformat"))), color_green); view.addRow (); // Add rows to table showing diffs. std::vector <std::string> all = context.getColumns (); // Now factor in the annotation attributes. for (auto& it : before) if (it.first.substr (0, 11) == "annotation_") all.push_back (it.first); for (auto& it : after) if (it.first.substr (0, 11) == "annotation_") all.push_back (it.first); // Now render all the attributes. std::sort (all.begin (), all.end ()); std::string before_att; std::string after_att; std::string last_att; for (auto& a : all) { if (a != last_att) // Skip duplicates. { last_att = a; before_att = before.get (a); after_att = after.get (a); // Don't report different uuid. // Show nothing if values are the unchanged. if (a == "uuid" || before_att == after_att) { // Show nothing - no point displaying that which did not change. // row = view.addRow (); // view.set (row, 0, *a + ":"); // view.set (row, 1, before_att); } // Attribute deleted. else if (before_att != "" && after_att == "") { row = view.addRow (); view.set (row, 0, "-" + a + ":", color_red); view.set (row, 1, before_att, color_red); row = view.addRow (); view.set (row, 0, "+" + a + ":", color_green); } // Attribute added. else if (before_att == "" && after_att != "") { row = view.addRow (); view.set (row, 0, "-" + a + ":", color_red); row = view.addRow (); view.set (row, 0, "+" + a + ":", color_green); view.set (row, 1, after_att, color_green); } // Attribute changed. else { row = view.addRow (); view.set (row, 0, "-" + a + ":", color_red); view.set (row, 1, before_att, color_red); row = view.addRow (); view.set (row, 0, "+" + a + ":", color_green); view.set (row, 1, after_att, color_green); } } } std::cout << "\n" << view.render () << "\n"; } }
//////////////////////////////////////////////////////////////////////////////// // 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")); Color label (context.config.get ("color.label")); view.colorHeader (label); 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; }
std::string CmdCalendar::renderMonths ( int firstMonth, int firstYear, const Date& today, std::vector <Task>& all, int monthsPerLine) { // 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_CMD_CAL_SUN_MON); // Build table for the number of months to be displayed. Color label (context.config.get ("color.label")); ViewText view; view.colorHeader (label); view.width (context.getWidth ()); for (int i = 0 ; i < (monthsPerLine * 8); i += 8) { if (weekStart == 1) { view.add (Column::factory ("string.right", " ")); view.add (Column::factory ("string.right", utf8_substr (Date::dayName (1), 0, 2))); view.add (Column::factory ("string.right", utf8_substr (Date::dayName (2), 0, 2))); view.add (Column::factory ("string.right", utf8_substr (Date::dayName (3), 0, 2))); view.add (Column::factory ("string.right", utf8_substr (Date::dayName (4), 0, 2))); view.add (Column::factory ("string.right", utf8_substr (Date::dayName (5), 0, 2))); view.add (Column::factory ("string.right", utf8_substr (Date::dayName (6), 0, 2))); view.add (Column::factory ("string.right", utf8_substr (Date::dayName (0), 0, 2))); } else { view.add (Column::factory ("string.right", " ")); view.add (Column::factory ("string.right", utf8_substr (Date::dayName (0), 0, 2))); view.add (Column::factory ("string.right", utf8_substr (Date::dayName (1), 0, 2))); view.add (Column::factory ("string.right", utf8_substr (Date::dayName (2), 0, 2))); view.add (Column::factory ("string.right", utf8_substr (Date::dayName (3), 0, 2))); view.add (Column::factory ("string.right", utf8_substr (Date::dayName (4), 0, 2))); view.add (Column::factory ("string.right", utf8_substr (Date::dayName (5), 0, 2))); view.add (Column::factory ("string.right", utf8_substr (Date::dayName (6), 0, 2))); } } // At most, we need 6 rows. view.addRow (); view.addRow (); view.addRow (); view.addRow (); view.addRow (); view.addRow (); // Set number of days per month, months to render, and years to render. std::vector<int> years; std::vector<int> months; std::vector<int> daysInMonth; int thisYear = firstYear; int thisMonth = firstMonth; for (int i = 0 ; i < monthsPerLine ; i++) { if (thisMonth < 13) { years.push_back (thisYear); } else { thisMonth -= 12; years.push_back (++thisYear); } months.push_back (thisMonth); daysInMonth.push_back (Date::daysInMonth (thisMonth++, thisYear)); } int row = 0; 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")); // Loop through months to be added on this line. for (int mpl = 0; mpl < monthsPerLine ; mpl++) { // Reset row counter for subsequent months if (mpl != 0) row = 0; // Loop through days in month and add to table. for (int d = 1; d <= daysInMonth[mpl]; ++d) { Date temp (months[mpl], d, years[mpl]); int dow = temp.dayOfWeek (); int woy = temp.weekOfYear (weekStart); if (context.config.getBoolean ("displayweeknumber")) view.set (row, (8 * mpl), woy, color_weeknumber); // Calculate column id. int thisCol = dow + // 0 = Sunday (weekStart == 1 ? 0 : 1) + // Offset for weekStart (8 * mpl); // Columns in 1 month if (thisCol == (8 * mpl)) thisCol += 7; view.set (row, thisCol, d); if (context.color ()) { Color cellColor; // colorize weekends if (dow == 0 || dow == 6) cellColor.blend (color_weekend); // colorize holidays if (context.config.get ("calendar.holidays") != "none") { Config::const_iterator hol; for (hol = context.config.begin (); hol != context.config.end (); ++hol) if (hol->first.substr (0, 8) == "holiday.") if (hol->first.substr (hol->first.size () - 4) == "date") { std::string value = hol->second; Date holDate (value.c_str (), context.config.get ("dateformat.holiday")); if (holDate.day () == d && holDate.month () == months[mpl] && holDate.year () == years[mpl]) cellColor.blend (color_holiday); } } // colorize today if (today.day () == d && today.month () == months.at (mpl) && today.year () == years.at (mpl)) cellColor.blend (color_today); // colorize due tasks if (context.config.get ("calendar.details") != "none") { context.config.set ("due", 0); std::vector <Task>::iterator task; for (task = all.begin (); task != all.end (); ++task) { if (task->getStatus () == Task::pending && !task->hasTag ("nocal") && task->has ("due")) { std::string due = task->get ("due"); Date duedmy (strtol (due.c_str(), NULL, 10)); if (duedmy.day () == d && duedmy.month () == months[mpl] && duedmy.year () == years[mpl]) { switch (task->getDateState ("due")) { case Task::dateNotDue: break; case Task::dateAfterToday: cellColor.blend (color_due); break; case Task::dateEarlierToday: case Task::dateLaterToday: cellColor.blend (color_duetoday); cellColor.blend (color_duetoday); break; case Task::dateBeforeToday: cellColor.blend (color_overdue); break; } } } } } view.set (row, thisCol, cellColor); } // Check for end of week, and... int eow = 6; if (weekStart == 1) eow = 0; if (dow == eow && d < daysInMonth[mpl]) row++; } } return view.render (); }
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 // Apply filter. handleRecurrence (); Filter filter; std::vector <Task> filtered; filter.subset (filtered); 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")); Color label (context.config.get ("color.label")); view.colorHeader (label); // 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 // Apply filter. handleRecurrence (); Filter filter; std::vector <Task> filtered; filter.subset (filtered); 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)); Color label (context.config.get ("color.label")); view.colorHeader (label); 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; }