diff --git a/ChangeLog b/ChangeLog index c198abdfd..554a0d367 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,8 @@ Bugs .taskrc file (thanks to Pietro Cerutti). Features + + The 'projects' command now outputs abstract parents and reduces + repetition by not printing parent names in front of children names. + Added Feature #1069, which gives a clearer error when a UDA is added without the uda..type variable. + Added framework for testing bash autocompletion. diff --git a/src/commands/CmdProjects.cpp b/src/commands/CmdProjects.cpp index 9c1978c03..b2d9c5895 100644 --- a/src/commands/CmdProjects.cpp +++ b/src/commands/CmdProjects.cpp @@ -27,6 +27,7 @@ #define L10N // Localization complete. +#include #include #include #include @@ -119,9 +120,22 @@ int CmdProjects::execute (std::string& output) view.add (Column::factory ("string.right", STRING_CMD_PROJECTS_PRI_M)); view.add (Column::factory ("string.right", STRING_CMD_PROJECTS_PRI_H)); + std::vector processed; std::map ::iterator project; for (project = unique.begin (); project != unique.end (); ++project) { + const std::vector parents = extractParents (project->first); + std::vector ::const_iterator parent; + for (parent = parents.begin (); parent != parents.end (); parent++) + { + if (std::find (processed.begin (), processed.end (), *parent) + == processed.end ()) + { + int row = view.addRow (); + view.set (row, 0, indentProject (*parent)); + processed.push_back (*parent); + } + } int row = view.addRow (); view.set (row, 0, (project->first == "" ? STRING_CMD_PROJECTS_NONE @@ -131,6 +145,7 @@ int CmdProjects::execute (std::string& output) view.set (row, 3, low[project->first]); view.set (row, 4, medium[project->first]); view.set (row, 5, high[project->first]); + processed.push_back (project->first); } int number_projects = unique.size (); diff --git a/src/util.cpp b/src/util.cpp index d9b6fb627..774736f97 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -602,10 +602,42 @@ const std::string indentProject ( // Count the delimiters in *i. std::string prefix = ""; std::string::size_type pos = 0; + std::string::size_type lastpos = 0; while ((pos = project.find (delimiter, pos + 1)) != std::string::npos) - prefix += whitespace; + { + if (pos != project.size () - 1) + { + prefix += whitespace; + lastpos = pos; + } + } - return prefix + project; + std::string child = ""; + if (lastpos == 0) + child = project; + else + child = project.substr (lastpos + 1); + + return prefix + child; +} + +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +const std::vector extractParents ( + const std::string& project, + const char& delimiter /* = '.' */) +{ + std::vector vec; + std::string::size_type pos = 0; + std::string::size_type copyUntil = 0; + while ((copyUntil = project.find (delimiter, pos + 1)) != std::string::npos) + { + if (copyUntil != project.size () - 1) + vec.push_back (project.substr (0, copyUntil)); + pos = copyUntil; + } + return vec; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/util.h b/src/util.h index d5d48831a..7c7576ad4 100644 --- a/src/util.h +++ b/src/util.h @@ -84,6 +84,10 @@ const std::string indentProject ( const std::string& whitespace = " ", char delimiter = '.'); +const std::vector extractParents ( + const std::string&, + const char& delimiter = '.'); + #ifndef HAVE_TIMEGM time_t timegm (struct tm *tm); #endif diff --git a/test/project.t b/test/project.t index bd0b0fe6a..72734f4c4 100755 --- a/test/project.t +++ b/test/project.t @@ -28,7 +28,7 @@ use strict; use warnings; -use Test::More tests => 11; +use Test::More tests => 18; # Create the rc file. if (open my $fh, '>', 'pro.rc') @@ -66,6 +66,91 @@ like ($output, qr/The project 'bar' has changed\. Project 'bar' is 0% complete $output = qx{../src/task rc:pro.rc 3 modify pro:\\"foo bar\\" 2>&1 >/dev/null}; like ($output, qr/The project 'foo bar' has changed\./, 'project with spaces'); +# Bug 1056: Project indentation. +# see also the tests of helper functions for CmdProjects in util.t.cpp +qx{../src/task rc:pro.rc add testing project:existingParent 2>&1 >/dev/null}; +qx{../src/task rc:pro.rc add testing project:existingParent.child 2>&1 >/dev/null}; +qx{../src/task rc:pro.rc add testing project:abstractParent.kid 2>&1 >/dev/null}; +qx{../src/task rc:pro.rc add testing project:.myProject 2>&1 >/dev/null}; +qx{../src/task rc:pro.rc add testing project:myProject. 2>&1 >/dev/null}; +qx{../src/task rc:pro.rc add testing project:.myProject. 2>&1 >/dev/null}; + +$output = qx{../src/task rc:pro.rc projects 2>&1}; +my @lines = split ('\n',$output); + +# It's easier to make a pattern for the end than the beginning because priority +# counts are more predictable than project names. +my $project_name_column; +if ($lines[4] =~ s/\d+\s+\d+\s+\d+\s+\d+\s+\d+$//) +{ + $project_name_column = $lines[4]; +} +else +{ + $project_name_column = "error"; +} +like ($project_name_column, qr/^\.myProject\s*$/, '\'.myProject\' not indented'); + +if ($lines[5] =~ s/\d+\s+\d+\s+\d+\s+\d+\s+\d+$//) +{ + $project_name_column = $lines[5]; +} +else +{ + $project_name_column = "error"; +} +like ($project_name_column, qr/^\.myProject\.\s*$/, '\'.myProject.\' not indented'); + +if ($lines[6] =~ s/\d+\s+\d+\s+\d+\s+\d+\s+\d+$//) +{ + $project_name_column = "error"; +} +else +{ + $project_name_column = $lines[6]; +} +like ($project_name_column, qr/^abstractParent\s*$/, 'abstract parent not indented and no priority columns'); + +if ($lines[7] =~ s/\d+\s+\d+\s+\d+\s+\d+\s+\d+$//) +{ + $project_name_column = $lines[7]; +} +else +{ + $project_name_column = "error"; +} +like ($project_name_column, qr/^ kid\s*$/, 'child indented and without parent name'); + +if ($lines[8] =~ s/\d+\s+\d+\s+\d+\s+\d+\s+\d+$//) +{ + $project_name_column = $lines[8]; +} +else +{ + $project_name_column = "error"; +} +like ($project_name_column, qr/^existingParent\s*$/, 'existing parent not indented and has priority columns'); + +if ($lines[9] =~ s/\d+\s+\d+\s+\d+\s+\d+\s+\d+$//) +{ + $project_name_column = $lines[9]; +} +else +{ + $project_name_column = "error"; +} +like ($project_name_column, qr/^ child\s*$/, 'child of existing parent indented and without parent name'); + +if ($lines[12] =~ s/\d+\s+\d+\s+\d+\s+\d+\s+\d+$//) +{ + $project_name_column = $lines[12]; +} +else +{ + $project_name_column = "error"; +} +like ($project_name_column, qr/^myProject\.\s*$/, '\'myProject.\' not indented'); + # Cleanup. unlink qw(pending.data completed.data undo.data backlog.data synch.key pro.rc); ok (! -r 'pending.data' && diff --git a/test/util.t.cpp b/test/util.t.cpp index faa3116e9..68ca8725f 100644 --- a/test/util.t.cpp +++ b/test/util.t.cpp @@ -113,6 +113,7 @@ int main (int argc, char** argv) t.is (vleft[3], 4, "1,2,3,4 + 3,4,5 -> 1,2,3,4,5"); t.is (vleft[4], 5, "1,2,3,4 + 3,4,5 -> 1,2,3,4,5"); + // see also indirect tests of indentProject and extractParents in `project.t'. // std::vector indentTree (const std::vector&, const std::string whitespace=" ", char delimiter='.'); std::vector flat; flat.push_back ("one"); @@ -124,16 +125,16 @@ int main (int argc, char** argv) std::vector structured = indentTree (flat, " ", '.'); t.is (structured.size (), (size_t) 5, "indentTree yields 5 strings"); t.is (structured[0], "one", "indentTree 'one' -> 'one'"); - t.is (structured[1], " one.two", "indentTree 'one.two' -> ' one.two'"); - t.is (structured[2], " one.two.three", "indentTree 'one.two.three' -> ' one.two.three'"); - t.is (structured[3], " one.four", "indentTree 'one.four' -> ' one.four'"); + t.is (structured[1], " two", "indentTree 'one.two' -> ' two'"); + t.is (structured[2], " three", "indentTree 'one.two.three' -> ' three'"); + t.is (structured[3], " four", "indentTree 'one.four' -> ' four'"); t.is (structured[4], "two", "indentTree 'two' -> 'two'"); // std::vector indentProject (const std::string&, const std::string whitespace=" ", char delimiter='.'); t.is (indentProject (""), "", "indentProject '' -> ''"); t.is (indentProject ("one"), "one", "indentProject 'one' -> 'one'"); - t.is (indentProject ("one.two"), " one.two", "indentProject 'one.two' -> ' one.two'"); - t.is (indentProject ("one.two.three"), " one.two.three", "indentProject 'one.two.three' -> ' one.two.three'"); + t.is (indentProject ("one.two"), " two", "indentProject 'one.two' -> ' two'"); + t.is (indentProject ("one.two.three"), " three", "indentProject 'one.two.three' -> ' three'"); // TODO const std::string encode (const std::string& value); // TODO const std::string decode (const std::string& value);