1//===- split-file.cpp - Input splitting utility ---------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// Split input into multipe parts separated by regex '^(.|//)--- ' and extract
10// the specified part.
11//
12//===----------------------------------------------------------------------===//
13
14#include "llvm/ADT/DenseMap.h"
15#include "llvm/ADT/StringExtras.h"
16#include "llvm/ADT/StringRef.h"
17#include "llvm/Option/ArgList.h"
18#include "llvm/Option/OptTable.h"
19#include "llvm/Option/Option.h"
20#include "llvm/Support/CommandLine.h"
21#include "llvm/Support/FileOutputBuffer.h"
22#include "llvm/Support/FileSystem.h"
23#include "llvm/Support/LineIterator.h"
24#include "llvm/Support/MemoryBuffer.h"
25#include "llvm/Support/Path.h"
26#include "llvm/Support/StringSaver.h"
27#include "llvm/Support/ToolOutputFile.h"
28#include "llvm/Support/WithColor.h"
29#include <string>
30#include <system_error>
31
32using namespace llvm;
33
34namespace {
35enum ID {
36 OPT_INVALID = 0, // This is not an option ID.
37#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
38#include "Opts.inc"
39#undef OPTION
40};
41
42using namespace llvm::opt;
43#define OPTTABLE_CODE
44#include "Opts.inc"
45
46class SplitFileOptTable : public opt::OptTable {
47public:
48 SplitFileOptTable() : OptTable(optionTables()) { setDashDashParsing(true); }
49};
50} // namespace
51
52static bool leadingLines;
53static StringRef toolName;
54static int errorCount;
55
56[[noreturn]] static void fatal(StringRef filename, const Twine &message) {
57 if (filename.empty())
58 WithColor::error(OS&: errs(), Prefix: toolName) << message << '\n';
59 else
60 WithColor::error(OS&: errs(), Prefix: toolName) << filename << ": " << message << '\n';
61 exit(status: 1);
62}
63
64static void error(StringRef filename, int64_t line, const Twine &message) {
65 ++errorCount;
66 errs() << filename << ':' << line << ": ";
67 WithColor::error(OS&: errs()) << message << '\n';
68}
69
70namespace {
71struct Part {
72 const char *begin = nullptr;
73 const char *end = nullptr;
74 int64_t leadingLines = 0;
75};
76} // namespace
77
78static int handle(MemoryBuffer &inputBuf, StringRef input, StringRef output) {
79 DenseMap<StringRef, Part> partToBegin;
80 StringRef lastPart, separator;
81 StringRef EOL = inputBuf.getBuffer().detectEOL();
82 for (line_iterator i(inputBuf, /*SkipBlanks=*/false, '\0'); !i.is_at_eof();) {
83 const int64_t lineNo = i.line_number();
84 const StringRef line = *i++;
85 const size_t markerLen = line.starts_with(Prefix: "//") ? 6 : 5;
86 if (!(line.size() >= markerLen &&
87 line.substr(Start: markerLen - 4).starts_with(Prefix: "--- ")))
88 continue;
89 separator = line.substr(Start: 0, N: markerLen);
90 const StringRef partName = line.substr(Start: markerLen);
91 if (partName.empty()) {
92 error(filename: input, line: lineNo, message: "empty part name");
93 continue;
94 }
95 if (isSpace(C: partName.front()) || isSpace(C: partName.back())) {
96 error(filename: input, line: lineNo, message: "part name cannot have leading or trailing space");
97 continue;
98 }
99
100 auto res = partToBegin.try_emplace(Key: partName);
101 if (!res.second) {
102 error(filename: input, line: lineNo,
103 message: "'" + separator + partName + "' occurs more than once");
104 continue;
105 }
106 if (!lastPart.empty())
107 partToBegin[lastPart].end = line.data();
108 Part &cur = res.first->second;
109 if (!i.is_at_eof())
110 cur.begin = i->data();
111 // If --leading-lines is specified, numEmptyLines is 0. Append newlines so
112 // that the extracted part preserves line numbers.
113 cur.leadingLines = leadingLines ? i.line_number() - 1 : 0;
114
115 lastPart = partName;
116 }
117 if (lastPart.empty())
118 fatal(filename: input, message: "no part separator was found");
119 if (errorCount)
120 return 1;
121 partToBegin[lastPart].end = inputBuf.getBufferEnd();
122
123 std::vector<std::unique_ptr<ToolOutputFile>> outputFiles;
124 SmallString<256> partPath;
125 for (auto &keyValue : partToBegin) {
126 partPath.clear();
127 sys::path::append(path&: partPath, a: output, b: keyValue.first);
128 std::error_code ec =
129 sys::fs::create_directories(path: sys::path::parent_path(path: partPath));
130 if (ec)
131 fatal(filename: input, message: ec.message());
132 auto f = std::make_unique<ToolOutputFile>(args: partPath.str(), args&: ec,
133 args: llvm::sys::fs::OF_Text);
134 if (!f)
135 fatal(filename: input, message: ec.message());
136
137 Part &part = keyValue.second;
138 for (int64_t i = 0; i != part.leadingLines; ++i)
139 (*f).os() << EOL;
140 if (part.begin)
141 (*f).os().write(Ptr: part.begin, Size: part.end - part.begin);
142 outputFiles.push_back(x: std::move(f));
143 }
144
145 for (std::unique_ptr<ToolOutputFile> &outputFile : outputFiles)
146 outputFile->keep();
147 return 0;
148}
149
150int main(int argc, char **argv) {
151 toolName = sys::path::stem(path: argv[0]);
152 BumpPtrAllocator alloc;
153 StringSaver saver(alloc);
154 SplitFileOptTable tbl;
155 opt::InputArgList args = tbl.parseArgs(Argc: argc, Argv: argv, Unknown: OPT_UNKNOWN, Saver&: saver,
156 ErrorFn: [](StringRef msg) { fatal(filename: "", message: msg); });
157 if (args.hasArg(Ids: OPT_help)) {
158 tbl.printHelp(OS&: outs(), Usage: "split-file [options] filename directory",
159 Title: "Split input into multiple parts separated by regex "
160 "'^(.|//)--- ' and extract the part specified by "
161 "'^(.|//)--- <part>'");
162 return 0;
163 }
164 if (args.hasArg(Ids: OPT_version)) {
165 cl::PrintVersionMessage();
166 return 0;
167 }
168 leadingLines = args.hasFlag(Pos: OPT_leading_lines, Neg: OPT_no_leading_lines, Default: false);
169
170 std::vector<std::string> positional = args.getAllArgValues(Id: OPT_INPUT);
171 if (positional.empty())
172 fatal(filename: "", message: "input filename is not specified");
173 if (positional.size() == 1)
174 fatal(filename: "", message: "output directory is not specified");
175 if (positional.size() > 2)
176 fatal(filename: "", message: "too many positional arguments");
177 StringRef input = positional[0], output = positional[1];
178 ErrorOr<std::unique_ptr<MemoryBuffer>> bufferOrErr =
179 MemoryBuffer::getFileOrSTDIN(Filename: input, /*IsText=*/true);
180 if (std::error_code ec = bufferOrErr.getError())
181 fatal(filename: input, message: ec.message());
182
183 // Delete output if it is a file or an empty directory, so that we can create
184 // a directory.
185 sys::fs::file_status status;
186 if (std::error_code ec = sys::fs::status(path: output, result&: status))
187 if (ec.value() != static_cast<int>(std::errc::no_such_file_or_directory))
188 fatal(filename: output, message: ec.message());
189 if (status.type() != sys::fs::file_type::file_not_found &&
190 status.type() != sys::fs::file_type::directory_file &&
191 status.type() != sys::fs::file_type::regular_file)
192 fatal(filename: output, message: "output cannot be a special file");
193 if (std::error_code ec = sys::fs::remove(path: output, /*IgnoreNonExisting=*/true))
194 if (ec.value() != static_cast<int>(std::errc::directory_not_empty) &&
195 ec.value() != static_cast<int>(std::errc::file_exists))
196 fatal(filename: output, message: ec.message());
197 return handle(inputBuf&: **bufferOrErr, input, output);
198}
199