class_docstr: Helper for globbing paths.
  
  Parameters
  ----------
  patterns : list of str
      Call `glob.glob` with each of these patterns. "." is considered as
      datalad's special "." path argument; it is not passed to glob and is
      always left unexpanded. Each set of glob results is sorted
      alphabetically.
  pwd : str, optional
      Glob in this directory.
  expand : bool, optional
     Whether the `paths` property returns unexpanded or expanded paths.
class_docstr_tok: ["Helper","for","globbing","paths",".","Parameters","--","--","--","--","--","patterns",":","list","of","str","Call","`","glob.glob","`","with","each","of","these","patterns",".","``",".","''","is","considered","as","datalad","'s","special","``",".","''","path","argument",";","it","is","not","passed","to","glob","and","is","always","left","unexpanded",".","Each","set","of","glob","results","is","sorted","alphabetically",".","pwd",":","str",",","optional","Glob","in","this","directory",".","expand",":","bool",",","optional","Whether","the","`","paths","`","property","returns","unexpanded","or","expanded","paths","."]
code: def _expand_globs(
      self,
  ) -> tuple[
      dict[str, list[str]], dict[str, list[str]], dict[str, list[str]]
  ]:
      def normalize_hit(h: str) -> str:
          normalized = op.relpath(h) + (
              "" if op.basename(h) else op.sep
          )
          if h == op.curdir + op.sep + normalized:
              # Don't let relpath prune "./fname" (gh-3034).
              return h
          return normalized
  
      hits: dict[str, list[str]] = {}
      partial_hits: dict[str, list[str]] = {}
      misses: dict[str, list[str]] = {}
      with chpwd(self.pwd):
          for pattern in self._patterns:
              full_hits = glob.glob(pattern, recursive=True)
              if full_hits:
                  hits[pattern] = sorted(map(normalize_hit, full_hits))
              else:
                  lgr.debug("No matching files found for '%s'", pattern)
                  # We didn't find a hit for the complete pattern. If we find
                  # a sub-pattern hit, that may mean we have an uninstalled
                  # subdataset.
                  for sub_pattern in self._get_sub_patterns(pattern):
                      sub_hits = glob.glob(sub_pattern, recursive=True)
                      if sub_hits:
                          partial_hits[pattern] = sorted(
                              map(normalize_hit, sub_hits)
                          )
                          break
                  else:
                      misses[pattern] = [pattern]
      return hits, partial_hits, misses
  
imports: from __future__ import annotations
  import glob
  import logging
  import os.path
  from functools import lru_cache
  from itertools import chain
  from typing import Iterable, Optional
  from datalad.utils import chpwd, ensure_unicode, getpwd, partition
label_desc: Use image node_id 4 for calling the GlobbedPaths obj's underlying member method code with example usage: obj._expand_globs() and returns: hits,  partial_hits,  misses, normalized, h