1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "tools/gn/path_output.h"
6
7#include "build/build_config.h"
8#include "tools/gn/filesystem_utils.h"
9#include "tools/gn/output_file.h"
10#include "tools/gn/string_utils.h"
11
12PathOutput::PathOutput(const SourceDir& current_dir, EscapingMode escaping)
13    : current_dir_(current_dir) {
14  CHECK(current_dir.is_source_absolute())
15      << "Currently this only supports writing to output directories inside "
16         "the source root. There needs to be some tweaks to PathOutput to make "
17         "doing this work correctly.";
18  inverse_current_dir_ = InvertDir(current_dir_);
19
20  options_.mode = escaping;
21}
22
23PathOutput::~PathOutput() {
24}
25
26void PathOutput::WriteFile(std::ostream& out, const SourceFile& file) const {
27  WritePathStr(out, file.value());
28}
29
30void PathOutput::WriteDir(std::ostream& out,
31                          const SourceDir& dir,
32                          DirSlashEnding slash_ending) const {
33  if (dir.value() == "/") {
34    // Writing system root is always a slash (this will normally only come up
35    // on Posix systems).
36    if (slash_ending == DIR_NO_LAST_SLASH)
37      out << "/.";
38    else
39      out << "/";
40  } else if (dir.value() == "//") {
41    // Writing out the source root.
42    if (slash_ending == DIR_NO_LAST_SLASH) {
43      // The inverse_current_dir_ will contain a [back]slash at the end, so we
44      // can't just write it out.
45      if (inverse_current_dir_.empty()) {
46        out << ".";
47      } else {
48        out.write(inverse_current_dir_.c_str(),
49                  inverse_current_dir_.size() - 1);
50      }
51    } else {
52      if (inverse_current_dir_.empty())
53        out << "./";
54      else
55        out << inverse_current_dir_;
56    }
57  } else if (dir == current_dir_) {
58    // Writing the same directory. This needs special handling here since
59    // we need to output something else other than the input.
60    if (slash_ending == DIR_INCLUDE_LAST_SLASH)
61      out << "./";
62    else
63      out << ".";
64  } else if (slash_ending == DIR_INCLUDE_LAST_SLASH) {
65    WritePathStr(out, dir.value());
66  } else {
67    // DIR_NO_LAST_SLASH mode, just trim the last char.
68    WritePathStr(out, base::StringPiece(dir.value().data(),
69                                        dir.value().size() - 1));
70  }
71}
72
73void PathOutput::WriteFile(std::ostream& out, const OutputFile& file) const {
74  // Here we assume that the path is already preprocessed.
75  EscapeStringToStream(out, file.value(), options_);
76}
77
78void PathOutput::WriteFiles(std::ostream& out,
79                            const std::vector<OutputFile>& files) const {
80  for (size_t i = 0; i < files.size(); i++) {
81    out << " ";
82    WriteFile(out, files[i]);
83  }
84}
85
86void PathOutput::WriteDir(std::ostream& out,
87                          const OutputFile& file,
88                          DirSlashEnding slash_ending) const {
89  DCHECK(file.value().empty() ||
90         file.value()[file.value().size() - 1] == '/');
91
92  switch (slash_ending) {
93    case DIR_INCLUDE_LAST_SLASH:
94      EscapeStringToStream(out, file.value(), options_);
95      break;
96    case DIR_NO_LAST_SLASH:
97      if (!file.value().empty() &&
98          file.value()[file.value().size() - 1] == '/') {
99        // Trim trailing slash.
100        EscapeStringToStream(
101            out,
102            base::StringPiece(file.value().data(), file.value().size() - 1),
103            options_);
104      } else {
105        // Doesn't end with a slash, write the whole thing.
106        EscapeStringToStream(out, file.value(), options_);
107      }
108      break;
109  }
110}
111
112void PathOutput::WriteFile(std::ostream& out,
113                           const base::FilePath& file) const {
114  // Assume native file paths are always absolute.
115  EscapeStringToStream(out, FilePathToUTF8(file), options_);
116}
117
118void PathOutput::WriteSourceRelativeString(
119    std::ostream& out,
120    const base::StringPiece& str) const {
121  if (options_.mode == ESCAPE_NINJA_COMMAND) {
122    // Shell escaping needs an intermediate string since it may end up
123    // quoting the whole thing.
124    std::string intermediate;
125    intermediate.reserve(inverse_current_dir_.size() + str.size());
126    intermediate.assign(inverse_current_dir_.c_str(),
127                        inverse_current_dir_.size());
128    intermediate.append(str.data(), str.size());
129
130    EscapeStringToStream(out,
131        base::StringPiece(intermediate.c_str(), intermediate.size()),
132        options_);
133  } else {
134    // Ninja (and none) escaping can avoid the intermediate string and
135    // reprocessing of the inverse_current_dir_.
136    out << inverse_current_dir_;
137    EscapeStringToStream(out, str, options_);
138  }
139}
140
141void PathOutput::WritePathStr(std::ostream& out,
142                              const base::StringPiece& str) const {
143  DCHECK(str.size() > 0 && str[0] == '/');
144
145  if (str.substr(0, current_dir_.value().size()) ==
146      base::StringPiece(current_dir_.value())) {
147    // The current dir is a prefix of the output file, so we can strip the
148    // prefix and write out the result.
149    EscapeStringToStream(out, str.substr(current_dir_.value().size()),
150                         options_);
151  } else if (str.size() >= 2 && str[1] == '/') {
152    WriteSourceRelativeString(out, str.substr(2));
153  } else {
154    // Input begins with one slash, don't write the current directory since
155    // it's system-absolute.
156#if defined(OS_WIN)
157    // On Windows, trim the leading slash, since the input for absolute
158    // paths will look like "/C:/foo/bar.txt".
159    EscapeStringToStream(out, str.substr(1), options_);
160#else
161    EscapeStringToStream(out, str, options_);
162#endif
163  }
164}
165