Repository: conan-io/conan

Issue:
Inconsistency between local and remote version of `conan search`
Depending on searching either in remotes or locally we're getting different results for situations where we don't use wildcards.  
Example:
```
$ conan search zlib
There are no packages matching the 'zlib' pattern

$ conan search zlib*
Existing package recipes:

zlib/1.2.8@conan/stable
zlib/1.2.11@conan/stable
```
```
$ conan search zlib -r conan-center
Existing package recipes:

zlib/1.2.8@conan/stable
zlib/1.2.11@conan/stable
zlib/1.2.11@conan/testing
```
Same for combinations such as `zlib/1.2.8`, `zlib/1.2.8@`, `zlib/1.2.8@conan`, `zlib/1.2.8@conan/` except for `zlib/`.

Proposition: make local search act in the same manner as remote search.


Discussion:

Resolving patch:
diff --git a/conans/search/search.py b/conans/search/search.py
--- a/conans/search/search.py
+++ b/conans/search/search.py
@@ -1,7 +1,6 @@
 import re
 import os
 
-
 from fnmatch import translate
 
 from conans.errors import ConanException, NotFoundException
@@ -90,11 +89,27 @@ def search_recipes(paths, pattern=None, ignorecase=True):
         for subdir in subdirs:
             conan_ref = ConanFileReference(*subdir.split("/"))
             if pattern:
-                if pattern.match(str(conan_ref)):
+                if _partial_match(pattern, conan_ref):
                     ret.append(conan_ref)
+
         return sorted(ret)
 
 
+def _partial_match(pattern, conan_ref):
+    """
+    Finds if pattern matches any of partial sums of tokens of conan reference
+    """
+    
+    tokens = str(conan_ref).replace('/', ' / ').replace('@', ' @ ').split()
+
+    def partial_sums(iterable):
+        sum = ''
+        for i in iterable:
+            sum += i
+            yield sum
+
+    return any(map(pattern.match, list(partial_sums(tokens))))
+
 def search_packages(paths, reference, query):
     """ Return a dict like this:
 


Test patch:
diff --git a/conans/test/command/search_test.py b/conans/test/command/search_test.py
--- a/conans/test/command/search_test.py
+++ b/conans/test/command/search_test.py
@@ -188,6 +188,59 @@ def recipe_search_test(self):
                       "Hello/1.4.11@fenix/testing\n"
                       "Hello/1.4.12@fenix/testing\n", self.client.user_io.out)
 
+    def search_partial_match_test(self):
+        self.client.run("search Hello")
+        self.assertEquals("Existing package recipes:\n\n"
+                          "Hello/1.4.10@fenix/testing\n"
+                          "Hello/1.4.11@fenix/testing\n"
+                          "Hello/1.4.12@fenix/testing\n", self.client.user_io.out)
+
+        self.client.run("search hello")
+        self.assertEquals("Existing package recipes:\n\n"
+                          "Hello/1.4.10@fenix/testing\n"
+                          "Hello/1.4.11@fenix/testing\n"
+                          "Hello/1.4.12@fenix/testing\n", self.client.user_io.out)
+
+        self.client.run("search Hello --case-sensitive")
+        self.assertEquals("Existing package recipes:\n\n"
+                          "Hello/1.4.10@fenix/testing\n"
+                          "Hello/1.4.11@fenix/testing\n"
+                          "Hello/1.4.12@fenix/testing\n", self.client.user_io.out)
+
+        self.client.run("search Hel")
+        self.assertEquals("There are no packages matching the 'Hel' pattern\n", self.client.user_io.out)
+
+        self.client.run("search Hello/")
+        self.assertEquals("Existing package recipes:\n\n"
+                          "Hello/1.4.10@fenix/testing\n"
+                          "Hello/1.4.11@fenix/testing\n"
+                          "Hello/1.4.12@fenix/testing\n", self.client.user_io.out)
+
+        self.client.run("search Hello/1.4.10")
+        self.assertEquals("Existing package recipes:\n\n"
+                          "Hello/1.4.10@fenix/testing\n", self.client.user_io.out)
+
+        self.client.run("search Hello/1.4")
+        self.assertEquals("There are no packages matching the 'Hello/1.4' pattern\n", self.client.user_io.out)
+
+        self.client.run("search Hello/1.4.10@")
+        self.assertEquals("Existing package recipes:\n\n"
+                          "Hello/1.4.10@fenix/testing\n", self.client.user_io.out)
+
+        self.client.run("search Hello/1.4.10@fenix")
+        self.assertEquals("Existing package recipes:\n\n"
+                          "Hello/1.4.10@fenix/testing\n", self.client.user_io.out)
+  
+        self.client.run("search Hello/1.4.10@fen")
+        self.assertEquals("There are no packages matching the 'Hello/1.4.10@fen' pattern\n", self.client.user_io.out)
+
+        self.client.run("search Hello/1.4.10@fenix/")
+        self.assertEquals("Existing package recipes:\n\n"
+                          "Hello/1.4.10@fenix/testing\n", self.client.user_io.out)
+
+        self.client.run("search Hello/1.4.10@fenix/test")
+        self.assertEquals("There are no packages for reference 'Hello/1.4.10@fenix/test'\n", self.client.user_io.out)
+
     def search_raw_test(self):
         self.client.run("search Hello* --raw")
         self.assertEquals("Hello/1.4.10@fenix/testing\n"