Skip to content

Console: sorting command suggestions #10893

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
flip111 opened this issue May 12, 2014 · 10 comments
Closed

Console: sorting command suggestions #10893

flip111 opened this issue May 12, 2014 · 10 comments

Comments

@flip111
Copy link
Contributor

flip111 commented May 12, 2014

The function to get the alternative commands does some kind of sorting on a $threshold variable as it looks like. The function docblock does not describe this functionality and i don't understand why it would not just order alphabetically which seems much more user friendly??

Typing console doctrine gives:

doctrine:cache:clear-collection-region
doctrine:ensure-production-settings
doctrine:cache:clear-entity-region
doctrine:cache:clear-query-region
doctrine:cache:clear-metadata
doctrine:migrations:generate
doctrine:migrations:migrate
doctrine:migrations:version
doctrine:migrations:execute
doctrine:cache:clear-result
doctrine:cache:clear-query
doctrine:migrations:latest
doctrine:migrations:status
doctrine:generate:entities
doctrine:schema:validate
doctrine:migrations:diff
doctrine:database:create
doctrine:mapping:convert
doctrine:generate:entity
doctrine:mapping:import
doctrine:database:drop
doctrine:schema:create
doctrine:schema:update
doctrine:fixtures:load
doctrine:generate:form
doctrine:generate:crud
doctrine:mapping:info
doctrine:schema:drop
doctrine:query:sql
doctrine:query:dql
generate:doctrine:form
generate:doctrine:crud
generate:doctrine:entity
generate:doctrine:entities

And alphabetically this would look like:

doctrine:cache:clear-collection-region
doctrine:cache:clear-entity-region
doctrine:cache:clear-metadata
doctrine:cache:clear-query
doctrine:cache:clear-query-region
doctrine:cache:clear-result
doctrine:database:create
doctrine:database:drop
doctrine:ensure-production-settings
doctrine:fixtures:load
doctrine:generate:crud
doctrine:generate:entities
doctrine:generate:entity
doctrine:generate:form
doctrine:mapping:convert
doctrine:mapping:import
doctrine:mapping:info
doctrine:migrations:diff
doctrine:migrations:execute
doctrine:migrations:generate
doctrine:migrations:latest
doctrine:migrations:migrate
doctrine:migrations:status
doctrine:migrations:version
doctrine:query:dql
doctrine:query:sql
doctrine:schema:create
doctrine:schema:drop
doctrine:schema:update
doctrine:schema:validate
generate:doctrine:crud
generate:doctrine:entities
generate:doctrine:entity
generate:doctrine:form

For example doctrine:migrations are now grouped together

@sstok
Copy link
Contributor

sstok commented May 12, 2014

👍

@stof stof added the Console label May 12, 2014
@adrianolek
Copy link
Contributor

What this function tries to do is to find best matching command, so if you type console doctrine:migr the migrations commands will be listed first along with other similar commands. However I feel like sorting using levenshtein distance isn't the most user friendly way (also I believe this function is buggy as the longest matching commands are on top).

@flip111
Copy link
Contributor Author

flip111 commented Jun 5, 2014

What about this?

1.If the exact string can be found in one of the commands (for example doctrine in doctrine:generate:form) then show only these commands sorted alphabetically.
2. If the exact string can not be found doctrone then show the best levenshtein matches sorted by best match.

Alternatively in case one a second list could be shown with the levenshtein matches. (but i think two lists below each other are confusing and maybe too long)

@fabpot
Copy link
Member

fabpot commented Jan 2, 2015

@flip111 Your suggestion looks good to me.

@flip111
Copy link
Contributor Author

flip111 commented Jan 25, 2015

