1111
1212#include < algorithm>
1313#include < cstdlib>
14+ #include < filesystem>
1415#include < ranges>
1516#include < spdlog/spdlog.h>
1617#include < string>
2021
2122namespace cabin {
2223
24+ namespace fs = std::filesystem;
25+
2326static Result<void > fmtMain (CliArgsView args);
2427
2528const Subcmd FMT_CMD =
@@ -33,7 +36,15 @@ const Subcmd FMT_CMD =
3336 " Do not exclude git-ignored files from formatting" ))
3437 .setMainFn(fmtMain);
3538
36- static std::vector<std::string>
39+ struct TargetFile {
40+ std::string path;
41+ fs::file_time_type modTime;
42+
43+ explicit TargetFile (std::string path) noexcept
44+ : path(std::move(path)), modTime(fs::last_write_time(this ->path)) {}
45+ };
46+
47+ static std::vector<TargetFile>
3748collectFormatTargets (const fs::path& manifestDir,
3849 const std::vector<fs::path>& excludes,
3950 bool useVcsIgnoreFiles) {
@@ -59,7 +70,7 @@ collectFormatTargets(const fs::path& manifestDir,
5970 };
6071
6172 // Automatically collects format-target files
62- std::vector<std::string> sources ;
73+ std::vector<TargetFile> files ;
6374 for (auto entry = fs::recursive_directory_iterator (manifestDir);
6475 entry != fs::recursive_directory_iterator (); ++entry) {
6576 if (entry->is_directory ()) {
@@ -80,11 +91,24 @@ collectFormatTargets(const fs::path& manifestDir,
8091
8192 const std::string ext = path.extension ().string ();
8293 if (SOURCE_FILE_EXTS.contains (ext) || HEADER_FILE_EXTS.contains (ext)) {
83- sources.push_back (path.string ());
94+ files.emplace_back (path.string ());
95+ }
96+ }
97+ }
98+ return files;
99+ }
100+
101+ static std::size_t countModifiedFiles (const std::vector<TargetFile>& files) {
102+ std::size_t changedFiles = 0 ;
103+ for (const TargetFile& file : files) {
104+ if (fs::exists (file.path )) {
105+ const auto afterTime = fs::last_write_time (file.path );
106+ if (file.modTime != afterTime) {
107+ ++changedFiles;
84108 }
85109 }
86110 }
87- return sources ;
111+ return changedFiles ;
88112}
89113
90114static Result<void > fmtMain (const CliArgsView args) {
@@ -126,9 +150,9 @@ static Result<void> fmtMain(const CliArgsView args) {
126150 };
127151
128152 const fs::path projectPath = manifest.path .parent_path ();
129- const std::vector<std::string> sources =
153+ const std::vector<TargetFile> files =
130154 collectFormatTargets (projectPath, excludes, useVcsIgnoreFiles);
131- if (sources .empty ()) {
155+ if (files .empty ()) {
132156 Diag::warn (" no files to format" );
133157 return Ok ();
134158 }
@@ -140,9 +164,10 @@ static Result<void> fmtMain(const CliArgsView args) {
140164 clangFormatArgs.emplace_back (" --dry-run" );
141165 } else {
142166 clangFormatArgs.emplace_back (" -i" );
143- Diag::info (" Formatting" , " {}" , manifest.package .name );
144167 }
145- clangFormatArgs.insert (clangFormatArgs.end (), sources.begin (), sources.end ());
168+ clangFormatArgs.reserve (clangFormatArgs.size () + files.size ());
169+ std::ranges::copy (files | std::views::transform (&TargetFile::path),
170+ std::back_inserter (clangFormatArgs));
146171
147172 const char * cabinFmt = std::getenv (" CABIN_FMT" );
148173 if (cabinFmt == nullptr ) {
@@ -154,6 +179,15 @@ static Result<void> fmtMain(const CliArgsView args) {
154179
155180 const ExitStatus exitStatus = Try (execCmd (clangFormat));
156181 if (exitStatus.success ()) {
182+ const std::size_t numFiles = files.size ();
183+ if (isCheck) {
184+ Diag::info (" Checked" , " {} file{} with no format required" , numFiles,
185+ numFiles == 1 ? " " : " s" );
186+ } else {
187+ const std::size_t modifiedFiles = countModifiedFiles (files);
188+ Diag::info (" Formatted" , " {} out of {} file{}" , modifiedFiles, numFiles,
189+ numFiles == 1 ? " " : " s" );
190+ }
157191 return Ok ();
158192 } else {
159193 Bail (" clang-format {}" , exitStatus);
0 commit comments