From 45678bc5bc12e7f06cdbadb770e36beabd6694c6 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Tue, 28 Feb 2023 19:45:18 +0100 Subject: [PATCH] Automatically generate .gitattributes and .gitignore with cmkr init --- src/cmake_generator.cpp | 77 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/src/cmake_generator.cpp b/src/cmake_generator.cpp index 2a00a7b..8a431c7 100644 --- a/src/cmake_generator.cpp +++ b/src/cmake_generator.cpp @@ -119,6 +119,19 @@ static void create_file(const fs::path &path, const std::string &contents) { ofs << contents; } +static std::string read_file(const fs::path &path) { + std::ifstream ifs(path, std::ios::binary); + if (!ifs) { + throw std::runtime_error("Failed to read " + path.string()); + } + std::string contents; + ifs.seekg(0, std::ios::end); + contents.resize(ifs.tellg()); + ifs.seekg(0, std::ios::beg); + ifs.read(&contents[0], contents.size()); + return contents; +} + // CMake target name rules: https://cmake.org/cmake/help/latest/policy/CMP0037.html [A-Za-z0-9_.+\-] // TOML bare keys: non-empty strings composed only of [A-Za-z0-9_-] // We replace all non-TOML bare key characters with _ @@ -135,12 +148,74 @@ static std::string escape_project_name(const std::string &name) { return escaped; } +static void generate_gitfile(const char *gitfile, const std::vector &desired_lines) { + // Reference: https://github.com/github/linguist/blob/master/docs/overrides.md#summary + auto lines = desired_lines; + auto generate = [&lines](const char *newline) { + std::string generated; + generated += "# cmkr"; + generated += newline; + for (const auto &line : lines) { + generated += line; + generated += newline; + } + return generated; + }; + if (!fs::exists(gitfile)) { + create_file(gitfile, generate("\n")); + } else { + auto contents = read_file(gitfile); + std::string line; + auto cr = 0, lf = 0; + auto flush_line = [&line, &lines]() { + auto itr = std::find(lines.begin(), lines.end(), line); + if (itr != lines.end()) { + printf("erase:%s\n", line.c_str()); + lines.erase(itr); + } + line.clear(); + }; + for (size_t i = 0; i < contents.length(); i++) { + if (contents[i] == '\r') { + cr++; + continue; + } + if (contents[i] == '\n') { + lf++; + flush_line(); + } else { + line += contents[i]; + } + } + if (!line.empty()) { + flush_line(); + } + + if (!lines.empty()) { + // Append the cmkr .gitattributes using the detected newline + auto newline = cr == lf ? "\r\n" : "\n"; + if (!contents.empty() && contents.back() != '\n') { + contents += newline; + } + contents += newline; + contents += generate(newline); + create_file(gitfile, contents); + } + } +} + void generate_project(const std::string &type) { const auto name = escape_project_name(fs::current_path().stem().string()); if (fs::exists(fs::current_path() / "cmake.toml")) { throw std::runtime_error("Cannot initialize a project when cmake.toml already exists!"); } + // Automatically generate .gitattributes to not skew the statistics of the repo + generate_gitfile(".gitattributes", {"/**/CMakeLists.txt linguist-generated", "/**/cmkr.cmake linguist-vendored"}); + + // Generate .gitignore with reasonable defaults for CMake + generate_gitfile(".gitignore", {"build*/", "cmake-build*/", ".idea/", ".vscode/"}); + tsl::ordered_map variables = { {"@name", name}, {"@type", type}, @@ -892,7 +967,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) { } } - auto contains_language_source = [&project_extensions](const std::vector& sources) { + auto contains_language_source = [&project_extensions](const std::vector &sources) { for (const auto &source : sources) { auto extension = fs::path(source).extension().string(); if (project_extensions.count(extension) > 0) {