From 9373eaad5496cb7ff4fb89db759a2ccba881bd64 Mon Sep 17 00:00:00 2001 From: e1528532 Date: Mon, 30 Jan 2017 19:47:16 +0100 Subject: [PATCH 1/7] =?UTF-8?q?kdb-complete:=20add=20support=20for=20names?= =?UTF-8?q?pace=20completion,=20don=E2=80=99t=20print=20leaf/node=20info?= =?UTF-8?q?=20without=20verbose=20parameter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tools/kdb/complete.cpp | 79 ++++++++++++++++++++++++++------------ src/tools/kdb/complete.hpp | 10 +++-- 2 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/tools/kdb/complete.cpp b/src/tools/kdb/complete.cpp index db784c87166..fce93aa0cc2 100644 --- a/src/tools/kdb/complete.cpp +++ b/src/tools/kdb/complete.cpp @@ -24,12 +24,11 @@ CompleteCommand::CompleteCommand () { } +/* + * McCabe complexity of 11, 1 caused by verbose switch so actually its ok + */ int CompleteCommand::execute (const Cmdline & cl) { - if (cl.arguments.size () != 1) - { - throw invalid_argument ("wrong number of arguments, 1 needed"); - } if (cl.maxDepth <= cl.minDepth) { throw invalid_argument ("the maximum depth has to be larger than the minimum depth"); @@ -49,10 +48,15 @@ int CompleteCommand::execute (const Cmdline & cl) cout.unsetf (ios_base::skipws); } - // Determine the actual root key, as for completion purpose originalRoot may not exist + // For namespace completion, we get all available keys via / as the actual root and filter via the argument + const bool hasArgument = cl.arguments.size () > 0; + const string originalInput = hasArgument ? cl.arguments[cl.arguments.size () - 1] : ""; + const Key originalUnprocessedKey (originalInput, KEY_END); KDB kdb; - const Key originalRoot = cl.createKey (cl.arguments.size () - 1); - Key root = originalRoot; + // Determine the actual root key, as for completion purpose originalRoot may not exist + // Maybe we want to complete an initial namespace, in that case bookmark expansion checks done by cl.createKey are useless + const Key originalRoot = originalUnprocessedKey.isValid () ? cl.createKey (cl.arguments.size () - 1) : originalUnprocessedKey; + Key root = originalUnprocessedKey.isValid () ? originalRoot : Key ("/", KEY_END); KeySet ks; printWarnings (cerr, root); @@ -71,7 +75,7 @@ int CompleteCommand::execute (const Cmdline & cl) // Now analyze the completion possibilities and print the results addMountpoints (ks, root); KeySet virtualKeys; - printResult (originalRoot, root, analyze (ks, root, virtualKeys, cl), virtualKeys, cl); + printResults (originalInput, originalRoot, root, analyze (ks, root, virtualKeys, cl), virtualKeys, cl); printWarnings (cerr, root); return 0; @@ -102,6 +106,17 @@ void CompleteCommand::addMountpoints (KeySet & ks, const Key root) printWarnings (cerr, mountpointPath); } +void CompleteCommand::addNamespaces (map> & hierarchy) +{ + // Currently assumed to be fixed, and they are on level 0 + const string namespaces[] = { "spec/", "proc/", "dir/", "user/", "system/" }; + for (const auto ns : namespaces) + { + Key nsKey (ns, KEY_END); + hierarchy[nsKey] = pair (1, 0); + } +} + /* * McCabe complexity of 13, 3 caused by debug switches so actually its ok */ @@ -111,6 +126,7 @@ const map> CompleteCommand::analyze (const KeySet & ks, cons stack keyStack; Key parent; Key last; + addNamespaces (hierarchy); ks.rewind (); if (!(ks.next ())) @@ -184,16 +200,17 @@ void CompleteCommand::increaseCount (map> & hierarchy, const } /* - * McCabe complexity of 12, 2 caused by debug/verbose switches so actually its ok + * McCabe complexity of 11, 3 caused by verbose switch so actually its ok */ -void CompleteCommand::printResult (const Key originalRoot, const Key root, const map> & hierarchy, - const KeySet & virtualKeys, const Cmdline & cl) +void CompleteCommand::printResults (const string originalInput, const Key originalRoot, const Key root, + const map> & hierarchy, const KeySet & virtualKeys, const Cmdline & cl) { - const function filterPredicate = determineFilterPredicate (originalRoot, root); + const function filterPredicate = determineFilterPredicate (originalInput, originalRoot, root); // Adjust the output offset, in case the given string exists in the hierarchy but not in the original ks + // or for namespace completion const bool limitMaxDepth = cl.maxDepth != numeric_limits::max (); - const int offset = originalRoot != root && virtualKeys.lookup (originalRoot); + const int offset = originalRoot != root && virtualKeys.lookup (originalRoot) ? 1 : (originalRoot.getFullName ().empty () ? -1 : 0); const int minDepth = cl.minDepth + offset; const int maxDepth = limitMaxDepth ? cl.maxDepth + offset : cl.maxDepth; if (cl.verbose) @@ -210,35 +227,49 @@ void CompleteCommand::printResult (const Key originalRoot, const Key root, const cout << endl; } - if (cl.debug) + for (const auto & it : hierarchy) { - cout << endl << "Dumping whole analyzation results" << endl; - for (const auto & it : hierarchy) + if (filterPredicate (it.first.getFullName ()) && it.second.second > minDepth && it.second.second <= maxDepth) { - cout << it.first << " " << it.second.first << " " << it.second.second << endl; + printResult (it, cl.verbose); } - cout << endl; } +} - for (const auto & it : hierarchy) +void CompleteCommand::printResult (const pair> & current, const bool verbose) +{ + cout << current.first; + if (current.second.first > 1) { - if (filterPredicate (it.first.getFullName ()) && it.second.second > minDepth && it.second.second <= maxDepth) + cout << "/ "; + if (verbose) { - cout << it.first << (it.second.first > 1 ? " node " + to_string (it.second.first - 1) : " leaf") << endl; + cout << "node " << to_string (current.second.first - 1); } } + else if (verbose) + { + cout << " leaf 0"; + } + if (verbose) + { + cout << " " << current.second.second; + } + cout << endl; } -const function CompleteCommand::determineFilterPredicate (const Key originalRoot, const Key root) +const function CompleteCommand::determineFilterPredicate (const string originalInput, const Key originalRoot, const Key root) { if (root == originalRoot) { return [](string) { return true; }; } - const string fullName = originalRoot.getFullName (); + + const bool namespaceCompletion = originalRoot.getFullName ().empty (); + const string fullName = namespaceCompletion ? originalInput : originalRoot.getFullName (); return [=](string test) { // For cascading keys, we ignore the preceding namespace for filtering - const int cascadationOffset = (root.isCascading () ? test.find ("/") : 0); + const int cascadationOffset = (!namespaceCompletion && root.isCascading () ? test.find ("/") : 0); return fullName.size () <= test.size () && equal (fullName.begin (), fullName.end (), test.begin () + cascadationOffset); }; } diff --git a/src/tools/kdb/complete.hpp b/src/tools/kdb/complete.hpp index 3f1e01a83e0..5c6cc2959f1 100644 --- a/src/tools/kdb/complete.hpp +++ b/src/tools/kdb/complete.hpp @@ -48,14 +48,18 @@ class CompleteCommand : public Command private: void addMountpoints (kdb::KeySet & ks, const kdb::Key root); + void addNamespaces (std::map> & hierarchy); const std::map> analyze (const kdb::KeySet & ks, const kdb::Key root, kdb::KeySet & virtualKeys, const Cmdline & cmdLine); const kdb::Key getParentKey (const kdb::Key key); void increaseCount (std::map> & hierarchy, const kdb::Key key, const std::function depthIncreaser); - void printResult (const kdb::Key originalRoot, const kdb::Key root, const std::map> & hierarchy, - const kdb::KeySet & virtualKeys, const Cmdline & cmdLine); - const std::function determineFilterPredicate (const kdb::Key originalRoot, const kdb::Key root); + void printResults (const std::string originalInput, const kdb::Key originalRoot, const kdb::Key root, + const std::map> & hierarchy, const kdb::KeySet & virtualKeys, + const Cmdline & cmdLine); + void printResult (const std::pair> & current, const bool verbose); + const std::function determineFilterPredicate (const std::string originalInput, const kdb::Key originalRoot, + const kdb::Key root); }; #endif From b10fd1201d93b13c71fcc60821457e3afdeaec27 Mon Sep 17 00:00:00 2001 From: e1528532 Date: Mon, 30 Jan 2017 20:12:06 +0100 Subject: [PATCH 2/7] kdb-complete: update manpage --- doc/help/kdb-complete.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/help/kdb-complete.md b/doc/help/kdb-complete.md index 2f76e6b7e80..81901ec777c 100644 --- a/doc/help/kdb-complete.md +++ b/doc/help/kdb-complete.md @@ -10,8 +10,9 @@ Where `path` is the path for which the user would like to receive completion sug ## DESCRIPTION Show suggestions how the current name could be completed. -Suggestions will include existing key names, path segments of existing key names and mountpoints. -Additionally, the output will indicate wheter the given path is a node or a leaf in the hierarchy of keys. +Suggestions will include existing key names, path segments of existing key names, namespaces and mountpoints. +Additionally, the output will indicate wheter the given path is a node or a leaf in the hierarchy of keys, +nodes end with '/' as opposed to leaves. ## OPTIONS @@ -26,7 +27,7 @@ Additionally, the output will indicate wheter the given path is a node or a leaf - `-M`, `--max-depth`=: Specify the maximum depth of completion suggestions (unlimited by default, 1 to show only the next level), inclusive. - `-v`, `--verbose`: - Explain what is happening. + Give a more detailed output, showing the number of child nodes and the depth level. - `-0`, `--null`: Use binary 0 termination. - `-d`, `--debug`: From efdd4ba86412bf959cfbaa016c11e0a294f0c410 Mon Sep 17 00:00:00 2001 From: e1528532 Date: Wed, 1 Feb 2017 15:23:19 +0100 Subject: [PATCH 3/7] kdb-complete: fix documentation, fix unnecessary space in output --- doc/help/kdb-complete.md | 16 ++++++++++++++-- src/tools/kdb/complete.cpp | 5 +++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/doc/help/kdb-complete.md b/doc/help/kdb-complete.md index 81901ec777c..605c97fd018 100644 --- a/doc/help/kdb-complete.md +++ b/doc/help/kdb-complete.md @@ -3,16 +3,19 @@ kdb-complete(1) -- Show suggestions how to complete a given path ## SYNOPSIS -`kdb complete ` +`kdb complete [path]` Where `path` is the path for which the user would like to receive completion suggestion. +If `path` is not specified, it will show every possible completion. Its synonymous to calling `kdb complete ""`. ## DESCRIPTION Show suggestions how the current name could be completed. Suggestions will include existing key names, path segments of existing key names, namespaces and mountpoints. -Additionally, the output will indicate wheter the given path is a node or a leaf in the hierarchy of keys, +Additionally, the output will indicate whether the given path is a node or a leaf in the hierarchy of keys, nodes end with '/' as opposed to leaves. +It will also work for cascading keys, and will additionally display a cascading key's namespace in the output +to indicate from which namespace this suggestion originates from. ## OPTIONS @@ -35,6 +38,15 @@ nodes end with '/' as opposed to leaves. ## EXAMPLES +To show all possible completions: +`kdb complete` + +To show namespace suggestions: +`kdb complete --max-depth=1` + +To show all possible completions for a cascading key: +`kdb complete /te` + To show all possible completions for `user/te`: `kdb complete user/te` diff --git a/src/tools/kdb/complete.cpp b/src/tools/kdb/complete.cpp index fce93aa0cc2..dfea2468013 100644 --- a/src/tools/kdb/complete.cpp +++ b/src/tools/kdb/complete.cpp @@ -50,6 +50,7 @@ int CompleteCommand::execute (const Cmdline & cl) // For namespace completion, we get all available keys via / as the actual root and filter via the argument const bool hasArgument = cl.arguments.size () > 0; + // Without specifying an argument, it will show every possible completion, like calling kdb complete "" const string originalInput = hasArgument ? cl.arguments[cl.arguments.size () - 1] : ""; const Key originalUnprocessedKey (originalInput, KEY_END); KDB kdb; @@ -241,10 +242,10 @@ void CompleteCommand::printResult (const pair> & current, co cout << current.first; if (current.second.first > 1) { - cout << "/ "; + cout << "/"; if (verbose) { - cout << "node " << to_string (current.second.first - 1); + cout << " node " << to_string (current.second.first - 1); } } else if (verbose) From 5066db71c8d4ea17cf030269ed4d7ab69bdbe571 Mon Sep 17 00:00:00 2001 From: e1528532 Date: Fri, 3 Feb 2017 10:51:42 +0100 Subject: [PATCH 4/7] kdb-complete: add detection for missing / outdated namespaces --- src/tools/kdb/complete.cpp | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/tools/kdb/complete.cpp b/src/tools/kdb/complete.cpp index dfea2468013..4d1e6cb2dc3 100644 --- a/src/tools/kdb/complete.cpp +++ b/src/tools/kdb/complete.cpp @@ -55,7 +55,7 @@ int CompleteCommand::execute (const Cmdline & cl) const Key originalUnprocessedKey (originalInput, KEY_END); KDB kdb; // Determine the actual root key, as for completion purpose originalRoot may not exist - // Maybe we want to complete an initial namespace, in that case bookmark expansion checks done by cl.createKey are useless + // If we want to complete an initial namespace, everything done by cl.createKey is unnecessary and will fail const Key originalRoot = originalUnprocessedKey.isValid () ? cl.createKey (cl.arguments.size () - 1) : originalUnprocessedKey; Key root = originalUnprocessedKey.isValid () ? originalRoot : Key ("/", KEY_END); KeySet ks; @@ -109,11 +109,34 @@ void CompleteCommand::addMountpoints (KeySet & ks, const Key root) void CompleteCommand::addNamespaces (map> & hierarchy) { - // Currently assumed to be fixed, and they are on level 0 - const string namespaces[] = { "spec/", "proc/", "dir/", "user/", "system/" }; - for (const auto ns : namespaces) + // Always add them on level 0 + const string namespaces[] = { + "spec/", "proc/", "dir/", "user/", "system/", + }; + + // Check for new namespaces, issue a warning in case + for (elektraNamespace ens = KEY_NS_FIRST; ens <= KEY_NS_LAST; ++ens) { - Key nsKey (ns, KEY_END); + // since ens are numbers, there is no way to get a string representation if not found in that case + bool found = false; + for (const string ns : namespaces) + { + found = found || ckdb::keyGetNamespace (Key (ns, KEY_END).getKey ()) == ens; + } + if (!found) + { + cerr << "Missing namespace detected:" << ens << ". \nPlease report this issue." << endl; + } + } + + for (const string ns : namespaces) + { + const Key nsKey (ns, KEY_END); + // Check for outdated namespaces, issue a warning in case + if (ckdb::keyGetNamespace (nsKey.getKey ()) == KEY_NS_EMPTY) + { + cerr << "Outdated namespace detected:" << ns << ".\nPlease report this issue." << endl; + } hierarchy[nsKey] = pair (1, 0); } } From 4a55d43445471b0a4851a86e063fdaa98bc4dabf Mon Sep 17 00:00:00 2001 From: e1528532 Date: Fri, 3 Feb 2017 12:04:03 +0100 Subject: [PATCH 5/7] kdb-complete: add bookmark support --- src/tools/kdb/complete.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tools/kdb/complete.cpp b/src/tools/kdb/complete.cpp index 4d1e6cb2dc3..1686b33ea67 100644 --- a/src/tools/kdb/complete.cpp +++ b/src/tools/kdb/complete.cpp @@ -55,9 +55,10 @@ int CompleteCommand::execute (const Cmdline & cl) const Key originalUnprocessedKey (originalInput, KEY_END); KDB kdb; // Determine the actual root key, as for completion purpose originalRoot may not exist - // If we want to complete an initial namespace, everything done by cl.createKey is unnecessary and will fail - const Key originalRoot = originalUnprocessedKey.isValid () ? cl.createKey (cl.arguments.size () - 1) : originalUnprocessedKey; - Key root = originalUnprocessedKey.isValid () ? originalRoot : Key ("/", KEY_END); + // If the input is not a valid key, it could still be a bookmark or a namespace + const bool isArgumentValidKey = originalUnprocessedKey.isValid () || (!originalInput.empty () && originalInput[0] == '+'); + const Key originalRoot = isArgumentValidKey ? cl.createKey (cl.arguments.size () - 1) : originalUnprocessedKey; + Key root = isArgumentValidKey ? originalRoot : Key ("/", KEY_END); KeySet ks; printWarnings (cerr, root); From fc5cb511953ceda92037777e9355f375b8571d57 Mon Sep 17 00:00:00 2001 From: e1528532 Date: Wed, 8 Feb 2017 13:34:04 +0100 Subject: [PATCH 6/7] kdb-complete: major refactoring and fixing all those small issues and inconsistencies avoid output in normal mode to avoid destroying completions --- doc/help/kdb-complete.md | 24 +-- src/tools/kdb/cmdline.cpp | 69 ++++++--- src/tools/kdb/cmdline.hpp | 1 + src/tools/kdb/complete.cpp | 295 ++++++++++++++++++++++++------------- src/tools/kdb/complete.hpp | 41 ++++-- 5 files changed, 286 insertions(+), 144 deletions(-) diff --git a/doc/help/kdb-complete.md b/doc/help/kdb-complete.md index 605c97fd018..44244802918 100644 --- a/doc/help/kdb-complete.md +++ b/doc/help/kdb-complete.md @@ -6,16 +6,19 @@ kdb-complete(1) -- Show suggestions how to complete a given path `kdb complete [path]` Where `path` is the path for which the user would like to receive completion suggestion. -If `path` is not specified, it will show every possible completion. Its synonymous to calling `kdb complete ""`. +If `path` is not specified, it will show every possible completion. Its synonymous +to calling `kdb complete ""`. ## DESCRIPTION Show suggestions how the current name could be completed. -Suggestions will include existing key names, path segments of existing key names, namespaces and mountpoints. -Additionally, the output will indicate whether the given path is a node or a leaf in the hierarchy of keys, -nodes end with '/' as opposed to leaves. -It will also work for cascading keys, and will additionally display a cascading key's namespace in the output -to indicate from which namespace this suggestion originates from. +Suggestions will include existing key names, path segments of existing key names, +namespaces and mountpoints. +Additionally, the output will indicate whether the given path is a node or a leaf +in the hierarchy of keys, nodes end with '/' as opposed to leaves. +It will also work for cascading keys, and will additionally display a cascading +key's namespace in the output to indicate from which namespace this suggestion +originates from. ## OPTIONS @@ -26,9 +29,11 @@ to indicate from which namespace this suggestion originates from. - `-p`, `--profile`=: Use a different kdb profile. - `-m`, `--min-depth`=: - Specify the minimum depth of completion suggestions (0 by default), exclusive. + Specify the minimum depth of completion suggestions (0 by default), exclusive + and relative to the name to complete. - `-M`, `--max-depth`=: - Specify the maximum depth of completion suggestions (unlimited by default, 1 to show only the next level), inclusive. + Specify the maximum depth of completion suggestions (unlimited by default, 1 + to show only the next level), inclusive and relative to the name to complete. - `-v`, `--verbose`: Give a more detailed output, showing the number of child nodes and the depth level. - `-0`, `--null`: @@ -55,5 +60,6 @@ To show all possible completions for the next level for `user/te`: ## SEE ALSO -- If you would only like to see existing keys under a given path, consider using the [kdb-ls(1)](kdb-ls.md) command. +- If you would only like to see existing keys under a given path, consider using + the [kdb-ls(1)](kdb-ls.md) command. - [elektra-key-names(7)](elektra-key-names.md) for an explanation of key names. \ No newline at end of file diff --git a/src/tools/kdb/cmdline.cpp b/src/tools/kdb/cmdline.cpp index 0ede997a2eb..adf43b97570 100644 --- a/src/tools/kdb/cmdline.cpp +++ b/src/tools/kdb/cmdline.cpp @@ -111,7 +111,9 @@ Cmdline::Cmdline (int argc, char ** argv, Command * command) { option o = { "min-depth", optional_argument, nullptr, 'm' }; long_options.push_back (o); - helpText += "-m --min-depth Specify the minimum depth of completion suggestions (0 by default), exclusive.\n"; + helpText += + "-m --min-depth Specify the minimum depth of completion suggestions (0 by default), inclusive " + "and relative to the name to complete.\n"; } if (acceptedOptions.find ('M') != string::npos) { @@ -119,7 +121,7 @@ Cmdline::Cmdline (int argc, char ** argv, Command * command) long_options.push_back (o); helpText += "-M --max-depth Specify the maximum depth of completion suggestions (unlimited by default, 1 to show " - "only the next level), inclusive.\n"; + "only the next level), exclusive and relative to the name to complete.\n"; } if (acceptedOptions.find ('n') != string::npos) { @@ -520,7 +522,40 @@ kdb::Key Cmdline::createKey (int pos) const throw invalid_argument (" is not a valid keyname. Please enter a valid one."); } + kdb::Key root (name, KEY_END); + if (name[0] == '+') + { + kdb::Key bookmark = resolveBookmark (name); + if (!bookmark.isValid ()) + { + throw invalid_argument ("cannot find bookmark " + bookmark.getFullName ()); + } + root = bookmark; + } + + if (!root.isValid ()) + { + throw invalid_argument (name + " is not a valid keyname" + "\n\n" + + "For absolute keys (starting without '/'), please note that only one of the predefined namespaces " + "can be used (see 'man elektra-namespaces').\n" + + "Please also ensure that the path is separated by a '/'.\n" + + "An example for a valid absolute key is user/a/key, and for a valid cascading key /a/key."); + } + + return root; +} + +/** + * @brief resolve the bookmark with the given name + * + * @param bookmark the name of the bookmark to resolve + * + * @return a key to the resolved bookmark, or an invalid key if no bookmark with the given name exists + */ +kdb::Key Cmdline::resolveBookmark (std::string name) const +{ + if (!name.empty () && name[0] == '+') { size_t found = name.find ('/'); std::string bookmark; @@ -536,30 +571,18 @@ kdb::Key Cmdline::createKey (int pos) const } auto realKeyIt = bookmarks.find (bookmark); std::string realKey; - if (realKeyIt == bookmarks.end ()) - { - throw invalid_argument ("cannot find bookmark " + bookmark); - } - realKey = realKeyIt->second; - name = realKey + "/" + restKey; - if (verbose) + if (realKeyIt != bookmarks.end ()) { - std::cout << "using bookmark " << bookmark << " which is: " << realKey << "-" << restKey << std::endl; + realKey = realKeyIt->second; + name = realKey + "/" + restKey; + if (verbose) + { + std::cout << "using bookmark " << bookmark << " which is: " << realKey << "-" << restKey << std::endl; + } + return kdb::Key (name, KEY_END); } } - - kdb::Key root (name, KEY_END); - - if (!root.isValid ()) - { - throw invalid_argument (name + " is not a valid keyname" + "\n\n" + - "For absolute keys (starting without '/'), please note that only one of the predefined namespaces " - "can be used (see 'man elektra-namespaces').\n" + - "Please also ensure that the path is separated by a '/'.\n" + - "An example for a valid absolute key is user/a/key, and for a valid cascading key /a/key."); - } - - return root; + return kdb::Key (); } std::ostream & operator<< (std::ostream & os, Cmdline & cl) diff --git a/src/tools/kdb/cmdline.hpp b/src/tools/kdb/cmdline.hpp index c6b8ead8a38..c9307d90a6c 100644 --- a/src/tools/kdb/cmdline.hpp +++ b/src/tools/kdb/cmdline.hpp @@ -92,6 +92,7 @@ class Cmdline std::string profile; kdb::Key createKey (int pos) const; + kdb::Key resolveBookmark (std::string name) const; kdb::KeySet getPluginsConfig (std::string basepath = "user/") const; diff --git a/src/tools/kdb/complete.cpp b/src/tools/kdb/complete.cpp index 1686b33ea67..1da1b3400a0 100644 --- a/src/tools/kdb/complete.cpp +++ b/src/tools/kdb/complete.cpp @@ -24,9 +24,6 @@ CompleteCommand::CompleteCommand () { } -/* - * McCabe complexity of 11, 1 caused by verbose switch so actually its ok - */ int CompleteCommand::execute (const Cmdline & cl) { if (cl.maxDepth <= cl.minDepth) @@ -48,39 +45,134 @@ int CompleteCommand::execute (const Cmdline & cl) cout.unsetf (ios_base::skipws); } - // For namespace completion, we get all available keys via / as the actual root and filter via the argument const bool hasArgument = cl.arguments.size () > 0; - // Without specifying an argument, it will show every possible completion, like calling kdb complete "" - const string originalInput = hasArgument ? cl.arguments[cl.arguments.size () - 1] : ""; - const Key originalUnprocessedKey (originalInput, KEY_END); - KDB kdb; - // Determine the actual root key, as for completion purpose originalRoot may not exist - // If the input is not a valid key, it could still be a bookmark or a namespace - const bool isArgumentValidKey = originalUnprocessedKey.isValid () || (!originalInput.empty () && originalInput[0] == '+'); - const Key originalRoot = isArgumentValidKey ? cl.createKey (cl.arguments.size () - 1) : originalUnprocessedKey; - Key root = isArgumentValidKey ? originalRoot : Key ("/", KEY_END); - KeySet ks; - printWarnings (cerr, root); + const string argument = hasArgument ? cl.arguments[cl.arguments.size () - 1] : ""; + complete (argument, cl); + + return 0; +} + +void CompleteCommand::complete (const string argument, const Cmdline & cl) +{ + using namespace std::placeholders; // for bind + + if (argument.empty ()) + { // No argument, show all completions by analyzing everything including namespaces, so adjust the offset for that + const Key root = Key ("/", KEY_END); + printResults (root, cl.minDepth, cl.maxDepth, cl, analyze (getKeys (root, false), cl), + bind (filterDepth, cl.minDepth, cl.maxDepth, _1), printResult); + } + else if (!argument.empty () && argument[0] == '+') + { // is it a bookmark? + // Try to resolve the bookmark + const Key resolvedBookmark = cl.resolveBookmark (argument); + if (resolvedBookmark.isValid ()) + { + complete (resolvedBookmark.getFullName (), cl); + } + else + { // Bookmark not resolvable, so try a bookmark completion + // since for legacy reasons its probably /sw/kdb, we use /sw as a root + const Key root = Key ("/sw", KEY_END); + printResults (root, 0, cl.maxDepth, cl, analyze (getKeys (root, true), cl), bind (filterBookmarks, argument, _1), + printBookmarkResult); + } + } + else + { + const Key parsedArgument (argument, KEY_END); + if ((!parsedArgument.isValid () || !shallShowNextLevel (argument)) && parsedArgument.getBaseName ().empty ()) + { // is it a namespace completion? + const Key root = Key ("/", KEY_END); + const auto filter = [&](const pair> & c) { + return filterDepth (cl.minDepth, cl.maxDepth, c) && filterName (argument, c); + }; + printResults (root, cl.minDepth, cl.maxDepth, cl, analyze (getKeys (root, false), cl), filter, printResult); + } + else + { // the "normal" completion cases + completeNormal (argument, parsedArgument, cl); + } + } +} + +void CompleteCommand::completeNormal (const string argument, const Key parsedArgument, const Cmdline & cl) +{ + Key root = parsedArgument; + const Key parent = getParentKey (root); + // Its important to use the parent element here as using non-existent elements may yield no keys + KeySet ks = getKeys (parent, false); + // the namespaces count as existent although not found by lookup + const bool isValidNamespace = parsedArgument.isValid () && parsedArgument.getBaseName ().empty (); + const bool rootExists = isValidNamespace || ks.lookup (root); + if (!rootExists) + { + root = parent; + } + const auto result = analyze (ks, cl); + + // we see depth relative to the completion level, if the root exists, distance will be higher so subtract 1 + // to add up for the offset added my shallShowNextLevel + const int offset = distance (root.begin (), root.end ()) - rootExists + shallShowNextLevel (argument); + const auto nameFilter = root.isCascading () ? filterCascading : filterName; + const auto filter = [&](const pair> & c) { + return filterDepth (cl.minDepth + offset, max (cl.maxDepth, cl.maxDepth + offset), c) && nameFilter (argument, c); + }; + printResults (root, cl.minDepth, cl.maxDepth, cl, result, filter, printResult); +} + +bool CompleteCommand::shallShowNextLevel (const string argument) +{ + auto it = argument.rbegin (); + // If the argument ends in / its an indicator to complete the next level (like done by shells), but not if its escaped + return it != argument.rend () && (*it) == '/' && ((++it) == argument.rend () || (*it) != '\\'); +} + +KeySet CompleteCommand::getKeys (Key root, const bool cutAtRoot) +{ + KeySet ks; + KDB kdb; kdb.get (ks, root); - if (!ks.lookup (root) && !root.getBaseName ().empty ()) + addMountpoints (ks, root); + if (cutAtRoot) { - if (cl.verbose) + ks = ks.cut (root); + } + return ks; +} + +void CompleteCommand::printResults (const Key root, const int minDepth, const int maxDepth, const Cmdline & cl, + const map> & result, + const std::function> & current)> filter, + const std::function> & current, const bool verbose)> printResult) +{ + if (cl.verbose) + { + cout << "Showing results for a minimum depth of " << minDepth; + if (maxDepth != numeric_limits::max ()) { - cout << originalRoot << " does not exist or is a cascading key, using " << root << " as the current completion path" - << endl; + cout << " and a maximum depth of " << maxDepth; } - root = getParentKey (root); + else + { + cout << " and no maximum depth"; + } + cout << endl; } - ks = ks.cut (root); - // Now analyze the completion possibilities and print the results - addMountpoints (ks, root); - KeySet virtualKeys; - printResults (originalInput, originalRoot, root, analyze (ks, root, virtualKeys, cl), virtualKeys, cl); - printWarnings (cerr, root); + for (const auto & it : result) + { + if (cl.debug || filter (it)) + { + printResult (it, cl.verbose); + } + } - return 0; + if (cl.debug || cl.verbose) + { // Only print this in debug mode to avoid destroying autocompletions because of warnings + printWarnings (cerr, root); + } } void CompleteCommand::addMountpoints (KeySet & ks, const Key root) @@ -108,34 +200,38 @@ void CompleteCommand::addMountpoints (KeySet & ks, const Key root) printWarnings (cerr, mountpointPath); } -void CompleteCommand::addNamespaces (map> & hierarchy) +/* + * McCabe complexity of 11, 4 caused by debug switches so its ok + */ +void CompleteCommand::addNamespaces (map> & hierarchy, const Cmdline & cl) { - // Always add them on level 0 const string namespaces[] = { "spec/", "proc/", "dir/", "user/", "system/", }; // Check for new namespaces, issue a warning in case - for (elektraNamespace ens = KEY_NS_FIRST; ens <= KEY_NS_LAST; ++ens) + if (cl.debug || cl.verbose) { - // since ens are numbers, there is no way to get a string representation if not found in that case - bool found = false; - for (const string ns : namespaces) - { - found = found || ckdb::keyGetNamespace (Key (ns, KEY_END).getKey ()) == ens; - } - if (!found) + for (elektraNamespace ens = KEY_NS_FIRST; ens <= KEY_NS_LAST; ++ens) { - cerr << "Missing namespace detected:" << ens << ". \nPlease report this issue." << endl; + // since ens are numbers, there is no way to get a string representation if not found in that case + bool found = false; + for (const string ns : namespaces) + { + found = found || ckdb::keyGetNamespace (Key (ns, KEY_END).getKey ()) == ens; + } + if (!found) + { + cerr << "Missing namespace detected:" << ens << ". \nPlease report this issue." << endl; + } } } for (const string ns : namespaces) { const Key nsKey (ns, KEY_END); - // Check for outdated namespaces, issue a warning in case - if (ckdb::keyGetNamespace (nsKey.getKey ()) == KEY_NS_EMPTY) - { + if ((cl.debug || cl.verbose) && ckdb::keyGetNamespace (nsKey.getKey ()) == KEY_NS_EMPTY) + { // Check for outdated namespaces, issue a warning in case cerr << "Outdated namespace detected:" << ns << ".\nPlease report this issue." << endl; } hierarchy[nsKey] = pair (1, 0); @@ -143,15 +239,15 @@ void CompleteCommand::addNamespaces (map> & hierarchy) } /* - * McCabe complexity of 13, 3 caused by debug switches so actually its ok + * McCabe complexity of 12, 3 caused by debug switches so its ok */ -const map> CompleteCommand::analyze (const KeySet & ks, const Key root, KeySet & virtualKeys, const Cmdline & cl) +const map> CompleteCommand::analyze (const KeySet & ks, const Cmdline & cl) { map> hierarchy; stack keyStack; Key parent; Key last; - addNamespaces (hierarchy); + addNamespaces (hierarchy, cl); ks.rewind (); if (!(ks.next ())) @@ -184,14 +280,13 @@ const map> CompleteCommand::analyze (const KeySet & ks, cons } else { // hierarchy does not fit the current parent, expand the current key to the stack to find the new parent - while (current.isBelow (root) && !current.getBaseName ().empty () && hierarchy[current].first == 0) + while (!current.getBaseName ().empty () && hierarchy[current].first == 0) { // Go back up in the hierarchy until we encounter a known key or are back at the namespace level if (cl.debug) { cout << "Expanding " << current << endl; } keyStack.push (current); - virtualKeys.append (current); current = getParentKey (current); } parent = getParentKey (current); @@ -224,79 +319,81 @@ void CompleteCommand::increaseCount (map> & hierarchy, const hierarchy[key] = pair (prev.first + 1, depthIncreaser (prev.second)); } -/* - * McCabe complexity of 11, 3 caused by verbose switch so actually its ok +bool CompleteCommand::filterDepth (const int minDepth, const int maxDepth, const pair> & current) +{ + return current.second.second >= minDepth && current.second.second < maxDepth; +} + +bool CompleteCommand::filterCascading (const string argument, const pair> & current) +{ + // For a cascading key completion, ignore the preceding namespace + const string test = current.first.getFullName (); + const int cascadationOffset = test.find ("/"); + return argument.size () <= test.size () && equal (argument.begin (), argument.end (), test.begin () + cascadationOffset); +} + +bool CompleteCommand::filterName (const string argument, const pair> & current) +{ + // For a namespace completion, compare by substring + const string test = current.first.getFullName (); + return argument.size () <= test.size () && equal (argument.begin (), argument.end (), test.begin ()); +} + +/** + * McCabe Complexity of 15 due to the boolean conjunctions, easy to understand so its ok */ -void CompleteCommand::printResults (const string originalInput, const Key originalRoot, const Key root, - const map> & hierarchy, const KeySet & virtualKeys, const Cmdline & cl) +bool CompleteCommand::filterBookmarks (const string bookmarkName, const pair> & current) { - const function filterPredicate = determineFilterPredicate (originalInput, originalRoot, root); - - // Adjust the output offset, in case the given string exists in the hierarchy but not in the original ks - // or for namespace completion - const bool limitMaxDepth = cl.maxDepth != numeric_limits::max (); - const int offset = originalRoot != root && virtualKeys.lookup (originalRoot) ? 1 : (originalRoot.getFullName ().empty () ? -1 : 0); - const int minDepth = cl.minDepth + offset; - const int maxDepth = limitMaxDepth ? cl.maxDepth + offset : cl.maxDepth; - if (cl.verbose) + // For a bookmark completion, ignore everything except the bookmarks by comparing the base name + const string test = current.first.getBaseName (); + // as we search in /sw due to legacy reasons, ensure we have an actual bookmark by checking the path + bool elektraFound = false; + bool kdbFound = false; + bool bookmarksFound = false; + bool bookmarkFound = false; + + for (const string part : current.first) { - cout << "Showing results for a minimum depth of " << minDepth; - if (limitMaxDepth) - { - cout << " and a maximum depth of " << maxDepth; - } - else - { - cout << " and no maximum depth"; - } - cout << endl; + // size 1 -> +, so show all bookmarks, order of these checks is important! + bookmarkFound = bookmarksFound && (bookmarkFound || bookmarkName.size () == 1 || + (bookmarkName.size () - 1 <= part.size () && + equal (bookmarkName.begin () + 1, bookmarkName.end (), part.begin ()))); + bookmarksFound = bookmarksFound || (part == "bookmarks" && !bookmarkFound); + kdbFound = kdbFound || (!bookmarksFound && part == "kdb"); + elektraFound = elektraFound || (!kdbFound && part == "elektra"); } - for (const auto & it : hierarchy) - { - if (filterPredicate (it.first.getFullName ()) && it.second.second > minDepth && it.second.second <= maxDepth) - { - printResult (it, cl.verbose); - } - } + return (elektraFound || kdbFound) && bookmarksFound && bookmarkFound; } -void CompleteCommand::printResult (const pair> & current, const bool verbose) -{ - cout << current.first; +void CompleteCommand::printBookmarkResult (const pair> & current, const bool verbose) +{ // Ignore the path for a bookmark completion + cout << "+" << current.first.getBaseName (); if (current.second.first > 1) { cout << "/"; - if (verbose) - { - cout << " node " << to_string (current.second.first - 1); - } - } - else if (verbose) - { - cout << " leaf 0"; } if (verbose) { - cout << " " << current.second.second; + cout << (current.second.first > 1 ? " node " : " leaf "); + cout << (current.second.first - 1) << " " << current.second.second; } cout << endl; } -const function CompleteCommand::determineFilterPredicate (const string originalInput, const Key originalRoot, const Key root) +void CompleteCommand::printResult (const pair> & current, const bool verbose) { - if (root == originalRoot) + cout << current.first.getFullName (); + if (current.second.first > 1) { - return [](string) { return true; }; + cout << "/"; } - - const bool namespaceCompletion = originalRoot.getFullName ().empty (); - const string fullName = namespaceCompletion ? originalInput : originalRoot.getFullName (); - return [=](string test) { - // For cascading keys, we ignore the preceding namespace for filtering - const int cascadationOffset = (!namespaceCompletion && root.isCascading () ? test.find ("/") : 0); - return fullName.size () <= test.size () && equal (fullName.begin (), fullName.end (), test.begin () + cascadationOffset); - }; + if (verbose) + { + cout << (current.second.first > 1 ? " node " : " leaf "); + cout << (current.second.first - 1) << " " << current.second.second; + } + cout << endl; } CompleteCommand::~CompleteCommand () diff --git a/src/tools/kdb/complete.hpp b/src/tools/kdb/complete.hpp index 5c6cc2959f1..ec07b924cfe 100644 --- a/src/tools/kdb/complete.hpp +++ b/src/tools/kdb/complete.hpp @@ -35,31 +35,46 @@ class CompleteCommand : public Command virtual std::string getShortHelpText () override { - return "Show suggestions how the current name could be completed."; + return "Show suggestions how to complete key names."; } virtual std::string getLongHelpText () override { return "Suggestions will include existing key names, path segments of existing key names and mountpoints.\n" - "Additionally, the output will indicate wheter the given path is a node or a leaf in the hierarchy of keys."; + "Additionally, the output indicates whether the given path is a node or a leaf in the hierarchy of keys."; } virtual int execute (const Cmdline & cmdline) override; private: - void addMountpoints (kdb::KeySet & ks, const kdb::Key root); - void addNamespaces (std::map> & hierarchy); - const std::map> analyze (const kdb::KeySet & ks, const kdb::Key root, kdb::KeySet & virtualKeys, - const Cmdline & cmdLine); - const kdb::Key getParentKey (const kdb::Key key); + void complete (const std::string argument, const Cmdline & cmdLine); + void completeNormal (const std::string argument, const kdb::Key parsedArgument, const Cmdline & cmdLine); + + const std::map> analyze (const kdb::KeySet & ks, const Cmdline & cmdLine); + void + printResults (const kdb::Key root, const int minDepth, const int maxDepth, const Cmdline & cmdLine, + const std::map> & result, + const std::function> & current)> filterPredicate, + const std::function> & current, const bool verbose)> printResult); + + // helper functions void increaseCount (std::map> & hierarchy, const kdb::Key key, const std::function depthIncreaser); - void printResults (const std::string originalInput, const kdb::Key originalRoot, const kdb::Key root, - const std::map> & hierarchy, const kdb::KeySet & virtualKeys, - const Cmdline & cmdLine); - void printResult (const std::pair> & current, const bool verbose); - const std::function determineFilterPredicate (const std::string originalInput, const kdb::Key originalRoot, - const kdb::Key root); + void addMountpoints (kdb::KeySet & ks, const kdb::Key root); + void addNamespaces (std::map> & hierarchy, const Cmdline & cl); + const kdb::Key getParentKey (const kdb::Key key); + kdb::KeySet getKeys (kdb::Key root, bool cutAtRoot); + bool shallShowNextLevel (const std::string argument); + + // print functions + static void printBookmarkResult (const std::pair> & current, const bool verbose); + static void printResult (const std::pair> & current, const bool verbose); + + // filter functions + static bool filterDepth (const int minDepth, const int maxDepth, const std::pair> & current); + static bool filterCascading (const std::string argument, const std::pair> & current); + static bool filterName (const std::string argument, const std::pair> & current); + static bool filterBookmarks (const std::string bookmarkName, const std::pair> & current); }; #endif From fdc3c1d763e1c192817bfa1b2f058fc0ff707ba2 Mon Sep 17 00:00:00 2001 From: e1528532 Date: Wed, 8 Feb 2017 13:37:01 +0100 Subject: [PATCH 7/7] kdb-complete: reorder methods to have some structure --- src/tools/kdb/complete.cpp | 178 ++++++++++++++++++------------------- src/tools/kdb/complete.hpp | 9 +- 2 files changed, 94 insertions(+), 93 deletions(-) diff --git a/src/tools/kdb/complete.cpp b/src/tools/kdb/complete.cpp index 1da1b3400a0..e46a0d85f65 100644 --- a/src/tools/kdb/complete.cpp +++ b/src/tools/kdb/complete.cpp @@ -122,24 +122,74 @@ void CompleteCommand::completeNormal (const string argument, const Key parsedArg printResults (root, cl.minDepth, cl.maxDepth, cl, result, filter, printResult); } -bool CompleteCommand::shallShowNextLevel (const string argument) +/* + * McCabe complexity of 12, 3 caused by debug switches so its ok + */ +const map> CompleteCommand::analyze (const KeySet & ks, const Cmdline & cl) { - auto it = argument.rbegin (); - // If the argument ends in / its an indicator to complete the next level (like done by shells), but not if its escaped - return it != argument.rend () && (*it) == '/' && ((++it) == argument.rend () || (*it) != '\\'); -} + map> hierarchy; + stack keyStack; + Key parent; + Key last; + addNamespaces (hierarchy, cl); -KeySet CompleteCommand::getKeys (Key root, const bool cutAtRoot) -{ - KeySet ks; - KDB kdb; - kdb.get (ks, root); - addMountpoints (ks, root); - if (cutAtRoot) + ks.rewind (); + if (!(ks.next ())) { - ks = ks.cut (root); + return hierarchy; } - return ks; + + int curDepth = 0; + keyStack.push (ks.current ()); + while (!keyStack.empty ()) + { + Key current = keyStack.top (); + keyStack.pop (); + if (current.isDirectBelow (last)) + { // down in the hierarchy, last element is new parent + parent = last; + curDepth++; + } + + if (cl.debug) + { + cout << "Analyzing " << current << ", the last processed key is " << last << " and the parent is " << parent + << " at depth " << curDepth << endl; + } + + if (current.isDirectBelow (parent)) + { // hierarchy continues at the current level + increaseCount (hierarchy, parent, [](int p) { return p; }); + increaseCount (hierarchy, current, [=](int) { return curDepth; }); + } + else + { // hierarchy does not fit the current parent, expand the current key to the stack to find the new parent + while (!current.getBaseName ().empty () && hierarchy[current].first == 0) + { // Go back up in the hierarchy until we encounter a known key or are back at the namespace level + if (cl.debug) + { + cout << "Expanding " << current << endl; + } + keyStack.push (current); + current = getParentKey (current); + } + parent = getParentKey (current); + curDepth = hierarchy[current].second; + if (cl.debug) + { + cout << "Finished expanding, resume at " << current << " with parent " << parent << " and depth " + << curDepth << endl; + } + } + + if (keyStack.empty () && (ks.next ())) + { // Current hierarchy processed, we can resume with the next + keyStack.push (ks.current ()); + } + last = current; + } + + return hierarchy; } void CompleteCommand::printResults (const Key root, const int minDepth, const int maxDepth, const Cmdline & cl, @@ -175,6 +225,31 @@ void CompleteCommand::printResults (const Key root, const int minDepth, const in } } +const Key CompleteCommand::getParentKey (const Key key) +{ + return Key (key.getFullName ().erase (key.getFullName ().size () - key.getBaseName ().size ()), KEY_END); +} + +KeySet CompleteCommand::getKeys (Key root, const bool cutAtRoot) +{ + KeySet ks; + KDB kdb; + kdb.get (ks, root); + addMountpoints (ks, root); + if (cutAtRoot) + { + ks = ks.cut (root); + } + return ks; +} + +bool CompleteCommand::shallShowNextLevel (const string argument) +{ + auto it = argument.rbegin (); + // If the argument ends in / its an indicator to complete the next level (like done by shells), but not if its escaped + return it != argument.rend () && (*it) == '/' && ((++it) == argument.rend () || (*it) != '\\'); +} + void CompleteCommand::addMountpoints (KeySet & ks, const Key root) { KDB kdb; @@ -238,81 +313,6 @@ void CompleteCommand::addNamespaces (map> & hierarchy, const } } -/* - * McCabe complexity of 12, 3 caused by debug switches so its ok - */ -const map> CompleteCommand::analyze (const KeySet & ks, const Cmdline & cl) -{ - map> hierarchy; - stack keyStack; - Key parent; - Key last; - addNamespaces (hierarchy, cl); - - ks.rewind (); - if (!(ks.next ())) - { - return hierarchy; - } - - int curDepth = 0; - keyStack.push (ks.current ()); - while (!keyStack.empty ()) - { - Key current = keyStack.top (); - keyStack.pop (); - if (current.isDirectBelow (last)) - { // down in the hierarchy, last element is new parent - parent = last; - curDepth++; - } - - if (cl.debug) - { - cout << "Analyzing " << current << ", the last processed key is " << last << " and the parent is " << parent - << " at depth " << curDepth << endl; - } - - if (current.isDirectBelow (parent)) - { // hierarchy continues at the current level - increaseCount (hierarchy, parent, [](int p) { return p; }); - increaseCount (hierarchy, current, [=](int) { return curDepth; }); - } - else - { // hierarchy does not fit the current parent, expand the current key to the stack to find the new parent - while (!current.getBaseName ().empty () && hierarchy[current].first == 0) - { // Go back up in the hierarchy until we encounter a known key or are back at the namespace level - if (cl.debug) - { - cout << "Expanding " << current << endl; - } - keyStack.push (current); - current = getParentKey (current); - } - parent = getParentKey (current); - curDepth = hierarchy[current].second; - if (cl.debug) - { - cout << "Finished expanding, resume at " << current << " with parent " << parent << " and depth " - << curDepth << endl; - } - } - - if (keyStack.empty () && (ks.next ())) - { // Current hierarchy processed, we can resume with the next - keyStack.push (ks.current ()); - } - last = current; - } - - return hierarchy; -} - -const Key CompleteCommand::getParentKey (const Key key) -{ - return Key (key.getFullName ().erase (key.getFullName ().size () - key.getBaseName ().size ()), KEY_END); -} - void CompleteCommand::increaseCount (map> & hierarchy, const Key key, const function depthIncreaser) { const pair prev = hierarchy[key]; diff --git a/src/tools/kdb/complete.hpp b/src/tools/kdb/complete.hpp index ec07b924cfe..231399781a7 100644 --- a/src/tools/kdb/complete.hpp +++ b/src/tools/kdb/complete.hpp @@ -58,14 +58,15 @@ class CompleteCommand : public Command const std::function> & current, const bool verbose)> printResult); // helper functions - void increaseCount (std::map> & hierarchy, const kdb::Key key, - const std::function depthIncreaser); - void addMountpoints (kdb::KeySet & ks, const kdb::Key root); - void addNamespaces (std::map> & hierarchy, const Cmdline & cl); const kdb::Key getParentKey (const kdb::Key key); kdb::KeySet getKeys (kdb::Key root, bool cutAtRoot); bool shallShowNextLevel (const std::string argument); + void addMountpoints (kdb::KeySet & ks, const kdb::Key root); + void addNamespaces (std::map> & hierarchy, const Cmdline & cl); + void increaseCount (std::map> & hierarchy, const kdb::Key key, + const std::function depthIncreaser); + // print functions static void printBookmarkResult (const std::pair> & current, const bool verbose); static void printResult (const std::pair> & current, const bool verbose);