@@ -119,6 +119,19 @@ static void create_file(const fs::path &path, const std::string &contents) {
119119 ofs << contents;
120120}
121121
122+ static std::string read_file (const fs::path &path) {
123+ std::ifstream ifs (path, std::ios::binary);
124+ if (!ifs) {
125+ throw std::runtime_error (" Failed to read " + path.string ());
126+ }
127+ std::string contents;
128+ ifs.seekg (0 , std::ios::end);
129+ contents.resize (ifs.tellg ());
130+ ifs.seekg (0 , std::ios::beg);
131+ ifs.read (&contents[0 ], contents.size ());
132+ return contents;
133+ }
134+
122135// CMake target name rules: https://cmake.org/cmake/help/latest/policy/CMP0037.html [A-Za-z0-9_.+\-]
123136// TOML bare keys: non-empty strings composed only of [A-Za-z0-9_-]
124137// We replace all non-TOML bare key characters with _
@@ -135,12 +148,74 @@ static std::string escape_project_name(const std::string &name) {
135148 return escaped;
136149}
137150
151+ static void generate_gitfile (const char *gitfile, const std::vector<std::string> &desired_lines) {
152+ // Reference: https://github.com/github/linguist/blob/master/docs/overrides.md#summary
153+ auto lines = desired_lines;
154+ auto generate = [&lines](const char *newline) {
155+ std::string generated;
156+ generated += " # cmkr" ;
157+ generated += newline;
158+ for (const auto &line : lines) {
159+ generated += line;
160+ generated += newline;
161+ }
162+ return generated;
163+ };
164+ if (!fs::exists (gitfile)) {
165+ create_file (gitfile, generate (" \n " ));
166+ } else {
167+ auto contents = read_file (gitfile);
168+ std::string line;
169+ auto cr = 0 , lf = 0 ;
170+ auto flush_line = [&line, &lines]() {
171+ auto itr = std::find (lines.begin (), lines.end (), line);
172+ if (itr != lines.end ()) {
173+ printf (" erase:%s\n " , line.c_str ());
174+ lines.erase (itr);
175+ }
176+ line.clear ();
177+ };
178+ for (size_t i = 0 ; i < contents.length (); i++) {
179+ if (contents[i] == ' \r ' ) {
180+ cr++;
181+ continue ;
182+ }
183+ if (contents[i] == ' \n ' ) {
184+ lf++;
185+ flush_line ();
186+ } else {
187+ line += contents[i];
188+ }
189+ }
190+ if (!line.empty ()) {
191+ flush_line ();
192+ }
193+
194+ if (!lines.empty ()) {
195+ // Append the cmkr .gitattributes using the detected newline
196+ auto newline = cr == lf ? " \r\n " : " \n " ;
197+ if (!contents.empty () && contents.back () != ' \n ' ) {
198+ contents += newline;
199+ }
200+ contents += newline;
201+ contents += generate (newline);
202+ create_file (gitfile, contents);
203+ }
204+ }
205+ }
206+
138207void generate_project (const std::string &type) {
139208 const auto name = escape_project_name (fs::current_path ().stem ().string ());
140209 if (fs::exists (fs::current_path () / " cmake.toml" )) {
141210 throw std::runtime_error (" Cannot initialize a project when cmake.toml already exists!" );
142211 }
143212
213+ // Automatically generate .gitattributes to not skew the statistics of the repo
214+ generate_gitfile (" .gitattributes" , {" /**/CMakeLists.txt linguist-generated" , " /**/cmkr.cmake linguist-vendored" });
215+
216+ // Generate .gitignore with reasonable defaults for CMake
217+ generate_gitfile (" .gitignore" , {" build*/" , " cmake-build*/" , " .idea/" , " .vscode/" });
218+
144219 tsl::ordered_map<std::string, std::string> variables = {
145220 {" @name" , name},
146221 {" @type" , type},
@@ -898,7 +973,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
898973 }
899974 }
900975
901- auto contains_language_source = [&project_extensions](const std::vector<std::string>& sources) {
976+ auto contains_language_source = [&project_extensions](const std::vector<std::string> & sources) {
902977 for (const auto &source : sources) {
903978 auto extension = fs::path (source).extension ().string ();
904979 if (project_extensions.count (extension) > 0 ) {
0 commit comments