Function: file-name-concat

file-name-concat is a function defined in fileio.c.

Signature

(file-name-concat DIRECTORY &rest COMPONENTS)

Documentation

Append COMPONENTS to DIRECTORY and return the resulting string.

Each element in COMPONENTS must be a string or nil. DIRECTORY or the non-final elements in COMPONENTS may or may not end with a slash -- if they don't end with a slash, a slash will be inserted before concatenating. In most cases, one or more calls to expand-file-name are better suited for the job than this function. Use this function only if some of the special expansions done by expand-file-name get in the way of what your program needs to do.

Other relevant functions are documented in the file-name group.

View in manual

Probably introduced at or before Emacs version 28.1.

Shortdoc

;; file-name
(file-name-concat "/tmp/" "foo")
    => "/tmp/foo"
  (file-name-concat "/tmp" "foo")
    => "/tmp/foo"
  (file-name-concat "/tmp" "foo" "bar/" "zot")
    => "/tmp/foo/bar/zot"
  (file-name-concat "/tmp" "~")
    => "/tmp/~"

Aliases

org-file-name-concat

Source Code

// Defined in /usr/src/emacs/src/fileio.c
{
  ptrdiff_t chars = 0, bytes = 0, multibytes = 0, eargs = 0;
  Lisp_Object *elements = args;
  Lisp_Object result;
  ptrdiff_t i;

  /* First go through the list to check the types and see whether
     they're all of the same multibyteness. */
  for (i = 0; i < nargs; i++)
    {
      Lisp_Object arg = args[i];
      /* Skip empty and nil elements. */
      if (NILP (arg))
	continue;
      CHECK_STRING (arg);
      if (SCHARS (arg) == 0)
	continue;
      eargs++;
      /* Multibyte and non-ASCII. */
      if (STRING_MULTIBYTE (arg) && SCHARS (arg) != SBYTES (arg))
	multibytes++;
      /* We're not adding a slash to the final part. */
      if (i == nargs - 1
	  || IS_DIRECTORY_SEP (*(SSDATA (arg) + SBYTES (arg) - 1)))
	{
	  bytes += SBYTES (arg);
	  chars += SCHARS (arg);
	}
      else
	{
	  bytes += SBYTES (arg) + 1;
	  chars += SCHARS (arg) + 1;
	}
    }

  /* Convert if needed. */
  if ((multibytes != 0 && multibytes != nargs)
      || eargs != nargs)
    {
      int j = 0;
      elements = xmalloc (eargs * sizeof *elements);
      bytes = 0;
      chars = 0;

      /* Filter out nil/"". */
      for (i = 0; i < nargs; i++)
	{
	  Lisp_Object arg = args[i];
	  if (!NILP (arg) && SCHARS (arg) != 0)
	    elements[j++] = arg;
	}

      for (i = 0; i < eargs; i++)
	{
	  Lisp_Object arg = elements[i];
	  /* Use multibyte or all-ASCII strings as is. */
	  if (!STRING_MULTIBYTE (arg) && !string_ascii_p (arg))
	    elements[i] = Fstring_to_multibyte (arg);
	  arg = elements[i];
	  /* We have to recompute the number of bytes. */
	  if (i == eargs - 1
	      || IS_DIRECTORY_SEP (*(SSDATA (arg) + SBYTES (arg) - 1)))
	    {
	      bytes += SBYTES (arg);
	      chars += SCHARS (arg);
	    }
	  else
	    {
	      bytes += SBYTES (arg) + 1;
	      chars += SCHARS (arg) + 1;
	    }
	}
    }

  /* Allocate an empty string. */
  if (multibytes == 0)
    result = make_uninit_string (chars);
  else
    result = make_uninit_multibyte_string (chars, bytes);
  /* Null-terminate the string. */
  *(SSDATA (result) + SBYTES (result)) = 0;

  /* Copy over the data. */
  char *p = SSDATA (result);
  for (i = 0; i < eargs; i++)
    {
      Lisp_Object arg = elements[i];
      memcpy (p, SSDATA (arg), SBYTES (arg));
      p += SBYTES (arg);
      /* The last element shouldn't have a slash added at the end. */
      if (i < eargs - 1 && !IS_DIRECTORY_SEP (*(p - 1)))
	*p++ = DIRECTORY_SEP;
    }

  if (elements != args)
    xfree (elements);

  return result;
}