普通文本  |  240行  |  8.59 KB

// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "tools/gn/err.h"
#include "tools/gn/filesystem_utils.h"
#include "tools/gn/functions.h"
#include "tools/gn/parse_tree.h"
#include "tools/gn/scope.h"
#include "tools/gn/value.h"

namespace functions {

namespace {

// Corresponds to the various values of "what" in the function call.
enum What {
  WHAT_FILE,
  WHAT_NAME,
  WHAT_EXTENSION,
  WHAT_DIR,
  WHAT_ABSPATH,
  WHAT_GEN_DIR,
  WHAT_OUT_DIR,
};

// Returns the directory containing the input (resolving it against the
// |current_dir|), regardless of whether the input is a directory or a file.
SourceDir DirForInput(const SourceDir& current_dir,
                      const std::string& input_string) {
  if (!input_string.empty() && input_string[input_string.size() - 1] == '/') {
    // Input is a directory.
    return current_dir.ResolveRelativeDir(input_string);
  }

  // Input is a directory.
  return current_dir.ResolveRelativeFile(input_string).GetDir();
}

std::string GetOnePathInfo(const Settings* settings,
                           const SourceDir& current_dir,
                           What what,
                           const Value& input,
                           Err* err) {
  if (!input.VerifyTypeIs(Value::STRING, err))
    return std::string();
  const std::string& input_string = input.string_value();
  if (input_string.empty()) {
    *err = Err(input, "Calling get_path_info on an empty string.");
    return std::string();
  }

  switch (what) {
    case WHAT_FILE: {
      return FindFilename(&input_string).as_string();
    }
    case WHAT_NAME: {
      std::string file = FindFilename(&input_string).as_string();
      size_t extension_offset = FindExtensionOffset(file);
      if (extension_offset == std::string::npos)
        return file;
      // Trim extension and dot.
      return file.substr(0, extension_offset - 1);
    }
    case WHAT_EXTENSION: {
      return FindExtension(&input_string).as_string();
    }
    case WHAT_DIR: {
      base::StringPiece dir_incl_slash = FindDir(&input_string);
      if (dir_incl_slash.empty())
        return std::string(".");
      // Trim slash since this function doesn't return trailing slashes. The
      // times we don't do this are if the result is "/" and "//" since those
      // slashes can't be trimmed.
      if (dir_incl_slash == "/")
        return std::string("/.");
      if (dir_incl_slash == "//")
        return std::string("//.");
      return dir_incl_slash.substr(0, dir_incl_slash.size() - 1).as_string();
    }
    case WHAT_GEN_DIR: {
      return DirectoryWithNoLastSlash(
          GetGenDirForSourceDir(settings,
                                DirForInput(current_dir, input_string)));
    }
    case WHAT_OUT_DIR: {
      return DirectoryWithNoLastSlash(
          GetOutputDirForSourceDir(settings,
                                   DirForInput(current_dir, input_string)));
    }
    case WHAT_ABSPATH: {
      if (!input_string.empty() && input_string[input_string.size() - 1] == '/')
        return current_dir.ResolveRelativeDir(input_string).value();
      else
        return current_dir.ResolveRelativeFile(input_string).value();
    }
    default:
      NOTREACHED();
      return std::string();
  }
}

}  // namespace

const char kGetPathInfo[] = "get_path_info";
const char kGetPathInfo_HelpShort[] =
    "get_path_info: Extract parts of a file or directory name.";
const char kGetPathInfo_Help[] =
    "get_path_info: Extract parts of a file or directory name.\n"
    "\n"
    "  get_path_info(input, what)\n"
    "\n"
    "  The first argument is either a string representing a file or\n"
    "  directory name, or a list of such strings. If the input is a list\n"
    "  the return value will be a list containing the result of applying the\n"
    "  rule to each item in the input.\n"
    "\n"
    "Possible values for the \"what\" parameter\n"
    "\n"
    "  \"file\"\n"
    "      The substring after the last slash in the path, including the name\n"
    "      and extension. If the input ends in a slash, the empty string will\n"
    "      be returned.\n"
    "        \"foo/bar.txt\" => \"bar.txt\"\n"
    "        \"bar.txt\" => \"bar.txt\"\n"
    "        \"foo/\" => \"\"\n"
    "        \"\" => \"\"\n"
    "\n"
    "  \"name\"\n"
    "     The substring of the file name not including the extension.\n"
    "        \"foo/bar.txt\" => \"bar\"\n"
    "        \"foo/bar\" => \"bar\"\n"
    "        \"foo/\" => \"\"\n"
    "\n"
    "  \"extension\"\n"
    "      The substring following the last period following the last slash,\n"
    "      or the empty string if not found. The period is not included.\n"
    "        \"foo/bar.txt\" => \"txt\"\n"
    "        \"foo/bar\" => \"\"\n"
    "\n"
    "  \"dir\"\n"
    "      The directory portion of the name, not including the slash.\n"
    "        \"foo/bar.txt\" => \"foo\"\n"
    "        \"//foo/bar\" => \"//foo\"\n"
    "        \"foo\" => \".\"\n"
    "\n"
    "      The result will never end in a slash, so if the resulting\n"
    "      is empty, the system (\"/\") or source (\"//\") roots, a \".\"\n"
    "      will be appended such that it is always legal to append a slash\n"
    "      and a filename and get a valid path.\n"
    "\n"
    "  \"out_dir\"\n"
    "      The output file directory corresponding to the path of the\n"
    "      given file, not including a trailing slash.\n"
    "        \"//foo/bar/baz.txt\" => \"//out/Default/obj/foo/bar\"\n"

