From ae56165c8004a335ec546d8b78c44f49cf28ea8d Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 9 Aug 2010 01:20:05 -0400 Subject: [PATCH] Bug - Modified argument parsing to prevent assertions and seg faults. - Added support for month names, with autocomplete. - Added autocomplete for "due". - Added unit tests to exercise all combinations, and errors. --- src/report.cpp | 131 ++++++++++++++++++++++++++++++-------------- src/tests/bug.cal.t | 119 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+), 41 deletions(-) create mode 100755 src/tests/bug.cal.t diff --git a/src/report.cpp b/src/report.cpp index 9a43e8dad..612e8eadf 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -2030,54 +2030,103 @@ int handleReportCalendar (std::string &outs) int mTo = mFrom; int yTo = yFrom; - // Determine what to do - int numberOfArgs = context.args.size(); + // Defaults. + monthsToDisplay = monthsPerLine; + mFrom = today.month (); + yFrom = today.year (); - if (numberOfArgs == 1) { - // task cal - monthsToDisplay = monthsPerLine; - mFrom = today.month(); - yFrom = today.year(); - } - else if (numberOfArgs == 2) { - if (context.args[1] == "y") { - // task cal y - monthsToDisplay = 12; - mFrom = today.month(); - yFrom = today.year(); - } - else if (context.args[1] == "due") { - // task cal due - monthsToDisplay = monthsPerLine; + // Set up a vector of commands (1), for autoComplete. + std::vector commandNames; + commandNames.push_back ("calendar"); + + // Set up a vector of keywords, for autoComplete. + std::vector keywordNames; + keywordNames.push_back ("due"); + + // Set up a vector of months, for autoComplete. + std::vector monthNames; + monthNames.push_back ("january"); + monthNames.push_back ("february"); + monthNames.push_back ("march"); + monthNames.push_back ("april"); + monthNames.push_back ("may"); + monthNames.push_back ("june"); + monthNames.push_back ("july"); + monthNames.push_back ("august"); + monthNames.push_back ("september"); + monthNames.push_back ("october"); + monthNames.push_back ("november"); + monthNames.push_back ("december"); + + // For autoComplete results. + std::vector matches; + + // Look at all args, regardless of sequence. + int argMonth = 0; + int argYear = 0; + bool argWholeYear = false; + foreach (arg, context.args) + { + // Some version of "calendar". + if (autoComplete (lowerCase (*arg), commandNames, matches) == 1) + continue; + + // "due". + else if (autoComplete (lowerCase (*arg), keywordNames, matches) == 1) getpendingdate = true; + + // "y". + else if (lowerCase (*arg) == "y") + argWholeYear = true; + + // YYYY. + else if (digitsOnly (*arg) && arg->length () == 4) + argYear = atoi (arg->c_str ()); + + // MM. + else if (digitsOnly (*arg) && arg->length () <= 2) + { + argMonth = atoi (arg->c_str ()); + if (argMonth < 1 || argMonth > 12) + throw std::string ("Argument '") + *arg + "' is not a valid month."; } - else { - // task cal 2010 - monthsToDisplay = 12; - mFrom = 1; - yFrom = atoi (context.args[1].c_str ()); + + // "January" etc. + else if (autoComplete (lowerCase (*arg), monthNames, matches) == 1) + { + argMonth = Date::monthOfYear (matches[0]); + if (argMonth == -1) + throw std::string ("Argument '") + *arg + "' is not a valid month."; } + + else + throw std::string ("Could not recognize argument '") + *arg + "'."; } - else if (numberOfArgs == 3) { - if (context.args[2] == "y") { - // task cal due y - monthsToDisplay = 12; - getpendingdate = true; - } - else { - // task cal 8 2010 - monthsToDisplay = monthsPerLine; - mFrom = atoi (context.args[1].c_str ()); - yFrom = atoi (context.args[2].c_str ()); - } - } - else if (numberOfArgs == 4) { - // task cal 8 2010 y + + // 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; - mFrom = atoi (context.args[1].c_str ()); - yFrom = atoi (context.args[2].c_str ()); - } + 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. diff --git a/src/tests/bug.cal.t b/src/tests/bug.cal.t new file mode 100755 index 000000000..c39902c6e --- /dev/null +++ b/src/tests/bug.cal.t @@ -0,0 +1,119 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2010, 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 => 32; + +# Create the rc file. +if (open my $fh, '>', 'cal.rc') +{ + print $fh "data.location=."; + + close $fh; + ok (-r 'cal.rc', 'Created cal.rc'); +} + +# Bug: The 'cal' command can fail when provided with challenging arguments. + +# Should not fail (because they are correct): +my $output = qx{../task rc:cal.rc cal 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal'); + +# y due 2010 donkey 8 +$output = qx{../task rc:cal.rc cal y 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal y'); +$output = qx{../task rc:cal.rc cal 8 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal 8'); +$output = qx{../task rc:cal.rc cal due 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal due'); +$output = qx{../task rc:cal.rc cal 2010 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal 2010'); +$output = qx{../task rc:cal.rc cal donkey 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal donkey'); + +# y due 2010 donkey 8 +$output = qx{../task rc:cal.rc cal y due 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal y due'); +$output = qx{../task rc:cal.rc cal y 8 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal y 8'); +$output = qx{../task rc:cal.rc cal y 2010 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal y 2010'); +$output = qx{../task rc:cal.rc cal y donkey 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal y donkey'); +$output = qx{../task rc:cal.rc cal 8 due 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal 8 due'); +$output = qx{../task rc:cal.rc cal 8 2010 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal 8 2010'); +$output = qx{../task rc:cal.rc cal 8 donkey 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal 8 donkey'); +$output = qx{../task rc:cal.rc cal due 2010 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal due 2010'); +$output = qx{../task rc:cal.rc cal due donkey 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal due donkey'); +$output = qx{../task rc:cal.rc cal 2010 donkey 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal 2010 donkey'); + +# y 8 due 2010 donkey +$output = qx{../task rc:cal.rc cal y 8 due 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal y 8 due'); +$output = qx{../task rc:cal.rc cal y 8 2010 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal y 8 2010'); +$output = qx{../task rc:cal.rc cal y 8 donkey 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal y 8 donkey'); +$output = qx{../task rc:cal.rc cal y due 2010 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal y due 2010'); +$output = qx{../task rc:cal.rc cal y due donkey 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal y due donkey'); +$output = qx{../task rc:cal.rc cal y 2010 donkey 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal y 2010 donkey'); +$output = qx{../task rc:cal.rc cal 8 due 2010 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal 8 due 2010'); +$output = qx{../task rc:cal.rc cal 8 due donkey 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal 8 due donkey'); +$output = qx{../task rc:cal.rc cal 8 2010 donkey 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal 8 2010 donkey'); +$output = qx{../task rc:cal.rc cal due 2010 8 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal due 2010 8'); +$output = qx{../task rc:cal.rc cal due 2010 donkey 2>&1}; +unlike ($output, qr/(?:Assertion failed|Could note recognize|not a valid)/, 'cal due 2010 donkey'); + +# 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 'cal.rc'; +ok (!-r 'cal.rc', 'Removed cal.rc'); + +exit 0;