prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Missing resource breaks rendering viewlet.resourceregistries.js
  if there's a typo or a missing JS resource defined in the resource registries, the `viewlet.resourceregistries.js` gives a traceback and all JS resources are missing.
  
  </issue>
  <code>
  [start of Products/CMFPlone/resources/utils.py]
  1 from Acquisition import aq_base
  2 from Acquisition import aq_inner
  3 from Acquisition import aq_parent
  4 from plone.base.interfaces.resources import OVERRIDE_RESOURCE_DIRECTORY_NAME
  5 from plone.resource.file import FilesystemFile
  6 from plone.resource.interfaces import IResourceDirectory
  7 from Products.CMFCore.Expression import createExprContext
  8 from Products.CMFCore.utils import getToolByName
  9 from zExceptions import NotFound
  10 from zope.component import queryUtility
  11 
  12 import logging
  13 
  14 
  15 PRODUCTION_RESOURCE_DIRECTORY = "production"
  16 logger = logging.getLogger(__name__)
  17 
  18 
  19 def get_production_resource_directory():
  20     persistent_directory = queryUtility(IResourceDirectory, name="persistent")
  21     if persistent_directory is None:
  22         return ""
  23     container = persistent_directory[OVERRIDE_RESOURCE_DIRECTORY_NAME]
  24     try:
  25         production_folder = container[PRODUCTION_RESOURCE_DIRECTORY]
  26     except NotFound:
  27         return "%s/++unique++1" % PRODUCTION_RESOURCE_DIRECTORY
  28     if "timestamp.txt" not in production_folder:
  29         return "%s/++unique++1" % PRODUCTION_RESOURCE_DIRECTORY
  30     timestamp = production_folder.readFile("timestamp.txt")
  31     if isinstance(timestamp, bytes):
  32         timestamp = timestamp.decode()
  33     return "{}/++unique++{}".format(PRODUCTION_RESOURCE_DIRECTORY, timestamp)
  34 
  35 
  36 def get_resource(context, path):
  37     if path.startswith("++plone++"):
  38         # ++plone++ resources can be customized, we return their override
  39         # value if any
  40         overrides = get_override_directory(context)
  41         filepath = path[9:]
  42         if overrides.isFile(filepath):
  43             return overrides.readFile(filepath)
  44 
  45     if "?" in path:
  46         # Example from plone.session:
  47         # "acl_users/session/refresh?session_refresh=true&type=css&minutes=5"
  48         # Traversing will not work then.  In this example we could split on "?"
  49         # and traverse to the first part, acl_users/session/refresh, but this
  50         # gives a function, and this fails when we call it below, missing a
  51         # REQUEST argument
  52         return
  53     try:
  54         resource = context.unrestrictedTraverse(path)
  55     except (NotFound, AttributeError):
  56         logger.warning(
  57             f"Could not find resource {path}. You may have to create it first."
  58         )  # noqa
  59         return
  60 
  61     if isinstance(resource, FilesystemFile):
  62         (directory, sep, filename) = path.rpartition("/")
  63         return context.unrestrictedTraverse(directory).readFile(filename)
  64 
  65     # calling the resource may modify the header, i.e. the content-type.
  66     # we do not want this, so keep the original header intact.
  67     response_before = context.REQUEST.response
  68     context.REQUEST.response = response_before.__class__()
  69     if hasattr(aq_base(resource), "GET"):
  70         # for FileResource
  71         result = resource.GET()
  72     else:
  73         # any BrowserView
  74         result = resource()
  75     context.REQUEST.response = response_before
  76     return result
  77 
  78 
  79 def get_override_directory(context):
  80     persistent_directory = queryUtility(IResourceDirectory, name="persistent")
  81     if persistent_directory is None:
  82         return
  83     if OVERRIDE_RESOURCE_DIRECTORY_NAME not in persistent_directory:
  84         persistent_directory.makeDirectory(OVERRIDE_RESOURCE_DIRECTORY_NAME)
  85     return persistent_directory[OVERRIDE_RESOURCE_DIRECTORY_NAME]
  86 
  87 
  88 def evaluateExpression(expression, context):
  89     """Evaluate an object's TALES condition to see if it should be
  90     displayed."""
  91     try:
  92         if expression.text and context is not None:
  93             portal = getToolByName(context, "portal_url").getPortalObject()
  94 
  95             # Find folder (code courtesy of CMFCore.ActionsTool)
  96             if context is None or not hasattr(context, "aq_base"):
  97                 folder = portal
  98             else:
  99                 folder = context
  100                 # Search up the containment hierarchy until we find an
  101                 # object that claims it's PrincipiaFolderish.
  102                 while folder is not None:
  103                     if getattr(aq_base(folder), "isPrincipiaFolderish", 0):
  104                         # found it.
  105                         break
  106                     else:
  107                         folder = aq_parent(aq_inner(folder))
  108 
  109             __traceback_info__ = (folder, portal, context, expression)
  110             ec = createExprContext(folder, portal, context)
  111             # add 'context' as an alias for 'object'
  112             ec.setGlobal("context", context)
  113             return expression(ec)
  114         return True
  115     except AttributeError:
  116         return True
  117
  [end of Products/CMFPlone/resources/utils.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/Products/CMFPlone/resources/utils.py b/Products/CMFPlone/resources/utils.py
  --- a/Products/CMFPlone/resources/utils.py
  +++ b/Products/CMFPlone/resources/utils.py
  @@ -52,7 +52,7 @@
           return
       try:
           resource = context.unrestrictedTraverse(path)
  -    except (NotFound, AttributeError):
  +    except (NotFound, AttributeError, KeyError):
           logger.warning(
               f"Could not find resource {path}. You may have to create it first."
           )  # noqa
  
verification_info: {"golden_diff": "diff --git a/Products/CMFPlone/resources/utils.py b/Products/CMFPlone/resources/utils.py\n--- a/Products/CMFPlone/resources/utils.py\n+++ b/Products/CMFPlone/resources/utils.py\n@@ -52,7 +52,7 @@\n         return\n     try:\n         resource = context.unrestrictedTraverse(path)\n-    except (NotFound, AttributeError):\n+    except (NotFound, AttributeError, KeyError):\n         logger.warning(\n             f\"Could not find resource {path}. You may have to create it first.\"\n         )  # noqa\n", "issue": "Missing resource breaks rendering viewlet.resourceregistries.js\nif there's a typo or a missing JS resource defined in the resource registries, the `viewlet.resourceregistries.js` gives a traceback and all JS resources are missing.\n", "before_files": [{"content": "from Acquisition import aq_base\nfrom Acquisition import aq_inner\nfrom Acquisition import aq_parent\nfrom plone.base.interfaces.resources import OVERRIDE_RESOURCE_DIRECTORY_NAME\nfrom plone.resource.file import FilesystemFile\nfrom plone.resource.interfaces import IResourceDirectory\nfrom Products.CMFCore.Expression import createExprContext\nfrom Products.CMFCore.utils import getToolByName\nfrom zExceptions import NotFound\nfrom zope.component import queryUtility\n\nimport logging\n\n\nPRODUCTION_RESOURCE_DIRECTORY = \"production\"\nlogger = logging.getLogger(__name__)\n\n\ndef get_production_resource_directory():\n    persistent_directory = queryUtility(IResourceDirectory, name=\"persistent\")\n    if persistent_directory is None:\n        return \"\"\n    container = persistent_directory[OVERRIDE_RESOURCE_DIRECTORY_NAME]\n    try:\n        production_folder = container[PRODUCTION_RESOURCE_DIRECTORY]\n    except NotFound:\n        return \"%s/++unique++1\" % PRODUCTION_RESOURCE_DIRECTORY\n    if \"timestamp.txt\" not in production_folder:\n        return \"%s/++unique++1\" % PRODUCTION_RESOURCE_DIRECTORY\n    timestamp = production_folder.readFile(\"timestamp.txt\")\n    if isinstance(timestamp, bytes):\n        timestamp = timestamp.decode()\n    return \"{}/++unique++{}\".format(PRODUCTION_RESOURCE_DIRECTORY, timestamp)\n\n\ndef get_resource(context, path):\n    if path.startswith(\"++plone++\"):\n        # ++plone++ resources can be customized, we return their override\n        # value if any\n        overrides = get_override_directory(context)\n        filepath = path[9:]\n        if overrides.isFile(filepath):\n            return overrides.readFile(filepath)\n\n    if \"?\" in path:\n        # Example from plone.session:\n        # \"acl_users/session/refresh?session_refresh=true&type=css&minutes=5\"\n        # Traversing will not work then.  In this example we could split on \"?\"\n        # and traverse to the first part, acl_users/session/refresh, but this\n        # gives a function, and this fails when we call it below, missing a\n        # REQUEST argument\n        return\n    try:\n        resource = context.unrestrictedTraverse(path)\n    except (NotFound, AttributeError):\n        logger.warning(\n            f\"Could not find resource {path}. You may have to create it first.\"\n        )  # noqa\n        return\n\n    if isinstance(resource, FilesystemFile):\n        (directory, sep, filename) = path.rpartition(\"/\")\n        return context.unrestrictedTraverse(directory).readFile(filename)\n\n    # calling the resource may modify the header, i.e. the content-type.\n    # we do not want this, so keep the original header intact.\n    response_before = context.REQUEST.response\n    context.REQUEST.response = response_before.__class__()\n    if hasattr(aq_base(resource), \"GET\"):\n        # for FileResource\n        result = resource.GET()\n    else:\n        # any BrowserView\n        result = resource()\n    context.REQUEST.response = response_before\n    return result\n\n\ndef get_override_directory(context):\n    persistent_directory = queryUtility(IResourceDirectory, name=\"persistent\")\n    if persistent_directory is None:\n        return\n    if OVERRIDE_RESOURCE_DIRECTORY_NAME not in persistent_directory:\n        persistent_directory.makeDirectory(OVERRIDE_RESOURCE_DIRECTORY_NAME)\n    return persistent_directory[OVERRIDE_RESOURCE_DIRECTORY_NAME]\n\n\ndef evaluateExpression(expression, context):\n    \"\"\"Evaluate an object's TALES condition to see if it should be\n    displayed.\"\"\"\n    try:\n        if expression.text and context is not None:\n            portal = getToolByName(context, \"portal_url\").getPortalObject()\n\n            # Find folder (code courtesy of CMFCore.ActionsTool)\n            if context is None or not hasattr(context, \"aq_base\"):\n                folder = portal\n            else:\n                folder = context\n                # Search up the containment hierarchy until we find an\n                # object that claims it's PrincipiaFolderish.\n                while folder is not None:\n                    if getattr(aq_base(folder), \"isPrincipiaFolderish\", 0):\n                        # found it.\n                        break\n                    else:\n                        folder = aq_parent(aq_inner(folder))\n\n            __traceback_info__ = (folder, portal, context, expression)\n            ec = createExprContext(folder, portal, context)\n            # add 'context' as an alias for 'object'\n            ec.setGlobal(\"context\", context)\n            return expression(ec)\n        return True\n    except AttributeError:\n        return True\n", "path": "Products/CMFPlone/resources/utils.py"}]}