Merge branch 'master' into 1.9.0

due to release of 1.8.5

Conflicts:
	ChangeLog
	configure.ac
	doc/man/task-tutorial.5
	doc/man/task.1
	doc/man/taskrc.5
	src/Config.cpp
	src/rules.cpp
This commit is contained in:
Federico Hernandez 2009-12-06 04:30:56 +01:00
commit f3d31834ee
24 changed files with 317 additions and 72 deletions

View file

@ -152,8 +152,8 @@ void Config::createDefaultRC (const std::string& rc, const std::string& data)
<< "\n"
<< "alias.rm=delete\n"
<< "\n"
<< "# Fields: id,uuid,project,priority,entry,start,due,recur,recur_ind,age,\n"
<< "# age_compact,active,tags,description,description_only\n"
<< "# Fields: id,uuid,project,priority,entry,start,due,recur,recurrence_indicator,age,\n"
<< "# age_compact,active,tags,tag_indicator,description,description_only\n"
<< "# Description: This report is ...\n"
<< "# Sort: due+,priority-,project+\n"
<< "# Filter: pro:x pri:H +bug limit:10\n"

View file

@ -50,7 +50,8 @@ Context::Context ()
, tdb ()
, stringtable ()
, program ("")
, overrides ("")
, file_override ("")
, var_overrides ("")
, cmd ()
, inShadow (false)
{
@ -66,6 +67,7 @@ void Context::initialize (int argc, char** argv)
{
// Capture the args.
for (int i = 0; i < argc; ++i)
{
if (i == 0)
{
program = argv[i];
@ -76,6 +78,7 @@ void Context::initialize (int argc, char** argv)
}
else
args.push_back (argv[i]);
}
initialize ();
}
@ -351,13 +354,14 @@ void Context::loadCorrectConfigFile ()
std::string rc = home + "/.taskrc";
std::string data = home + "/.task";
// Is there an override for rc?
// Is there an file_override for rc:?
foreach (arg, args)
{
if (*arg == "--")
break;
else if (arg->substr (0, 3) == "rc:")
{
file_override = *arg;
rc = arg->substr (3, std::string::npos);
home = rc;
@ -381,7 +385,7 @@ void Context::loadCorrectConfigFile ()
if (config.get ("data.location") != "")
data = config.get ("data.location");
// Is there an override for data?
// Are there any var_overrides for data.location?
foreach (arg, args)
{
if (*arg == "--")
@ -442,7 +446,7 @@ void Context::loadCorrectConfigFile ()
n.getUntilEOS (value))
{
config.set (name, value);
overrides += " " + *arg;
var_overrides += " " + *arg;
footnote (std::string ("Configuration override ") + // TODO i18n
arg->substr (3, std::string::npos));
}
@ -660,15 +664,21 @@ void Context::parse (
if (parseCmd.command == "" && parseArgs.size () == 0)
{
// Apply overrides, if any.
std::string defaultCommand = config.get ("default.command") + overrides;
std::string defaultCommand = config.get ("default.command");
if (defaultCommand != "")
{
// Add on the overrides.
defaultCommand += " " + file_override + " " + var_overrides;
// Stuff the command line.
args.clear ();
split (args, defaultCommand, ' ');
header ("[task " + defaultCommand + "]");
// Reinitialize the context and recurse.
file_override = "";
var_overrides = "";
footnotes.clear ();
initialize ();
parse (args, cmd, task, sequence, subst, filter);
}
@ -693,6 +703,8 @@ void Context::clear ()
// stringtable.clear ();
program = "";
args.clear ();
file_override = "";
var_overrides = "";
cmd.command = "";
tagAdditions.clear ();
tagRemovals.clear ();

View file

@ -83,7 +83,8 @@ public:
StringTable stringtable;
std::string program;
std::vector <std::string> args;
std::string overrides;
std::string file_override;
std::string var_overrides;
Cmd cmd;
std::map <std::string, std::string> aliases;
std::vector <std::string> tagAdditions;

View file

@ -37,6 +37,7 @@ extern Context context;
Permission::Permission ()
: needConfirmation (false)
, allConfirmed (false)
, quit (false)
{
// Turning confirmations off is the same as entering "all".
if (context.config.get ("confirmation", true) == false)
@ -46,6 +47,9 @@ Permission::Permission ()
////////////////////////////////////////////////////////////////////////////////
bool Permission::confirmed (const Task& task, const std::string& question)
{
if (quit)
return false;
if (!needConfirmation)
return true;
@ -67,13 +71,15 @@ bool Permission::confirmed (const Task& task, const std::string& question)
std::cout << std::endl;
int answer = confirm3 (question);
int answer = confirm4 (question);
if (answer == 2)
allConfirmed = true;
if (answer > 0)
if (answer == 1 || answer == 2)
return true;
if (answer == 3)
quit = true;
return false;
}

View file

@ -44,6 +44,7 @@ public:
private:
bool needConfirmation;
bool allConfirmed;
bool quit;
};
#endif

View file

@ -527,7 +527,7 @@ void Task::validate () const
if (!has ("uuid") ||
!has ("entry") ||
!has ("description"))
throw std::string ("A task must have a uuid, entry date and description in order to be valid."); // TODO i18n
throw std::string ("A task must have a description in order to be valid."); // TODO i18n
if (get ("description") == "") // No i18n
throw std::string ("Cannot add a task that is blank, or contains <CR> or <LF> characters."); // TODO i18n

View file

@ -858,7 +858,7 @@ int handleDone (std::string &outs)
if (taskDiff (before, *task))
{
if (permission.confirmed (before, taskDifferences (before, *task) + "Are you sure?"))
if (permission.confirmed (before, taskDifferences (before, *task) + "Proceed with change?"))
{
context.tdb.update (*task);
@ -979,6 +979,15 @@ int handleModify (std::string &outs)
!task->has ("recur"))
throw std::string ("You cannot specify an until date for a non-recurring task.");
if (task->has ("due") &&
!context.task.has ("due") &&
context.task.has ("recur"))
throw std::string ("You cannot remove the due date from a recurring task.");
if (task->has ("recur") &&
!context.task.has ("recur"))
throw std::string ("You cannot remove the recurrence from a recurring task.");
// Make all changes.
foreach (other, all)
{
@ -1012,7 +1021,7 @@ int handleModify (std::string &outs)
if (taskDiff (before, *other))
{
if (changes && permission.confirmed (before, taskDifferences (before, *other) + "Are you sure?"))
if (changes && permission.confirmed (before, taskDifferences (before, *other) + "Proceed with change?"))
{
context.tdb.update (*other);
++count;
@ -1072,7 +1081,7 @@ int handleAppend (std::string &outs)
if (taskDiff (before, *other))
{
if (changes && permission.confirmed (before, taskDifferences (before, *other) + "Are you sure?"))
if (changes && permission.confirmed (before, taskDifferences (before, *other) + "Proceed with change?"))
{
context.tdb.update (*other);
@ -1228,12 +1237,21 @@ int handleDuplicate (std::string &outs)
++count;
}
if (context.config.get ("echo.command", true))
{
out << "Duplicated " << count << " task" << (count == 1 ? "" : "s") << std::endl;
#ifdef FEATURE_NEW_ID
// All this, just for an id number.
std::vector <Task> all;
Filter none;
context.tdb.loadPending (all, none);
out << "Created task " << context.tdb.nextId () << std::endl;
#endif
}
context.tdb.commit ();
context.tdb.unlock ();
if (context.config.get ("echo.command", true))
out << "Duplicated " << count << " task" << (count == 1 ? "" : "s") << std::endl;
outs = out.str ();
return 0;
}
@ -1258,13 +1276,9 @@ void handleShell ()
<< std::endl
<< std::endl;
// Preserve any special override arguments, and reapply them for each
// shell command.
std::vector <std::string> special;
foreach (arg, context.args)
if (arg->substr (0, 3) == "rc." ||
arg->substr (0, 3) == "rc:")
special.push_back (*arg);
// Make a copy because context.clear will delete them.
std::string permanentOverrides = " " + context.file_override
+ " " + context.var_overrides;
std::string quit = "quit"; // TODO i18n
std::string command;
@ -1276,11 +1290,13 @@ void handleShell ()
command = "";
std::getline (std::cin, command);
command = lowerCase (trim (command));
std::string decoratedCommand = trim (command + permanentOverrides);
if (command.length () > 0 &&
command.length () <= quit.length () &&
command == quit.substr (0, command.length ()))
// When looking for the 'quit' command, use 'command', not
// 'decoratedCommand'.
if (command.length () > 0 &&
command.length () <= quit.length () &&
lowerCase (command) == quit.substr (0, command.length ()))
{
keepGoing = false;
}
@ -1291,8 +1307,7 @@ void handleShell ()
context.clear ();
std::vector <std::string> args;
split (args, command, ' ');
foreach (arg, special) context.args.push_back (*arg);
split (args, decoratedCommand, ' ');
foreach (arg, args) context.args.push_back (*arg);
context.initialize ();
@ -1311,6 +1326,9 @@ void handleShell ()
}
}
while (keepGoing && !std::cin.eof ());
// No need to repeat any overrides after the shell quits.
context.clearMessages ();
}
#endif
@ -1443,7 +1461,7 @@ int handleAnnotate (std::string &outs)
if (taskDiff (before, *task))
{
if (permission.confirmed (before, taskDifferences (before, *task) + "Are you sure?"))
if (permission.confirmed (before, taskDifferences (before, *task) + "Proceed with change?"))
{
context.tdb.update (*task);

66
src/tests/bug.327.t Executable file
View file

@ -0,0 +1,66 @@
#! /usr/bin/perl
################################################################################
## task - a command line task list manager.
##
## Copyright 2006 - 2009, Paul Beckingham.
## All rights reserved.
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
## Foundation; either version 2 of the License, or (at your option) any later
## version.
##
## This program is distributed in the hope that it will be useful, but WITHOUT
## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
## details.
##
## You should have received a copy of the GNU General Public License along with
## this program; if not, write to the
##
## Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor,
## Boston, MA
## 02110-1301
## USA
##
################################################################################
use strict;
use warnings;
use Test::More tests => 6;
# Create the rc file.
if (open my $fh, '>', 'bug.rc')
{
print $fh "data.location=.\n",
"confirmation=no\n";
close $fh;
ok (-r 'bug.rc', 'Created bug.rc');
}
# Setup: Add a recurring task then remove the due date.
qx{../task rc:bug.rc add foo recur:yearly due:eoy};
qx{../task rc:bug.rc li};
qx{../task rc:bug.rc 2 due:};
# Result: Somehow the due date is incremented and wraps around to 12/31/1969,
# then keeps going back to today.
my $output = qx{../task rc:bug.rc li};
like ($output, qr/^1 task$/ms, 'Should only be one task');
# Cleanup.
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'completed.data';
ok (!-r 'completed.data', 'Removed completed.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'bug.rc';
ok (!-r 'bug.rc', 'Removed bug.rc');
exit 0;

View file

@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 14;
use Test::More tests => 15;
# Create the rc file.
if (open my $fh, '>', 'bulk.rc')
@ -49,13 +49,13 @@ qx{../task rc:bulk.rc add t4 due:thursday};
qx{../task rc:bulk.rc add t5 due:friday};
qx{../task rc:bulk.rc add t6 due:saturday};
my $output = qx{yes|../task rc:bulk.rc pro:p1 pri:M 4 5 6};
my $output = qx{echo "quit"|../task rc:bulk.rc pro:p1 pri:M 4 5 6};
like ($output, qr/Modified 0 tasks/, '"quit" prevents any further modifications');
my $output = qx{echo "all"|../task rc:bulk.rc pro:p1 pri:M 4 5 6};
unlike ($output, qr/Task 4 "t4"\n - No changes were made/, 'Task 4 modified');
unlike ($output, qr/Task 5 "t5"\n - No changes were made/, 'Task 5 modified');
unlike ($output, qr/Task 6 "t6"\n - No changes were made/, 'Task 6 modified');
#diag ("---");
#diag ($output);
#diag ("---");
$output = qx{../task rc:bulk.rc info 4};
like ($output, qr/Project\s+p1/, 'project applied to 4');

View file

@ -49,10 +49,10 @@ if (open my $fh, '>', 'response.txt')
qx{../task rc:confirm.rc add foo} for 1 .. 10;
# Test the various forms of "yes".
my $output = qx{echo "yes" | ../task rc:confirm.rc del 1};
like ($output, qr/Permanently delete task 1 'foo'\? \(y\/n\)/, 'confirmation - yes works');
unlike ($output, qr/Task not deleted\./, 'confirmation - yes works');
# Test the various forms of "Yes".
my $output = qx{echo "Yes" | ../task rc:confirm.rc del 1};
like ($output, qr/Permanently delete task 1 'foo'\? \(y\/n\)/, 'confirmation - Yes works');
unlike ($output, qr/Task not deleted\./, 'confirmation - Yes works');
$output = qx{echo "ye" | ../task rc:confirm.rc del 2};
like ($output, qr/Permanently delete task 2 'foo'\? \(y\/n\)/, 'confirmation - ye works');

View file

@ -34,7 +34,7 @@ use Test::More tests => 17;
if (open my $fh, '>', 'default.rc')
{
print $fh "data.location=.\n",
"default.command=rc:default.rc list\n",
"default.command=list\n",
"default.project=PROJECT\n",
"default.priority=M\n";
close $fh;

View file

@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 12;
use Test::More tests => 15;
# Create the rc file.
if (open my $fh, '>', 'dup.rc')
@ -55,6 +55,12 @@ like ($output, qr/Description\s+FOO/, 'duplicate modified description');
like ($output, qr/Priority\s+H/, 'duplicate added priority');
like ($output, qr/Tags\s+tag/, 'duplicate added tag');
# Test the output of the duplicate command - returning id of duplicated task
$output = qx{../task rc:dup.rc duplicate 1};
like ($output, qr/Duplicated\s+1\s+'foo'/, 'duplicate output task id and description');
like ($output, qr/Duplicated\s+1\s+task/, 'duplicate output number of tasks duplicated');
like ($output, qr/Created\s+task\s+4/, 'duplicate output of new task id');
# Cleanup.
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');

View file

@ -34,7 +34,7 @@ Context context;
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
UnitTest t (109);
UnitTest t (117);
// void wrapText (std::vector <std::string>& lines, const std::string& text, const int width)
std::string text = "This is a test of the line wrapping code.";
@ -104,12 +104,19 @@ int main (int argc, char** argv)
t.is (items.size (), (size_t) 1, "split 'a' '-' -> 1 item");
t.is (items[0], "a", "split 'a' '-' -> 'a'");
split (items, unsplit, '-');
t.is (items.size (), (size_t) 1, "split 'a' '-' -> 1 item");
t.is (items[0], "a", "split 'a' '-' -> 'a'");
unsplit = "-";
split (items, unsplit, '-');
t.is (items.size (), (size_t) 2, "split '-' '-' -> '' ''");
t.is (items[0], "", "split '-' '-' -> [0] ''");
t.is (items[1], "", "split '-' '-' -> [1] ''");
split_minimal (items, unsplit, '-');
t.is (items.size (), (size_t) 1, "split '-' '-' -> '-'");
unsplit = "-a-bc-def";
split (items, unsplit, '-');
t.is (items.size (), (size_t) 4, "split '-a-bc-def' '-' -> '' 'a' 'bc' 'def'");
@ -118,11 +125,20 @@ int main (int argc, char** argv)
t.is (items[2], "bc", "split '-a-bc-def' '-' -> [2] 'bc'");
t.is (items[3], "def", "split '-a-bc-def' '-' -> [3] 'def'");
split_minimal (items, unsplit, '-');
t.is (items.size (), (size_t) 3, "split '-a-bc-def' '-' -> 'a' 'bc' 'def'");
t.is (items[0], "a", "split '-a-bc-def' '-' -> [1] 'a'");
t.is (items[1], "bc", "split '-a-bc-def' '-' -> [2] 'bc'");
t.is (items[2], "def", "split '-a-bc-def' '-' -> [3] 'def'");
// void split (std::vector<std::string>& results, const std::string& input, const std::string& delimiter)
unsplit = "";
split (items, unsplit, "--");
t.is (items.size (), (size_t) 0, "split '' '--' -> 0 items");
split_minimal (items, unsplit, "--");
t.is (items.size (), (size_t) 0, "split '' '--' -> 0");
unsplit = "a";
split (items, unsplit, "--");
t.is (items.size (), (size_t) 1, "split 'a' '--' -> 1 item");

View file

@ -38,6 +38,7 @@ int main (int argc, char** argv)
// TODO bool confirm (const std::string&);
// TODO int confirm3 (const std::string&);
// TODO int confirm4 (const std::string&);
// TODO void delay (float);
// TODO std::string formatSeconds (time_t);
// TODO std::string formatSecondsCompact (time_t);
@ -83,15 +84,15 @@ int main (int argc, char** argv)
Task rightAgain (right);
std::string output = taskDifferences (left, right);
t.ok (taskDiff (left, right), "Detected changes");
t.ok (output.find ("zero was changed from '0' to '00'") != std::string::npos, "Detected change zero:0 -> zero:00");
t.ok (output.find ("one was deleted") != std::string::npos, "Detected deletion one:1 ->");
t.ok (output.find ("two") == std::string::npos, "Detected no change two:2 -> two:2");
t.ok (output.find ("three was set to '3'") != std::string::npos, "Detected addition -> three:3");
t.ok (taskDiff (left, right), "Detected changes");
t.ok (output.find ("zero will be changed from '0' to '00'") != std::string::npos, "Detected change zero:0 -> zero:00");
t.ok (output.find ("one will be deleted") != std::string::npos, "Detected deletion one:1 ->");
t.ok (output.find ("two") == std::string::npos, "Detected no change two:2 -> two:2");
t.ok (output.find ("three will be set to '3'") != std::string::npos, "Detected addition -> three:3");
t.notok (taskDiff (right, rightAgain), "No changes detected");
t.notok (taskDiff (right, rightAgain), "No changes detected");
output = taskDifferences (right, rightAgain);
t.ok (output.find ("No changes were made") != std::string::npos, "No changes detected");
t.ok (output.find ("No changes will be made") != std::string::npos, "No changes detected");
return 0;
}

View file

@ -72,6 +72,26 @@ void split (
results.push_back (input.substr (start, std::string::npos));
}
////////////////////////////////////////////////////////////////////////////////
void split_minimal (
std::vector<std::string>& results,
const std::string& input,
const char delimiter)
{
results.clear ();
std::string::size_type start = 0;
std::string::size_type i;
while ((i = input.find (delimiter, start)) != std::string::npos)
{
if (i != start)
results.push_back (input.substr (start, i - start));
start = i + 1;
}
if (input.length ())
results.push_back (input.substr (start, std::string::npos));
}
////////////////////////////////////////////////////////////////////////////////
void split (
std::vector<std::string>& results,
@ -93,6 +113,28 @@ void split (
results.push_back (input.substr (start, std::string::npos));
}
////////////////////////////////////////////////////////////////////////////////
void split_minimal (
std::vector<std::string>& results,
const std::string& input,
const std::string& delimiter)
{
results.clear ();
std::string::size_type length = delimiter.length ();
std::string::size_type start = 0;
std::string::size_type i;
while ((i = input.find (delimiter, start)) != std::string::npos)
{
if (i != start)
results.push_back (input.substr (start, i - start));
start = i + length;
}
if (input.length ())
results.push_back (input.substr (start, std::string::npos));
}
////////////////////////////////////////////////////////////////////////////////
void join (
std::string& result,

View file

@ -40,6 +40,8 @@ std::string unquoteText (const std::string&);
void extractLine (std::string&, std::string&, int);
void split (std::vector<std::string>&, const std::string&, const char);
void split (std::vector<std::string>&, const std::string&, const std::string&);
void split_minimal (std::vector<std::string>&, const std::string&, const char);
void split_minimal (std::vector<std::string>&, const std::string&, const std::string&);
void join (std::string&, const std::string&, const std::vector<std::string>&);
std::string commify (const std::string&);
std::string lowerCase (const std::string&);

View file

@ -82,7 +82,7 @@ bool confirm (const std::string& question)
int confirm3 (const std::string& question)
{
std::vector <std::string> options;
options.push_back ("yes");
options.push_back ("Yes");
options.push_back ("no");
options.push_back ("all");
@ -104,11 +104,49 @@ int confirm3 (const std::string& question)
}
while (matches.size () != 1);
if (matches[0] == "yes") return 1;
if (matches[0] == "Yes") return 1;
else if (matches[0] == "all") return 2;
else return 0;
}
////////////////////////////////////////////////////////////////////////////////
// 0 = no
// 1 = yes
// 2 = all
// 3 = quit
int confirm4 (const std::string& question)
{
std::vector <std::string> options;
options.push_back ("Yes");
options.push_back ("no");
options.push_back ("all");
options.push_back ("quit");
std::string answer;
std::vector <std::string> matches;
do
{
std::cout << question
<< " ("
<< options[0] << "/"
<< options[1] << "/"
<< options[2] << "/"
<< options[3]
<< ") ";
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 if (matches[0] == "quit") return 3;
else return 0;
}
////////////////////////////////////////////////////////////////////////////////
void delay (float f)
{
@ -500,22 +538,21 @@ std::string taskDifferences (const Task& before, const Task& after)
foreach (name, beforeOnly)
out << " - "
<< *name
<< " was deleted\n";
<< " will be deleted\n";
foreach (name, afterOnly)
out << " - "
<< *name
<< " was set to '"
<< " will be set to '"
<< renderAttribute (*name, after.get (*name))
<< "'\n";
foreach (name, beforeAtts)
if (*name != "uuid" &&
after.get (*name) != "" &&
before.get (*name) != after.get (*name))
out << " - "
<< *name
<< " was changed from '"
<< " will be changed from '"
<< renderAttribute (*name, before.get (*name))
<< "' to '"
<< renderAttribute (*name, after.get (*name))
@ -523,7 +560,7 @@ std::string taskDifferences (const Task& before, const Task& after)
// Shouldn't just say nothing.
if (out.str ().length () == 0)
out << " - No changes were made\n";
out << " - No changes will be made\n";
return out.str ();
}

View file

@ -53,6 +53,7 @@ for (typeof (c) *foreach_p = & (c); \
// util.cpp
bool confirm (const std::string&);
int confirm3 (const std::string&);
int confirm4 (const std::string&);
void delay (float);
std::string formatSeconds (time_t);
std::string formatSecondsCompact (time_t);