diff --git a/src/Att.cpp b/src/Att.cpp index c3b1734ba..f7e92adcd 100644 --- a/src/Att.cpp +++ b/src/Att.cpp @@ -571,7 +571,7 @@ bool Att::match (const Att& other) const } else if (which == "text") { - if (::strcmp (mValue.c_str (), other.mValue.c_str ()) <= 0) + if (mValue <= other.mValue) return false; } } @@ -601,7 +601,7 @@ bool Att::match (const Att& other) const } else if (which == "text") { - if (::strcmp (mValue.c_str (), other.mValue.c_str ()) >= 0) + if (mValue >= other.mValue) return false; } } diff --git a/src/Cmd.cpp b/src/Cmd.cpp index 554b2b5e8..1e6919e54 100644 --- a/src/Cmd.cpp +++ b/src/Cmd.cpp @@ -147,12 +147,19 @@ void Cmd::load () if (i->substr (0, 7) == "report.") { std::string report = i->substr (7, std::string::npos); + + // Oh, what a massive hack. Shame. Shame. + // The "next" report is in limbo between being a built-in report and + // a custom report. The projection is defined as a custom report, but + // the restriction is different. + if (report.substr (0, 4) == "next") + continue; + std::string::size_type columns = report.find (".columns"); if (columns != std::string::npos) { report = report.substr (0, columns); - // Make sure a custom report does not clash with a built-in // command. if (std::find (commands.begin (), commands.end (), report) != commands.end ()) @@ -165,30 +172,6 @@ void Cmd::load () } } } - -/* - // Now load the aliases. - foreach (i, all) - { - if (i->substr (0, 6) == "alias.") - { - std::string name = i->substr (6, std::string::npos); - std::string alias = context.config.get (name); - - // Make sure a custom report does not clash with a built-in - // command. - if (std::find (commands.begin (), commands.end (), report) != commands.end ()) - throw std::string ("Alias '") + name + - "' conflicts with built-in task command."; - - if (std::find (customReports.begin (), customReports.end (), report) != customReports.end ()) - throw std::string ("Alias '") + name + - "' conflicts with custom report."; - - aliases[name] = alias; - } - } -*/ } } diff --git a/src/Cmd.h b/src/Cmd.h index 15ff7aaa8..314a6802d 100644 --- a/src/Cmd.h +++ b/src/Cmd.h @@ -27,10 +27,8 @@ #ifndef INCLUDED_CMD #define INCLUDED_CMD -//#include #include #include -#include "Cmd.h" class Cmd { @@ -59,7 +57,6 @@ private: private: std::vector commands; std::vector customReports; -// std::map aliases; }; #endif diff --git a/src/Config.cpp b/src/Config.cpp index b8106d65a..0f67e8c6f 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -224,6 +224,17 @@ void Config::createDefault (const std::string& home) fprintf (out, "report.waiting.sort=wait+,priority-,project+\n"); // TODO i18n fprintf (out, "report.waiting.filter=status:waiting\n"); // TODO i18n + fprintf (out, "report.all.description=Lists all tasks matching the specified criteria\n"); // TODO i18n + fprintf (out, "report.all.columns=id,project,priority,due,active,age,description\n"); // TODO i18n + fprintf (out, "report.all.labels=ID,Project,Pri,Due,Active,Age,Description\n"); // TODO i18n + fprintf (out, "report.all.sort=due+,priority-,project+\n"); // TODO i18n + + fprintf (out, "report.next.description=Lists all tasks matching the specified criteria\n"); // TODO i18n + fprintf (out, "report.next.columns=id,project,priority,due,active,age,description\n"); // TODO i18n + fprintf (out, "report.next.labels=ID,Project,Pri,Due,Active,Age,Description\n"); // TODO i18n + fprintf (out, "report.next.sort=due+,priority-,project+\n"); // TODO i18n + fprintf (out, "report.next.filter=status:pending\n"); // TODO i18n + fclose (out); std::cout << "Done." << std::endl; // TODO i18n @@ -302,6 +313,17 @@ void Config::setDefaults () set ("report.waiting.labels", "ID,Project,Pri,Wait,Age,Description"); // TODO i18n set ("report.waiting.sort", "wait+,priority-,project+"); // TODO i18n set ("report.waiting.filter", "status:waiting"); // TODO i18n + + set ("report.all.description", "Lists all tasks matching the specified criteria"); // TODO i18n + set ("report.all.columns", "id,project,priority,due,active,age,description"); // TODO i18n + set ("report.all.labels", "ID,Project,Pri,Due,Active,Age,Description"); // TODO i18n + set ("report.all.sort", "due+,priority-,project+"); // TODO i18n + + set ("report.next.description", "Lists all tasks matching the specified criteria"); // TODO i18n + set ("report.next.columns", "id,project,priority,due,active,age,description"); // TODO i18n + set ("report.next.labels", "ID,Project,Pri,Due,Active,Age,Description"); // TODO i18n + set ("report.next.sort", "due+,priority-,project+"); // TODO i18n + set ("report.next.filter", "status:pending"); // TODO i18n } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Context.cpp b/src/Context.cpp index 54e4a850e..6b4c043c6 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -93,6 +93,7 @@ void Context::initialize () // Load the configuration file from the home directory. If the file cannot // be found, offer to create a sample one. loadCorrectConfigFile (); + loadAliases (); // When redirecting output to a file, do not use color, curses. if (!isatty (fileno (stdout))) @@ -289,6 +290,24 @@ void Context::shadow () } } +//////////////////////////////////////////////////////////////////////////////// +// Only allows aliases 10 deep. +std::string Context::canonicalize (const std::string& input) const +{ + std::string canonical = input; + + // Follow the chain. + int i = 10; // Safety valve. + std::map ::const_iterator found; + while ((found = aliases.find (canonical)) != aliases.end () && i-- > 0) + canonical = found->second; + + if (i < 1) + return input; + + return canonical; +} + //////////////////////////////////////////////////////////////////////////////// void Context::loadCorrectConfigFile () { @@ -352,6 +371,26 @@ void Context::loadCorrectConfigFile () args = filtered; } +//////////////////////////////////////////////////////////////////////////////// +void Context::loadAliases () +{ + aliases.clear (); + + std::vector vars; + config.all (vars); + foreach (var, vars) + { + if (var->substr (0, 6) == "alias.") + { + std::string alias = var->substr (6, std::string::npos); + std::string canonical = config.get (*var); + + aliases[alias] = canonical; + debug (std::string ("Alias ") + alias + " -> " + canonical); + } + } +} + //////////////////////////////////////////////////////////////////////////////// void Context::parse () { diff --git a/src/Context.h b/src/Context.h index da55ec719..23003e2db 100644 --- a/src/Context.h +++ b/src/Context.h @@ -65,8 +65,11 @@ public: void parse (std::vector &, Cmd&, Task&, Sequence&, Subst&, Filter&); void clear (); + std::string canonicalize (const std::string&) const; + private: void loadCorrectConfigFile (); + void loadAliases (); void autoFilter (Task&, Filter&); public: @@ -81,7 +84,8 @@ public: std::string program; std::vector args; Cmd cmd; - std::vector tagAdditions; // TODO This is redundant, remove. + std::map aliases; + std::vector tagAdditions; std::vector tagRemovals; private: diff --git a/src/TDB.cpp b/src/TDB.cpp index 413d62141..f5efb43f0 100644 --- a/src/TDB.cpp +++ b/src/TDB.cpp @@ -382,19 +382,6 @@ int TDB::commit () return quantity; } -//////////////////////////////////////////////////////////////////////////////// -// TODO -> FF4 -void TDB::upgrade () -{ - // TODO Read all pending - // TODO Write out all pending - - // TODO Read all completed - // TODO Write out all completed - - throw std::string ("unimplemented TDB::upgrade"); -} - //////////////////////////////////////////////////////////////////////////////// // Scans the pending tasks for any that are completed or deleted, and if so, // moves them to the completed.data file. Returns a count of tasks moved. @@ -435,7 +422,10 @@ int TDB::gc () // Wake up tasks that are waiting. Date wait_date (::atoi (task->get ("wait").c_str ())); if (now > wait_date) + { task->setStatus (Task::pending); + task->remove ("wait"); + } still_pending.push_back (*task); } diff --git a/src/TDB.h b/src/TDB.h index 3ca81c404..5112e4ad9 100644 --- a/src/TDB.h +++ b/src/TDB.h @@ -59,7 +59,6 @@ public: void add (const Task&); // Single task add to pending void update (const Task&); // Single task update to pending int commit (); // Write out all tasks - void upgrade (); // Convert both files to FF4 int gc (); // Clean up pending int nextId (); diff --git a/src/Timer.cpp b/src/Timer.cpp index e934ac06d..5d25dd4f4 100644 --- a/src/Timer.cpp +++ b/src/Timer.cpp @@ -52,8 +52,10 @@ Timer::~Timer () << mDescription << " " << std::setprecision (6) + << std::fixed << ((end.tv_sec - mStart.tv_sec) + ((end.tv_usec - mStart.tv_usec ) - / 1000000.0)); + / 1000000.0)) + << " sec"; context.debug (s.str ()); } diff --git a/src/custom.cpp b/src/custom.cpp index b9b378970..b38692268 100644 --- a/src/custom.cpp +++ b/src/custom.cpp @@ -55,29 +55,10 @@ std::string handleCustomReport (const std::string& report) { // Load report configuration. std::string columnList = context.config.get ("report." + report + ".columns"); - std::vector columns; - split (columns, columnList, ','); - validReportColumns (columns); - - std::string labelList = context.config.get ("report." + report + ".labels"); - std::vector labels; - split (labels, labelList, ','); - - if (columns.size () != labels.size () && labels.size () != 0) - throw std::string ("There are a different number of columns than labels ") + - "for report '" + report + "'."; - - std::map columnLabels; - if (labels.size ()) - for (unsigned int i = 0; i < columns.size (); ++i) - columnLabels[columns[i]] = labels[i]; - - std::string sortList = context.config.get ("report." + report + ".sort"); - std::vector sortOrder; - split (sortOrder, sortList, ','); - validSortColumns (columns, sortOrder); - + std::string labelList = context.config.get ("report." + report + ".labels"); + std::string sortList = context.config.get ("report." + report + ".sort"); std::string filterList = context.config.get ("report." + report + ".filter"); + std::vector filterArgs; split (filterArgs, filterList, ' '); { @@ -106,6 +87,68 @@ std::string handleCustomReport (const std::string& report) context.tdb.commit (); context.tdb.unlock (); + return runCustomReport ( + report, + columnList, + labelList, + sortList, + filterList, + tasks); +} + +//////////////////////////////////////////////////////////////////////////////// +// This report will eventually become the one report that many others morph into +// via the .taskrc file. + +std::string runCustomReport ( + const std::string& report, + const std::string& columnList, + const std::string& labelList, + const std::string& sortList, + const std::string& filterList, + std::vector & tasks) +{ + // Load report configuration. + std::vector columns; + split (columns, columnList, ','); + validReportColumns (columns); + + std::vector labels; + split (labels, labelList, ','); + + if (columns.size () != labels.size () && labels.size () != 0) + throw std::string ("There are a different number of columns than labels ") + + "for report '" + report + "'."; + + std::map columnLabels; + if (labels.size ()) + for (unsigned int i = 0; i < columns.size (); ++i) + columnLabels[columns[i]] = labels[i]; + + std::vector sortOrder; + split (sortOrder, sortList, ','); + validSortColumns (columns, sortOrder); + + std::vector filterArgs; + split (filterArgs, filterList, ' '); + { + Cmd cmd (report); + Task task; + Sequence sequence; + Subst subst; + Filter filter; + context.parse (filterArgs, cmd, task, sequence, subst, filter); + + context.sequence.combine (sequence); + + // Allow limit to be overridden by the command line. + if (!context.task.has ("limit") && task.has ("limit")) + context.task.set ("limit", task.get ("limit")); + + foreach (att, filter) + context.filter.push_back (*att); + } + // Filter sequence. if (context.sequence.size ()) context.filter.applySequence (tasks, context.sequence); diff --git a/src/main.h b/src/main.h index deb4ec7a0..cb3b07bb0 100644 --- a/src/main.h +++ b/src/main.h @@ -45,7 +45,7 @@ void validSortColumns (const std::vector &, const std::vector &, std::vector &); +void gatherNextTasks (std::vector &); void onChangeCallback (); // recur.cpp @@ -101,6 +101,9 @@ std::string getDueDate (Task&); // custom.cpp std::string handleCustomReport (const std::string&); +std::string runCustomReport (const std::string&, const std::string&, + const std::string&, const std::string&, + const std::string&, std::vector &); // rules.cpp void initializeColorRules (); diff --git a/src/report.cpp b/src/report.cpp index d7172eb91..56a52130f 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -679,138 +679,50 @@ std::string handleReportSummary () // std::string handleReportNext () { + // Load report configuration. + std::string columnList = context.config.get ("report.next.columns"); + std::string labelList = context.config.get ("report.next.labels"); + std::string sortList = context.config.get ("report.next.sort"); + std::string filterList = context.config.get ("report.next.filter"); + + std::vector filterArgs; + split (filterArgs, filterList, ' '); + { + Cmd cmd ("next"); + Task task; + Sequence sequence; + Subst subst; + Filter filter; + context.parse (filterArgs, cmd, task, sequence, subst, filter); + + context.sequence.combine (sequence); + + // Allow limit to be overridden by the command line. + if (!context.task.has ("limit") && task.has ("limit")) + context.task.set ("limit", task.get ("limit")); + + foreach (att, filter) + context.filter.push_back (*att); + } + // Get all the tasks. std::vector tasks; context.tdb.lock (context.config.get ("locking", true)); handleRecurrence (); - context.tdb.loadPending (tasks, context.filter); + context.tdb.load (tasks, context.filter); context.tdb.commit (); context.tdb.unlock (); // Restrict to matching subset. - std::vector matching; - gatherNextTasks (tasks, matching); + gatherNextTasks (tasks); - initializeColorRules (); - - // Create a table for output. - Table table; - table.setTableWidth (context.getWidth ()); - table.setDateFormat (context.config.get ("dateformat", "m/d/Y")); - table.addColumn ("ID"); - table.addColumn ("Project"); - table.addColumn ("Pri"); - table.addColumn ("Due"); - table.addColumn ("Active"); - table.addColumn ("Age"); - table.addColumn ("Description"); - - if ((context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) && - context.config.get (std::string ("fontunderline"), "true")) - { - table.setColumnUnderline (0); - table.setColumnUnderline (1); - table.setColumnUnderline (2); - table.setColumnUnderline (3); - table.setColumnUnderline (4); - table.setColumnUnderline (5); - table.setColumnUnderline (6); - } - else - table.setTableDashedUnderline (); - - table.setColumnWidth (0, Table::minimum); - table.setColumnWidth (1, Table::minimum); - table.setColumnWidth (2, Table::minimum); - table.setColumnWidth (3, Table::minimum); - table.setColumnWidth (4, Table::minimum); - table.setColumnWidth (5, Table::minimum); - table.setColumnWidth (6, Table::flexible); - - table.setColumnJustification (0, Table::right); - table.setColumnJustification (3, Table::right); - table.setColumnJustification (5, Table::right); - - table.sortOn (3, Table::ascendingDate); - table.sortOn (2, Table::descendingPriority); - table.sortOn (1, Table::ascendingCharacter); - - // Iterate over each task, and apply selection criteria. - foreach (i, matching) - { - Date now; - - // Now format the matching task. - bool imminent = false; - bool overdue = false; - std::string due = tasks[*i].get ("due"); - if (due.length ()) - { - switch (getDueState (due)) - { - case 2: overdue = true; break; - case 1: imminent = true; break; - case 0: - default: break; - } - - Date dt (::atoi (due.c_str ())); - due = dt.toString (context.config.get ("dateformat", "m/d/Y")); - } - - std::string active; - if (tasks[*i].has ("start")) - active = "*"; - - std::string age; - std::string created = tasks[*i].get ("entry"); - if (created.length ()) - { - Date dt (::atoi (created.c_str ())); - age = formatSeconds ((time_t) (now - dt)); - } - - // All criteria match, so add tasks[*i] to the output table. - int row = table.addRow (); - table.addCell (row, 0, tasks[*i].id); - table.addCell (row, 1, tasks[*i].get ("project")); - table.addCell (row, 2, tasks[*i].get ("priority")); - table.addCell (row, 3, due); - table.addCell (row, 4, active); - table.addCell (row, 5, age); - table.addCell (row, 6, getFullDescription (tasks[*i])); - - if (context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false)) - { - Text::color fg = Text::colorCode (tasks[*i].get ("fg")); - Text::color bg = Text::colorCode (tasks[*i].get ("bg")); - autoColorize (tasks[*i], fg, bg); - table.setRowFg (row, fg); - table.setRowBg (row, bg); - - if (fg == Text::nocolor) - { - if (overdue) - table.setCellFg (row, 3, Text::colorCode (context.config.get ("color.overdue", "red"))); - else if (imminent) - table.setCellFg (row, 3, Text::colorCode (context.config.get ("color.due", "yellow"))); - } - } - } - - std::stringstream out; - if (table.rowCount ()) - out << optionalBlankLine () - << table.render () - << optionalBlankLine () - << table.rowCount () - << (table.rowCount () == 1 ? " task" : " tasks") - << std::endl; - else - out << "No matches." - << std::endl; - - return out.str (); + return runCustomReport ( + "next", + columnList, + labelList, + sortList, + filterList, + tasks); } //////////////////////////////////////////////////////////////////////////////// @@ -1919,34 +1831,33 @@ std::string handleReportStats () } //////////////////////////////////////////////////////////////////////////////// -void gatherNextTasks ( - std::vector & tasks, - std::vector & all) +void gatherNextTasks (std::vector & tasks) { // For counting tasks by project. std::map countByProject; std::map matching; - + std::vector filtered; Date now; // How many items per project? Default 3. int limit = context.config.get ("next", 3); // due:< 1wk, pri:* - for (unsigned int i = 0; i < tasks.size (); ++i) + foreach (task, tasks) { - if (tasks[i].getStatus () == Task::pending) + if (task->getStatus () == Task::pending) { - if (tasks[i].has ("due")) + if (task->has ("due")) { - Date d (::atoi (tasks[i].get ("due").c_str ())); + Date d (::atoi (task->get ("due").c_str ())); if (d < now + (7 * 24 * 60 * 60)) // if due:< 1wk { - std::string project = tasks[i].get ("project"); - if (countByProject[project] < limit && matching.find (i) == matching.end ()) + std::string project = task->get ("project"); + if (countByProject[project] < limit && matching.find (task->id) == matching.end ()) { ++countByProject[project]; - matching[i] = true; + matching[task->id] = true; + filtered.push_back (*task); } } } @@ -1954,20 +1865,21 @@ void gatherNextTasks ( } // due:*, pri:H - for (unsigned int i = 0; i < tasks.size (); ++i) + foreach (task, tasks) { - if (tasks[i].getStatus () == Task::pending) + if (task->getStatus () == Task::pending) { - if (tasks[i].has ("due")) + if (task->has ("due")) { - std::string priority = tasks[i].get ("priority"); + std::string priority = task->get ("priority"); if (priority == "H") { - std::string project = tasks[i].get ("project"); - if (countByProject[project] < limit && matching.find (i) == matching.end ()) + std::string project = task->get ("project"); + if (countByProject[project] < limit && matching.find (task->id) == matching.end ()) { ++countByProject[project]; - matching[i] = true; + matching[task->id] = true; + filtered.push_back (*task); } } } @@ -1975,38 +1887,40 @@ void gatherNextTasks ( } // pri:H - for (unsigned int i = 0; i < tasks.size (); ++i) + foreach (task, tasks) { - if (tasks[i].getStatus () == Task::pending) + if (task->getStatus () == Task::pending) { - std::string priority = tasks[i].get ("priority"); + std::string priority = task->get ("priority"); if (priority == "H") { - std::string project = tasks[i].get ("project"); - if (countByProject[project] < limit && matching.find (i) == matching.end ()) + std::string project = task->get ("project"); + if (countByProject[project] < limit && matching.find (task->id) == matching.end ()) { ++countByProject[project]; - matching[i] = true; + matching[task->id] = true; + filtered.push_back (*task); } } } } // due:*, pri:M - for (unsigned int i = 0; i < tasks.size (); ++i) + foreach (task, tasks) { - if (tasks[i].getStatus () == Task::pending) + if (task->getStatus () == Task::pending) { - if (tasks[i].has ("due")) + if (task->has ("due")) { - std::string priority = tasks[i].get ("priority"); + std::string priority = task->get ("priority"); if (priority == "M") { - std::string project = tasks[i].get ("project"); - if (countByProject[project] < limit && matching.find (i) == matching.end ()) + std::string project = task->get ("project"); + if (countByProject[project] < limit && matching.find (task->id) == matching.end ()) { ++countByProject[project]; - matching[i] = true; + matching[task->id] = true; + filtered.push_back (*task); } } } @@ -2014,38 +1928,40 @@ void gatherNextTasks ( } // pri:M - for (unsigned int i = 0; i < tasks.size (); ++i) + foreach (task, tasks) { - if (tasks[i].getStatus () == Task::pending) + if (task->getStatus () == Task::pending) { - std::string priority = tasks[i].get ("priority"); + std::string priority = task->get ("priority"); if (priority == "M") { - std::string project = tasks[i].get ("project"); - if (countByProject[project] < limit && matching.find (i) == matching.end ()) + std::string project = task->get ("project"); + if (countByProject[project] < limit && matching.find (task->id) == matching.end ()) { ++countByProject[project]; - matching[i] = true; + matching[task->id] = true; + filtered.push_back (*task); } } } } // due:*, pri:L - for (unsigned int i = 0; i < tasks.size (); ++i) + foreach (task, tasks) { - if (tasks[i].getStatus () == Task::pending) + if (task->getStatus () == Task::pending) { - if (tasks[i].has ("due")) + if (task->has ("due")) { - std::string priority = tasks[i].get ("priority"); + std::string priority = task->get ("priority"); if (priority == "L") { - std::string project = tasks[i].get ("project"); - if (countByProject[project] < limit && matching.find (i) == matching.end ()) + std::string project = task->get ("project"); + if (countByProject[project] < limit && matching.find (task->id) == matching.end ()) { ++countByProject[project]; - matching[i] = true; + matching[task->id] = true; + filtered.push_back (*task); } } } @@ -2053,47 +1969,47 @@ void gatherNextTasks ( } // pri:L - for (unsigned int i = 0; i < tasks.size (); ++i) + foreach (task, tasks) { - if (tasks[i].getStatus () == Task::pending) + if (task->getStatus () == Task::pending) { - std::string priority = tasks[i].get ("priority"); + std::string priority = task->get ("priority"); if (priority == "L") { - std::string project = tasks[i].get ("project"); - if (countByProject[project] < limit && matching.find (i) == matching.end ()) + std::string project = task->get ("project"); + if (countByProject[project] < limit && matching.find (task->id) == matching.end ()) { ++countByProject[project]; - matching[i] = true; + matching[task->id] = true; + filtered.push_back (*task); } } } } // due:, pri: - for (unsigned int i = 0; i < tasks.size (); ++i) + foreach (task, tasks) { - if (tasks[i].getStatus () == Task::pending) + if (task->getStatus () == Task::pending) { - if (tasks[i].has ("due")) + if (task->has ("due")) { - std::string priority = tasks[i].get ("priority"); + std::string priority = task->get ("priority"); if (priority == "") { - std::string project = tasks[i].get ("project"); - if (countByProject[project] < limit && matching.find (i) == matching.end ()) + std::string project = task->get ("project"); + if (countByProject[project] < limit && matching.find (task->id) == matching.end ()) { ++countByProject[project]; - matching[i] = true; + matching[task->id] = true; + filtered.push_back (*task); } } } } } - // Convert map to vector. - foreach (i, matching) - all.push_back (i->first); + tasks = filtered; } /////////////////////////////////////////////////////////////////////////////// diff --git a/src/tests/.gitignore b/src/tests/.gitignore index dd6441689..ffffbf248 100644 --- a/src/tests/.gitignore +++ b/src/tests/.gitignore @@ -15,3 +15,4 @@ subst.t filt.t cmd.t config.t +*.log diff --git a/src/util.cpp b/src/util.cpp index fdf934ac3..9c59002c8 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -74,6 +74,40 @@ bool confirm (const std::string& question) return (answer == "y" || answer == "ye" || answer == "yes") ? true : false; // TODO i18n } +//////////////////////////////////////////////////////////////////////////////// +// 0 = no +// 1 = yes +// 2 = all +int confirm3 (const std::string& question) +{ + std::vector options; + options.push_back ("yes"); + options.push_back ("no"); + options.push_back ("all"); + + std::string answer; + std::vector matches; + + do + { + std::cout << question + << " (" + << options[0] << "/" + << options[1] << "/" + << options[2] + << ") "; + + std::getline (std::cin, answer); + answer = trim (answer); + autoComplete (answer, options, matches); + } + while (matches.size () != 1); + + if (matches[0] == "yes") return 1; + else if (matches[0] == "all") return 2; + else return 0; +} + //////////////////////////////////////////////////////////////////////////////// void delay (float f) { diff --git a/src/util.h b/src/util.h index b72e8303f..19f7af6f2 100644 --- a/src/util.h +++ b/src/util.h @@ -51,6 +51,7 @@ for (typeof (c) *foreach_p = & (c); \ // util.cpp bool confirm (const std::string&); +int confirm3 (const std::string&); void delay (float); std::string formatSeconds (time_t); std::string formatSecondsCompact (time_t);