code: def run_jsonhook(hook, spec, res, dsarg=None):
      """Execute a hook on a given result
  
      A hook definition's 'call' specification may contain placeholders that
      will be expanded using matching values in the given result record. In
      addition to keys in the result a '{dsarg}' placeholder is supported.
      The characters '{' and '}' in the 'call' specification that are not part
      of format() placeholders have to be escaped as '{{' and '}}'. Example
      'call' specification to execute the DataLad ``unlock`` command::
  
          unlock {{"dataset": "{dsarg}", "path": "{path}"}}
  
      Parameters
      ----------
      hook : str
        Name of the hook
      spec : dict
        Hook definition as returned by `get_hooks_from_config()`
      res : dict
        Result records that were found to match the hook definition.
      dsarg : Dataset or str or None, optional
        Value to substitute a {dsarg} placeholder in a hook 'call' specification
        with. Non-string values are automatically converted.
  
      Yields
      ------
      dict
        Any result yielded by the command executed as hook.
      """
      import datalad.api as dl
  
      cmd_name = spec["cmd"]
      if not hasattr(dl, cmd_name):
          # TODO maybe a proper error result?
          lgr.warning(
              "Hook %s requires unknown command %s, skipped",
              hook,
              cmd_name,
          )
          return
      cmd = getattr(dl, cmd_name)
      # apply potential substitutions on the string form of the args
      # for this particular result
      # take care of proper JSON encoding for each value
      enc = json.JSONEncoder().encode
      # we have to ensure JSON encoding of all values (some might be Path instances),
      # we are taking off the outer quoting, to enable flexible combination
      # of individual items in supplied command and argument templates
      args = spec["args"].format(
          # we cannot use a dataset instance directly but must take the
          # detour over the path location in order to have string substitution
          # be possible
          dsarg=""
          if dsarg is None
          else enc(dsarg.path).strip('"')
          if isinstance(dsarg, dl.Dataset)
          else enc(dsarg).strip('"'),
          # skip any present logger that we only carry for internal purposes
          **{
              k: enc(str(v)).strip('"')
              for k, v in res.items()
              if k != "logger"
          },
      )
      # now load
      try:
          args = json.loads(args)
      except Exception as e:
          ce = CapturedException(e)
          lgr.warning(
              "Invalid argument specification for hook %s "
              "(after parameter substitutions): %s [%s], "
              "hook will be skipped",
              hook,
              args,
              ce,
          )
          return
      # only debug level, the hook can issue its own results and communicate
      # through them
      lgr.debug("Running hook %s: %s%s", hook, cmd_name, args)
      for r in cmd(**args):
          yield r
  
imports: import logging
  import json
  from datalad.support.exceptions import CapturedException
label_desc: Use image node_id 3 for calling a global function with example usage: run_jsonhook(hook, spec, res, dsarg) without return types