| 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 | |
| 32 | using namespace llvm; |
| 33 | |
| 34 | namespace { |
| 35 | enum 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 | |
| 42 | using namespace llvm::opt; |
| 43 | #define OPTTABLE_CODE |
| 44 | #include "Opts.inc" |
| 45 | |
| 46 | class SplitFileOptTable : public opt::OptTable { |
| 47 | public: |
| 48 | SplitFileOptTable() : OptTable(optionTables()) { setDashDashParsing(true); } |
| 49 | }; |
| 50 | } // namespace |
| 51 | |
| 52 | static bool leadingLines; |
| 53 | static StringRef toolName; |
| 54 | static 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 | |
| 64 | static 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 | |
| 70 | namespace { |
| 71 | struct Part { |
| 72 | const char *begin = nullptr; |
| 73 | const char *end = nullptr; |
| 74 | int64_t leadingLines = 0; |
| 75 | }; |
| 76 | } // namespace |
| 77 | |
| 78 | static 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 | |
| 150 | int 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 | |