| //===- split-file.cpp - Input splitting utility ---------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Split input into multipe parts separated by regex '^(.|//)--- ' and extract |
| // the specified part. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/ADT/DenseMap.h" |
| #include "llvm/ADT/StringExtras.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/Option/ArgList.h" |
| #include "llvm/Option/OptTable.h" |
| #include "llvm/Option/Option.h" |
| #include "llvm/Support/CommandLine.h" |
| #include "llvm/Support/FileOutputBuffer.h" |
| #include "llvm/Support/FileSystem.h" |
| #include "llvm/Support/LineIterator.h" |
| #include "llvm/Support/MemoryBuffer.h" |
| #include "llvm/Support/Path.h" |
| #include "llvm/Support/StringSaver.h" |
| #include "llvm/Support/ToolOutputFile.h" |
| #include "llvm/Support/WithColor.h" |
| #include <string> |
| #include <system_error> |
| |
| using namespace llvm; |
| |
| namespace { |
| enum ID { |
| OPT_INVALID = 0, // This is not an option ID. |
| #define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__), |
| #include "Opts.inc" |
| #undef OPTION |
| }; |
| |
| using namespace llvm::opt; |
| #define OPTTABLE_CODE |
| #include "Opts.inc" |
| |
| class SplitFileOptTable : public opt::OptTable { |
| public: |
| SplitFileOptTable() : OptTable(optionTables()) { setDashDashParsing(true); } |
| }; |
| } // namespace |
| |
| static bool leadingLines; |
| static StringRef toolName; |
| static int errorCount; |
| |
| [[noreturn]] static void fatal(StringRef filename, const Twine &message) { |
| if (filename.empty()) |
| WithColor::error(errs(), toolName) << message << '\n'; |
| else |
| WithColor::error(errs(), toolName) << filename << ": " << message << '\n'; |
| exit(1); |
| } |
| |
| static void error(StringRef filename, int64_t line, const Twine &message) { |
| ++errorCount; |
| errs() << filename << ':' << line << ": "; |
| WithColor::error(errs()) << message << '\n'; |
| } |
| |
| namespace { |
| struct Part { |
| const char *begin = nullptr; |
| const char *end = nullptr; |
| int64_t leadingLines = 0; |
| }; |
| } // namespace |
| |
| static int handle(MemoryBuffer &inputBuf, StringRef input, StringRef output) { |
| DenseMap<StringRef, Part> partToBegin; |
| StringRef lastPart, separator; |
| StringRef EOL = inputBuf.getBuffer().detectEOL(); |
| for (line_iterator i(inputBuf, /*SkipBlanks=*/false, '\0'); !i.is_at_eof();) { |
| const int64_t lineNo = i.line_number(); |
| const StringRef line = *i++; |
| const size_t markerLen = line.starts_with("//") ? 6 : 5; |
| if (!(line.size() >= markerLen && |
| line.substr(markerLen - 4).starts_with("--- "))) |
| continue; |
| separator = line.substr(0, markerLen); |
| const StringRef partName = line.substr(markerLen); |
| if (partName.empty()) { |
| error(input, lineNo, "empty part name"); |
| continue; |
| } |
| if (isSpace(partName.front()) || isSpace(partName.back())) { |
| error(input, lineNo, "part name cannot have leading or trailing space"); |
| continue; |
| } |
| |
| auto res = partToBegin.try_emplace(partName); |
| if (!res.second) { |
| error(input, lineNo, |
| "'" + separator + partName + "' occurs more than once"); |
| continue; |
| } |
| if (!lastPart.empty()) |
| partToBegin[lastPart].end = line.data(); |
| Part &cur = res.first->second; |
| if (!i.is_at_eof()) |
| cur.begin = i->data(); |
| // If --leading-lines is specified, numEmptyLines is 0. Append newlines so |
| // that the extracted part preserves line numbers. |
| cur.leadingLines = leadingLines ? i.line_number() - 1 : 0; |
| |
| lastPart = partName; |
| } |
| if (lastPart.empty()) |
| fatal(input, "no part separator was found"); |
| if (errorCount) |
| return 1; |
| partToBegin[lastPart].end = inputBuf.getBufferEnd(); |
| |
| std::vector<std::unique_ptr<ToolOutputFile>> outputFiles; |
| SmallString<256> partPath; |
| for (auto &keyValue : partToBegin) { |
| partPath.clear(); |
| sys::path::append(partPath, output, keyValue.first); |
| std::error_code ec = |
| sys::fs::create_directories(sys::path::parent_path(partPath)); |
| if (ec) |
| fatal(input, ec.message()); |
| auto f = std::make_unique<ToolOutputFile>(partPath.str(), ec, |
| llvm::sys::fs::OF_Text); |
| if (!f) |
| fatal(input, ec.message()); |
| |
| Part &part = keyValue.second; |
| for (int64_t i = 0; i != part.leadingLines; ++i) |
| (*f).os() << EOL; |
| if (part.begin) |
| (*f).os().write(part.begin, part.end - part.begin); |
| outputFiles.push_back(std::move(f)); |
| } |
| |
| for (std::unique_ptr<ToolOutputFile> &outputFile : outputFiles) |
| outputFile->keep(); |
| return 0; |
| } |
| |
| int main(int argc, char **argv) { |
| toolName = sys::path::stem(argv[0]); |
| BumpPtrAllocator alloc; |
| StringSaver saver(alloc); |
| SplitFileOptTable tbl; |
| opt::InputArgList args = tbl.parseArgs(argc, argv, OPT_UNKNOWN, saver, |
| [](StringRef msg) { fatal("", msg); }); |
| if (args.hasArg(OPT_help)) { |
| tbl.printHelp(outs(), "split-file [options] filename directory", |
| "Split input into multiple parts separated by regex " |
| "'^(.|//)--- ' and extract the part specified by " |
| "'^(.|//)--- <part>'"); |
| return 0; |
| } |
| if (args.hasArg(OPT_version)) { |
| cl::PrintVersionMessage(); |
| return 0; |
| } |
| leadingLines = args.hasFlag(OPT_leading_lines, OPT_no_leading_lines, false); |
| |
| std::vector<std::string> positional = args.getAllArgValues(OPT_INPUT); |
| if (positional.empty()) |
| fatal("", "input filename is not specified"); |
| if (positional.size() == 1) |
| fatal("", "output directory is not specified"); |
| if (positional.size() > 2) |
| fatal("", "too many positional arguments"); |
| StringRef input = positional[0], output = positional[1]; |
| ErrorOr<std::unique_ptr<MemoryBuffer>> bufferOrErr = |
| MemoryBuffer::getFileOrSTDIN(input, /*IsText=*/true); |
| if (std::error_code ec = bufferOrErr.getError()) |
| fatal(input, ec.message()); |
| |
| // Delete output if it is a file or an empty directory, so that we can create |
| // a directory. |
| sys::fs::file_status status; |
| if (std::error_code ec = sys::fs::status(output, status)) |
| if (ec.value() != static_cast<int>(std::errc::no_such_file_or_directory)) |
| fatal(output, ec.message()); |
| if (status.type() != sys::fs::file_type::file_not_found && |
| status.type() != sys::fs::file_type::directory_file && |
| status.type() != sys::fs::file_type::regular_file) |
| fatal(output, "output cannot be a special file"); |
| if (std::error_code ec = sys::fs::remove(output, /*IgnoreNonExisting=*/true)) |
| if (ec.value() != static_cast<int>(std::errc::directory_not_empty) && |
| ec.value() != static_cast<int>(std::errc::file_exists)) |
| fatal(output, ec.message()); |
| return handle(**bufferOrErr, input, output); |
| } |