    "  \"gen_dir\"\n"
    "      The generated file directory corresponding to the path of the\n"
    "      given file, not including a trailing slash.\n"
    "        \"//foo/bar/baz.txt\" => \"//out/Default/gen/foo/bar\"\n"
    "\n"
    "  \"abspath\"\n"
    "      The full absolute path name to the file or directory. It will be\n"
    "      resolved relative to the currebt directory, and then the source-\n"
    "      absolute version will be returned. If the input is system-\n"
    "      absolute, the same input will be returned.\n"
    "        \"foo/bar.txt\" => \"//mydir/foo/bar.txt\"\n"
    "        \"foo/\" => \"//mydir/foo/\"\n"
    "        \"//foo/bar\" => \"//foo/bar\"  (already absolute)\n"
    "        \"/usr/include\" => \"/usr/include\"  (already absolute)\n"
    "\n"
    "      If you want to make the path relative to another directory, or to\n"
    "      be system-absolute, see rebase_path().\n"
    "\n"
    "Examples\n"
    "  sources = [ \"foo.cc\", \"foo.h\" ]\n"
    "  result = get_path_info(source, \"abspath\")\n"
    "  # result will be [ \"//mydir/foo.cc\", \"//mydir/foo.h\" ]\n"
    "\n"
    "  result = get_path_info(\"//foo/bar/baz.cc\", \"dir\")\n"
    "  # result will be \"//foo/bar\"\n"
    "\n"
    "  # Extract the source-absolute directory name,\n"
    "  result = get_path_info(get_path_info(path, \"dir\"), \"abspath\")\n";

Value RunGetPathInfo(Scope* scope,
                     const FunctionCallNode* function,
                     const std::vector<Value>& args,
                     Err* err) {
  if (args.size() != 2) {
    *err = Err(function, "Expecting two arguments to get_path_info.");
    return Value();
  }

  // Extract the "what".
  if (!args[1].VerifyTypeIs(Value::STRING, err))
    return Value();
  What what;
  if (args[1].string_value() == "file") {
    what = WHAT_FILE;
  } else if (args[1].string_value() == "name") {
    what = WHAT_NAME;
  } else if (args[1].string_value() == "extension") {
    what = WHAT_EXTENSION;
  } else if (args[1].string_value() == "dir") {
    what = WHAT_DIR;
  } else if (args[1].string_value() == "out_dir") {
    what = WHAT_OUT_DIR;
  } else if (args[1].string_value() == "gen_dir") {
    what = WHAT_GEN_DIR;
  } else if (args[1].string_value() == "abspath") {
    what = WHAT_ABSPATH;
  } else {
    *err = Err(args[1], "Unknown value for 'what'.");
    return Value();
  }

  const SourceDir& current_dir = scope->GetSourceDir();
  if (args[0].type() == Value::STRING) {
    return Value(function, GetOnePathInfo(scope->settings(), current_dir, what,
                                          args[0], err));
  } else if (args[0].type() == Value::LIST) {
    const std::vector<Value>& input_list = args[0].list_value();
    Value result(function, Value::LIST);
    for (size_t i = 0; i < input_list.size(); i++) {
      result.list_value().push_back(Value(function,
          GetOnePathInfo(scope->settings(), current_dir, what,
                         input_list[i], err)));
      if (err->has_error())
        return Value();
    }
    return result;
  }

  *err = Err(args[0], "Path must be a string or a list of strings.");
  return Value();
}

}  // namespace functions