LCOV - code coverage report
Current view: top level - src/tools/kdb - complete.cpp (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 142 202 70.3 %
Date: 2019-09-12 12:28:41 Functions: 20 24 83.3 %

          Line data    Source code
       1             : /**
       2             :  * @file
       3             :  *
       4             :  * @brief
       5             :  *
       6             :  * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
       7             :  */
       8             : 
       9             : #include "complete.hpp"
      10             : 
      11             : #include <functional>
      12             : #include <iostream>
      13             : #include <limits>
      14             : #include <stack>
      15             : 
      16             : #include "cmdline.hpp"
      17             : #include <kdb.hpp>
      18             : #include <keysetio.hpp>
      19             : 
      20             : using namespace kdb;
      21             : using namespace std;
      22             : 
      23          85 : CompleteCommand::CompleteCommand ()
      24             : {
      25          85 : }
      26             : 
      27           7 : int CompleteCommand::execute (Cmdline const & cl)
      28             : {
      29           7 :         if (cl.maxDepth <= cl.minDepth)
      30             :         {
      31           0 :                 throw invalid_argument ("the maximum depth has to be larger than the minimum depth");
      32             :         }
      33           7 :         if (cl.maxDepth < 0)
      34             :         {
      35           0 :                 throw invalid_argument ("the maximum depth has to be a positive number");
      36             :         }
      37           7 :         if (cl.minDepth < 0)
      38             :         {
      39           0 :                 throw invalid_argument ("the minimum depth has to be a positive number");
      40             :         }
      41             : 
      42           7 :         cout.setf (ios_base::unitbuf);
      43           7 :         if (cl.null)
      44             :         {
      45             :                 cout.unsetf (ios_base::skipws);
      46             :         }
      47             : 
      48          14 :         const bool hasArgument = cl.arguments.size () > 0;
      49          22 :         const string argument = hasArgument ? cl.arguments[cl.arguments.size () - 1] : "";
      50           7 :         complete (argument, cl);
      51             : 
      52          14 :         return 0;
      53             : }
      54             : 
      55           7 : void CompleteCommand::complete (string const & argument, Cmdline const & cl)
      56             : {
      57             :         using namespace std::placeholders; // for bind
      58             : 
      59           7 :         if (argument.empty ())
      60             :         { // No argument, show all completions by analyzing everything including namespaces, so adjust the offset for that
      61           2 :                 const Key root = Key ("/", KEY_END);
      62           8 :                 printResults (root, cl.minDepth, cl.maxDepth, cl, analyze (getKeys (root, false, cl), cl),
      63           4 :                               bind (filterDepth, cl.minDepth, cl.maxDepth, _1), printResult);
      64             :         }
      65          12 :         else if (!argument.empty () && argument[0] == '+')
      66             :         { // is it a bookmark?
      67             :                 // Try to resolve the bookmark
      68           0 :                 const Key resolvedBookmark = cl.resolveBookmark (argument);
      69           0 :                 if (resolvedBookmark.isValid ())
      70             :                 {
      71           0 :                         complete (resolvedBookmark.getFullName (), cl);
      72             :                 }
      73             :                 else
      74             :                 { // Bookmark not resolvable, so try a bookmark completion
      75             :                         // since for legacy reasons its probably /sw/kdb, we use /sw as a root
      76           0 :                         const Key root = Key ("/sw", KEY_END);
      77           0 :                         printResults (root, 0, cl.maxDepth, cl, analyze (getKeys (root, true, cl), cl),
      78           0 :                                       bind (filterBookmarks, argument, _1), printBookmarkResult);
      79             :                 }
      80             :         }
      81             :         else
      82             :         {
      83          18 :                 const Key parsedArgument (argument, KEY_END);
      84          26 :                 if ((!parsedArgument.isValid () || !shallShowNextLevel (argument)) && parsedArgument.getBaseName ().empty ())
      85             :                 { // is it a namespace completion?
      86           4 :                         const Key root = Key ("/", KEY_END);
      87         200 :                         const auto filter = [&](const pair<Key, pair<int, int>> & c) {
      88         400 :                                 return filterDepth (cl.minDepth, cl.maxDepth, c) && filterName (argument, c);
      89         202 :                         };
      90          16 :                         printResults (root, cl.minDepth, cl.maxDepth, cl, analyze (getKeys (root, false, cl), cl), filter, printResult);
      91             :                 }
      92             :                 else
      93             :                 { // the "normal" completion cases
      94           4 :                         completeNormal (argument, parsedArgument, cl);
      95             :                 }
      96             :         }
      97           7 : }
      98             : 
      99           4 : void CompleteCommand::completeNormal (string const & argument, Key const & parsedArgument, Cmdline const & cl)
     100             : {
     101           8 :         Key root = parsedArgument;
     102           8 :         const Key parent = getParentKey (root);
     103             :         // Its important to use the parent element here as using non-existent elements may yield no keys
     104          12 :         KeySet ks = getKeys (parent, false, cl);
     105             :         // the namespaces count as existent although not found by lookup
     106          20 :         const bool isValidNamespace = parsedArgument.isValid () && parsedArgument.getBaseName ().empty ();
     107          13 :         const bool rootExists = isValidNamespace || ks.lookup (root);
     108           4 :         if (!rootExists)
     109             :         {
     110             :                 root = parent;
     111             :         }
     112           8 :         const auto result = analyze (ks, cl);
     113             : 
     114             :         // we see depth relative to the completion level, if the root exists, distance will be higher so subtract 1
     115             :         // to add up for the offset added my shallShowNextLevel
     116           8 :         const int offset = getKeyDepth (root) - rootExists + shallShowNextLevel (argument);
     117           4 :         if (cl.debug)
     118             :         {
     119           0 :                 cout << "Root is " << root << " with a key depth of " << getKeyDepth (root) << endl;
     120           0 :                 cout << "The root exists: " << rootExists << "and we shall show the next level: " << shallShowNextLevel (argument) << endl;
     121           0 :                 cout << "Offset relative to completion level is " << offset << endl;
     122             :         }
     123             : 
     124           4 :         const auto nameFilter = root.isCascading () ? filterCascading : filterName;
     125             :         // Let elektra handle the escaping of the input for us
     126           8 :         const string argumentEscaped = parsedArgument.getFullName ();
     127          72 :         const auto filter = [&](const pair<Key, pair<int, int>> & c) {
     128         288 :                 return filterDepth (cl.minDepth + offset,
     129         297 :                                     max (cl.maxDepth, cl.maxDepth > INT_MAX - offset ? INT_MAX : cl.maxDepth + offset), c) &&
     130           9 :                        nameFilter (argument, c);
     131          76 :         };
     132          12 :         printResults (root, cl.minDepth, cl.maxDepth, cl, result, filter, printResult);
     133           4 : }
     134             : 
     135             : /*
     136             :  * McCabe complexity of 12, 3 caused by debug switches so its ok
     137             :  */
     138           7 : const map<Key, pair<int, int>> CompleteCommand::analyze (KeySet const & ks, Cmdline const & cl)
     139             : {
     140           7 :         map<Key, pair<int, int>> hierarchy;
     141          21 :         stack<Key> keyStack;
     142          14 :         Key parent;
     143          14 :         Key last;
     144           7 :         addNamespaces (hierarchy, cl);
     145             : 
     146           7 :         ks.rewind ();
     147          21 :         if (!(ks.next ()))
     148             :         {
     149             :                 return hierarchy;
     150             :         }
     151             : 
     152           7 :         const Key first = ks.current ();
     153           7 :         int curDepth = getKeyDepth (first) - 1;
     154          21 :         hierarchy[first] = pair<int, int> (0, curDepth);
     155             :         keyStack.push (first);
     156             : 
     157         857 :         while (!keyStack.empty ())
     158             :         {
     159        1275 :                 Key current = keyStack.top ();
     160         425 :                 keyStack.pop ();
     161         843 :                 if (last.isValid () && current.isDirectBelow (last))
     162             :                 { // down in the hierarchy, last element is new parent
     163         135 :                         parent = last;
     164         135 :                         curDepth++;
     165             :                 }
     166             : 
     167         425 :                 if (cl.debug)
     168             :                 {
     169           0 :                         cout << "Analyzing " << current << ", the last processed key is " << last << " and the parent is " << parent
     170           0 :                              << " at depth " << curDepth << endl;
     171             :                 }
     172             : 
     173         425 :                 if (current.isDirectBelow (parent))
     174             :                 { // hierarchy continues at the current level
     175        1113 :                         increaseCount (hierarchy, parent, [](int p) { return p; });
     176        1484 :                         increaseCount (hierarchy, current, [=](int) { return curDepth; });
     177             :                 }
     178             :                 else
     179             :                 { // hierarchy does not fit the current parent, expand the current key to the stack to find the new parent
     180          54 :                         Key tmp = current;
     181         649 :                         while (!hierarchy[current].first && !(current.getBaseName ().empty () && 1 == getKeyDepth (current)))
     182             :                         { // Go back up in the hierarchy until we encounter a known key or are back at the namespace level
     183         119 :                                 if (cl.debug)
     184             :                                 {
     185           0 :                                         cout << "Expanding " << tmp << endl;
     186             :                                 }
     187         119 :                                 keyStack.push (tmp);
     188         119 :                                 current = tmp;
     189         357 :                                 tmp = getParentKey (tmp);
     190             :                         }
     191         162 :                         parent = getParentKey (current);
     192          54 :                         curDepth = hierarchy[current].second;
     193          54 :                         if (cl.debug)
     194             :                         {
     195           0 :                                 cout << "Finished expanding, resume at " << current << " with parent " << parent << " and depth "
     196           0 :                                      << curDepth << endl;
     197             :                         }
     198             :                 }
     199             : 
     200        1768 :                 if (keyStack.empty () && (ks.next ()))
     201             :                 { // Current hierarchy processed, we can resume with the next
     202         897 :                         keyStack.push (ks.current ());
     203             :                 }
     204         425 :                 last = current;
     205             :         }
     206             : 
     207             :         return hierarchy;
     208             : }
     209             : 
     210           7 : void CompleteCommand::printResults (
     211             :         Key const & root, const int minDepth, const int maxDepth, Cmdline const & cl, map<Key, pair<int, int>> const & result,
     212             :         std::function<bool(pair<Key, pair<int, int>> const & current)> const & filter,
     213             :         std::function<void(pair<Key, pair<int, int>> const & current, const bool verbose)> const & resultPrinter)
     214             : {
     215           7 :         if (cl.verbose)
     216             :         {
     217           0 :                 cout << "Showing results for a minimum depth of " << minDepth;
     218           0 :                 if (maxDepth != numeric_limits<int>::max ())
     219             :                 {
     220           0 :                         cout << " and a maximum depth of " << maxDepth;
     221             :                 }
     222             :                 else
     223             :                 {
     224           0 :                         cout << " and no maximum depth";
     225             :                 }
     226             :                 cout << endl;
     227             :         }
     228             : 
     229         393 :         for (const auto & it : result)
     230             :         {
     231        1488 :                 if (cl.debug || filter (it))
     232             :                 {
     233          64 :                         resultPrinter (it, cl.verbose);
     234             :                 }
     235             :         }
     236             : 
     237           7 :         if (cl.debug || cl.verbose)
     238             :         { // Only print this in debug mode to avoid destroying autocompletions because of warnings
     239           0 :                 printWarnings (cerr, root, cl.verbose, cl.debug);
     240             :         }
     241           7 : }
     242             : 
     243          11 : int CompleteCommand::getKeyDepth (Key const & key)
     244             : {
     245          22 :         return std::distance (key.begin (), key.end ());
     246             : }
     247             : 
     248         177 : const Key CompleteCommand::getParentKey (Key const & key)
     249             : {
     250         354 :         Key parentKey = key.dup (); // We can't set baseName on keys in keysets, so duplicate it
     251         177 :         ckdb::keySetBaseName (parentKey.getKey (), NULL);
     252         177 :         return parentKey;
     253             : }
     254             : 
     255           7 : KeySet CompleteCommand::getKeys (Key root, const bool cutAtRoot, Cmdline const & cl)
     256             : {
     257           7 :         KeySet ks;
     258          14 :         KDB kdb;
     259           7 :         kdb.get (ks, root);
     260           7 :         addMountpoints (ks, root, cl);
     261           7 :         if (cutAtRoot)
     262             :         {
     263           0 :                 ks = ks.cut (root);
     264             :         }
     265           7 :         return ks;
     266             : }
     267             : 
     268           0 : bool CompleteCommand::shallShowNextLevel (string const & argument)
     269             : {
     270           9 :         auto it = argument.rbegin ();
     271             :         // If the argument ends in / its an indicator to complete the next level (like done by shells), but not if its escaped
     272          60 :         return it != argument.rend () && (*it) == '/' && ((++it) == argument.rend () || (*it) != '\\');
     273             : }
     274             : 
     275           7 : void CompleteCommand::addMountpoints (KeySet & ks, Key const & root, Cmdline const & cl)
     276             : {
     277          14 :         KDB kdb;
     278          14 :         Key mountpointPath ("system/elektra/mountpoints", KEY_END);
     279          14 :         KeySet mountpoints;
     280             : 
     281           7 :         kdb.get (mountpoints, mountpointPath);
     282          35 :         mountpoints = mountpoints.cut (mountpointPath);
     283             : 
     284          21 :         for (const Key mountpoint : mountpoints)
     285             :         {
     286           0 :                 if (mountpoint.isDirectBelow (mountpointPath))
     287             :                 {
     288           0 :                         const string actualName = mountpoints.lookup (mountpoint.getFullName () + "/mountpoint").getString ();
     289           0 :                         Key mountpointKey (actualName, KEY_END);
     290             :                         // If the mountpoint already has some contents, its expanded with a namespace, so leave it out then
     291           0 :                         if (mountpointKey.isBelow (root) && !KeySet (ks).cut (mountpointKey).size ())
     292             :                         {
     293             :                                 ks.append (mountpointKey);
     294             :                         }
     295             :                 }
     296             :         }
     297             : 
     298           7 :         if (cl.debug || cl.verbose)
     299             :         { // Only print this in debug mode to avoid destroying autocompletions because of warnings
     300           0 :                 printWarnings (cerr, mountpointPath, cl.verbose, cl.debug);
     301             :         }
     302           7 : }
     303             : 
     304             : /*
     305             :  * McCabe complexity of 11, 4 caused by debug switches so its ok
     306             :  */
     307           7 : void CompleteCommand::addNamespaces (map<Key, pair<int, int>> & hierarchy, Cmdline const & cl)
     308             : {
     309             :         const string namespaces[] = {
     310             :                 "spec/", "proc/", "dir/", "user/", "system/",
     311         119 :         };
     312             : 
     313             :         // Check for new namespaces, issue a warning in case
     314           7 :         if (cl.debug || cl.verbose)
     315             :         {
     316           0 :                 for (elektraNamespace ens = KEY_NS_FIRST; ens <= KEY_NS_LAST; ++ens)
     317             :                 {
     318             :                         // since ens are numbers, there is no way to get a string representation if not found in that case
     319             :                         bool found = false;
     320           0 :                         for (const string ns : namespaces)
     321             :                         {
     322           0 :                                 found = found || ckdb::keyGetNamespace (Key (ns, KEY_END).getKey ()) == ens;
     323             :                         }
     324           0 :                         if (!found)
     325             :                         {
     326           0 :                                 cerr << "Missing namespace detected:" << ens << ". \nPlease report this issue." << endl;
     327             :                         }
     328             :                 }
     329             :         }
     330             : 
     331         112 :         for (const string ns : namespaces)
     332             :         {
     333         105 :                 const Key nsKey (ns, KEY_END);
     334          35 :                 if ((cl.debug || cl.verbose) && ckdb::keyGetNamespace (nsKey.getKey ()) == KEY_NS_EMPTY)
     335             :                 { // Check for outdated namespaces, issue a warning in case
     336           0 :                         cerr << "Outdated namespace detected:" << ns << ".\nPlease report this issue." << endl;
     337             :                 }
     338         105 :                 hierarchy[nsKey] = pair<int, int> (1, 0);
     339             :         }
     340           7 : }
     341             : 
     342         742 : void CompleteCommand::increaseCount (map<Key, pair<int, int>> & hierarchy, Key const & key, function<int(int)> const & depthIncreaser)
     343             : {
     344         742 :         const pair<int, int> prev = hierarchy[key];
     345        2968 :         hierarchy[key] = pair<int, int> (prev.first + 1, depthIncreaser (prev.second));
     346         742 : }
     347             : 
     348         100 : bool CompleteCommand::filterDepth (const int minDepth, const int maxDepth, pair<Key, pair<int, int>> const & current)
     349             : {
     350         372 :         return current.second.second >= minDepth && current.second.second < maxDepth;
     351             : }
     352             : 
     353           8 : bool CompleteCommand::filterCascading (string const & argument, pair<Key, pair<int, int>> const & current)
     354             : {
     355             :         // For a cascading key completion, ignore the preceding namespace
     356          16 :         const string test = current.first.getFullName ();
     357           8 :         size_t cascadationOffset = test.find ("/");
     358           8 :         if (cascadationOffset == string::npos)
     359             :         {
     360           0 :                 cascadationOffset = 0;
     361             :         }
     362          24 :         return argument.size () <= test.size () - cascadationOffset &&
     363          56 :                equal (argument.begin (), argument.end (), test.begin () + cascadationOffset);
     364             : }
     365             : 
     366          11 : bool CompleteCommand::filterName (string const & argument, pair<Key, pair<int, int>> const & current)
     367             : {
     368          22 :         const string test = current.first.getFullName ();
     369          62 :         return argument.size () <= test.size () && equal (argument.begin (), argument.end (), test.begin ());
     370             : }
     371             : 
     372             : /**
     373             :  * McCabe Complexity of 15 due to the boolean conjunctions, easy to understand so its ok
     374             :  */
     375           0 : bool CompleteCommand::filterBookmarks (string const & bookmarkName, pair<Key, pair<int, int>> const & current)
     376             : {
     377             :         // For a bookmark completion, ignore everything except the bookmarks by comparing the base name
     378             :         // as we search in /sw due to legacy reasons, ensure we have an actual bookmark by checking the path
     379           0 :         bool elektraFound = false;
     380           0 :         bool kdbFound = false;
     381           0 :         bool bookmarksFound = false;
     382           0 :         bool bookmarkFound = false;
     383             : 
     384           0 :         for (const string part : current.first)
     385             :         {
     386             :                 // size 1 -> +, so show all bookmarks, order of these checks is important!
     387           0 :                 bookmarkFound = bookmarksFound && (bookmarkFound || bookmarkName.size () == 1 ||
     388           0 :                                                    (bookmarkName.size () - 1 <= part.size () &&
     389           0 :                                                     equal (bookmarkName.begin () + 1, bookmarkName.end (), part.begin ())));
     390           0 :                 bookmarksFound = bookmarksFound || (part == "bookmarks" && !bookmarkFound);
     391           0 :                 kdbFound = kdbFound || (!bookmarksFound && part == "kdb");
     392           0 :                 elektraFound = elektraFound || (!kdbFound && part == "elektra");
     393             :         }
     394             : 
     395           0 :         return (elektraFound || kdbFound) && bookmarksFound && bookmarkFound;
     396             : }
     397             : 
     398           0 : void CompleteCommand::printBookmarkResult (pair<Key, pair<int, int>> const & current, const bool verbose)
     399             : { // Ignore the path for a bookmark completion
     400           0 :         cout << "+" << current.first.getBaseName ();
     401           0 :         if (current.second.first > 1)
     402             :         {
     403           0 :                 cout << "/";
     404             :         }
     405           0 :         if (verbose)
     406             :         {
     407           0 :                 cout << (current.second.first > 1 ? " node " : " leaf ");
     408           0 :                 cout << (current.second.first - 1) << " " << current.second.second;
     409             :         }
     410           0 :         cout << endl;
     411           0 : }
     412             : 
     413          16 : void CompleteCommand::printResult (pair<Key, pair<int, int>> const & current, const bool verbose)
     414             : {
     415          48 :         cout << current.first.getFullName ();
     416          16 :         if (current.second.first > 1)
     417             :         {
     418           9 :                 cout << "/";
     419             :         }
     420          16 :         if (verbose)
     421             :         {
     422           0 :                 cout << (current.second.first > 1 ? " node " : " leaf ");
     423           0 :                 cout << (current.second.first - 1) << " " << current.second.second;
     424             :         }
     425          16 :         cout << endl;
     426          16 : }
     427             : 
     428          85 : CompleteCommand::~CompleteCommand ()
     429             : {
     430        7249 : }

Generated by: LCOV version 1.13