prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN 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.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `conans/search/search.py`
  Content:
  ```
  1 import re
  2 import os
  3 
  4 
  5 from fnmatch import translate
  6 
  7 from conans.errors import ConanException, NotFoundException
  8 from conans.model.info import ConanInfo
  9 from conans.model.ref import PackageReference, ConanFileReference
  10 from conans.paths import CONANINFO
  11 from conans.util.log import logger
  12 from conans.search.query_parse import infix_to_postfix, evaluate_postfix
  13 from conans.util.files import list_folder_subdirs, load
  14 
  15 
  16 def filter_outdated(packages_infos, recipe_hash):
  17     result = {}
  18     for package_id, info in packages_infos.items():
  19         try:  # Existing package_info of old package might not have recipe_hash
  20             if info["recipe_hash"] != recipe_hash:
  21                 result[package_id] = info
  22         except KeyError:
  23             pass
  24     return result
  25 
  26 
  27 def filter_packages(query, package_infos):
  28     if query is None:
  29         return package_infos
  30     try:
  31         if "!" in query:
  32             raise ConanException("'!' character is not allowed")
  33         if " not " in query or query.startswith("not "):
  34             raise ConanException("'not' operator is not allowed")
  35         postfix = infix_to_postfix(query) if query else []
  36         result = {}
  37         for package_id, info in package_infos.items():
  38             if evaluate_postfix_with_info(postfix, info):
  39                 result[package_id] = info
  40         return result
  41     except Exception as exc:
  42         raise ConanException("Invalid package query: %s. %s" % (query, exc))
  43 
  44 
  45 def evaluate_postfix_with_info(postfix, conan_vars_info):
  46 
  47     # Evaluate conaninfo with the expression
  48 
  49     def evaluate_info(expression):
  50         """Receives an expression like compiler.version="12"
  51         Uses conan_vars_info in the closure to evaluate it"""
  52         name, value = expression.split("=", 1)
  53         value = value.replace("\"", "")
  54         return evaluate(name, value, conan_vars_info)
  55 
  56     return evaluate_postfix(postfix, evaluate_info)
  57 
  58 
  59 def evaluate(prop_name, prop_value, conan_vars_info):
  60     """
  61     Evaluates a single prop_name, prop_value like "os", "Windows" against conan_vars_info.serialize_min()
  62     """
  63 
  64     def compatible_prop(setting_value, prop_value):
  65         return setting_value is None or prop_value == setting_value
  66 
  67     info_settings = conan_vars_info.get("settings", [])
  68     info_options = conan_vars_info.get("options", [])
  69 
  70     if prop_name in ["os", "compiler", "arch", "build_type"] or prop_name.startswith("compiler."):
  71         return compatible_prop(info_settings.get(prop_name, None), prop_value)
  72     else:
  73         return compatible_prop(info_options.get(prop_name, None), prop_value)
  74     return False
  75 
  76 
  77 def search_recipes(paths, pattern=None, ignorecase=True):
  78     # Conan references in main storage
  79     if pattern:
  80         if isinstance(pattern, ConanFileReference):
  81             pattern = str(pattern)
  82         pattern = translate(pattern)
  83         pattern = re.compile(pattern, re.IGNORECASE) if ignorecase else re.compile(pattern)
  84 
  85     subdirs = list_folder_subdirs(basedir=paths.store, level=4)
  86     if not pattern:
  87         return sorted([ConanFileReference(*folder.split("/")) for folder in subdirs])
  88     else:
  89         ret = []
  90         for subdir in subdirs:
  91             conan_ref = ConanFileReference(*subdir.split("/"))
  92             if pattern:
  93                 if pattern.match(str(conan_ref)):
  94                     ret.append(conan_ref)
  95         return sorted(ret)
  96 
  97 
  98 def search_packages(paths, reference, query):
  99     """ Return a dict like this:
  100 
  101             {package_ID: {name: "OpenCV",
  102                            version: "2.14",
  103                            settings: {os: Windows}}}
  104     param conan_ref: ConanFileReference object
  105     """
  106     infos = _get_local_infos_min(paths, reference)
  107     return filter_packages(query, infos)
  108 
  109 
  110 def _get_local_infos_min(paths, reference):
  111     result = {}
  112     packages_path = paths.packages(reference)
  113     subdirs = list_folder_subdirs(packages_path, level=1)
  114     for package_id in subdirs:
  115         # Read conaninfo
  116         try:
  117             package_reference = PackageReference(reference, package_id)
  118             info_path = os.path.join(paths.package(package_reference,
  119                                                    short_paths=None), CONANINFO)
  120             if not os.path.exists(info_path):
  121                 raise NotFoundException("")
  122             conan_info_content = load(info_path)
  123             conan_vars_info = ConanInfo.loads(conan_info_content).serialize_min()
  124             result[package_id] = conan_vars_info
  125 
  126         except Exception as exc:
  127             logger.error("Package %s has no ConanInfo file" % str(package_reference))
  128             if str(exc):
  129                 logger.error(str(exc))
  130 
  131     return result
  132
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: 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 @@
           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:
  
verification_info: {"golden_diff": "diff --git a/conans/search/search.py b/conans/search/search.py\n--- a/conans/search/search.py\n+++ b/conans/search/search.py\n@@ -1,7 +1,6 @@\n import re\n import os\n \n-\n from fnmatch import translate\n \n from conans.errors import ConanException, NotFoundException\n@@ -90,11 +89,27 @@\n         for subdir in subdirs:\n             conan_ref = ConanFileReference(*subdir.split(\"/\"))\n             if pattern:\n-                if pattern.match(str(conan_ref)):\n+                if _partial_match(pattern, conan_ref):\n                     ret.append(conan_ref)\n+\n         return sorted(ret)\n \n \n+def _partial_match(pattern, conan_ref):\n+    \"\"\"\n+    Finds if pattern matches any of partial sums of tokens of conan reference\n+    \"\"\"\n+    \n+    tokens = str(conan_ref).replace('/', ' / ').replace('@', ' @ ').split()\n+\n+    def partial_sums(iterable):\n+        sum = ''\n+        for i in iterable:\n+            sum += i\n+            yield sum\n+\n+    return any(map(pattern.match, list(partial_sums(tokens))))\n+\n def search_packages(paths, reference, query):\n     \"\"\" Return a dict like this:\n", "issue": "Inconsistency between local and remote version of `conan search`\nDepending on searching either in remotes or locally we're getting different results for situations where we don't use wildcards.  \r\nExample:\r\n```\r\n$ conan search zlib\r\nThere are no packages matching the 'zlib' pattern\r\n\r\n$ conan search zlib*\r\nExisting package recipes:\r\n\r\nzlib/1.2.8@conan/stable\r\nzlib/1.2.11@conan/stable\r\n```\r\n```\r\n$ conan search zlib -r conan-center\r\nExisting package recipes:\r\n\r\nzlib/1.2.8@conan/stable\r\nzlib/1.2.11@conan/stable\r\nzlib/1.2.11@conan/testing\r\n```\r\nSame 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/`.\r\n\r\nProposition: make local search act in the same manner as remote search.\n", "before_files": [{"content": "import re\nimport os\n\n\nfrom fnmatch import translate\n\nfrom conans.errors import ConanException, NotFoundException\nfrom conans.model.info import ConanInfo\nfrom conans.model.ref import PackageReference, ConanFileReference\nfrom conans.paths import CONANINFO\nfrom conans.util.log import logger\nfrom conans.search.query_parse import infix_to_postfix, evaluate_postfix\nfrom conans.util.files import list_folder_subdirs, load\n\n\ndef filter_outdated(packages_infos, recipe_hash):\n    result = {}\n    for package_id, info in packages_infos.items():\n        try:  # Existing package_info of old package might not have recipe_hash\n            if info[\"recipe_hash\"] != recipe_hash:\n                result[package_id] = info\n        except KeyError:\n            pass\n    return result\n\n\ndef filter_packages(query, package_infos):\n    if query is None:\n        return package_infos\n    try:\n        if \"!\" in query:\n            raise ConanException(\"'!' character is not allowed\")\n        if \" not \" in query or query.startswith(\"not \"):\n            raise ConanException(\"'not' operator is not allowed\")\n        postfix = infix_to_postfix(query) if query else []\n        result = {}\n        for package_id, info in package_infos.items():\n            if evaluate_postfix_with_info(postfix, info):\n                result[package_id] = info\n        return result\n    except Exception as exc:\n        raise ConanException(\"Invalid package query: %s. %s\" % (query, exc))\n\n\ndef evaluate_postfix_with_info(postfix, conan_vars_info):\n\n    # Evaluate conaninfo with the expression\n\n    def evaluate_info(expression):\n        \"\"\"Receives an expression like compiler.version=\"12\"\n        Uses conan_vars_info in the closure to evaluate it\"\"\"\n        name, value = expression.split(\"=\", 1)\n        value = value.replace(\"\\\"\", \"\")\n        return evaluate(name, value, conan_vars_info)\n\n    return evaluate_postfix(postfix, evaluate_info)\n\n\ndef evaluate(prop_name, prop_value, conan_vars_info):\n    \"\"\"\n    Evaluates a single prop_name, prop_value like \"os\", \"Windows\" against conan_vars_info.serialize_min()\n    \"\"\"\n\n    def compatible_prop(setting_value, prop_value):\n        return setting_value is None or prop_value == setting_value\n\n    info_settings = conan_vars_info.get(\"settings\", [])\n    info_options = conan_vars_info.get(\"options\", [])\n\n    if prop_name in [\"os\", \"compiler\", \"arch\", \"build_type\"] or prop_name.startswith(\"compiler.\"):\n        return compatible_prop(info_settings.get(prop_name, None), prop_value)\n    else:\n        return compatible_prop(info_options.get(prop_name, None), prop_value)\n    return False\n\n\ndef search_recipes(paths, pattern=None, ignorecase=True):\n    # Conan references in main storage\n    if pattern:\n        if isinstance(pattern, ConanFileReference):\n            pattern = str(pattern)\n        pattern = translate(pattern)\n        pattern = re.compile(pattern, re.IGNORECASE) if ignorecase else re.compile(pattern)\n\n    subdirs = list_folder_subdirs(basedir=paths.store, level=4)\n    if not pattern:\n        return sorted([ConanFileReference(*folder.split(\"/\")) for folder in subdirs])\n    else:\n        ret = []\n        for subdir in subdirs:\n            conan_ref = ConanFileReference(*subdir.split(\"/\"))\n            if pattern:\n                if pattern.match(str(conan_ref)):\n                    ret.append(conan_ref)\n        return sorted(ret)\n\n\ndef search_packages(paths, reference, query):\n    \"\"\" Return a dict like this:\n\n            {package_ID: {name: \"OpenCV\",\n                           version: \"2.14\",\n                           settings: {os: Windows}}}\n    param conan_ref: ConanFileReference object\n    \"\"\"\n    infos = _get_local_infos_min(paths, reference)\n    return filter_packages(query, infos)\n\n\ndef _get_local_infos_min(paths, reference):\n    result = {}\n    packages_path = paths.packages(reference)\n    subdirs = list_folder_subdirs(packages_path, level=1)\n    for package_id in subdirs:\n        # Read conaninfo\n        try:\n            package_reference = PackageReference(reference, package_id)\n            info_path = os.path.join(paths.package(package_reference,\n                                                   short_paths=None), CONANINFO)\n            if not os.path.exists(info_path):\n                raise NotFoundException(\"\")\n            conan_info_content = load(info_path)\n            conan_vars_info = ConanInfo.loads(conan_info_content).serialize_min()\n            result[package_id] = conan_vars_info\n\n        except Exception as exc:\n            logger.error(\"Package %s has no ConanInfo file\" % str(package_reference))\n            if str(exc):\n                logger.error(str(exc))\n\n    return result\n", "path": "conans/search/search.py"}], "after_files": [{"content": "import re\nimport os\n\nfrom fnmatch import translate\n\nfrom conans.errors import ConanException, NotFoundException\nfrom conans.model.info import ConanInfo\nfrom conans.model.ref import PackageReference, ConanFileReference\nfrom conans.paths import CONANINFO\nfrom conans.util.log import logger\nfrom conans.search.query_parse import infix_to_postfix, evaluate_postfix\nfrom conans.util.files import list_folder_subdirs, load\n\n\ndef filter_outdated(packages_infos, recipe_hash):\n    result = {}\n    for package_id, info in packages_infos.items():\n        try:  # Existing package_info of old package might not have recipe_hash\n            if info[\"recipe_hash\"] != recipe_hash:\n                result[package_id] = info\n        except KeyError:\n            pass\n    return result\n\n\ndef filter_packages(query, package_infos):\n    if query is None:\n        return package_infos\n    try:\n        if \"!\" in query:\n            raise ConanException(\"'!' character is not allowed\")\n        if \" not \" in query or query.startswith(\"not \"):\n            raise ConanException(\"'not' operator is not allowed\")\n        postfix = infix_to_postfix(query) if query else []\n        result = {}\n        for package_id, info in package_infos.items():\n            if evaluate_postfix_with_info(postfix, info):\n                result[package_id] = info\n        return result\n    except Exception as exc:\n        raise ConanException(\"Invalid package query: %s. %s\" % (query, exc))\n\n\ndef evaluate_postfix_with_info(postfix, conan_vars_info):\n\n    # Evaluate conaninfo with the expression\n\n    def evaluate_info(expression):\n        \"\"\"Receives an expression like compiler.version=\"12\"\n        Uses conan_vars_info in the closure to evaluate it\"\"\"\n        name, value = expression.split(\"=\", 1)\n        value = value.replace(\"\\\"\", \"\")\n        return evaluate(name, value, conan_vars_info)\n\n    return evaluate_postfix(postfix, evaluate_info)\n\n\ndef evaluate(prop_name, prop_value, conan_vars_info):\n    \"\"\"\n    Evaluates a single prop_name, prop_value like \"os\", \"Windows\" against conan_vars_info.serialize_min()\n    \"\"\"\n\n    def compatible_prop(setting_value, prop_value):\n        return setting_value is None or prop_value == setting_value\n\n    info_settings = conan_vars_info.get(\"settings\", [])\n    info_options = conan_vars_info.get(\"options\", [])\n\n    if prop_name in [\"os\", \"compiler\", \"arch\", \"build_type\"] or prop_name.startswith(\"compiler.\"):\n        return compatible_prop(info_settings.get(prop_name, None), prop_value)\n    else:\n        return compatible_prop(info_options.get(prop_name, None), prop_value)\n    return False\n\n\ndef search_recipes(paths, pattern=None, ignorecase=True):\n    # Conan references in main storage\n    if pattern:\n        if isinstance(pattern, ConanFileReference):\n            pattern = str(pattern)\n        pattern = translate(pattern)\n        pattern = re.compile(pattern, re.IGNORECASE) if ignorecase else re.compile(pattern)\n\n    subdirs = list_folder_subdirs(basedir=paths.store, level=4)\n    if not pattern:\n        return sorted([ConanFileReference(*folder.split(\"/\")) for folder in subdirs])\n    else:\n        ret = []\n        for subdir in subdirs:\n            conan_ref = ConanFileReference(*subdir.split(\"/\"))\n            if pattern:\n                if _partial_match(pattern, conan_ref):\n                    ret.append(conan_ref)\n\n        return sorted(ret)\n\n\ndef _partial_match(pattern, conan_ref):\n    \"\"\"\n    Finds if pattern matches any of partial sums of tokens of conan reference\n    \"\"\"\n    \n    tokens = str(conan_ref).replace('/', ' / ').replace('@', ' @ ').split()\n\n    def partial_sums(iterable):\n        sum = ''\n        for i in iterable:\n            sum += i\n            yield sum\n\n    return any(map(pattern.match, list(partial_sums(tokens))))\n\ndef search_packages(paths, reference, query):\n    \"\"\" Return a dict like this:\n\n            {package_ID: {name: \"OpenCV\",\n                           version: \"2.14\",\n                           settings: {os: Windows}}}\n    param conan_ref: ConanFileReference object\n    \"\"\"\n    infos = _get_local_infos_min(paths, reference)\n    return filter_packages(query, infos)\n\n\ndef _get_local_infos_min(paths, reference):\n    result = {}\n    packages_path = paths.packages(reference)\n    subdirs = list_folder_subdirs(packages_path, level=1)\n    for package_id in subdirs:\n        # Read conaninfo\n        try:\n            package_reference = PackageReference(reference, package_id)\n            info_path = os.path.join(paths.package(package_reference,\n                                                   short_paths=None), CONANINFO)\n            if not os.path.exists(info_path):\n                raise NotFoundException(\"\")\n            conan_info_content = load(info_path)\n            conan_vars_info = ConanInfo.loads(conan_info_content).serialize_min()\n            result[package_id] = conan_vars_info\n\n        except Exception as exc:\n            logger.error(\"Package %s has no ConanInfo file\" % str(package_reference))\n            if str(exc):\n                logger.error(str(exc))\n\n    return result\n", "path": "conans/search/search.py"}]}