So i made this now

    private function permutation($array) {
      switch (count($array)) {
        case 0:
        case 1:
          return array($array);
      }

      foreach ($array as $k => $v) {
        $copy = $array;
        unset($copy[$k]);

        foreach ($this->permutation($copy) as $other) {
          $other[] = $v;
          $return[] = $other;
        }
      }

      return $return;
    }

    /**
     * Finds alternative of $name among $collection,
     * if nothing is found in $collection, try in $abbrevs.
     *
     * @param string             $name       The string
     * @param array|\Traversable $collection The collection
     *
     * @return array A sorted array of similar string
     */
    private function findAlternatives($name, $collection)
    {
        $alternatives = array();

        $nameParts = explode(':', $name);
        $namePartsCount = count($nameParts);

        foreach ($collection as $item) {
            // Saving the collectionParts by item, we might need them later
            $collectionParts[$item] = explode(':', $item);

            $notFoundCount = count(array_diff($nameParts, $collectionParts[$item]));

            if ($notFoundCount < $namePartsCount) {
              $alternatives[] = $item;
            }
        }

        if (count($alternatives) !== 0) {
          sort($alternatives);
          return $alternatives;
        }

        foreach ($collectionParts as $fullName => $parts) {
          $score = PHP_INT_MAX;

          // Find all different orders of how to match collectionName to name
          // use the best match
          foreach ($this->permutation($parts) as $i => $parts2) {
            // We can only match one part onto one subname
            $parts2 = array_slice($parts2, 0, $namePartsCount);

            $scoreAllParts = 0;
            foreach ($parts2 as $i2 => $part2) {
              // We suppose it's more likely that the user gets the command
              // wrong the longer it is. So we compensate by the name length.
              $scoreAllParts += levenshtein($nameParts[$i2], $part2) / count($nameParts[$i2]);
            }

            if ($scoreAllParts < $score) {
              $score = $scoreAllParts;
            }
          }

          $alternatives[$fullName] = $score;
        }

        // Don't return result which are too unlikely to match
        $alternatives = array_filter($alternatives, function($el) {
          return $el <= 3;
        });

        // Sort by levenshtein first, then alfabetically
        array_multisort($alternatives, array_values($alternatives), SORT_ASC, array_keys($alternatives), SORT_ASC);

        return array_keys($alternatives);
    }

results:

php app/console doctfig

  [InvalidArgumentException]
  Command "doctfig" is not defined.
  Did you mean one of these?
      config:debug
      config:dump-reference
      debug:config
      doctrine:cache:clear-metadata
      doctrine:cache:clear-query
      doctrine:cache:clear-result
      doctrine:database:create
      doctrine:database:drop
      doctrine:ensure-production-settings
      doctrine:generate:crud
      doctrine:generate:entities
      doctrine:generate:entity
      doctrine:generate:form
      doctrine:mapping:convert
      doctrine:mapping:import
      doctrine:mapping:info
      doctrine:query:dql
      doctrine:query:sql
      doctrine:schema:create
      doctrine:schema:drop
      doctrine:schema:update
      doctrine:schema:validate
      generate:doctrine:crud
      generate:doctrine:entities
      generate:doctrine:entity
      generate:doctrine:form
php app/console doctrine

  [InvalidArgumentException]
  Command "doctrine" is not defined.
  Did you mean one of these?
      doctrine:cache:clear-metadata
      doctrine:cache:clear-query
      doctrine:cache:clear-result
      doctrine:database:create
      doctrine:database:drop
      doctrine:ensure-production-settings
      doctrine:generate:crud
      doctrine:generate:entities
      doctrine:generate:entity
      doctrine:generate:form
      doctrine:mapping:convert
      doctrine:mapping:import
      doctrine:mapping:info
      doctrine:query:dql
      doctrine:query:sql
      doctrine:schema:create
      doctrine:schema:drop
      doctrine:schema:update
      doctrine:schema:validate
      generate:doctrine:crud
      generate:doctrine:entities
      generate:doctrine:entity
      generate:doctrine:form

I think the original implementation was confusing and also had some weird edge cases. For this implementation there probably need to be some unit tests but that's still a hurdle to take ^^

@OskarStark
Copy link
Contributor

the results looks good to me, thats what i expected.
i will check the code soon, but some unit tests would be nice.

another improvement coould be if "Did you mean" has exactly one result, the console ould ask you interactively if the command should be executed!

@OskarStark
Copy link
Contributor

@flip111 +1

@OskarStark
Copy link
Contributor

ping @fabpot what do you think about the interactive shell to execute the command?

@javiereguiluz
Copy link
Member

@kix you recently authored #17485, which is similar to the feature discussed here. Do you think it would be easy for you to create a new pull request to implement the idea proposed by this issue? Thanks!

@kix
Copy link
Contributor

kix commented Jan 27, 2016

@javiereguiluz, I think this shouldn't be a problem, thanks for the suggestion; I'll investigate and try to come up with a PR for this.

fabpot added a commit that referenced this issue Mar 22, 2017
… found (javiereguiluz)

This PR was squashed before being merged into the 3.3-dev branch (closes #19887).

Discussion
----------

Sort alternatives alphabetically when a command is not found

| Q | A |
| --- | --- |
| Branch? | master |
| Bug fix? | no |
| New feature? | yes |
| BC breaks? | no |
| Deprecations? | no |
| Tests pass? | yes |
| Fixed tickets | #10893 |
| License | MIT |
| Doc PR | - |

Commits
-------

ba6c946 Sort commands like a human would do
f04b1bd Sort alternatives alphabetically when a command is not found
@fabpot fabpot closed this as completed Mar 22, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants