From d52eebaa66d5b5c790dcb6cc8cd0dbac0c2d3d2b Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 11 Mar 2014 23:28:57 -0400 Subject: [PATCH 01/31] Bug TW-1288 - TW-1288 Added missing locking for task modifications (thanks to Kosta H, Ralph Bean, Adam Coddington). --- ChangeLog | 2 ++ src/TDB2.cpp | 3 +++ 2 files changed, 5 insertions(+) diff --git a/ChangeLog b/ChangeLog index 87cf746e8..cf0272023 100644 --- a/ChangeLog +++ b/ChangeLog @@ -35,6 +35,8 @@ Bugs to Michele Vetturi). - TD-45 Fix preprocessor define (thanks to Jochen Sprickerhof). - TW-1282 incorrect URLs in man task-sync (thanks to Jeremiah Marks). +- TW-1288 Added missing locking for task modifications (thanks to Kosta H, + Ralph Bean, Adam Coddington). - #1511 sync init crashes if client certification file is empty or invalid (thanks to Marton Suranyi). - #1508 Show command highlight configuration (thanks to Nicolas Appriou). diff --git a/src/TDB2.cpp b/src/TDB2.cpp index a74ab1e4a..475e86568 100644 --- a/src/TDB2.cpp +++ b/src/TDB2.cpp @@ -233,6 +233,9 @@ void TF2::commit () { if (_file.open ()) { + if (context.config.getBoolean ("locking")) + _file.waitForLock (); + // Truncate the file and rewrite. _file.truncate (); From 065cb2ef0149c6d9aebb703ec92c6d23d5049eb4 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Thu, 13 Mar 2014 21:15:42 -0400 Subject: [PATCH 02/31] Bug - Correct a DST error in date handling. --- src/ISO8601.cpp | 2 +- test/iso8601d.t.cpp | 42 ++++++++++++++++++++++++------------------ 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/ISO8601.cpp b/src/ISO8601.cpp index fe610118f..118a13c3a 100644 --- a/src/ISO8601.cpp +++ b/src/ISO8601.cpp @@ -633,7 +633,7 @@ void ISO8601d::resolve () else if (! offset) { #ifdef HAVE_TM_GMTOFF - offset = local_now->tm_gmtoff; + offset = local_now->tm_gmtoff - (local_now->tm_isdst == 1 ? 3600 : 0); #else // TODO Umm... #endif diff --git a/test/iso8601d.t.cpp b/test/iso8601d.t.cpp index e5d27b1a5..ddbcee8c4 100644 --- a/test/iso8601d.t.cpp +++ b/test/iso8601d.t.cpp @@ -91,21 +91,24 @@ int main (int argc, char** argv) int local_s = (local_now->tm_hour * 3600) + (local_now->tm_min * 60) + local_now->tm_sec; - local_now->tm_hour = 0; - local_now->tm_min = 0; - local_now->tm_sec = 0; + local_now->tm_hour = 0; + local_now->tm_min = 0; + local_now->tm_sec = 0; + local_now->tm_isdst = -1; time_t local = mktime (local_now); std::cout << "# local midnight today " << local << "\n"; - local_now->tm_year = 2013 - 1900; - local_now->tm_mon = 12 - 1; - local_now->tm_mday = 6; + local_now->tm_year = 2013 - 1900; + local_now->tm_mon = 12 - 1; + local_now->tm_mday = 6; + local_now->tm_isdst = 0; time_t local6 = mktime (local_now); std::cout << "# local midnight 2013-12-06 " << local6 << "\n"; - local_now->tm_year = 2013 - 1900; - local_now->tm_mon = 12 - 1; - local_now->tm_mday = 1; + local_now->tm_year = 2013 - 1900; + local_now->tm_mon = 12 - 1; + local_now->tm_mday = 1; + local_now->tm_isdst = 0; time_t local1 = mktime (local_now); std::cout << "# local midnight 2013-12-01 " << local1 << "\n"; @@ -113,21 +116,24 @@ int main (int argc, char** argv) int utc_s = (utc_now->tm_hour * 3600) + (utc_now->tm_min * 60) + utc_now->tm_sec; - utc_now->tm_hour = 0; - utc_now->tm_min = 0; - utc_now->tm_sec = 0; + utc_now->tm_hour = 0; + utc_now->tm_min = 0; + utc_now->tm_sec = 0; + utc_now->tm_isdst = -1; time_t utc = timegm (utc_now); std::cout << "# utc midnight today " << utc << "\n"; - utc_now->tm_year = 2013 - 1900; - utc_now->tm_mon = 12 - 1; - utc_now->tm_mday = 6; + utc_now->tm_year = 2013 - 1900; + utc_now->tm_mon = 12 - 1; + utc_now->tm_mday = 6; + utc_now->tm_isdst = 0; time_t utc6 = timegm (utc_now); std::cout << "# utc midnight 2013-12-06 " << utc6 << "\n"; - utc_now->tm_year = 2013 - 1900; - utc_now->tm_mon = 12 - 1; - utc_now->tm_mday = 1; + utc_now->tm_year = 2013 - 1900; + utc_now->tm_mon = 12 - 1; + utc_now->tm_mday = 1; + utc_now->tm_isdst = 0; time_t utc1 = timegm (utc_now); std::cout << "# utc midnight 2013-12-01 " << utc1 << "\n"; From f05b01f9fabee8803d3dae2ba779d26dfc1fcd58 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Fri, 14 Mar 2014 23:46:14 -0400 Subject: [PATCH 03/31] Eval - Enabled unary minus operator. --- src/Eval.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Eval.cpp b/src/Eval.cpp index 80aa5efb2..0963eb9ef 100644 --- a/src/Eval.cpp +++ b/src/Eval.cpp @@ -59,7 +59,7 @@ static struct { "_hastag_", 9, 'b', 'l'}, // +tag [Pseudo-op] { "_notag_", 9, 'b', 'l'}, // -tag [Pseudo-op] -// { "-", 15, 'u', 'r' }, // Unary minus + { "-", 15, 'u', 'r' }, // Unary minus { "*", 13, 'b', 'l' }, // Multiplication { "/", 13, 'b', 'l' }, // Division { "%", 13, 'b', 'l' }, // Modulus From 122cc2f56d7fb74ffdc5fa54ca5b61b666f235f9 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Fri, 14 Mar 2014 23:48:56 -0400 Subject: [PATCH 04/31] ISO8601 - Simplified and debugged ISO8601::resolve method regarding UTC, local and specified time offsets. --- src/ISO8601.cpp | 148 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 98 insertions(+), 50 deletions(-) diff --git a/src/ISO8601.cpp b/src/ISO8601.cpp index 118a13c3a..aba9bb9d6 100644 --- a/src/ISO8601.cpp +++ b/src/ISO8601.cpp @@ -603,6 +603,8 @@ bool ISO8601d::validate () // long tm_gmtoff; offset from UTC in seconds void ISO8601d::resolve () { + //std::cout << "# start ------------------------\n"; + // Don't touch the original values. int year = _year; int month = _month; @@ -613,55 +615,69 @@ void ISO8601d::resolve () int seconds = _seconds; int offset = _offset; bool utc = _utc; + //std::cout << "# input\n" + // << "# year=" << year << "\n" + // << "# month=" << month << "\n" + // << "# week=" << week << "\n" + // << "# weekday=" << weekday << "\n" + // << "# julian=" << julian << "\n" + // << "# day=" << day << "\n" + // << "# seconds=" << seconds << "\n" + // << "# offset=" << offset << "\n" + // << "# utc=" << utc << "\n"; - struct tm t = {0}; - - // Requests that mktime determine summer time effect. - t.tm_isdst = -1; - - // Determine local time. + // Get current time. time_t now = time (NULL); - struct tm* local_now = localtime (&now); - struct tm* utc_now = gmtime (&now); + //std::cout << "# now=" << now << "\n"; - // What is a complete TZ? - // utc - // offset - // local (get default) - if (utc) - offset = 0; - else if (! offset) + // A UTC offset needs to be accommodated. Once the offset is subtracted, + // only local and UTC times remain. + if (offset) { -#ifdef HAVE_TM_GMTOFF - offset = local_now->tm_gmtoff - (local_now->tm_isdst == 1 ? 3600 : 0); -#else - // TODO Umm... -#endif + seconds -= offset; + now -= offset; + utc = true; } - // Subtract the offset, to project local to UTC. - seconds -= offset; + // Get 'now' in the relevant location. + struct tm* t_now = utc ? gmtime (&now) : localtime (&now); + //std::cout << "# t_now\n" + // << "# tm_year=" << t_now->tm_year << "\n" + // << "# tm_mon=" << t_now->tm_mon << "\n" + // << "# tm_mday=" << t_now->tm_mday << "\n" + // << "# tm_hour=" << t_now->tm_hour << "\n" + // << "# tm_min=" << t_now->tm_min << "\n" + // << "# tm_sec=" << t_now->tm_sec << "\n" + // << "# tm_isdst=" << t_now->tm_isdst << "\n"; - // If the time is specified without a date, if it is earlier than 'now', then - // it refers to tomorrow. - int seconds_utc_now = utc_now->tm_hour * 3600 + - utc_now->tm_min * 60 + - utc_now->tm_sec; + int seconds_now = (t_now->tm_hour * 3600) + + (t_now->tm_min * 60) + + t_now->tm_sec; + //std::cout << "# seconds_now=" << seconds_now << "\n"; + + // Project forward one day if the specified seconds are earlier in the day + // than the current seconds. if (year == 0 && month == 0 && day == 0 && week == 0 && weekday == 0 && - seconds < seconds_utc_now) + seconds < seconds_now) { + //std::cout << "# earlier today, therefore seconds += 86400\n" + // << "# seconds=" << seconds << "\n"; seconds += 86400; } - // Conversion of week + weekday to julian. + // Convert week + weekday --> julian. if (week) { julian = (week * 7) + weekday - dayOfWeek (year, 1, 4) - 3; + //std::cout << "# week=" << week << " weekday=" << weekday << " specified\n" + // << "# julian=" << julian << "\n"; } + + // Provide default values for year, month, day. else { // Default values for year, month, day: @@ -673,9 +689,9 @@ void ISO8601d::resolve () // if (year == 0) { - year = local_now->tm_year + 1900; - month = local_now->tm_mon + 1; - day = local_now->tm_mday; + year = t_now->tm_year + 1900; + month = t_now->tm_mon + 1; + day = t_now->tm_mday; } else { @@ -688,43 +704,75 @@ void ISO8601d::resolve () day = 1; } } + //std::cout << "# Applied default y m d\n" + // << "# year=" << year << "\n" + // << "# month=" << month << "\n" + // << "# day=" << day << "\n"; if (julian) { month = 1; day = julian; + //std::cout << "# julian=" << julian << " specified\n" + // << "# month=" << month << "\n" + // << "# day=" << day << "\n"; } + struct tm t = {0}; + t.tm_isdst = -1; // Requests that mktime/gmtime determine summer time effect. t.tm_year = year - 1900; t.tm_mon = month - 1; t.tm_mday = day; - // What is a complete time spec? - // seconds - if (seconds) + if (seconds > 86400) { - if (seconds > 86400) - { - int days = seconds / 86400; - t.tm_mday += days; - seconds -= days * 86400; - } + //std::cout << "# seconds=" << seconds << " is more than a day\n"; + int days = seconds / 86400; + t.tm_mday += days; + seconds %= 86400; + //std::cout << "# t.tm_mday=" << t.tm_mday << "\n" + // << "# seconds=" << seconds << "\n"; + } t.tm_hour = seconds / 3600; t.tm_min = (seconds % 3600) / 60; t.tm_sec = seconds % 60; - } - else - { - // User-provided default. - t.tm_hour = _default_seconds / 3600; - t.tm_min = (_default_seconds % 3600) / 60; - t.tm_sec = _default_seconds % 60; - } - _value = timegm (&t); + //std::cout << "# Final t\n" + // << "# tm_year=" << t.tm_year << "\n" + // << "# tm_mon=" << t.tm_mon << "\n" + // << "# tm_mday=" << t.tm_mday << "\n" + // << "# tm_hour=" << t.tm_hour << "\n" + // << "# tm_min=" << t.tm_min << "\n" + // << "# tm_sec=" << t.tm_sec << "\n" + // << "# tm_isdst=" << t.tm_isdst << "\n"; + + _value = utc ? timegm (&t) : timelocal (&t); + //std::cout << "# _value " << _value << "\n"; + + //std::cout << "# end --------------------------\n"; + //dump (); } +//////////////////////////////////////////////////////////////////////////////// +/* +void ISO8601d::dump () +{ + std::cout << "# Y=" << _year + << " M=" << _month + << " W=" << _week + << " WD=" << _weekday + << " J=" << _julian + << " d=" << _day + << " s=" << _seconds + << " o=" << _offset + << " Z=" << _utc + << " ambi=" << _ambiguity + << " --> " << _value + << "\n"; +} +*/ + //////////////////////////////////////////////////////////////////////////////// ISO8601p::ISO8601p () { From 5774c31dfd8ed6f1f12011e8d392b8c142547a16 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Fri, 14 Mar 2014 23:50:05 -0400 Subject: [PATCH 05/31] E9 - Removed dead code. --- src/E9.cpp | 56 ------------------------------------------------------ src/E9.h | 4 ---- 2 files changed, 60 deletions(-) diff --git a/src/E9.cpp b/src/E9.cpp index 2b76f9187..a77446022 100644 --- a/src/E9.cpp +++ b/src/E9.cpp @@ -146,10 +146,6 @@ void E9::eval (const Task& task, std::vector & value_stack) else if (arg->_raw == "=") operator_equal (result, left, right, case_sensitive); else if (arg->_raw == "~") operator_match (result, left, right, case_sensitive, task); else if (arg->_raw == "!~") operator_nomatch (result, left, right, case_sensitive, task); - else if (arg->_raw == "*") operator_multiply (result, left, right); - else if (arg->_raw == "/") operator_divide (result, left, right); - else if (arg->_raw == "+") operator_add (result, left, right); - else if (arg->_raw == "-") operator_subtract (result, left, right); else if (arg->_raw == "_hastag_") operator_hastag (result, right, false, task); else if (arg->_raw == "_notag_") operator_hastag (result, right, true, task); else @@ -252,8 +248,6 @@ void E9::operator_not (Arg& result, Arg& right) result._value = "false"; else result._value = "true"; - -// std::cout << "# " << right << " --> " << result << "\n"; } //////////////////////////////////////////////////////////////////////////////// @@ -261,8 +255,6 @@ void E9::operator_negate (Arg& result, Arg& right) { result = coerce (right, Arg::type_number); result._value = format (- strtod (result._value.c_str (), NULL)); - -// std::cout << "# " << right << " --> " << result << "\n"; } //////////////////////////////////////////////////////////////////////////////// @@ -276,8 +268,6 @@ void E9::operator_and (Arg& result, Arg& left, Arg& right) { result._value = "true"; } - -// std::cout << "# " << left << " " << right << " --> " << result << "\n"; } //////////////////////////////////////////////////////////////////////////////// @@ -291,8 +281,6 @@ void E9::operator_or (Arg& result, Arg& left, Arg& right) { result._value = "true"; } - -// std::cout << "# " << left << " " << right << " --> " << result << "\n"; } //////////////////////////////////////////////////////////////////////////////// @@ -309,8 +297,6 @@ void E9::operator_xor (Arg& result, Arg& left, Arg& right) { result._value = "true"; } - -// std::cout << "# " << left << " " << right << " --> " << result << "\n"; } //////////////////////////////////////////////////////////////////////////////// @@ -373,8 +359,6 @@ void E9::operator_lt (Arg& result, Arg& left, Arg& right) } result._type = Arg::type_bool; - -// std::cout << "# " << left << " " << right << " --> " << result << "\n"; } //////////////////////////////////////////////////////////////////////////////// @@ -438,8 +422,6 @@ void E9::operator_lte (Arg& result, Arg& left, Arg& right) } result._type = Arg::type_bool; - -// std::cout << "# " << left << " " << right << " --> " << result << "\n"; } //////////////////////////////////////////////////////////////////////////////// @@ -503,8 +485,6 @@ void E9::operator_gte (Arg& result, Arg& left, Arg& right) } result._type = Arg::type_bool; - -// std::cout << "# " << left << " " << right << " --> " << result << "\n"; } //////////////////////////////////////////////////////////////////////////////// @@ -567,8 +547,6 @@ void E9::operator_gt (Arg& result, Arg& left, Arg& right) } result._type = Arg::type_bool; - -// std::cout << "# " << left << " " << right << " --> " << result << "\n"; } //////////////////////////////////////////////////////////////////////////////// @@ -582,8 +560,6 @@ void E9::operator_inequal ( result._value = result._value == "false" ? "true" : "false"; - -// std::cout << "# " << left << " " << right << " --> " << result << "\n"; } //////////////////////////////////////////////////////////////////////////////// @@ -656,8 +632,6 @@ void E9::operator_equal ( ? "true" : "false"; } - -// std::cout << "# " << left << " " << right << " --> " << result << "\n"; } //////////////////////////////////////////////////////////////////////////////// @@ -696,8 +670,6 @@ void E9::operator_match ( } else result._value = "false"; - -// std::cout << "# " << left << " " << right << " --> " << result << "\n"; } //////////////////////////////////////////////////////////////////////////////// @@ -735,32 +707,6 @@ void E9::operator_nomatch ( } } } - -// std::cout << "# " << left << " " << right << " --> " << result << "\n"; -} - -//////////////////////////////////////////////////////////////////////////////// -void E9::operator_multiply (Arg& result, Arg& left, Arg& right) -{ -// std::cout << "# " << left << " " << right << " --> " << result << "\n"; -} - -//////////////////////////////////////////////////////////////////////////////// -void E9::operator_divide (Arg& result, Arg& left, Arg& right) -{ -// std::cout << "# " << left << " " << right << " --> " << result << "\n"; -} - -//////////////////////////////////////////////////////////////////////////////// -void E9::operator_add (Arg& result, Arg& left, Arg& right) -{ -// std::cout << "# " << left << " " << right << " --> " << result << "\n"; -} - -//////////////////////////////////////////////////////////////////////////////// -void E9::operator_subtract (Arg& result, Arg& left, Arg& right) -{ -// std::cout << "# " << left << " " << right << " --> " << result << "\n"; } //////////////////////////////////////////////////////////////////////////////// @@ -776,8 +722,6 @@ void E9::operator_hastag ( result._value = invert ? "false" : "true"; else result._value = invert ? "true" : "false"; - -// std::cout << "# tags" << (invert ? " " : " ") << right << " --> " << result << "\n"; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/E9.h b/src/E9.h index 330a54092..f38330070 100644 --- a/src/E9.h +++ b/src/E9.h @@ -61,10 +61,6 @@ private: void operator_equal (Arg&, Arg&, Arg&, bool); void operator_match (Arg&, Arg&, Arg&, bool, const Task&); void operator_nomatch (Arg&, Arg&, Arg&, bool, const Task&); - void operator_multiply (Arg&, Arg&, Arg&); - void operator_divide (Arg&, Arg&, Arg&); - void operator_add (Arg&, Arg&, Arg&); - void operator_subtract (Arg&, Arg&, Arg&); void operator_hastag (Arg&, Arg&, bool, const Task&); const Arg coerce (const Arg&, const Arg::type); From 88b94ac2fc22809a3eeb8a962fd5429ad60e4f81 Mon Sep 17 00:00:00 2001 From: Alexander Sulfrian Date: Mon, 17 Mar 2014 18:50:52 +0100 Subject: [PATCH 06/31] TLSClient: do certification verification with old gnutls The automatic verification for the server certificate with gnutls_certificate_set_verify_function does only work with gnutls >=2.9.10. So with older versions we should call the verify function manually after the gnutls handshake. Signed-off-by: Paul Beckingham --- AUTHORS | 1 + ChangeLog | 2 ++ src/TLSClient.cpp | 14 ++++++++++++++ 3 files changed, 17 insertions(+) diff --git a/AUTHORS b/AUTHORS index fa202af31..dc7bba0df 100644 --- a/AUTHORS +++ b/AUTHORS @@ -101,6 +101,7 @@ The following submitted code, packages or analysis, and deserve special thanks: Marton Suranyi Nicolas Appriou Jochen Sprickerhof + Alexander Sulfrian Thanks to the following, who submitted detailed bug reports and excellent suggestions: diff --git a/ChangeLog b/ChangeLog index cf0272023..8f9f1c50d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -45,6 +45,8 @@ Bugs - #1473 Make TASK_RCDIR customizable (thanks to Elias Probst). - #1486 Truncated sentence in task-sync(5) manpage (thanks to Jakub Wilk). - #1487 `tasksh` segmentation fault (thanks to Hector Arciga). +- Added certificate verification to GnuTLS versions < 2.9.10 (thanks to Alexander + Sulfrian) - Removed debugging code. ------ current release --------------------------- diff --git a/src/TLSClient.cpp b/src/TLSClient.cpp index fbf542557..0bf6b5a08 100644 --- a/src/TLSClient.cpp +++ b/src/TLSClient.cpp @@ -183,6 +183,10 @@ void TLSClient::init ( throw std::string ("Missing CERT file."); #if GNUTLS_VERSION_NUMBER >= 0x02090a + // The automatic verification for the server certificate with + // gnutls_certificate_set_verify_function only works with gnutls + // >=2.9.10. So with older versions we should call the verify function + // manually after the gnutls handshake. gnutls_certificate_set_verify_function (_credentials, verify_certificate_callback); #endif gnutls_init (&_session, GNUTLS_CLIENT); @@ -267,6 +271,16 @@ void TLSClient::connect (const std::string& host, const std::string& port) if (ret < 0) throw format (STRING_CMD_SYNC_HANDSHAKE, gnutls_strerror (ret)); +#if GNUTLS_VERSION_NUMBER < 0x02090a + // The automatic verification for the server certificate with + // gnutls_certificate_set_verify_function does only work with gnutls + // >=2.9.10. So with older versions we should call the verify function + // manually after the gnutls handshake. + ret = verify_certificate_callback(_session); + if (ret < 0) + throw std::string (STRING_TLS_INIT_FAIL); +#endif + if (_debug) { #if GNUTLS_VERSION_NUMBER >= 0x03010a From 40dd95ddfb47028e8d2c0dff9197f2b49f51aefa Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 17 Mar 2014 18:45:02 -0400 Subject: [PATCH 07/31] Code Cleanup - Removed debugging and redundant code. - Removed socket cast. - Added diagnostic message on handshake fail. --- src/TLSClient.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/TLSClient.cpp b/src/TLSClient.cpp index 0bf6b5a08..881a6b4e5 100644 --- a/src/TLSClient.cpp +++ b/src/TLSClient.cpp @@ -64,9 +64,6 @@ static int verify_certificate_callback (gnutls_session_t session) if (trust_override) return 0; - // Get the hostname from the session. - const char* hostname = (const char*) gnutls_session_get_ptr (session); - // This verification function uses the trusted CAs in the credentials // structure. So you must have installed one or more CA certificates. unsigned int status = 0; @@ -85,8 +82,6 @@ static int verify_certificate_callback (gnutls_session_t session) if (ret < 0) return GNUTLS_E_CERTIFICATE_ERROR; - //std::cout << "c: INFO " << out.data << "\n"; - gnutls_free (out.data); #endif @@ -258,7 +253,7 @@ void TLSClient::connect (const std::string& host, const std::string& port) #if GNUTLS_VERSION_NUMBER >= 0x030109 gnutls_transport_set_int (_session, _socket); #else - gnutls_transport_set_ptr (_session, (gnutls_transport_ptr_t) (long) _socket); + gnutls_transport_set_ptr (_session, (gnutls_transport_ptr_t) _socket); #endif // Perform the TLS handshake @@ -278,7 +273,11 @@ void TLSClient::connect (const std::string& host, const std::string& port) // manually after the gnutls handshake. ret = verify_certificate_callback(_session); if (ret < 0) + { + if (_debug) + std::cout << "c: ERROR Certificate verification failed.\n"; throw std::string (STRING_TLS_INIT_FAIL); + } #endif if (_debug) From fdcc04d13e046ac3ac57313f2d1f4636b2d8e061 Mon Sep 17 00:00:00 2001 From: Alexander Sulfrian Date: Tue, 18 Mar 2014 19:17:07 +0100 Subject: [PATCH 08/31] TLSClient: add verify_certificate as member function Certificate verification is now done in a member function of the TLSClient, so that the member variables could be accessed. --- src/TLSClient.cpp | 80 +++++++++++++++++++++++++---------------------- src/TLSClient.h | 2 ++ 2 files changed, 45 insertions(+), 37 deletions(-) diff --git a/src/TLSClient.cpp b/src/TLSClient.cpp index 881a6b4e5..d2823c3da 100644 --- a/src/TLSClient.cpp +++ b/src/TLSClient.cpp @@ -50,8 +50,6 @@ static int verify_certificate_callback (gnutls_session_t); -static bool trust_override = false; - //////////////////////////////////////////////////////////////////////////////// static void gnutls_log_function (int level, const char* message) { @@ -61,35 +59,8 @@ static void gnutls_log_function (int level, const char* message) //////////////////////////////////////////////////////////////////////////////// static int verify_certificate_callback (gnutls_session_t session) { - if (trust_override) - return 0; - - // This verification function uses the trusted CAs in the credentials - // structure. So you must have installed one or more CA certificates. - unsigned int status = 0; -#if GNUTLS_VERSION_NUMBER >= 0x030104 - int ret = gnutls_certificate_verify_peers3 (session, NULL, &status); -#else - int ret = gnutls_certificate_verify_peers2 (session, &status); -#endif - if (ret < 0) - return GNUTLS_E_CERTIFICATE_ERROR; - -#if GNUTLS_VERSION_NUMBER >= 0x030105 - gnutls_certificate_type_t type = gnutls_certificate_type_get (session); - gnutls_datum_t out; - ret = gnutls_certificate_verification_status_print (status, type, &out, 0); - if (ret < 0) - return GNUTLS_E_CERTIFICATE_ERROR; - - gnutls_free (out.data); -#endif - - if (status != 0) - return GNUTLS_E_CERTIFICATE_ERROR; - - // Continue handshake. - return 0; + const TLSClient* client = (TLSClient*) gnutls_session_get_ptr (session); + return client->verify_certificate(); } //////////////////////////////////////////////////////////////////////////////// @@ -101,6 +72,7 @@ TLSClient::TLSClient () , _socket (0) , _limit (0) , _debug (false) +, _trust(false) { } @@ -139,10 +111,10 @@ void TLSClient::debug (int level) //////////////////////////////////////////////////////////////////////////////// void TLSClient::trust (bool value) { - trust_override = value; + _trust = value; if (_debug) { - if (trust_override) + if (_trust) std::cout << "c: INFO Server certificate trusted automatically.\n"; else std::cout << "c: INFO Server certificate trust verified.\n"; @@ -207,9 +179,9 @@ void TLSClient::init ( //////////////////////////////////////////////////////////////////////////////// void TLSClient::connect (const std::string& host, const std::string& port) { - // Store the host name, so the verification callback can access it during the - // handshake below. - gnutls_session_set_ptr (_session, (void*) host.c_str ()); + // Store the TLSClient instance, so that the verification callback can access + // it during the handshake below and call the verifcation method. + gnutls_session_set_ptr (_session, (void*) this); // use IPv4 or IPv6, does not matter. struct addrinfo hints = {0}; @@ -271,7 +243,7 @@ void TLSClient::connect (const std::string& host, const std::string& port) // gnutls_certificate_set_verify_function does only work with gnutls // >=2.9.10. So with older versions we should call the verify function // manually after the gnutls handshake. - ret = verify_certificate_callback(_session); + ret = verify_certificate(); if (ret < 0) { if (_debug) @@ -298,6 +270,40 @@ void TLSClient::bye () gnutls_bye (_session, GNUTLS_SHUT_RDWR); } +//////////////////////////////////////////////////////////////////////////////// +int TLSClient::verify_certificate () const +{ + if (_trust) + return 0; + + // This verification function uses the trusted CAs in the credentials + // structure. So you must have installed one or more CA certificates. + unsigned int status = 0; +#if GNUTLS_VERSION_NUMBER >= 0x030104 + int ret = gnutls_certificate_verify_peers3 (_session, NULL, &status); +#else + int ret = gnutls_certificate_verify_peers2 (_session, &status); +#endif + if (ret < 0) + return GNUTLS_E_CERTIFICATE_ERROR; + +#if GNUTLS_VERSION_NUMBER >= 0x030105 + gnutls_certificate_type_t type = gnutls_certificate_type_get (_session); + gnutls_datum_t out; + ret = gnutls_certificate_verification_status_print (status, type, &out, 0); + if (ret < 0) + return GNUTLS_E_CERTIFICATE_ERROR; + + gnutls_free (out.data); +#endif + + if (status != 0) + return GNUTLS_E_CERTIFICATE_ERROR; + + // Continue handshake. + return 0; +} + //////////////////////////////////////////////////////////////////////////////// void TLSClient::send (const std::string& data) { diff --git a/src/TLSClient.h b/src/TLSClient.h index 8df335020..98bc7d54a 100644 --- a/src/TLSClient.h +++ b/src/TLSClient.h @@ -43,6 +43,7 @@ public: void init (const std::string&, const std::string&, const std::string&); void connect (const std::string&, const std::string&); void bye (); + int verify_certificate() const; void send (const std::string&); void recv (std::string&); @@ -57,6 +58,7 @@ private: int _socket; int _limit; bool _debug; + bool _trust; }; #endif From 7fb1487993320e5026a4ce45039d38fa75d2611a Mon Sep 17 00:00:00 2001 From: Alexander Sulfrian Date: Tue, 18 Mar 2014 19:21:49 +0100 Subject: [PATCH 09/31] TLSClient: add hostname verifcation The CN or subjectAltNames of the TLS certification is now matched with the hostname connected to. taskd.trust is now a tristate value (allow all, ignore hostname, strict) to optionally disable the new hostname verification. --- src/TLSClient.cpp | 60 ++++++++++++++++++++++++++++----- src/TLSClient.h | 8 +++-- src/commands/CmdDiagnostics.cpp | 8 +++-- src/commands/CmdSync.cpp | 11 +++--- src/commands/CmdSync.h | 3 +- 5 files changed, 73 insertions(+), 17 deletions(-) diff --git a/src/TLSClient.cpp b/src/TLSClient.cpp index d2823c3da..f3ef90942 100644 --- a/src/TLSClient.cpp +++ b/src/TLSClient.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #define MAX_BUF 16384 @@ -68,11 +69,13 @@ TLSClient::TLSClient () : _ca ("") , _cert ("") , _key ("") +, _host ("") +, _port ("") , _session(0) , _socket (0) , _limit (0) , _debug (false) -, _trust(false) +, _trust(strict) { } @@ -109,13 +112,15 @@ void TLSClient::debug (int level) } //////////////////////////////////////////////////////////////////////////////// -void TLSClient::trust (bool value) +void TLSClient::trust (const enum trust_level value) { _trust = value; if (_debug) { - if (_trust) + if (_trust == allow_all) std::cout << "c: INFO Server certificate trusted automatically.\n"; + else if (_trust == ignore_hostname) + std::cout << "c: INFO Server certificate trust verified but hostname ignored.\n"; else std::cout << "c: INFO Server certificate trust verified.\n"; } @@ -179,6 +184,9 @@ void TLSClient::init ( //////////////////////////////////////////////////////////////////////////////// void TLSClient::connect (const std::string& host, const std::string& port) { + _host = host; + _port = port; + // Store the TLSClient instance, so that the verification callback can access // it during the handshake below and call the verifcation method. gnutls_session_set_ptr (_session, (void*) this); @@ -273,19 +281,55 @@ void TLSClient::bye () //////////////////////////////////////////////////////////////////////////////// int TLSClient::verify_certificate () const { - if (_trust) + if (_trust == TLSClient::allow_all) return 0; // This verification function uses the trusted CAs in the credentials // structure. So you must have installed one or more CA certificates. unsigned int status = 0; + + const char* hostname = _host.c_str(); #if GNUTLS_VERSION_NUMBER >= 0x030104 - int ret = gnutls_certificate_verify_peers3 (_session, NULL, &status); -#else - int ret = gnutls_certificate_verify_peers2 (_session, &status); -#endif + if (_trust == TLSClient::ignore_hostname) + hostname = NULL; + + int ret = gnutls_certificate_verify_peers3 (_session, hostname, &status); if (ret < 0) return GNUTLS_E_CERTIFICATE_ERROR; +#else + int ret = gnutls_certificate_verify_peers2 (_session, &status); + if (ret < 0) + return GNUTLS_E_CERTIFICATE_ERROR; + + if ((status == 0) && (_trust != TLSClient::ignore_hostname)) + { + if (gnutls_certificate_type_get (_session) == GNUTLS_CRT_X509) + { + const gnutls_datum* cert_list; + unsigned int cert_list_size; + gnutls_x509_crt cert; + + cert_list = gnutls_certificate_get_peers (_session, &cert_list_size); + if (cert_list_size == 0) + return GNUTLS_E_CERTIFICATE_ERROR; + + ret = gnutls_x509_crt_init (&cert); + if (ret < 0) + return GNUTLS_E_CERTIFICATE_ERROR; + + ret = gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER); + if (ret < 0) + gnutls_x509_crt_deinit(cert); + status = GNUTLS_E_CERTIFICATE_ERROR; + + if (gnutls_x509_crt_check_hostname (cert, hostname) == 0) + gnutls_x509_crt_deinit(cert); + return GNUTLS_E_CERTIFICATE_ERROR; + } + else + return GNUTLS_E_CERTIFICATE_ERROR; + } +#endif #if GNUTLS_VERSION_NUMBER >= 0x030105 gnutls_certificate_type_t type = gnutls_certificate_type_get (_session); diff --git a/src/TLSClient.h b/src/TLSClient.h index 98bc7d54a..ca99a82a3 100644 --- a/src/TLSClient.h +++ b/src/TLSClient.h @@ -34,11 +34,13 @@ class TLSClient { public: + enum trust_level { strict, ignore_hostname, allow_all }; + TLSClient (); ~TLSClient (); void limit (int); void debug (int); - void trust (bool); + void trust (const enum trust_level); void ciphers (const std::string&); void init (const std::string&, const std::string&, const std::string&); void connect (const std::string&, const std::string&); @@ -53,12 +55,14 @@ private: std::string _cert; std::string _key; std::string _ciphers; + std::string _host; + std::string _port; gnutls_certificate_credentials_t _credentials; gnutls_session_t _session; int _socket; int _limit; bool _debug; - bool _trust; + enum trust_level _trust; }; #endif diff --git a/src/commands/CmdDiagnostics.cpp b/src/commands/CmdDiagnostics.cpp index 5dd764fba..f00a4b8b8 100644 --- a/src/commands/CmdDiagnostics.cpp +++ b/src/commands/CmdDiagnostics.cpp @@ -232,8 +232,12 @@ int CmdDiagnostics::execute (std::string& output) ? " (readable)" : " (not readable)") << "\n"; - if (context.config.get ("taskd.trust") != "") - out << " Trust: override\n"; + if (context.config.get ("taskd.trust") == "allow all") + out << " Trust: allow all\n"; + else if (context.config.get ("taskd.trust") == "ignore hostname") + out << " Trust: ignore hostanme\n"; + else + out << " Trust: strict\n"; out << " Cert: " << context.config.get ("taskd.certificate") diff --git a/src/commands/CmdSync.cpp b/src/commands/CmdSync.cpp index ecaa98ff8..618d2279c 100644 --- a/src/commands/CmdSync.cpp +++ b/src/commands/CmdSync.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -87,14 +86,18 @@ int CmdSync::execute (std::string& output) if (credentials.size () != 3) throw std::string (STRING_CMD_SYNC_BAD_CRED); - bool trust = context.config.getBoolean ("taskd.trust"); + enum TLSClient::trust_level trust = TLSClient::strict; + if (context.config.get ("taskd.trust") == "allow all") + trust = TLSClient::allow_all; + else if (context.config.get ("taskd.trust") == "ignore hostname") + trust = TLSClient::ignore_hostname; // CA must exist, if provided. File ca (context.config.get ("taskd.ca")); if (ca._data != "" && ! ca.exists ()) throw std::string (STRING_CMD_SYNC_BAD_CA); - if (trust && ca._data != "") + if (trust == TLSClient::allow_all && ca._data != "") throw std::string (STRING_CMD_SYNC_TRUST_CA); File certificate (context.config.get ("taskd.certificate")); @@ -319,7 +322,7 @@ bool CmdSync::send ( const std::string& ca, const std::string& certificate, const std::string& key, - bool trust, + const enum TLSClient::trust_level trust, const Msg& request, Msg& response) { diff --git a/src/commands/CmdSync.h b/src/commands/CmdSync.h index 9bfc90561..9dd50e373 100644 --- a/src/commands/CmdSync.h +++ b/src/commands/CmdSync.h @@ -30,6 +30,7 @@ #include #include #include +#include class CmdSync : public Command { @@ -38,7 +39,7 @@ public: int execute (std::string&); private: - bool send (const std::string&, const std::string&, const std::string&, const std::string&, bool, const Msg&, Msg&); + bool send (const std::string&, const std::string&, const std::string&, const std::string&, const enum TLSClient::trust_level, const Msg&, Msg&); }; #endif From 325d0d1738599bb1bad6774c4c24a793983f2538 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 5 Apr 2014 10:37:53 -0400 Subject: [PATCH 10/31] Documentation - Mentioned the hostname verification. --- ChangeLog | 5 +++-- src/TLSClient.cpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8f9f1c50d..3837ff8ba 100644 --- a/ChangeLog +++ b/ChangeLog @@ -41,12 +41,13 @@ Bugs (thanks to Marton Suranyi). - #1508 Show command highlight configuration (thanks to Nicolas Appriou). - #1503 build failure with musl libc due to undefined GLOB_BRACE and GLOB_TILDE - (thanks to Natanael Copa) + (thanks to Natanael Copa). - #1473 Make TASK_RCDIR customizable (thanks to Elias Probst). - #1486 Truncated sentence in task-sync(5) manpage (thanks to Jakub Wilk). - #1487 `tasksh` segmentation fault (thanks to Hector Arciga). - Added certificate verification to GnuTLS versions < 2.9.10 (thanks to Alexander - Sulfrian) + Sulfrian). +- Added certificate hostname verification (thanks to Alexander Sulfrian). - Removed debugging code. ------ current release --------------------------- diff --git a/src/TLSClient.cpp b/src/TLSClient.cpp index f3ef90942..9d06b09aa 100644 --- a/src/TLSClient.cpp +++ b/src/TLSClient.cpp @@ -311,7 +311,7 @@ int TLSClient::verify_certificate () const cert_list = gnutls_certificate_get_peers (_session, &cert_list_size); if (cert_list_size == 0) - return GNUTLS_E_CERTIFICATE_ERROR; + return GNUTLS_E_CERTIFICATE_ERROR; ret = gnutls_x509_crt_init (&cert); if (ret < 0) From 5965a851517cdff192dff490a5836798607cecc9 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 5 Apr 2014 10:39:38 -0400 Subject: [PATCH 11/31] - Bug TW-1296 - TW-1296 make test/run_all exit with non-zero code if a test fail (thanks to Jakub Wilk). --- ChangeLog | 2 ++ test/run_all.in | 7 +++++-- test/test.cpp | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3837ff8ba..6f8e16982 100644 --- a/ChangeLog +++ b/ChangeLog @@ -37,6 +37,8 @@ Bugs - TW-1282 incorrect URLs in man task-sync (thanks to Jeremiah Marks). - TW-1288 Added missing locking for task modifications (thanks to Kosta H, Ralph Bean, Adam Coddington). +- TW-1296 make test/run_all exit with non-zero code if a test fail (thanks to + Jakub Wilk). - #1511 sync init crashes if client certification file is empty or invalid (thanks to Marton Suranyi). - #1508 Show command highlight configuration (thanks to Nicolas Appriou). diff --git a/test/run_all.in b/test/run_all.in index 25ac62f44..204b8fd96 100755 --- a/test/run_all.in +++ b/test/run_all.in @@ -1,5 +1,6 @@ #! /bin/sh +rc=0 if [ x"$1" = x"--verbose" ]; then for i in ${TESTBLOB} @@ -9,9 +10,10 @@ then while read LINE do echo "$LINE" - done < test.log + done < test.log || rc=1 rm test.log done + exit $rc else date > all.log @@ -37,7 +39,7 @@ else COUNT=`expr $COUNT + 1` fi - $i >> all.log 2>&1 + $i >> all.log 2>&1 || rc=1 done if [ $BAR -eq 1 ]; then @@ -53,4 +55,5 @@ else printf "Fail: %5d\n" `grep -c '^not' all.log` printf "Skipped: %5d\n" `grep -c '^skip' all.log` printf "Runtime: %5d seconds\n" $RUNTIME + exit $rc fi diff --git a/test/test.cpp b/test/test.cpp index 718df542b..07234e20c 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include /////////////////////////////////////////////////////////////////////////////// @@ -84,6 +85,7 @@ UnitTest::~UnitTest () << " skipped. " << std::setprecision (3) << percentPassed << "% passed.\n"; + exit (_failed > 0); } /////////////////////////////////////////////////////////////////////////////// From 6354bc09abbdf8d5c79d9c536117c59f3935369d Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 5 Apr 2014 11:25:20 -0400 Subject: [PATCH 12/31] Bug TW-1295 - TW-1295 test/time.t fails on the last day of the month (thanks to Jakub Wilk). --- ChangeLog | 2 ++ test/count.t | 47 ++++++++++++++++++++++++++++------------------- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/ChangeLog b/ChangeLog index 6f8e16982..40406dbed 100644 --- a/ChangeLog +++ b/ChangeLog @@ -37,6 +37,8 @@ Bugs - TW-1282 incorrect URLs in man task-sync (thanks to Jeremiah Marks). - TW-1288 Added missing locking for task modifications (thanks to Kosta H, Ralph Bean, Adam Coddington). +- TW-1295 test/time.t fails on the last day of the month (thanks to Jakub + Wilk). - TW-1296 make test/run_all exit with non-zero code if a test fail (thanks to Jakub Wilk). - #1511 sync init crashes if client certification file is empty or invalid diff --git a/test/count.t b/test/count.t index 920a44e7d..85d3c1729 100755 --- a/test/count.t +++ b/test/count.t @@ -27,6 +27,7 @@ use strict; use warnings; +use POSIX qw(mktime); use Test::More tests => 7; # Ensure environment has no influence. @@ -49,30 +50,38 @@ qx{../src/task rc:count.rc add three 2>&1}; qx{../src/task rc:count.rc 2 delete 2>&1}; qx{../src/task rc:count.rc add four wait:eom 2>&1}; -# TODO This fails when today == eom. For example, on 2013-04-30 at 8:00:00, the -# value for 'eom' is 2013-04-30 0:00:00, which is already past due, which -# means a second child task is generated. This would be fixed by 'eom' -# expanding to 2013-04-30 24:00:00, as per ISO-8601. -qx{../src/task rc:count.rc add five due:eom recur:monthly 2>&1}; +TODO: { + my @today = localtime; + my @tomorrow = @today; + $tomorrow[3] += 1; + @tomorrow = localtime(mktime(@tomorrow)); + local $TODO = 'can fail when today == eom' if $today[4] != $tomorrow[4]; -diag ("Problem: the next test fails at EOM"); -my $output = qx{../src/task rc:count.rc count 2>&1}; -like ($output, qr/^5\n/ms, 'count'); + # TODO This fails when today == eom. For example, on 2013-04-30 at 8:00:00, the + # value for 'eom' is 2013-04-30 0:00:00, which is already past due, which + # means a second child task is generated. This would be fixed by 'eom' + # expanding to 2013-04-30 24:00:00, as per ISO-8601. + qx{../src/task rc:count.rc add five due:eom recur:monthly 2>&1}; -$output = qx{../src/task rc:count.rc count status:deleted rc.debug:1 2>&1}; -like ($output, qr/^1\n/ms, 'count status:deleted'); + diag ("Problem: the next test fails at EOM"); + my $output = qx{../src/task rc:count.rc count 2>&1}; + like ($output, qr/^5\n/ms, 'count'); -diag ("Problem: the next test fails at EOM"); -$output = qx{../src/task rc:count.rc count e 2>&1}; -like ($output, qr/^3\n/ms, 'count e'); + $output = qx{../src/task rc:count.rc count status:deleted rc.debug:1 2>&1}; + like ($output, qr/^1\n/ms, 'count status:deleted'); -diag ("Problem: the next test fails at EOM"); -$output = qx{../src/task rc:count.rc count description.startswith:f 2>&1}; -like ($output, qr/^2\n/ms, 'count description.startswith:f'); + diag ("Problem: the next test fails at EOM"); + $output = qx{../src/task rc:count.rc count e 2>&1}; + like ($output, qr/^3\n/ms, 'count e'); -diag ("Problem: the next test fails at EOM"); -$output = qx{../src/task rc:count.rc count due.any: 2>&1}; -like ($output, qr/^1\n/ms, 'count due.any:'); + diag ("Problem: the next test fails at EOM"); + $output = qx{../src/task rc:count.rc count description.startswith:f 2>&1}; + like ($output, qr/^2\n/ms, 'count description.startswith:f'); + + diag ("Problem: the next test fails at EOM"); + $output = qx{../src/task rc:count.rc count due.any: 2>&1}; + like ($output, qr/^1\n/ms, 'count due.any:'); +} # Cleanup. unlink qw(pending.data completed.data undo.data backlog.data count.rc); From 620f9b40b207ce5dc974124c27625be8be28fc06 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 5 Apr 2014 12:13:02 -0400 Subject: [PATCH 13/31] Documentation - Added man page updates to reflect new 'taskd.trust' settings. --- NEWS | 2 ++ doc/man/taskrc.5.in | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 8cc9372f1..197b9f9d5 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,8 @@ New commands in taskwarrior 2.4.0 New configuration options in taskwarrior 2.4.0 + - The 'taskd.trust' setting is now a tri-state, supporting values 'strict', + 'ignore hostname' and 'allow all', for server certificate validation. - New themes: dark-default-16.theme, dark-gray-blue-256.theme Newly deprecated features in taskwarrior 2.4.0 diff --git a/doc/man/taskrc.5.in b/doc/man/taskrc.5.in index fdd0eaaa3..44a3e138f 100644 --- a/doc/man/taskrc.5.in +++ b/doc/man/taskrc.5.in @@ -1358,11 +1358,13 @@ using a self-signed certificate. Optional. .RE .TP -.B taskd.trust=yes|no +.B taskd.trust=strict|ignore hostname|allow all .RS -If you do not specify a CA certificate when your Taskserver is using a self- -signed certificate, you can override the certificate validation by setting this -value to 'yes'. Default is not to trust a server certificate. +This settings allows you to override the trust level when server certificates +are validated. With "allow all", the server certificate is trusted +automatically. With "ignore hostname", the server certificate is verified but +the hostname is ignored. With "strict", the server certificate is verified. +Default is "strict", which requires full validation. .RE .TP From 70ba19fd5eb7d24d6704ad50033742ea3431581a Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 6 Apr 2014 00:29:14 -0400 Subject: [PATCH 14/31] Bug TW-1254 - TW-1254 Calc command can segfault on negative numbers (thanks to Renato Alves). - Implemented a recursive descent parser to syntax check the expression, and disambiguate unary minus. The syntax checking is not generating any diagnostics yet. --- ChangeLog | 2 + src/Eval.cpp | 426 +++++++++++++++++++++++++++++++++++++++++++++--- src/Eval.h | 11 ++ src/ISO8601.cpp | 5 +- src/ISO8601.h | 1 + 5 files changed, 424 insertions(+), 21 deletions(-) diff --git a/ChangeLog b/ChangeLog index 40406dbed..303874c67 100644 --- a/ChangeLog +++ b/ChangeLog @@ -34,6 +34,8 @@ Bugs - TD-42 Cannot compile taskd - GNUTLS_VERSION undefined in diag.cpp (thanks to Michele Vetturi). - TD-45 Fix preprocessor define (thanks to Jochen Sprickerhof). +- TW-1254 Calc command can segfault on negative numbers (thanks to Renato + Alves). - TW-1282 incorrect URLs in man task-sync (thanks to Jeremiah Marks). - TW-1288 Added missing locking for task modifications (thanks to Kosta H, Ralph Bean, Adam Coddington). diff --git a/src/Eval.cpp b/src/Eval.cpp index 0963eb9ef..69ffdfef8 100644 --- a/src/Eval.cpp +++ b/src/Eval.cpp @@ -40,32 +40,38 @@ static struct } operators[] = { // Operator Precedence Type Associativity - { "and", 5, 'b', 'l' }, // Conjunction - { "xor", 4, 'b', 'l' }, // Disjunction + { "^", 16, 'b', 'r' }, // Exponent - { "or", 3, 'b', 'l' }, // Disjunction - { "<=", 10, 'b', 'l' }, // Less than or equal - { ">=", 10, 'b', 'l' }, // Greater than or equal - { "!~", 9, 'b', 'l' }, // Regex non-match - { "!=", 9, 'b', 'l' }, // Inequal + { "!", 15, 'u', 'r' }, // Unary not + { "_neg_", 15, 'u', 'r' }, // Unary minus + { "_pos_", 15, 'u', 'r' }, // Unary plus - { "==", 9, 'b', 'l' }, // Equal - { "=", 9, 'b', 'l' }, // Equal - { "^", 16, 'b', 'r' }, // Exponent - { ">", 10, 'b', 'l' }, // Greater than - { "~", 9, 'b', 'l' }, // Regex match - { "!", 15, 'u', 'r' }, // Not + { "_hastag_", 14, 'b', 'l'}, // +tag [Pseudo-op] + { "_notag_", 14, 'b', 'l'}, // -tag [Pseudo-op] - { "_hastag_", 9, 'b', 'l'}, // +tag [Pseudo-op] - { "_notag_", 9, 'b', 'l'}, // -tag [Pseudo-op] - - { "-", 15, 'u', 'r' }, // Unary minus { "*", 13, 'b', 'l' }, // Multiplication { "/", 13, 'b', 'l' }, // Division { "%", 13, 'b', 'l' }, // Modulus + { "+", 12, 'b', 'l' }, // Addition { "-", 12, 'b', 'l' }, // Subtraction + + { "<=", 10, 'b', 'l' }, // Less than or equal + { ">=", 10, 'b', 'l' }, // Greater than or equal + { ">", 10, 'b', 'l' }, // Greater than { "<", 10, 'b', 'l' }, // Less than + + { "=", 9, 'b', 'l' }, // Equal + { "==", 9, 'b', 'l' }, // Equal + { "!=", 9, 'b', 'l' }, // Inequal + + { "~", 8, 'b', 'l' }, // Regex match + { "!~", 8, 'b', 'l' }, // Regex non-match + + { "and", 5, 'b', 'l' }, // Conjunction + { "or", 4, 'b', 'l' }, // Disjunction + { "xor", 3, 'b', 'l' }, // Disjunction + { "(", 0, 'b', 'l' }, // Precedence start { ")", 0, 'b', 'l' }, // Precedence end }; @@ -106,6 +112,9 @@ void Eval::evaluateInfixExpression (const std::string& e, Variant& v) const std::cout << "# token infix '" << token << "' " << Lexer::type_name (type) << "\n"; } + // Parse for syntax checking and operator replacement. + infixParse (tokens); + // Convert infix --> postfix. infixToPostfix (tokens); @@ -163,7 +172,7 @@ void Eval::evaluatePostfixStack ( std::vector >::const_iterator token; for (token = tokens.begin (); token != tokens.end (); ++token) { - // Unary operator. + // Unary operators. if (token->second == Lexer::typeOperator && token->first == "!") { @@ -171,6 +180,18 @@ void Eval::evaluatePostfixStack ( values.pop_back (); values.push_back (! right); } + else if (token->second == Lexer::typeOperator && + token->first == "_neg_") + { + Variant right = values.back (); + values.pop_back (); + values.push_back (Variant (0) - right); + } + else if (token->second == Lexer::typeOperator && + token->first == "_pos_") + { + // NOP? + } // Binary operators. else if (token->second == Lexer::typeOperator) @@ -279,6 +300,373 @@ void Eval::evaluatePostfixStack ( result = values[0]; } +//////////////////////////////////////////////////////////////////////////////// +// +// Grammar: +// Logical --> Regex {( "and" | "or" | "xor" ) Regex} +// Regex --> Equality {( "~" | "!~" ) Equality} +// Equality --> Comparative {( "==" | "=" | "!=" ) Comparative} +// Comparative --> Arithmetic {( "<=" | "<" | ">=" | ">" ) Arithmetic} +// Arithmetic --> Geometric {( "+" | "-" ) Geometric} +// Geometric --> Tag {( "*" | "/" | "%" ) Tag} +// Tag --> Unary {( "_hastag_" | "_notag_" ) Unary} +// Unary --> [( "-" | "+" | "!" )] Exponent +// Exponent --> Primitive ["^" Primitive] +// Primitive --> "(" Logical ")" | Variant +// +void Eval::infixParse ( + std::vector >& infix) const +{ + if (_debug) + std::cout << "# infixParse\n"; + + try + { + int i = 0; + if (parseLogical (infix, i)) + if (_debug) + std::cout << "# no errors.\n"; + } + + catch (const std::string& error) + { + std::cerr << error << "\n"; + } + + catch (...) + { + std::cerr << "Unknown error.\n"; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Logical --> Regex {( "and" | "or" | "xor" ) Regex} +bool Eval::parseLogical ( + std::vector >& infix, + int &i) const +{ + if (_debug) + std::cout << "# parseLogical\n"; + + if (i < infix.size () && + parseRegex (infix, i)) + { + while ((infix[i].first == "and" || + infix[i].first == "or" || + infix[i].first == "xor") && + infix[i].second == Lexer::typeOperator) + { + if (_debug) + std::cout << "# " << infix[i].first << "\n"; + + ++i; + if (! parseRegex (infix, i)) + return false; + } + + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// Regex --> Equality {( "~" | "!~" ) Equality} +bool Eval::parseRegex ( + std::vector >& infix, + int &i) const +{ + if (_debug) + std::cout << "# parseRegex\n"; + + if (i < infix.size () && + parseEquality (infix, i)) + { + while ((infix[i].first == "~" || + infix[i].first == "!~") && + infix[i].second == Lexer::typeOperator) + { + if (_debug) + std::cout << "# " << infix[i].first << "\n"; + + ++i; + if (! parseEquality (infix, i)) + return false; + } + + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// Equality --> Comparative {( "==" | "=" | "!=" ) Comparative} +bool Eval::parseEquality ( + std::vector >& infix, + int &i) const +{ + if (_debug) + std::cout << "# parseEquality\n"; + + if (i < infix.size () && + parseComparative (infix, i)) + { + while ((infix[i].first == "==" || + infix[i].first == "=" || + infix[i].first == "!=") && + infix[i].second == Lexer::typeOperator) + { + if (_debug) + std::cout << "# " << infix[i].first << "\n"; + + ++i; + if (! parseComparative (infix, i)) + return false; + } + + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// Comparative --> Arithmetic {( "<=" | "<" | ">=" | ">" ) Arithmetic} +bool Eval::parseComparative ( + std::vector >& infix, + int &i) const +{ + if (_debug) + std::cout << "# parseComparative\n"; + + if (i < infix.size () && + parseArithmetic (infix, i)) + { + while ((infix[i].first == "<=" || + infix[i].first == "<" || + infix[i].first == ">=" || + infix[i].first == ">") && + infix[i].second == Lexer::typeOperator) + { + if (_debug) + std::cout << "# " << infix[i].first << "\n"; + + ++i; + if (! parseArithmetic (infix, i)) + return false; + } + + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// Arithmetic --> Geometric {( "+" | "-" ) Geometric} +bool Eval::parseArithmetic ( + std::vector >& infix, + int &i) const +{ + if (_debug) + std::cout << "# parseArithmetic\n"; + + if (i < infix.size () && + parseGeometric (infix, i)) + { + while ((infix[i].first == "+" || + infix[i].first == "-") && + infix[i].second == Lexer::typeOperator) + { + if (_debug) + std::cout << "# " << infix[i].first << "\n"; + + ++i; + if (! parseGeometric (infix, i)) + return false; + } + + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// Geometric --> Tag {( "*" | "/" | "%" ) Tag} +bool Eval::parseGeometric ( + std::vector >& infix, + int &i) const +{ + if (_debug) + std::cout << "# parseGeometric\n"; + + if (i < infix.size () && + parseTag (infix, i)) + { + while ((infix[i].first == "*" || + infix[i].first == "/" || + infix[i].first == "%") && + infix[i].second == Lexer::typeOperator) + { + if (_debug) + std::cout << "# " << infix[i].first << "\n"; + + ++i; + if (! parseTag (infix, i)) + return false; + } + + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// Tag --> Unary {( "_hastag_" | "_notag_" ) Unary} +bool Eval::parseTag ( + std::vector >& infix, + int &i) const +{ + if (_debug) + std::cout << "# parseTag\n"; + + if (i < infix.size () && + parseUnary (infix, i)) + { + while ((infix[i].first == "_hastag_" || + infix[i].first == "_notag_") && + infix[i].second == Lexer::typeOperator) + { + if (_debug) + std::cout << "# " << infix[i].first << "\n"; + + ++i; + if (! parseUnary (infix, i)) + return false; + } + + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// Unary --> [( "-" | "+" | "!" )] Exponent +bool Eval::parseUnary ( + std::vector >& infix, + int &i) const +{ + if (_debug) + std::cout << "# parseUnary\n"; + + if (i < infix.size ()) + { + if (infix[i].first == "-") + { + if (_debug) + std::cout << "# '-' --> '_neg_'\n"; + + infix[i].first = "_neg_"; + ++i; + } + else if (infix[i].first == "+") + { + if (_debug) + std::cout << "# '+' --> '_pos_'\n"; + + infix[i].first = "_pos_"; + ++i; + } + else if (infix[i].first == "!") + { + if (_debug) + std::cout << "# " << infix[i].first << "\n"; + ++i; + } + } + + return parseExponent (infix, i); +} + +//////////////////////////////////////////////////////////////////////////////// +// Exponent --> Primitive ["^" Primitive] +bool Eval::parseExponent ( + std::vector >& infix, + int &i) const +{ + if (_debug) + std::cout << "# parseExponent\n"; + + if (i < infix.size () && + parsePrimitive (infix, i)) + { + while (infix[i].first == "^" && + infix[i].second == Lexer::typeOperator) + { + if (_debug) + std::cout << "# " << infix[i].first << "\n"; + + ++i; + if (! parsePrimitive (infix, i)) + return false; + } + + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// Primitive --> "(" Logical ")" | Variant +bool Eval::parsePrimitive ( + std::vector >& infix, + int &i) const +{ + if (_debug) + std::cout << "# parseVariant\n"; + + if (i < infix.size ()) + { + if (infix[i].first == "(") + { + if (_debug) + std::cout << "# " << infix[i].first << "\n"; + + ++i; + if (parseLogical (infix, i)) + { + if (i < infix.size () && + infix[i].first == ")") + { + if (_debug) + std::cout << "# " << infix[i].first << "\n"; + + ++i; + return true; + } + } + } + else + { + if (infix[i].second != Lexer::typeOperator) + { + if (_debug) + std::cout << "# " << infix[i].first << "\n"; + + ++i; + return true; + } + } + } + + return false; +} + //////////////////////////////////////////////////////////////////////////////// // Dijkstra Shunting Algorithm. // http://en.wikipedia.org/wiki/Shunting-yard_algorithm @@ -311,7 +699,7 @@ void Eval::evaluatePostfixStack ( // Pop the operator onto the output queue. // Exit. // -void Eval:: infixToPostfix ( +void Eval::infixToPostfix ( std::vector >& infix) const { // Short circuit. diff --git a/src/Eval.h b/src/Eval.h index 3094c303a..258673a0c 100644 --- a/src/Eval.h +++ b/src/Eval.h @@ -50,6 +50,17 @@ public: private: void evaluatePostfixStack (const std::vector >&, Variant&) const; void infixToPostfix (std::vector >&) const; + void infixParse (std::vector >&) const; + bool parseLogical (std::vector >&, int &) const; + bool parseRegex (std::vector >&, int &) const; + bool parseEquality (std::vector >&, int &) const; + bool parseComparative (std::vector >&, int &) const; + bool parseArithmetic (std::vector >&, int &) const; + bool parseGeometric (std::vector >&, int &) const; + bool parseTag (std::vector >&, int &) const; + bool parseUnary (std::vector >&, int &) const; + bool parseExponent (std::vector >&, int &) const; + bool parsePrimitive (std::vector >&, int &) const; bool identifyOperator (const std::string&, char&, int&, char&) const; private: diff --git a/src/ISO8601.cpp b/src/ISO8601.cpp index aba9bb9d6..ef6397205 100644 --- a/src/ISO8601.cpp +++ b/src/ISO8601.cpp @@ -25,6 +25,7 @@ //////////////////////////////////////////////////////////////////////////////// #include +//#include // TODO Remove #include //////////////////////////////////////////////////////////////////////////////// @@ -755,9 +756,9 @@ void ISO8601d::resolve () } //////////////////////////////////////////////////////////////////////////////// -/* void ISO8601d::dump () { +/* std::cout << "# Y=" << _year << " M=" << _month << " W=" << _week @@ -770,8 +771,8 @@ void ISO8601d::dump () << " ambi=" << _ambiguity << " --> " << _value << "\n"; -} */ +} //////////////////////////////////////////////////////////////////////////////// ISO8601p::ISO8601p () diff --git a/src/ISO8601.h b/src/ISO8601.h index 85a5779de..c892a31dd 100644 --- a/src/ISO8601.h +++ b/src/ISO8601.h @@ -59,6 +59,7 @@ private: int dayOfWeek (int, int, int); bool validate (); void resolve (); + void dump (); public: bool _ambiguity; From 25b8082fbd7cccfdde935c205a33640c99ef31a0 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 6 Apr 2014 23:57:15 -0400 Subject: [PATCH 15/31] Diagnostics - Added diagnostic token dump to 'calc' in debug mode, after token substitution so the new tokens can be seen. - Added unit tests to look for parser syntax errors. - Added test for '2--3'. --- src/Eval.cpp | 10 ++++++---- test/calc.t | 12 +++++++++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Eval.cpp b/src/Eval.cpp index 69ffdfef8..bc0b8ef3e 100644 --- a/src/Eval.cpp +++ b/src/Eval.cpp @@ -106,14 +106,16 @@ void Eval::evaluateInfixExpression (const std::string& e, Variant& v) const std::string token; Lexer::Type type; while (l.token (token, type)) - { tokens.push_back (std::pair (token, type)); - if (_debug) - std::cout << "# token infix '" << token << "' " << Lexer::type_name (type) << "\n"; - } // Parse for syntax checking and operator replacement. infixParse (tokens); + if (_debug) + { + std::vector >::iterator i; + for (i = tokens.begin (); i != tokens.end (); ++i) + std::cout << "# token infix '" << i->first << "' " << Lexer::type_name (i->second) << "\n"; + } // Convert infix --> postfix. infixToPostfix (tokens); diff --git a/test/calc.t b/test/calc.t index 12b4b2825..cb892b8ad 100755 --- a/test/calc.t +++ b/test/calc.t @@ -27,7 +27,7 @@ use strict; use warnings; -use Test::More tests => 14; +use Test::More tests => 22; # '15min' is seen as '15', 'min', not '15min' duration. my $output = qx{../src/calc --debug --noambiguous '12 * 3600 + 34 * 60 + 56'}; @@ -36,6 +36,7 @@ like ($output, qr/token infix '3600' Number/, 'Number 3600'); like ($output, qr/token infix '34' Number/, 'Number 60'); like ($output, qr/token infix '60' Number/, 'Number 60'); like ($output, qr/token infix '56' Number/, 'Number 56'); +like ($output, qr/no errors/ms, 'No syntax errors'); like ($output, qr/^45296$/ms, 'Result 45296'); unlike ($output, qr/Error/, 'No errors'); @@ -48,5 +49,14 @@ like ($output, qr/token postfix '56' Number/, 'Number 56'); like ($output, qr/^45296$/ms, 'Result 45296'); unlike ($output, qr/Error/, 'No errors'); +$output = qx{../src/calc --debug --noambiguous '2--3'}; +like ($output, qr/token infix '2' Number/ms, 'Number 2'); +like ($output, qr/token infix '-' Operator/ms, 'Operator -'); +like ($output, qr/token infix '_neg_' Operator/ms, 'operator _neg_'); +like ($output, qr/token infix '3' Number/ms, 'Number 3'); +like ($output, qr/no errors/ms, 'No syntax errors'); +like ($output, qr/^5$/ms, 'Result 5'); +unlike ($output, qr/Error/, 'No errors'); + exit 0; From a49ed165c332556737e91a282deb977bbea9a64e Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 12 Apr 2014 11:12:24 -0400 Subject: [PATCH 16/31] Bug TW-1302 - TW-1302 CmdShow.cpp:244: bad length in substr ? (thanks to David Binderman). --- AUTHORS | 1 + ChangeLog | 1 + src/commands/CmdShow.cpp | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index dc7bba0df..736d0a6fc 100644 --- a/AUTHORS +++ b/AUTHORS @@ -102,6 +102,7 @@ The following submitted code, packages or analysis, and deserve special thanks: Nicolas Appriou Jochen Sprickerhof Alexander Sulfrian + David Binderman Thanks to the following, who submitted detailed bug reports and excellent suggestions: diff --git a/ChangeLog b/ChangeLog index 303874c67..08f56d116 100644 --- a/ChangeLog +++ b/ChangeLog @@ -16,6 +16,7 @@ Features - TW-1261 Migrate test bug.360.t to new unit testing framework (thanks to Renato Alves). - TW-1274 Map 'modification' attribute to 'modified' (thanks to jck). +- TW-1302 CmdShow.cpp:244: bad length in substr ? (thanks to David Binderman). - Removed deprecated 'echo.command' setting, in favor of the 'header' and 'affected' verbosity tokens. - Removed deprecated 'edit.verbose' setting, in favor of the 'edit' verbosity diff --git a/src/commands/CmdShow.cpp b/src/commands/CmdShow.cpp index ae10bd001..7d936665f 100644 --- a/src/commands/CmdShow.cpp +++ b/src/commands/CmdShow.cpp @@ -232,7 +232,7 @@ int CmdShow::execute (std::string& output) i->first.substr (0, 6) != "alias." && i->first.substr (0, 5) != "hook." && i->first.substr (0, 4) != "uda." && - i->first.substr (0, 4) != "default." && + i->first.substr (0, 8) != "default." && i->first.substr (0, 21) != "urgency.user.project." && i->first.substr (0, 17) != "urgency.user.tag." && i->first.substr (0, 12) != "urgency.uda.") From c6b0c0d92709740cc7f795ff7ec08dc5c00ba5df Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 12 Apr 2014 11:31:47 -0400 Subject: [PATCH 17/31] TW-1301 - TW-1301 Virtual tag +PENDING (thanks to Profpatsch). - Also added +COMPLETED and +DELETED. --- AUTHORS | 1 + ChangeLog | 5 +++-- NEWS | 2 +- doc/man/task.1.in | 3 +++ src/Task.cpp | 3 +++ 5 files changed, 11 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index 736d0a6fc..b13f74bad 100644 --- a/AUTHORS +++ b/AUTHORS @@ -205,3 +205,4 @@ suggestions: jck Michele Vetturi Jeremiah Marks + Profpatsch diff --git a/ChangeLog b/ChangeLog index 08f56d116..c66a1f8bb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,14 +9,13 @@ Features - TW-255 'Mask' instead of 'iMask' shown in info report (thanks to Benjamin Weber) - TW-261 Easy to create "not deletable" task (thanks to Jan Kunder). -- TW-278 Cygwin throws warnings building mk_wcwidth() in wcwidth6.c. - TW-1255 New testing framework (thanks to Renato Alves). - TW-1258 Portuguese Localization (thanks to Renato Alves). - TW-1260 New virtual tags YESTERDAY, TOMORROW. - TW-1261 Migrate test bug.360.t to new unit testing framework (thanks to Renato Alves). - TW-1274 Map 'modification' attribute to 'modified' (thanks to jck). -- TW-1302 CmdShow.cpp:244: bad length in substr ? (thanks to David Binderman). +- TW-1301 Virtual tag +PENDING (thanks to Profpatsch). - Removed deprecated 'echo.command' setting, in favor of the 'header' and 'affected' verbosity tokens. - Removed deprecated 'edit.verbose' setting, in favor of the 'edit' verbosity @@ -35,6 +34,7 @@ Bugs - TD-42 Cannot compile taskd - GNUTLS_VERSION undefined in diag.cpp (thanks to Michele Vetturi). - TD-45 Fix preprocessor define (thanks to Jochen Sprickerhof). +- TW-278 Cygwin throws warnings building mk_wcwidth() in wcwidth6.c. - TW-1254 Calc command can segfault on negative numbers (thanks to Renato Alves). - TW-1282 incorrect URLs in man task-sync (thanks to Jeremiah Marks). @@ -44,6 +44,7 @@ Bugs Wilk). - TW-1296 make test/run_all exit with non-zero code if a test fail (thanks to Jakub Wilk). +- TW-1302 CmdShow.cpp:244: bad length in substr ? (thanks to David Binderman). - #1511 sync init crashes if client certification file is empty or invalid (thanks to Marton Suranyi). - #1508 Show command highlight configuration (thanks to Nicolas Appriou). diff --git a/NEWS b/NEWS index 197b9f9d5..262440ccf 100644 --- a/NEWS +++ b/NEWS @@ -5,7 +5,7 @@ New Features in taskwarrior 2.4.0 - Removed deprecated commands 'push', 'pull' and 'merge'. - Portuguese (por-PRT) localization. - Better handling for deletion of recurring tasks. - - New virtual tags: YESTERDAY, TOMORROW, READY. + - New virtual tags: YESTERDAY, TOMORROW, READY, PENDING, COMPLETED, DELETED. New commands in taskwarrior 2.4.0 diff --git a/doc/man/task.1.in b/doc/man/task.1.in index c7840819d..42c26ea09 100644 --- a/doc/man/task.1.in +++ b/doc/man/task.1.in @@ -588,6 +588,9 @@ are: UNTIL Matches if the task expires WAITING Matches if the task is waiting ANNOTATED Matches if the task has annotations + PENDING Matches if the task has pending status + COMPLETED Matches if the task has completed status + DELETED Matches if the task has deleted status You can use +BLOCKED to filter blocked tasks, or -BLOCKED for unblocked tasks. Similarly, -BLOCKED is equivalent to +UNBLOCKED. diff --git a/src/Task.cpp b/src/Task.cpp index 27149e258..8bee7d291 100644 --- a/src/Task.cpp +++ b/src/Task.cpp @@ -1169,6 +1169,9 @@ bool Task::hasTag (const std::string& tag) const if (tag == "WAITING") return has ("wait"); if (tag == "ANNOTATED") return hasAnnotations (); if (tag == "PARENT") return has ("mask"); + if (tag == "PENDING") return get ("status") == "pending"; + if (tag == "COMPLETED") return get ("status") == "completed"; + if (tag == "DELETED") return get ("status") == "deleted"; // Concrete tags. std::vector tags; From 62eafb663924be7374908f106f3bc0b72343c100 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 12 Apr 2014 11:33:57 -0400 Subject: [PATCH 18/31] ChangeLog - No longer separating bugs from features. Too difficult to keep it all straight. Now the ChangeLog is a sorted list of issues resolved. --- ChangeLog | 47 ++++++++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/ChangeLog b/ChangeLog index c66a1f8bb..b73be5fbe 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,10 +1,23 @@ 2.4.0 () - -Features +- TD-42 Cannot compile taskd - GNUTLS_VERSION undefined in diag.cpp (thanks + to Michele Vetturi). +- TD-45 Fix preprocessor define (thanks to Jochen Sprickerhof). +- TW-278 Cygwin throws warnings building mk_wcwidth() in wcwidth6.c. +- TW-1254 Calc command can segfault on negative numbers (thanks to Renato + Alves). - #1255 l10n translation utility improvements (thanks to Renato Alves). +- #1473 Make TASK_RCDIR customizable (thanks to Elias Probst). +- #1486 Truncated sentence in task-sync(5) manpage (thanks to Jakub Wilk). +- #1487 `tasksh` segmentation fault (thanks to Hector Arciga). - #1492 task show to display default values when appropriate (thanks to Renato Alves). - #1501 info report streamlining - partially implemented. +- #1503 build failure with musl libc due to undefined GLOB_BRACE and GLOB_TILDE + (thanks to Natanael Copa). +- #1508 Show command highlight configuration (thanks to Nicolas Appriou). +- #1511 sync init crashes if client certification file is empty or invalid + (thanks to Marton Suranyi). - TW-197 New virtual tag READY. - TW-255 'Mask' instead of 'iMask' shown in info report (thanks to Benjamin Weber) @@ -15,7 +28,15 @@ Features - TW-1261 Migrate test bug.360.t to new unit testing framework (thanks to Renato Alves). - TW-1274 Map 'modification' attribute to 'modified' (thanks to jck). +- TW-1282 incorrect URLs in man task-sync (thanks to Jeremiah Marks). +- TW-1288 Added missing locking for task modifications (thanks to Kosta H, + Ralph Bean, Adam Coddington). +- TW-1295 test/time.t fails on the last day of the month (thanks to Jakub + Wilk). +- TW-1296 make test/run_all exit with non-zero code if a test fail (thanks to + Jakub Wilk). - TW-1301 Virtual tag +PENDING (thanks to Profpatsch). +- TW-1302 CmdShow.cpp:244: bad length in substr ? (thanks to David Binderman). - Removed deprecated 'echo.command' setting, in favor of the 'header' and 'affected' verbosity tokens. - Removed deprecated 'edit.verbose' setting, in favor of the 'edit' verbosity @@ -29,30 +50,6 @@ Features - Old-style color names including underscores are no longer supported. - Removed priority counts from the 'projects' report. - New themes: dark-default-16.theme, dark-gray-blue-256.theme - -Bugs -- TD-42 Cannot compile taskd - GNUTLS_VERSION undefined in diag.cpp (thanks - to Michele Vetturi). -- TD-45 Fix preprocessor define (thanks to Jochen Sprickerhof). -- TW-278 Cygwin throws warnings building mk_wcwidth() in wcwidth6.c. -- TW-1254 Calc command can segfault on negative numbers (thanks to Renato - Alves). -- TW-1282 incorrect URLs in man task-sync (thanks to Jeremiah Marks). -- TW-1288 Added missing locking for task modifications (thanks to Kosta H, - Ralph Bean, Adam Coddington). -- TW-1295 test/time.t fails on the last day of the month (thanks to Jakub - Wilk). -- TW-1296 make test/run_all exit with non-zero code if a test fail (thanks to - Jakub Wilk). -- TW-1302 CmdShow.cpp:244: bad length in substr ? (thanks to David Binderman). -- #1511 sync init crashes if client certification file is empty or invalid - (thanks to Marton Suranyi). -- #1508 Show command highlight configuration (thanks to Nicolas Appriou). -- #1503 build failure with musl libc due to undefined GLOB_BRACE and GLOB_TILDE - (thanks to Natanael Copa). -- #1473 Make TASK_RCDIR customizable (thanks to Elias Probst). -- #1486 Truncated sentence in task-sync(5) manpage (thanks to Jakub Wilk). -- #1487 `tasksh` segmentation fault (thanks to Hector Arciga). - Added certificate verification to GnuTLS versions < 2.9.10 (thanks to Alexander Sulfrian). - Added certificate hostname verification (thanks to Alexander Sulfrian). From 05c90b17793f299ba9dfafe720f7e00fc6d3e1fd Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 12 Apr 2014 12:28:48 -0400 Subject: [PATCH 19/31] Bug - Fixed bug in named entity lookup. - Removed obsolete failing test. --- src/Eval.cpp | 21 ++++++++++++++++++++- test/eval.t.cpp | 28 ++++++++++++++++++++-------- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/Eval.cpp b/src/Eval.cpp index bc0b8ef3e..5855fd6b4 100644 --- a/src/Eval.cpp +++ b/src/Eval.cpp @@ -28,6 +28,7 @@ #include #include +//////////////////////////////////////////////////////////////////////////////// // Supported operators, borrowed from C++, particularly the precedence. // Note: table is sorted by length of operator string, so searches match // longest first. @@ -78,11 +79,25 @@ static struct #define NUM_OPERATORS (sizeof (operators) / sizeof (operators[0])) +//////////////////////////////////////////////////////////////////////////////// +// Built-in support for some named constants. +static bool namedConstants (const std::string& name, Variant& value) +{ + if (name == "true") value = Variant (true); + else if (name == "false") value = Variant (false); + else if (name == "pi") value = Variant (3.14159165); + else + return false; + + return true; +} + //////////////////////////////////////////////////////////////////////////////// Eval::Eval () : _ambiguity (true) , _debug (false) { + addSource (namedConstants); } //////////////////////////////////////////////////////////////////////////////// @@ -233,6 +248,8 @@ void Eval::evaluatePostfixStack ( values.push_back (left); } + + // Literals and identifiers. else { Variant v (token->first); @@ -253,6 +270,7 @@ void Eval::evaluatePostfixStack ( case Lexer::typeIdentifier: { + bool found = false; std::vector ::const_iterator source; for (source = _sources.begin (); source != _sources.end (); ++source) { @@ -260,12 +278,13 @@ void Eval::evaluatePostfixStack ( { if (_debug) std::cout << "# [" << values.size () << "] eval source '" << token->first << "' --> '" << (std::string) v << "'\n"; + found = true; break; } } // An identifier that fails lookup is a string. - if (source == _sources.end ()) + if (!found) { v.cast (Variant::type_string); if (_debug) diff --git a/test/eval.t.cpp b/test/eval.t.cpp index 0cf7a74c7..e0fa43849 100644 --- a/test/eval.t.cpp +++ b/test/eval.t.cpp @@ -35,16 +35,18 @@ Context context; // A few hard-coded symbols. bool get (const std::string& name, Variant& value) { - if (name == "pi") {value = Variant (3.14159165); return true;} - else if (name == "x") {value = Variant (true); return true;} + if (name == "x") + value = Variant (true); + else + return false; - return false; + return true; } //////////////////////////////////////////////////////////////////////////////// int main (int argc, char** argv) { - UnitTest t (43); + UnitTest t (46); // Test the source independently. Variant v; @@ -54,10 +56,6 @@ int main (int argc, char** argv) t.is (v.type (), Variant::type_boolean, "get(x) --> boolean"); t.is (v.get_bool (), true, "get(x) --> true"); - t.ok (get ("pi", v), "true <-- get(pi)"); - t.is (v.type (), Variant::type_real, "get(pi) --> real"); - t.is (v.get_real (), 3.141592, 0.00001, "get(pi) --> 3.14159265"); - Eval e; e.addSource (get); Variant result; @@ -138,6 +136,20 @@ int main (int argc, char** argv) t.is (result.type (), Variant::type_integer, "infix '2*3+1' --> integer"); t.is (result.get_integer (), 7, "infix '2*3+1' --> 7"); + // TW-1254 - Unary minus support. + e.evaluateInfixExpression ("2--3", result); + t.is (result.type (), Variant::type_integer, "infix '2--3' --> integer"); + t.is (result.get_integer (), 5, "infix '2--3' --> 5"); + + //e.debug (); + e.evaluateInfixExpression ("!false", result); + t.is (result.type (), Variant::type_boolean, "infix '!false' --> boolean"); + t.is (result.get_bool (), true, "infix '!false' --> true"); + + e.evaluateInfixExpression ("!true", result); + t.is (result.type (), Variant::type_boolean, "infix '!true' --> boolean"); + t.is (result.get_bool (), false, "infix '!true' --> false"); + return 0; } From 32272addd063c87450b445e043ebe176d7ed635c Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 12 Apr 2014 12:29:06 -0400 Subject: [PATCH 20/31] Code Cleanup - Simplified leap year code. - Began documenting date constants (eom, eow ...). - Implemented 'soq', 'eoq'. - Stubbed all the missing named dates. - Removed unrelated constants. --- src/Dates.cpp | 205 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 186 insertions(+), 19 deletions(-) diff --git a/src/Dates.cpp b/src/Dates.cpp index 1343f49ed..6b507bc6d 100644 --- a/src/Dates.cpp +++ b/src/Dates.cpp @@ -78,15 +78,8 @@ static bool isDay (const std::string& name, int& i) //////////////////////////////////////////////////////////////////////////////// static bool leapYear (int year) { - bool ly = false; - - // (year % 4 == 0) && (year % 100 !=0) OR - // (year % 400 == 0) - // are leapyears - - if (((!(year % 4)) && (year % 100)) || (!(year % 400))) ly = true; - - return ly; + return ((!(year % 4)) && (year % 100)) || + (!(year % 400)); } //////////////////////////////////////////////////////////////////////////////// @@ -99,6 +92,39 @@ static int daysInMonth (int year, int month) } //////////////////////////////////////////////////////////////////////////////// +// now = current date/time. +// today = previous midnight. +// sod = previous midnight. +// yesterday = 2nd previous midnight. +// tomorrow = next midnight. +// eod = next midnight. +// = midnight at start of next . +// = midnight on the 1st of . +// soy = midnight on January 1st, . +// eoy = midnight on December 31st, . +// socm = midnight on the 1st of current month. +// som = midnight on the 1st of next month. +// eom = midnight on the 1st of the next month. +// eocm = midnight on the 1st of the next month. +// sow = +// eow = +// eocw = +// socw = +// soww = +// eoww = +// soq = +// eoq = +// later = midnight, Jan 18th, 2038. +// someday = midnight, Jan 18th, 2038. +// easter = +// eastermonday = +// ascension = +// pentecost = +// goodfriday = +// midsommar = +// midsommarafton = +// Nth = + bool namedDates (const std::string& name, Variant& value) { time_t now = time (NULL); @@ -209,6 +235,69 @@ bool namedDates (const std::string& name, Variant& value) value = Variant (mktime (t), Variant::type_date); } + else if (name == "sow") + { +/* + Date sow (_t); + sow -= (dayOfWeek () * 86400); + return Date (sow.month (), sow.day (), sow.year (), 0, 0, 0); +*/ + } + + else if (name == "eow" || name == "eocw") + { +/* + if (found == "eow" || found == "eoww") + dow = 5; +*/ + } + + else if (name == "socw") + { +/* + Date sow (_t); + sow -= (dayOfWeek () * 86400); + return Date (sow.month (), sow.day (), sow.year (), 0, 0, 0); +*/ + } + + else if (name == "soww") + { +/* + Date sow (_t); + sow -= (dayOfWeek () * 86400); + return Date (sow.month (), sow.day (), sow.year (), 0, 0, 0); +*/ + } + + else if (name == "eoww") + { +/* + if (found == "eow" || found == "eoww") + dow = 5; +*/ + } + + else if (name == "soq" || name == "eoq") + { + struct tm* t = localtime (&now); + t->tm_hour = t->tm_min = t->tm_sec = 0; + + t->tm_mon += 3 - (t->tm_mon % 3); + if (t->tm_mon > 11) + { + t->tm_mon -= 12; + ++t->tm_year; + } + + // TODO eoq: should be 24:00:00 + // t->tm_mday = daysInMonth (t->tm_year + 1900, t->tm_mon + 1); + + t->tm_mday = 1; + + value = Variant (mktime (t), Variant::type_date); + } + else if (name == "later" || name == "someday") { struct tm* t = localtime (&now); @@ -256,19 +345,97 @@ bool namedDates (const std::string& name, Variant& value) value = Variant (mktime (t), Variant::type_date); } - // TODO + else if (name == "midsommar") + { /* - {s,e}o{w,q,ww,cw} - - midsommar - midsommarafton - 23rd + for (int midsommar = 20; midsommar <= 26; midsommar++) + { + Date then (6, midsommar, today.year ()); + if (6 == then.dayOfWeek ()) + { + _t = then._t; + return true; + } + } */ + } + + else if (name == "midsommarafton") + { +/* + for (int midsommar = 19; midsommar <= 25; midsommar++) + { + Date then (6, midsommar, today.year ()); + if (5 == then.dayOfWeek ()) + { + _t = then._t; + return true; + } + } +*/ + } + + // 1st + // 2nd + // 3rd + else if (name == "????") + { +/* + int number; + std::string ordinal; + + if (isdigit (in[1])) + { + number = atoi (in.substr (0, 2).c_str ()); + ordinal = lowerCase (in.substr (2)); + } + else + { + number = atoi (in.substr (0, 2).c_str ()); + ordinal = lowerCase (in.substr (1)); + } + + // Sanity check. + if (number <= 31) + { + if (ordinal == "st" || + ordinal == "nd" || + ordinal == "rd" || + ordinal == "th") + { + int m = today.month (); + int d = today.day (); + int y = today.year (); + + // If it is this month. + if (d < number && + number <= Date::daysInMonth (m, y)) + { + Date then (m, number, y); + _t = then._t; + return true; + } + + do + { + m++; + + if (m > 12) + { + m = 1; + y++; + } + } + while (number > Date::daysInMonth (m, y)); + + Date then (m, number, y); + _t = then._t; + return true; + } + } +*/ + } - // Constants. - else if (name == "pi") { value = Variant (3.14159165); } - else if (name == "true") { value = Variant (true); } - else if (name == "false") { value = Variant (false); } else return false; From 501194abfa5db8e6e77f9cab9c2b94f062f1c87f Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 12 Apr 2014 13:32:35 -0400 Subject: [PATCH 21/31] TW-1300 - TW-1300 _get could use return codes (thanks to Scott Kostyshak). --- ChangeLog | 1 + NEWS | 1 + doc/man/task.1.in | 3 ++ src/DOM.cpp | 6 +-- src/commands/CmdGet.cpp | 10 ++++- test/feature.1300.t | 91 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 107 insertions(+), 5 deletions(-) create mode 100755 test/feature.1300.t diff --git a/ChangeLog b/ChangeLog index b73be5fbe..7bccf8541 100644 --- a/ChangeLog +++ b/ChangeLog @@ -35,6 +35,7 @@ Wilk). - TW-1296 make test/run_all exit with non-zero code if a test fail (thanks to Jakub Wilk). +- TW-1300 _get could use return codes (thanks to Scott Kostyshak). - TW-1301 Virtual tag +PENDING (thanks to Profpatsch). - TW-1302 CmdShow.cpp:244: bad length in substr ? (thanks to David Binderman). - Removed deprecated 'echo.command' setting, in favor of the 'header' and diff --git a/NEWS b/NEWS index 262440ccf..962b16a6a 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,7 @@ New Features in taskwarrior 2.4.0 - Portuguese (por-PRT) localization. - Better handling for deletion of recurring tasks. - New virtual tags: YESTERDAY, TOMORROW, READY, PENDING, COMPLETED, DELETED. + - The '_get' command properly uses exit codes. New commands in taskwarrior 2.4.0 diff --git a/doc/man/task.1.in b/doc/man/task.1.in index 42c26ea09..3113ee1b3 100644 --- a/doc/man/task.1.in +++ b/doc/man/task.1.in @@ -537,6 +537,9 @@ from tasks, or the system. Supported DOM references are: Note that the 'rc.' reference may need to be escaped using '--' to prevent the reference from being interpreted as an override. +Note that if the DOM reference is not valid, or the reference evaluates to a +missing value, the command exits with 1. + .SH ATTRIBUTES AND METADATA .TP diff --git a/src/DOM.cpp b/src/DOM.cpp index d98cb8463..2c7296df5 100644 --- a/src/DOM.cpp +++ b/src/DOM.cpp @@ -157,9 +157,9 @@ const std::string DOM::get (const std::string& name, const Task& task) std::string canonical; // - if (name == "id") return format (task.id); - else if (name == "urgency") return format (task.urgency_c ()); - else if (A3::is_attribute (name, canonical)) return task.get (canonical); + if (task.size () && name == "id") return format (task.id); + else if (task.size () && name == "urgency") return format (task.urgency_c ()); + else if (task.size () && A3::is_attribute (name, canonical)) return task.get (canonical); // . if (n.getInt (id)) diff --git a/src/commands/CmdGet.cpp b/src/commands/CmdGet.cpp index 5c5bb06ed..4b8e45512 100644 --- a/src/commands/CmdGet.cpp +++ b/src/commands/CmdGet.cpp @@ -52,17 +52,23 @@ int CmdGet::execute (std::string& output) if (words.size () == 0) throw std::string (STRING_CMD_GET_NO_DOM); + bool found = false; std::vector results; std::vector ::iterator word; for (word = words.begin (); word != words.end (); ++word) { Task t; - results.push_back (context.dom.get (*word, t)); + std::string result = context.dom.get (*word, t); + results.push_back (result); + + if (result != "" && + result != *word) + found = true; } join (output, " ", results); output += "\n"; - return 0; + return found ? 0 : 1; } //////////////////////////////////////////////////////////////////////////////// diff --git a/test/feature.1300.t b/test/feature.1300.t new file mode 100755 index 000000000..7a0e0f1d0 --- /dev/null +++ b/test/feature.1300.t @@ -0,0 +1,91 @@ +#!/usr/bin/env python2.7 +# -*- coding: utf-8 -*- +################################################################################ +## +## Copyright 2006 - 2014, Paul Beckingham, Federico Hernandez. +## +## Permission is hereby granted, free of charge, to any person obtaining a copy +## of this software and associated documentation files (the "Software"), to deal +## in the Software without restriction, including without limitation the rights +## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +## copies of the Software, and to permit persons to whom the Software is +## furnished to do so, subject to the following conditions: +## +## The above copyright notice and this permission notice shall be included +## in all copies or substantial portions of the Software. +## +## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +## OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +## THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +## SOFTWARE. +## +## http://www.opensource.org/licenses/mit-license.php +## +################################################################################ + +import sys +import os +import signal +from glob import glob +# Ensure python finds the local simpletap and basetest modules +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from basetest import BaseTestCase + + +class BaseTestBug1300(BaseTestCase): + @classmethod + def prepare(cls): + with open("bug.rc", 'w') as fh: + fh.write("data.location=.\n" + "confirmation=no\n") + + def tearDown(self): + """Needed after each test or setUp will cause duplicated data at start + of the next test. + """ + for file in glob("*.data"): + os.remove(file) + + @classmethod + def cleanup(cls): + os.remove("bug.rc") + + +class TestBug1300(BaseTestBug1300): + def test_dom_exit_status_good(self): + """If the DOM recognizes a reference, it should return '0' + """ + args = ["rc:bug.rc", "_get", "context.program"] + + self.run_command(args, 0) + + def test_dom_exit_status_bad(self): + """If the DOM does not recognize a reference, it should return '1' + """ + args = ["rc:bug.rc", "_get", "XYZ"] + + self.run_command(args, 1) + + def run_command(self, args, expected_status): + code, out, err = self.callTask(args) + + # We shouldn't get a segmentation fault + # (negative exit code == 128 - real_exit_code) + expected = -signal.SIGSEGV + self.assertNotEqual(expected, code, "Task segfaulted") + + # Instead we expect a clean exit + self.assertEqual(expected_status, code, + "Exit code was not ({0}), but ({0})".format(expected_status, code)) + + +if __name__ == "__main__": + from simpletap import TAPTestRunner + import unittest + unittest.main(testRunner=TAPTestRunner()) + +# vim: ai sts=4 et sw=4 From 1922c4f6a43dacc7c0bcbe82cdeb6b9944c028da Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 12 Apr 2014 14:01:48 -0400 Subject: [PATCH 22/31] TW-1257 - TW-1257 The 'Syncing with :' message ignores verbosity tokens. --- ChangeLog | 1 + doc/man/taskrc.5.in | 2 +- src/commands/CmdSync.cpp | 34 +++++++++++++++++++--------------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/ChangeLog b/ChangeLog index 7bccf8541..6f0ff1020 100644 --- a/ChangeLog +++ b/ChangeLog @@ -23,6 +23,7 @@ Weber) - TW-261 Easy to create "not deletable" task (thanks to Jan Kunder). - TW-1255 New testing framework (thanks to Renato Alves). +- TW-1257 The 'Syncing with :' message ignores verbosity tokens. - TW-1258 Portuguese Localization (thanks to Renato Alves). - TW-1260 New virtual tags YESTERDAY, TOMORROW. - TW-1261 Migrate test bug.360.t to new unit testing framework (thanks to diff --git a/doc/man/taskrc.5.in b/doc/man/taskrc.5.in index 44a3e138f..b57163a1e 100644 --- a/doc/man/taskrc.5.in +++ b/doc/man/taskrc.5.in @@ -253,7 +253,7 @@ control specific occasions when output is generated. This list may contain: edit Used the verbose template for the 'edit' command special Feedback when applying special tags project Feedback about project status changes - sync Feedback about the need for sync + sync Feedback about sync Note that the "on" setting is equivalent to all the tokens being specified, and the "nothing" setting is equivalent to none of the tokens being specified. diff --git a/src/commands/CmdSync.cpp b/src/commands/CmdSync.cpp index 618d2279c..3b042d369 100644 --- a/src/commands/CmdSync.cpp +++ b/src/commands/CmdSync.cpp @@ -149,8 +149,9 @@ int CmdSync::execute (std::string& output) request.setPayload (payload); - out << format (STRING_CMD_SYNC_PROGRESS, connection) - << "\n"; + if (context.verbose ("sync")) + out << format (STRING_CMD_SYNC_PROGRESS, connection) + << "\n"; // Ignore harmful signals. signal (SIGHUP, SIG_IGN); @@ -196,22 +197,24 @@ int CmdSync::execute (std::string& output) Task dummy; if (context.tdb2.get (uuid, dummy)) { - out << " " - << colorChanged.colorize ( - format (STRING_CMD_SYNC_MOD, - uuid, - from_server.get ("description"))) - << "\n"; + if (context.verbose ("sync")) + out << " " + << colorChanged.colorize ( + format (STRING_CMD_SYNC_MOD, + uuid, + from_server.get ("description"))) + << "\n"; context.tdb2.modify (from_server, false); } else { - out << " " - << colorAdded.colorize ( - format (STRING_CMD_SYNC_ADD, - uuid, - from_server.get ("description"))) - << "\n"; + if (context.verbose ("sync")) + out << " " + << colorAdded.colorize ( + format (STRING_CMD_SYNC_ADD, + uuid, + from_server.get ("description"))) + << "\n"; context.tdb2.add (from_server, false); } } @@ -297,7 +300,8 @@ int CmdSync::execute (std::string& output) status = 1; } - out << "\n"; + if (context.verbose ("sync")) + out << "\n"; output = out.str (); // Restore signal handling. From 5706cca20723b94c14e0396b647539f9b850fd5d Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 12 Apr 2014 14:12:59 -0400 Subject: [PATCH 23/31] TW-115 - TW-115 allow "0day" durations for UDAs. --- ChangeLog | 7 ++++--- src/Variant.cpp | 47 +++++++++++++++++++++++++++-------------------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/ChangeLog b/ChangeLog index 6f0ff1020..cececf08f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,9 +3,6 @@ - TD-42 Cannot compile taskd - GNUTLS_VERSION undefined in diag.cpp (thanks to Michele Vetturi). - TD-45 Fix preprocessor define (thanks to Jochen Sprickerhof). -- TW-278 Cygwin throws warnings building mk_wcwidth() in wcwidth6.c. -- TW-1254 Calc command can segfault on negative numbers (thanks to Renato - Alves). - #1255 l10n translation utility improvements (thanks to Renato Alves). - #1473 Make TASK_RCDIR customizable (thanks to Elias Probst). - #1486 Truncated sentence in task-sync(5) manpage (thanks to Jakub Wilk). @@ -18,10 +15,14 @@ - #1508 Show command highlight configuration (thanks to Nicolas Appriou). - #1511 sync init crashes if client certification file is empty or invalid (thanks to Marton Suranyi). +- TW-115 allow "0day" durations for UDAs. - TW-197 New virtual tag READY. - TW-255 'Mask' instead of 'iMask' shown in info report (thanks to Benjamin Weber) - TW-261 Easy to create "not deletable" task (thanks to Jan Kunder). +- TW-278 Cygwin throws warnings building mk_wcwidth() in wcwidth6.c. +- TW-1254 Calc command can segfault on negative numbers (thanks to Renato + Alves). - TW-1255 New testing framework (thanks to Renato Alves). - TW-1257 The 'Syncing with :' message ignores verbosity tokens. - TW-1258 Portuguese Localization (thanks to Renato Alves). diff --git a/src/Variant.cpp b/src/Variant.cpp index af9716069..63877ea46 100644 --- a/src/Variant.cpp +++ b/src/Variant.cpp @@ -1302,28 +1302,35 @@ Variant::operator std::string () const { time_t t = _duration; - int seconds = t % 60; t /= 60; - int minutes = t % 60; t /= 60; - int hours = t % 24; t /= 24; - int days = t % 30; t /= 30; - int months = t % 12; t /= 12; - int years = t; - - std::stringstream s; - s << 'P'; - if (years) s << years << 'Y'; - if (months) s << months << 'M'; - if (days) s << days << 'D'; - - if (hours || minutes || seconds) + if (t) { - s << 'T'; - if (hours) s << hours << 'H'; - if (minutes) s << minutes << 'M'; - if (seconds) s << seconds << 'S'; - } + int seconds = t % 60; t /= 60; + int minutes = t % 60; t /= 60; + int hours = t % 24; t /= 24; + int days = t % 30; t /= 30; + int months = t % 12; t /= 12; + int years = t; - return s.str (); + std::stringstream s; + s << 'P'; + if (years) s << years << 'Y'; + if (months) s << months << 'M'; + if (days) s << days << 'D'; + + if (hours || minutes || seconds) + { + s << 'T'; + if (hours) s << hours << 'H'; + if (minutes) s << minutes << 'M'; + if (seconds) s << seconds << 'S'; + } + + return s.str (); + } + else + { + return "P0S"; + } } case type_unknown: From c933ed2cf7392fbf38dc587dac66553a7619927f Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 12 Apr 2014 16:03:19 -0400 Subject: [PATCH 24/31] Code Cleanup - Migrated the recur.cpp getDueState() function into Task::getDateState(), which can assess any date attribute. - Improved implementation to distinguish between: - not due, or not due for rc.due days - due after today - due later today - due earlier today - due before today This greater precision should address some outstanding issues. --- src/Task.cpp | 46 ++++++++++++++++++++++-- src/Task.h | 5 +++ src/commands/CmdCalendar.cpp | 27 +++++++------- src/main.h | 2 -- src/recur.cpp | 69 ++---------------------------------- src/rules.cpp | 7 ++-- 6 files changed, 68 insertions(+), 88 deletions(-) diff --git a/src/Task.cpp b/src/Task.cpp index 8bee7d291..909538966 100644 --- a/src/Task.cpp +++ b/src/Task.cpp @@ -323,6 +323,40 @@ void Task::setStatus (Task::status status) recalc_urgency = true; } +//////////////////////////////////////////////////////////////////////////////// +// Determines status of a date attribute. +Task::dateState Task::getDateState (const std::string& name) const +{ + std::string value = get (name); + if (value.length ()) + { + Date reference (value); + Date now; + Date today ("today"); + + if (reference < today) + return dateBeforeToday; + + if (reference.sameDay (now)) + { + if (reference < now) + return dateEarlierToday; + else + return dateLaterToday; + } + + int imminentperiod = context.config.getInteger ("due"); + if (imminentperiod == 0) + return dateAfterToday; + + Date imminentDay = today + imminentperiod * 86400; + if (reference < imminentDay) + return dateAfterToday; + } + + return dateNotDue; +} + #ifdef PRODUCT_TASKWARRIOR //////////////////////////////////////////////////////////////////////////////// // Ready means pending, not blocked and either not scheduled or scheduled before @@ -345,7 +379,9 @@ bool Task::is_due () const if (status != Task::completed && status != Task::deleted) { - if (getDueState (get ("due")) == 1) + Task::dateState state = getDateState ("due"); + if (state == dateAfterToday || + state == dateLaterToday) return true; } } @@ -381,7 +417,9 @@ bool Task::is_duetoday () const if (status != Task::completed && status != Task::deleted) { - if (getDueState (get ("due")) == 2) + Task::dateState state = getDateState ("due"); + if (state == dateEarlierToday || + state == dateLaterToday) return true; } } @@ -479,7 +517,9 @@ bool Task::is_overdue () const if (status != Task::completed && status != Task::deleted) { - if (getDueState (get ("due")) == 3) + Task::dateState state = getDateState ("due"); + if (state == dateEarlierToday || + state == dateBeforeToday) return true; } } diff --git a/src/Task.h b/src/Task.h index 74372968a..ed1c024b9 100644 --- a/src/Task.h +++ b/src/Task.h @@ -72,6 +72,9 @@ public: // Status values. enum status {pending, completed, deleted, recurring, waiting}; + // Date state values. + enum dateState {dateNotDue, dateAfterToday, dateLaterToday, dateEarlierToday, dateBeforeToday}; + // Public data. int id; float urgency_value; @@ -117,6 +120,8 @@ public: status getStatus () const; void setStatus (status); + dateState getDateState (const std::string&) const; + int getTagCount () const; bool hasTag (const std::string&) const; void addTag (const std::string&); diff --git a/src/commands/CmdCalendar.cpp b/src/commands/CmdCalendar.cpp index 7e903b97b..484710b73 100644 --- a/src/commands/CmdCalendar.cpp +++ b/src/commands/CmdCalendar.cpp @@ -561,23 +561,24 @@ std::string CmdCalendar::renderMonths ( duedmy.month () == months[mpl] && duedmy.year () == years[mpl]) { - switch (getDueState (due)) + switch (task->getDateState ("due")) { - case 1: // imminent - cellColor.blend (color_due); - break; + case Task::dateNotDue: + break; - case 2: // today - cellColor.blend (color_duetoday); - break; + case Task::dateAfterToday: + cellColor.blend (color_due); + break; - case 3: // overdue - cellColor.blend (color_overdue); - break; + case Task::dateEarlierToday: + case Task::dateLaterToday: + cellColor.blend (color_duetoday); + cellColor.blend (color_duetoday); + break; - case 0: // not due at all - default: - break; + case Task::dateBeforeToday: + cellColor.blend (color_overdue); + break; } } } diff --git a/src/main.h b/src/main.h index 9f9389242..df6990882 100644 --- a/src/main.h +++ b/src/main.h @@ -43,8 +43,6 @@ void handleRecurrence (); Date getNextRecurrence (Date&, std::string&); bool generateDueDates (Task&, std::vector &); void updateRecurrenceMask (Task&); -int getDueState (const std::string&); -int getDueState (const Date&); bool nag (Task&); // rules.cpp diff --git a/src/recur.cpp b/src/recur.cpp index 5c6b55d1c..b5e69523c 100644 --- a/src/recur.cpp +++ b/src/recur.cpp @@ -382,71 +382,6 @@ void updateRecurrenceMask (Task& task) } } -//////////////////////////////////////////////////////////////////////////////// -// Determines whether a task is overdue. Returns -// 0 = not due at all -// 1 = imminent -// 2 = today -// 3 = overdue -int getDueState (const std::string& due) -{ - if (due.length ()) - { - Date dt (::atoi (due.c_str ())); - - // rightNow is the current date + time. - static Date rightNow; - Date thisDay (rightNow.month (), rightNow.day (), rightNow.year ()); - - if (dt < rightNow) - return 3; - - if (rightNow.sameDay (dt)) - return 2; - - int imminentperiod = context.config.getInteger ("due"); - - if (imminentperiod == 0) - return 1; - - Date imminentDay = thisDay + imminentperiod * 86400; - if (dt < imminentDay) - return 1; - } - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// -// Determines whether a task is overdue. Returns -// 0 = not due at all -// 1 = imminent -// 2 = today -// 3 = overdue -int getDueState (const Date& due) -{ - // rightNow is the current date + time. - static Date rightNow; - Date thisDay (rightNow.month (), rightNow.day (), rightNow.year ()); - - if (due < rightNow) - return 3; - - if (rightNow.sameDay (due)) - return 2; - - int imminentperiod = context.config.getInteger ("due"); - - if (imminentperiod == 0) - return 1; - - Date imminentDay = thisDay + imminentperiod * 86400; - if (due < imminentDay) - return 1; - - return 0; -} - //////////////////////////////////////////////////////////////////////////////// // Returns a Boolean indicator as to whether a nag message was generated, so // that commands can control the number of nag messages displayed (ie one is @@ -477,7 +412,7 @@ bool nag (Task& task) { if (t->id == task.id) { - if (getDueState (t->get ("due")) == 3) + if (t->getDateState ("due") == Task::dateBeforeToday) isOverdue = true; std::string priority = t->get ("priority"); @@ -486,7 +421,7 @@ bool nag (Task& task) } else if (t->getStatus () == Task::pending) { - if (getDueState (t->get ("due")) == 3) + if (t->getDateState ("due") == Task::dateBeforeToday) overdue++; std::string priority = t->get ("priority"); diff --git a/src/rules.cpp b/src/rules.cpp index 88f5f11b4..3d7652a70 100644 --- a/src/rules.cpp +++ b/src/rules.cpp @@ -222,7 +222,7 @@ static void colorizeDue (Task& task, const Color& base, Color& c) Task::status status = task.getStatus (); if (status != Task::completed && status != Task::deleted && - getDueState (task.get ("due")) == 1) + task.getDateState ("due") == Task::dateAfterToday) c.blend (base); } } @@ -233,9 +233,10 @@ static void colorizeDueToday (Task& task, const Color& base, Color& c) if (task.has ("due")) { Task::status status = task.getStatus (); + Task::dateState dateState = task.getDateState ("due"); if (status != Task::completed && status != Task::deleted && - getDueState (task.get ("due")) == 2) + (dateState == Task::dateLaterToday || dateState == Task::dateEarlierToday)) c.blend (base); } } @@ -248,7 +249,7 @@ static void colorizeOverdue (Task& task, const Color& base, Color& c) Task::status status = task.getStatus (); if (status != Task::completed && status != Task::deleted && - getDueState (task.get ("due")) == 3) + task.getDateState ("due") == Task::dateBeforeToday) c.blend (base); } } From adde9eec2abaed01911a0853d93f4885083a04b4 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 12 Apr 2014 16:07:32 -0400 Subject: [PATCH 25/31] Unit Tests - Renaming new tests from bug.*.t and feature.*.t to tw-*.t. It is not important whether the test covers a bug or a feature. --- test/{feature.1300.t => tw-1300.t} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{feature.1300.t => tw-1300.t} (100%) diff --git a/test/feature.1300.t b/test/tw-1300.t similarity index 100% rename from test/feature.1300.t rename to test/tw-1300.t From 88346f8ed3509ca3c25fee8891fe5db7d6d8f5d0 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 12 Apr 2014 18:23:48 -0400 Subject: [PATCH 26/31] Unit Tests - Cleaned up unit tests. --- test/tw-1300.t | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/test/tw-1300.t b/test/tw-1300.t index 7a0e0f1d0..2fa288243 100755 --- a/test/tw-1300.t +++ b/test/tw-1300.t @@ -59,28 +59,12 @@ class TestBug1300(BaseTestBug1300): def test_dom_exit_status_good(self): """If the DOM recognizes a reference, it should return '0' """ - args = ["rc:bug.rc", "_get", "context.program"] - - self.run_command(args, 0) + self.callTaskSuccess(["rc:bug.rc", "_get", "context.program"]) def test_dom_exit_status_bad(self): """If the DOM does not recognize a reference, it should return '1' """ - args = ["rc:bug.rc", "_get", "XYZ"] - - self.run_command(args, 1) - - def run_command(self, args, expected_status): - code, out, err = self.callTask(args) - - # We shouldn't get a segmentation fault - # (negative exit code == 128 - real_exit_code) - expected = -signal.SIGSEGV - self.assertNotEqual(expected, code, "Task segfaulted") - - # Instead we expect a clean exit - self.assertEqual(expected_status, code, - "Exit code was not ({0}), but ({0})".format(expected_status, code)) + self.callTaskError(["rc:bug.rc", "_get", "XYZ"]) if __name__ == "__main__": From 3038b11780578d6d68edaa640ec830b340c63eec Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 13 Apr 2014 09:18:58 -0400 Subject: [PATCH 27/31] TW-285 - TW-285 DUETODAY doesn't give any output (thanks to Jostein Berntsen). - Added unit tests. --- ChangeLog | 1 + src/Task.cpp | 14 +++++- test/tw-285.t | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 1 deletion(-) create mode 100755 test/tw-285.t diff --git a/ChangeLog b/ChangeLog index cececf08f..a1c00144c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -21,6 +21,7 @@ Weber) - TW-261 Easy to create "not deletable" task (thanks to Jan Kunder). - TW-278 Cygwin throws warnings building mk_wcwidth() in wcwidth6.c. +- TW-285 DUETODAY doesn't give any output (thanks to Jostein Berntsen). - TW-1254 Calc command can segfault on negative numbers (thanks to Renato Alves). - TW-1255 New testing framework (thanks to Renato Alves). diff --git a/src/Task.cpp b/src/Task.cpp index 909538966..ccf786aa5 100644 --- a/src/Task.cpp +++ b/src/Task.cpp @@ -24,6 +24,7 @@ // //////////////////////////////////////////////////////////////////////////////// +#include // TODO Remove. #include #include #include @@ -380,7 +381,8 @@ bool Task::is_due () const status != Task::deleted) { Task::dateState state = getDateState ("due"); - if (state == dateAfterToday || + if (state == dateAfterToday || + state == dateEarlierToday || state == dateLaterToday) return true; } @@ -1184,6 +1186,16 @@ int Task::getTagCount () const } //////////////////////////////////////////////////////////////////////////////// +// +// OVERDUE YESTERDAY DUE TODAY TOMORROW WEEK MONTH YEAR +// due:-1week Y - - - - ? ? ? +// due:-1day Y Y - - - ? ? ? +// due:today Y - Y Y - ? ? ? +// due:tomorrow - - Y - Y ? ? ? +// due:3days - - Y - - ? ? ? +// due:1month - - - - - - - ? +// due:1year - - - - - - - - +// bool Task::hasTag (const std::string& tag) const { // Synthetic tags - dynamically generated, but do not occupy storage space. diff --git a/test/tw-285.t b/test/tw-285.t new file mode 100755 index 000000000..9ab25e772 --- /dev/null +++ b/test/tw-285.t @@ -0,0 +1,116 @@ +#!/usr/bin/env python2.7 +# -*- coding: utf-8 -*- +################################################################################ +## +## Copyright 2006 - 2014, Paul Beckingham, Federico Hernandez. +## +## Permission is hereby granted, free of charge, to any person obtaining a copy +## of this software and associated documentation files (the "Software"), to deal +## in the Software without restriction, including without limitation the rights +## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +## copies of the Software, and to permit persons to whom the Software is +## furnished to do so, subject to the following conditions: +## +## The above copyright notice and this permission notice shall be included +## in all copies or substantial portions of the Software. +## +## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +## OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +## THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +## SOFTWARE. +## +## http://www.opensource.org/licenses/mit-license.php +## +################################################################################ + +import sys +import os +import signal +from glob import glob +# Ensure python finds the local simpletap and basetest modules +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from basetest import BaseTestCase + + +class BaseTest285(BaseTestCase): + @classmethod + def prepare(cls): + with open("bug.rc", 'w') as fh: + fh.write("data.location=.\n" + "verbose=nothing\n" + "confirmation=no\n") + + def setUp(self): + """Executed before each test in the class""" + + # OVERDUE YESTERDAY DUE TODAY TOMORROW WEEK MONTH YEAR + # due:-1week Y - - - - ? ? ? + # due:-1day Y Y - - - ? ? ? + # due:today Y - Y Y - ? ? ? + # due:tomorrow - - Y - Y ? ? ? + # due:3days - - Y - - ? ? ? + # due:1month - - - - - - - ? + # due:1year - - - - - - - - + + self.callTaskSuccess(['rc:bug.rc', 'add', 'due_last_week', 'due:-1week']) + self.callTaskSuccess(['rc:bug.rc', 'add', 'due_yesterday', 'due:-1day']) + self.callTaskSuccess(['rc:bug.rc', 'add', 'due_earlier_today', 'due:today']) + self.callTaskSuccess(['rc:bug.rc', 'add', 'due_later_today', 'due:tomorrow']) + self.callTaskSuccess(['rc:bug.rc', 'add', 'due_three_days', 'due:3days']) + self.callTaskSuccess(['rc:bug.rc', 'add', 'due_next_month', 'due:1month']) + self.callTaskSuccess(['rc:bug.rc', 'add', 'due_next_year', 'due:1year']) + + def tearDown(self): + """Needed after each test or setUp will cause duplicated data at start + of the next test. + """ + for file in glob("*.data"): + os.remove(file) + + @classmethod + def cleanup(cls): + os.remove("bug.rc") + + +class Test285(BaseTest285): + def test_overdue(self): + """+OVERDUE""" + code, out, err = self.callTaskSuccess(["rc:bug.rc", "+OVERDUE", "count"]) + self.assertEqual(out, "3\n", "+OVERDUE == 3 tasks") + + def test_yesterday(self): + """+YESTERDAY""" + code, out, err = self.callTaskSuccess(["rc:bug.rc", "+YESTERDAY", "count"]) + self.assertEqual(out, "1\n", "+YESTERDAY == 1 task") + + def test_due(self): + """+DUE""" + code, out, err = self.callTaskSuccess(["rc:bug.rc", "+DUE", "count"]) + self.assertEqual(out, "3\n", "+DUE == 3 task") + + def test_today(self): + """+TODAY""" + code, out, err = self.callTaskSuccess(["rc:bug.rc", "+TODAY", "count"]) + self.assertEqual(out, "1\n", "+TODAY == 1 task") + + def test_duetoday(self): + """+DUETODAY""" + code, out, err = self.callTaskSuccess(["rc:bug.rc", "+DUETODAY", "count"]) + self.assertEqual(out, "1\n", "+DUETODAY == 1 task") + + def test_tomorrow(self): + """+TOMORROW""" + code, out, err = self.callTaskSuccess(["rc:bug.rc", "+TOMORROW", "count"]) + self.assertEqual(out, "1\n", "+TOMORROW == 1 task") + + +if __name__ == "__main__": + from simpletap import TAPTestRunner + import unittest + unittest.main(testRunner=TAPTestRunner()) + +# vim: ai sts=4 et sw=4 From cbb32b4747e0230f1013206309f3f5793deb57c5 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 13 Apr 2014 09:55:25 -0400 Subject: [PATCH 28/31] Documentation - Gradually replacing support email with answers.tasktools.org. --- DEVELOPER | 15 +++++++++++---- README.md | 6 +++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/DEVELOPER b/DEVELOPER index 2e98be83f..2b7a62a0d 100644 --- a/DEVELOPER +++ b/DEVELOPER @@ -13,6 +13,10 @@ General Statement suggestions, testing and discussions have taken place there. It is also the quickest way to get help, or confirm a bug. + - Join https://answers.tasktools.org and help us by asking, answering and + voting on questions and answers, directly helping those who ask, and + indirectly helping those who search. + - Review documentation: there are man pages, online articles, tutorials and so on, and these may contain errors, or they may not convey ideas in the best way. Perhaps you can help improve it. Contact us - documentation is @@ -68,6 +72,8 @@ Deprecated Code - Priorities in core. This will be migrated to become a UDA as soon as we have the right support in place for custom sorting. + - Attribute modifiers. + New Code Needs This is code that needs to be written, usually down at the C++ function/method level. @@ -95,7 +101,7 @@ New Code Needs outside the core code, we want to make it be that way. - Take a look at: - http://taskwarrior.org/versions/show/42 + https://bug.tasktools.org/browse/EX This 'extension' release is a collection of all the requested features that lie outside of the core product, and will be implemented as external scripts @@ -112,9 +118,9 @@ Documentation Needed - Tutorials - Cookbook - This is all in the new git://tasktools.git/docs.git repository, where all the - documents are sourced as markdown. We welcome everyone with writing skills to - help us improve this critical resource. + This is all in the new https://git.tasktools.org/projects/ST/repos/tw.org + repository, where all the documents are sourced as markdown. We welcome + everyone with writing skills to help us improve this critical resource. Unit Tests Needed There are always more unit tests needed. More specifically, better unit tests @@ -211,3 +217,4 @@ Current Codebase Condition 2012-05-12 Added general statement about how to contribute. 2013-09-09 Updated branch info. 2014-01-19 Updated for 2.4.0. +2014-04-13 Added answers.tasktools.org, corrected URLs. diff --git a/README.md b/README.md index 751e2fba3..ba5c5b2de 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,11 @@ Your contributions are especially welcome. Whether it comes in the form of code patches, ideas, discussion, bug reports, encouragement or criticism, your input is needed. -Please send your support questions and code patches to: +For support options, take a look at: + + http://taskwarrior.org/support + +Please send your code patches to: support@taskwarrior.org From ac45f263d8e701cd40effa5717c0109007665150 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 13 Apr 2014 10:26:43 -0400 Subject: [PATCH 29/31] Code Cleanup - Modified burndown code to conform to coding convention, and be more readable while researching TW-306. --- src/commands/CmdBurndown.cpp | 406 +++++++++++++++++------------------ 1 file changed, 203 insertions(+), 203 deletions(-) diff --git a/src/commands/CmdBurndown.cpp b/src/commands/CmdBurndown.cpp index d9f987426..9948dc2f4 100644 --- a/src/commands/CmdBurndown.cpp +++ b/src/commands/CmdBurndown.cpp @@ -41,7 +41,7 @@ extern Context context; // Helper macro. -#define LOC(y,x) (((y) * (width + 1)) + (x)) +#define LOC(y,x) (((y) * (_width + 1)) + (x)) //////////////////////////////////////////////////////////////////////////////// class Bar @@ -53,26 +53,26 @@ public: ~Bar (); public: - int offset; // from left of chart - std::string major_label; // x-axis label, major (year/-/month) - std::string minor_label; // x-axis label, minor (month/week/day) - int pending; // Number of pending tasks in period - int started; // Number of started tasks in period - int done; // Number of done tasks in period - int added; // Number added in period - int removed; // Number removed in period + int _offset; // from left of chart + std::string _major_label; // x-axis label, major (year/-/month) + std::string _minor_label; // x-axis label, minor (month/week/day) + int _pending; // Number of pending tasks in period + int _started; // Number of started tasks in period + int _done; // Number of done tasks in period + int _added; // Number added in period + int _removed; // Number removed in period }; //////////////////////////////////////////////////////////////////////////////// Bar::Bar () -: offset (0) -, major_label ("") -, minor_label ("") -, pending (0) -, started (0) -, done (0) -, added (0) -, removed (0) +: _offset (0) +, _major_label ("") +, _minor_label ("") +, _pending (0) +, _started (0) +, _done (0) +, _added (0) +, _removed (0) { } @@ -87,14 +87,14 @@ Bar& Bar::operator= (const Bar& other) { if (this != &other) { - offset = other.offset; - major_label = other.major_label; - minor_label = other.minor_label; - pending = other.pending; - started = other.started; - done = other.done; - added = other.added; - removed = other.removed; + _offset = other._offset; + _major_label = other._major_label; + _minor_label = other._minor_label; + _pending = other._pending; + _started = other._started; + _done = other._done; + _added = other._added; + _removed = other._removed; } return *this; @@ -159,25 +159,25 @@ private: void calculateRates (std::vector &); public: - int width; // Terminal width - int height; // Terminal height - int graph_width; // Width of plot area - int graph_height; // Height of plot area - int max_value; // Largest combined bar value - int max_label; // Longest y-axis label - std::vector labels; // Y-axis labels - int estimated_bars; // Estimated bar count - int actual_bars; // Calculated bar count - std::map bars; // Epoch-indexed set of bars - Date earliest; // Date of earliest estimated bar - int carryover_done; // Number of 'done' tasks prior to chart range - char period; // D, W, M - std::string title; // Additional description - std::string grid; // String representing grid of characters + int _width; // Terminal width + int _height; // Terminal height + int _graph_width; // Width of plot area + int _graph_height; // Height of plot area + int _max_value; // Largest combined bar value + int _max_label; // Longest y-axis label + std::vector _labels; // Y-axis labels + int _estimated_bars; // Estimated bar count + int _actual_bars; // Calculated bar count + std::map _bars; // Epoch-indexed set of bars + Date _earliest; // Date of earliest estimated bar + int _carryover_done; // Number of 'done' tasks prior to chart range + char _period; // D, W, M + std::string _title; // Additional description + std::string _grid; // String representing grid of characters - float find_rate; // Calculated find rate - float fix_rate; // Calculated fix rate - std::string completion; // Estimated completion date + float _find_rate; // Calculated find rate + float _fix_rate; // Calculated fix rate + std::string _completion; // Estimated completion date }; //////////////////////////////////////////////////////////////////////////////// @@ -185,27 +185,27 @@ Chart::Chart (char type) { // How much space is there to render in? This chart will occupy the // maximum space, and the width drives various other parameters. - width = context.getWidth (); - height = context.getHeight () - 1; // Allow for new line with prompt. - max_value = 0; - max_label = 1; - graph_height = height - 7; - graph_width = width - max_label - 14; + _width = context.getWidth (); + _height = context.getHeight () - 1; // Allow for new line with prompt. + _max_value = 0; + _max_label = 1; + _graph_height = _height - 7; + _graph_width = _width - _max_label - 14; // Estimate how many 'bars' can be dsplayed. This will help subset a // potentially enormous data set. - estimated_bars = (width - 1 - 14) / 3; + _estimated_bars = (_width - 1 - 14) / 3; - actual_bars = 0; - period = type; - carryover_done = 0; + _actual_bars = 0; + _period = type; + _carryover_done = 0; // Rates are calculated last. - find_rate = 0.0; - fix_rate = 0.0; + _find_rate = 0.0; + _fix_rate = 0.0; // Set the title. - title = "("; + _title = "("; std::vector ::const_iterator arg; for (arg = context.a3.begin (); arg != context.a3.end (); ++arg) { @@ -217,13 +217,13 @@ Chart::Chart (char type) ; else { - if (title.length () > 1) - title += " "; + if (_title.length () > 1) + _title += " "; - title += arg->_raw; + _title += arg->_raw; } } - title += ")"; + _title += ")"; } //////////////////////////////////////////////////////////////////////////////// @@ -247,8 +247,8 @@ void Chart::scan (std::vector & tasks) Date from = quantize (Date (task->get_date ("entry"))); epoch = from.toEpoch (); - if (bars.find (epoch) != bars.end ()) - ++bars[epoch].added; + if (_bars.find (epoch) != _bars.end ()) + ++_bars[epoch]._added; // e--> e--s--> // ppp> pppsss> @@ -262,14 +262,14 @@ void Chart::scan (std::vector & tasks) while (from < start) { epoch = from.toEpoch (); - if (bars.find (epoch) != bars.end ()) ++bars[epoch].pending; + if (_bars.find (epoch) != _bars.end ()) ++_bars[epoch]._pending; from = increment (from); } while (from < now) { epoch = from.toEpoch (); - if (bars.find (epoch) != bars.end ()) ++bars[epoch].started; + if (_bars.find (epoch) != _bars.end ()) ++_bars[epoch]._started; from = increment (from); } } @@ -278,7 +278,7 @@ void Chart::scan (std::vector & tasks) while (from < now) { epoch = from.toEpoch (); - if (bars.find (epoch) != bars.end ()) ++bars[epoch].pending; + if (_bars.find (epoch) != _bars.end ()) ++_bars[epoch]._pending; from = increment (from); } } @@ -292,14 +292,14 @@ void Chart::scan (std::vector & tasks) Date end = quantize (Date (task->get_date ("end"))); epoch = end.toEpoch (); - if (bars.find (epoch) != bars.end ()) - ++bars[epoch].removed; + if (_bars.find (epoch) != _bars.end ()) + ++_bars[epoch]._removed; // Maintain a running total of 'done' tasks that are off the left of the // chart. - if (end < earliest) + if (end < _earliest) { - ++carryover_done; + ++_carryover_done; continue; } @@ -309,21 +309,21 @@ void Chart::scan (std::vector & tasks) while (from < start) { epoch = from.toEpoch (); - if (bars.find (epoch) != bars.end ()) ++bars[epoch].pending; + if (_bars.find (epoch) != _bars.end ()) ++_bars[epoch]._pending; from = increment (from); } while (from < end) { epoch = from.toEpoch (); - if (bars.find (epoch) != bars.end ()) ++bars[epoch].started; + if (_bars.find (epoch) != _bars.end ()) ++_bars[epoch]._started; from = increment (from); } while (from < now) { epoch = from.toEpoch (); - if (bars.find (epoch) != bars.end ()) ++bars[epoch].done; + if (_bars.find (epoch) != _bars.end ()) ++_bars[epoch]._done; from = increment (from); } } @@ -333,14 +333,14 @@ void Chart::scan (std::vector & tasks) while (from < end) { epoch = from.toEpoch (); - if (bars.find (epoch) != bars.end ()) ++bars[epoch].pending; + if (_bars.find (epoch) != _bars.end ()) ++_bars[epoch]._pending; from = increment (from); } while (from < now) { epoch = from.toEpoch (); - if (bars.find (epoch) != bars.end ()) ++bars[epoch].done; + if (_bars.find (epoch) != _bars.end ()) ++_bars[epoch]._done; from = increment (from); } } @@ -353,10 +353,10 @@ void Chart::scan (std::vector & tasks) // Skip old deleted tasks. Date end = quantize (Date (task->get_date ("end"))); epoch = end.toEpoch (); - if (bars.find (epoch) != bars.end ()) - ++bars[epoch].removed; + if (_bars.find (epoch) != _bars.end ()) + ++_bars[epoch]._removed; - if (end < earliest) + if (end < _earliest) continue; if (task->has ("start")) @@ -365,14 +365,14 @@ void Chart::scan (std::vector & tasks) while (from < start) { epoch = from.toEpoch (); - if (bars.find (epoch) != bars.end ()) ++bars[epoch].pending; + if (_bars.find (epoch) != _bars.end ()) ++_bars[epoch]._pending; from = increment (from); } while (from < end) { epoch = from.toEpoch (); - if (bars.find (epoch) != bars.end ()) ++bars[epoch].started; + if (_bars.find (epoch) != _bars.end ()) ++_bars[epoch]._started; from = increment (from); } } @@ -382,7 +382,7 @@ void Chart::scan (std::vector & tasks) while (from < end) { epoch = from.toEpoch (); - if (bars.find (epoch) != bars.end ()) ++bars[epoch].pending; + if (_bars.find (epoch) != _bars.end ()) ++_bars[epoch]._pending; from = increment (from); } } @@ -414,23 +414,23 @@ void Chart::scan (std::vector & tasks) // +---------------------------------------------------------------------+ std::string Chart::render () { - if (graph_height < 5 || // a 4-line graph is essentially unreadable. - graph_width < 2) // A single-bar graph is useless. + if (_graph_height < 5 || // a 4-line graph is essentially unreadable. + _graph_width < 2) // A single-bar graph is useless. { return std::string (STRING_CMD_BURN_TOO_SMALL) + "\n"; } - if (max_value == 0) + if (_max_value == 0) context.footnote (STRING_FEEDBACK_NO_MATCH); // Create a grid, folded into a string. - grid = ""; - for (int i = 0; i < height; ++i) - grid += std::string (width, ' ') + "\n"; + _grid = ""; + for (int i = 0; i < _height; ++i) + _grid += std::string (_width, ' ') + "\n"; // Title. std::string full_title; - switch (period) + switch (_period) { case 'D': full_title = STRING_CMD_BURN_DAILY; break; case 'W': full_title = STRING_CMD_BURN_WEEKLY; break; @@ -439,118 +439,118 @@ std::string Chart::render () full_title += std::string (" ") + STRING_CMD_BURN_TITLE; - if (title.length ()) + if (_title.length ()) { - if (full_title.length () + 1 + title.length () < (unsigned) width) + if (full_title.length () + 1 + _title.length () < (unsigned) _width) { - full_title += " " + title; - grid.replace (LOC (0, (width - full_title.length ()) / 2), full_title.length (), full_title); + full_title += " " + _title; + _grid.replace (LOC (0, (_width - full_title.length ()) / 2), full_title.length (), full_title); } else { - grid.replace (LOC (0, (width - full_title.length ()) / 2), full_title.length (), full_title); - grid.replace (LOC (1, (width - title.length ()) / 2), title.length (), title); + _grid.replace (LOC (0, (_width - full_title.length ()) / 2), full_title.length (), full_title); + _grid.replace (LOC (1, (_width - _title.length ()) / 2), _title.length (), _title); } } else { - grid.replace (LOC (0, (width - full_title.length ()) / 2), full_title.length (), full_title); + _grid.replace (LOC (0, (_width - full_title.length ()) / 2), full_title.length (), full_title); } // Legend. - grid.replace (LOC (graph_height / 2 - 1, width - 10), 10, "DD " + leftJustify (STRING_CMD_BURN_DONE, 7)); - grid.replace (LOC (graph_height / 2, width - 10), 10, "SS " + leftJustify (STRING_CMD_BURN_STARTED, 7)); - grid.replace (LOC (graph_height / 2 + 1, width - 10), 10, "PP " + leftJustify (STRING_CMD_BURN_PENDING, 7)); + _grid.replace (LOC (_graph_height / 2 - 1, _width - 10), 10, "DD " + leftJustify (STRING_CMD_BURN_DONE, 7)); + _grid.replace (LOC (_graph_height / 2, _width - 10), 10, "SS " + leftJustify (STRING_CMD_BURN_STARTED, 7)); + _grid.replace (LOC (_graph_height / 2 + 1, _width - 10), 10, "PP " + leftJustify (STRING_CMD_BURN_PENDING, 7)); // Determine y-axis labelling. - std::vector labels; - yLabels (labels); - max_label = (int) log10 ((double) labels[2]) + 1; + std::vector _labels; + yLabels (_labels); + _max_label = (int) log10 ((double) _labels[2]) + 1; // Draw y-axis. - for (int i = 0; i < graph_height; ++i) - grid.replace (LOC (i + 1, max_label + 1), 1, "|"); + for (int i = 0; i < _graph_height; ++i) + _grid.replace (LOC (i + 1, _max_label + 1), 1, "|"); // Draw y-axis labels. char label [12]; - sprintf (label, "%*d", max_label, labels[2]); - grid.replace (LOC (1, max_label - strlen (label)), strlen (label), label); - sprintf (label, "%*d", max_label, labels[1]); - grid.replace (LOC (1 + (graph_height / 2), max_label - strlen (label)), strlen (label), label); - grid.replace (LOC (graph_height + 1, max_label - 1), 1, "0"); + sprintf (label, "%*d", _max_label, _labels[2]); + _grid.replace (LOC (1, _max_label - strlen (label)), strlen (label), label); + sprintf (label, "%*d", _max_label, _labels[1]); + _grid.replace (LOC (1 + (_graph_height / 2), _max_label - strlen (label)), strlen (label), label); + _grid.replace (LOC (_graph_height + 1, _max_label - 1), 1, "0"); // Draw x-axis. - grid.replace (LOC (height - 6, max_label + 1), 1, "+"); - grid.replace (LOC (height - 6, max_label + 2), graph_width, std::string (graph_width, '-')); + _grid.replace (LOC (_height - 6, _max_label + 1), 1, "+"); + _grid.replace (LOC (_height - 6, _max_label + 2), _graph_width, std::string (_graph_width, '-')); // Draw x-axis labels. std::vector bars_in_sequence; std::map ::iterator it; - for (it = bars.begin (); it != bars.end (); ++it) + for (it = _bars.begin (); it != _bars.end (); ++it) bars_in_sequence.push_back (it->first); std::sort (bars_in_sequence.begin (), bars_in_sequence.end ()); std::vector ::iterator seq; - std::string major_label; + std::string _major_label; for (seq = bars_in_sequence.begin (); seq != bars_in_sequence.end (); ++seq) { - Bar bar = bars[*seq]; + Bar bar = _bars[*seq]; // If it fits within the allowed space. - if (bar.offset < actual_bars) + if (bar._offset < _actual_bars) { - grid.replace (LOC (height - 5, max_label + 3 + ((actual_bars - bar.offset - 1) * 3)), bar.minor_label.length (), bar.minor_label); + _grid.replace (LOC (_height - 5, _max_label + 3 + ((_actual_bars - bar._offset - 1) * 3)), bar._minor_label.length (), bar._minor_label); - if (major_label != bar.major_label) - grid.replace (LOC (height - 4, max_label + 2 + ((actual_bars - bar.offset - 1) * 3)), bar.major_label.length (), " " + bar.major_label); + if (_major_label != bar._major_label) + _grid.replace (LOC (_height - 4, _max_label + 2 + ((_actual_bars - bar._offset - 1) * 3)), bar._major_label.length (), " " + bar._major_label); - major_label = bar.major_label; + _major_label = bar._major_label; } } // Draw bars. for (seq = bars_in_sequence.begin (); seq != bars_in_sequence.end (); ++seq) { - Bar bar = bars[*seq]; + Bar bar = _bars[*seq]; // If it fits within the allowed space. - if (bar.offset < actual_bars) + if (bar._offset < _actual_bars) { - int pending = ( bar.pending * graph_height) / labels[2]; - int started = ((bar.pending + bar.started) * graph_height) / labels[2]; - int done = ((bar.pending + bar.started + bar.done + carryover_done) * graph_height) / labels[2]; + int pending = ( bar._pending * _graph_height) / _labels[2]; + int started = ((bar._pending + bar._started) * _graph_height) / _labels[2]; + int done = ((bar._pending + bar._started + bar._done + _carryover_done) * _graph_height) / _labels[2]; for (int b = 0; b < pending; ++b) - grid.replace (LOC (graph_height - b, max_label + 3 + ((actual_bars - bar.offset - 1) * 3)), 2, "PP"); + _grid.replace (LOC (_graph_height - b, _max_label + 3 + ((_actual_bars - bar._offset - 1) * 3)), 2, "PP"); for (int b = pending; b < started; ++b) - grid.replace (LOC (graph_height - b, max_label + 3 + ((actual_bars - bar.offset - 1) * 3)), 2, "SS"); + _grid.replace (LOC (_graph_height - b, _max_label + 3 + ((_actual_bars - bar._offset - 1) * 3)), 2, "SS"); for (int b = started; b < done; ++b) - grid.replace (LOC (graph_height - b, max_label + 3 + ((actual_bars - bar.offset - 1) * 3)), 2, "DD"); + _grid.replace (LOC (_graph_height - b, _max_label + 3 + ((_actual_bars - bar._offset - 1) * 3)), 2, "DD"); } } // Draw rates. calculateRates (bars_in_sequence); char rate[12]; - if (find_rate != 0.0) - sprintf (rate, "%.1f/d", find_rate); + if (_find_rate != 0.0) + sprintf (rate, "%.1f/d", _find_rate); else strcpy (rate, "-"); - grid.replace (LOC (height - 2, max_label + 3), 18 + strlen (rate), std::string ("Add rate: ") + rate); + _grid.replace (LOC (_height - 2, _max_label + 3), 18 + strlen (rate), std::string ("Add rate: ") + rate); - if (fix_rate != 0.0) - sprintf (rate, "%.1f/d", fix_rate); + if (_fix_rate != 0.0) + sprintf (rate, "%.1f/d", _fix_rate); else strcpy (rate, "-"); - grid.replace (LOC (height - 1, max_label + 3), 18 + strlen (rate), std::string ("Done/Delete rate: ") + rate); + _grid.replace (LOC (_height - 1, _max_label + 3), 18 + strlen (rate), std::string ("Done/Delete rate: ") + rate); // Draw completion date. - if (completion.length ()) - grid.replace (LOC (height - 2, max_label + 32), 22 + completion.length (), "Estimated completion: " + completion); + if (_completion.length ()) + _grid.replace (LOC (_height - 2, _max_label + 32), 22 + _completion.length (), "Estimated completion: " + _completion); optimizeGrid (); @@ -563,53 +563,53 @@ std::string Chart::render () // Replace DD, SS, PP with colored strings. std::string::size_type i; - while ((i = grid.find ("PP")) != std::string::npos) - grid.replace (i, 2, color_pending.colorize (" ")); + while ((i = _grid.find ("PP")) != std::string::npos) + _grid.replace (i, 2, color_pending.colorize (" ")); - while ((i = grid.find ("SS")) != std::string::npos) - grid.replace (i, 2, color_started.colorize (" ")); + while ((i = _grid.find ("SS")) != std::string::npos) + _grid.replace (i, 2, color_started.colorize (" ")); - while ((i = grid.find ("DD")) != std::string::npos) - grid.replace (i, 2, color_done.colorize (" ")); + while ((i = _grid.find ("DD")) != std::string::npos) + _grid.replace (i, 2, color_done.colorize (" ")); } else { // Replace DD, SS, PP with ./+/X strings. std::string::size_type i; - while ((i = grid.find ("PP")) != std::string::npos) - grid.replace (i, 2, " X"); + while ((i = _grid.find ("PP")) != std::string::npos) + _grid.replace (i, 2, " X"); - while ((i = grid.find ("SS")) != std::string::npos) - grid.replace (i, 2, " +"); + while ((i = _grid.find ("SS")) != std::string::npos) + _grid.replace (i, 2, " +"); - while ((i = grid.find ("DD")) != std::string::npos) - grid.replace (i, 2, " ."); + while ((i = _grid.find ("DD")) != std::string::npos) + _grid.replace (i, 2, " ."); } - return grid; + return _grid; } //////////////////////////////////////////////////////////////////////////////// -// grid =~ /\s+$//g +// _grid =~ /\s+$//g void Chart::optimizeGrid () { std::string::size_type ws; - while ((ws = grid.find (" \n")) != std::string::npos) + while ((ws = _grid.find (" \n")) != std::string::npos) { std::string::size_type non_ws = ws; - while (grid[non_ws] == ' ') + while (_grid[non_ws] == ' ') --non_ws; - grid.replace (non_ws + 1, ws - non_ws + 1, "\n"); + _grid.replace (non_ws + 1, ws - non_ws + 1, "\n"); } } //////////////////////////////////////////////////////////////////////////////// Date Chart::quantize (const Date& input) { - if (period == 'D') return input.startOfDay (); - if (period == 'W') return input.startOfWeek (); - if (period == 'M') return input.startOfMonth (); + if (_period == 'D') return input.startOfDay (); + if (_period == 'W') return input.startOfWeek (); + if (_period == 'M') return input.startOfMonth (); return input; } @@ -624,7 +624,7 @@ Date Chart::increment (const Date& input) int days; - switch (period) + switch (_period) { case 'D': if (++d > Date::daysInMonth (m, y)) @@ -675,7 +675,7 @@ Date Chart::decrement (const Date& input) int m = input.month (); int y = input.year (); - switch (period) + switch (_period) { case 'D': if (--d == 0) @@ -718,14 +718,14 @@ Date Chart::decrement (const Date& input) } //////////////////////////////////////////////////////////////////////////////// -// Do 'bars[epoch] = Bar' for every bar that may appear on a chart. +// Do '_bars[epoch] = Bar' for every bar that may appear on a chart. void Chart::generateBars () { Bar bar; // Determine the last bar date. Date cursor; - switch (period) + switch (_period) { case 'D': cursor = Date ().startOfDay (); break; case 'W': cursor = Date ().startOfWeek (); break; @@ -734,43 +734,43 @@ void Chart::generateBars () // Iterate and determine all the other bar dates. char str[12]; - for (int i = 0; i < estimated_bars; ++i) + for (int i = 0; i < _estimated_bars; ++i) { // Create the major and minor labels. - switch (period) + switch (_period) { case 'D': // month/day { std::string month = Date::monthName (cursor.month ()); - bar.major_label = month.substr (0, 3); + bar._major_label = month.substr (0, 3); sprintf (str, "%02d", cursor.day ()); - bar.minor_label = str; + bar._minor_label = str; } break; case 'W': // year/week sprintf (str, "%d", cursor.year ()); - bar.major_label = str; + bar._major_label = str; sprintf (str, "%02d", cursor.weekOfYear (0)); - bar.minor_label = str; + bar._minor_label = str; break; case 'M': // year/month sprintf (str, "%d", cursor.year ()); - bar.major_label = str; + bar._major_label = str; sprintf (str, "%02d", cursor.month ()); - bar.minor_label = str; + bar._minor_label = str; break; } - bar.offset = i; - bars[cursor.toEpoch ()] = bar; + bar._offset = i; + _bars[cursor.toEpoch ()] = bar; // Record the earliest date, for use as a cutoff when scanning data. - earliest = cursor; + _earliest = cursor; // Move to the previous period. cursor = decrement (cursor); @@ -780,39 +780,39 @@ void Chart::generateBars () //////////////////////////////////////////////////////////////////////////////// void Chart::maxima () { - max_value = 0; - max_label = 1; + _max_value = 0; + _max_label = 1; std::map ::iterator it; - for (it = bars.begin (); it != bars.end (); it++) + for (it = _bars.begin (); it != _bars.end (); it++) { - // Determine max_label. - int total = it->second.pending + - it->second.started + - it->second.done + - carryover_done; + // Determine _max_label. + int total = it->second._pending + + it->second._started + + it->second._done + + _carryover_done; - // Determine max_value. - if (total > max_value) - max_value = total; + // Determine _max_value. + if (total > _max_value) + _max_value = total; int length = (int) log10 ((double) total) + 1; - if (length > max_label) - max_label = length; + if (length > _max_label) + _max_label = length; } // How many bars can be shown? - actual_bars = (width - max_label - 14) / 3; - graph_width = width - max_label - 14; + _actual_bars = (_width - _max_label - 14) / 3; + _graph_width = _width - _max_label - 14; } //////////////////////////////////////////////////////////////////////////////// // Given the vertical chart area size (graph_height), the largest value -// (max_value), populate a vector of labels for the y axis. +// (_max_value), populate a vector of labels for the y axis. void Chart::yLabels (std::vector & labels) { // Calculate may Y using a nice algorithm that rounds the data. - int high = burndown_size (max_value); + int high = burndown_size (_max_value); int half = high / 2; labels.push_back (0); @@ -825,7 +825,7 @@ void Chart::calculateRates (std::vector & sequence) { // If there are no current pending tasks, then it is meaningless to find // rates or estimated completion date. - if (bars[sequence.back ()].pending == 0) + if (_bars[sequence.back ()]._pending == 0) return; // Calculate how many items we have. @@ -844,7 +844,7 @@ void Chart::calculateRates (std::vector & sequence) // How many days do these sums represent? int half_days = 1; int quarter_days = 1; - switch (period) + switch (_period) { case 'D': half_days = half; @@ -869,14 +869,14 @@ void Chart::calculateRates (std::vector & sequence) for (unsigned int i = half; i < sequence.size (); ++i) { - total_added_50 += bars[sequence[i]].added; - total_removed_50 += bars[sequence[i]].removed; + total_added_50 += _bars[sequence[i]]._added; + total_removed_50 += _bars[sequence[i]]._removed; } for (unsigned int i = half + quarter; i < sequence.size (); ++i) { - total_added_75 += bars[sequence[i]].added; - total_removed_75 += bars[sequence[i]].removed; + total_added_75 += _bars[sequence[i]]._added; + total_removed_75 += _bars[sequence[i]]._removed; } float find_rate_50 = 1.0 * total_added_50 / half_days; @@ -887,8 +887,8 @@ void Chart::calculateRates (std::vector & sequence) // Make configurable. float bias = (float) context.config.getReal ("burndown.bias"); - find_rate = (find_rate_50 * (1.0 - bias) + find_rate_75 * bias); - fix_rate = (fix_rate_50 * (1.0 - bias) + fix_rate_75 * bias); + _find_rate = (find_rate_50 * (1.0 - bias) + find_rate_75 * bias); + _fix_rate = (fix_rate_50 * (1.0 - bias) + fix_rate_75 * bias); // Q: Why is this equation written out as a debug message? // A: People are going to want to know how the rates and the completion date @@ -908,7 +908,7 @@ void Chart::calculateRates (std::vector & sequence) << " days) * " << bias << ") = " - << find_rate + << _find_rate << "\nChart::calculateRates fix rate: " << "(" << total_removed_50 @@ -923,20 +923,20 @@ void Chart::calculateRates (std::vector & sequence) << " days) * " << bias << ") = " - << fix_rate; + << _fix_rate; context.debug (rates.str ()); // Estimate completion - if (fix_rate > find_rate) + if (_fix_rate > _find_rate) { - int current_pending = bars[sequence.back ()].pending; - int remaining_days = (int) (current_pending / (fix_rate - find_rate)); + int current_pending = _bars[sequence.back ()]._pending; + int remaining_days = (int) (current_pending / (_fix_rate - _find_rate)); Date now; OldDuration delta (remaining_days * 86400); now += delta; - completion = now.toString (context.config.get ("dateformat")) + _completion = now.toString (context.config.get ("dateformat")) + " (" + delta.format () + ")"; @@ -945,18 +945,18 @@ void Chart::calculateRates (std::vector & sequence) est << "Chart::calculateRates Completion: " << current_pending << " tasks / (" - << fix_rate + << _fix_rate << " - " - << find_rate + << _find_rate << ") = " << remaining_days << " days = " - << completion; + << _completion; context.debug (est.str ()); } else { - completion = STRING_CMD_BURN_NO_CONVERGE; + _completion = STRING_CMD_BURN_NO_CONVERGE; } } From fe0d03664c9e5a19d97ecda4dc90eeefd6730417 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 13 Apr 2014 10:34:17 -0400 Subject: [PATCH 30/31] TW-306 - TW-306 Wrong date format in burndown view (thanks to Michele Santullo). --- ChangeLog | 1 + src/commands/CmdBurndown.cpp | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index a1c00144c..7b4b14513 100644 --- a/ChangeLog +++ b/ChangeLog @@ -22,6 +22,7 @@ - TW-261 Easy to create "not deletable" task (thanks to Jan Kunder). - TW-278 Cygwin throws warnings building mk_wcwidth() in wcwidth6.c. - TW-285 DUETODAY doesn't give any output (thanks to Jostein Berntsen). +- TW-306 Wrong date format in burndown view (thanks to Michele Santullo). - TW-1254 Calc command can segfault on negative numbers (thanks to Renato Alves). - TW-1255 New testing framework (thanks to Renato Alves). diff --git a/src/commands/CmdBurndown.cpp b/src/commands/CmdBurndown.cpp index 9948dc2f4..77c2ef058 100644 --- a/src/commands/CmdBurndown.cpp +++ b/src/commands/CmdBurndown.cpp @@ -936,7 +936,12 @@ void Chart::calculateRates (std::vector & sequence) OldDuration delta (remaining_days * 86400); now += delta; - _completion = now.toString (context.config.get ("dateformat")) + // Prefer dateformat.report over dateformat. + std::string format = context.config.get ("dateformat.report"); + if (format == "") + format = context.config.get ("dateformat"); + + _completion = now.toString (format) + " (" + delta.format () + ")"; From d8b7d914acca17f8b8850c057c29bb32b9703078 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 13 Apr 2014 12:00:17 -0400 Subject: [PATCH 31/31] TW-5 - TW-5 color.due.today does not work (thanks to Max Muller). - Inadvertently fixed while addressing TW-285. --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index 7b4b14513..2c2d2e805 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,6 +15,7 @@ - #1508 Show command highlight configuration (thanks to Nicolas Appriou). - #1511 sync init crashes if client certification file is empty or invalid (thanks to Marton Suranyi). +- TW-5 color.due.today does not work (thanks to Max Muller). - TW-115 allow "0day" durations for UDAs. - TW-197 New virtual tag READY. - TW-255 'Mask' instead of 'iMask' shown in info report (thanks to Benjamin