@@ -12,35 +12,42 @@ namespace LineCount;
1212// The excessive exception handling is necessitated by the fact that thrown exceptions don't carry any information about the file that caused them, rendering top-level exception handling infeasible.
1313public static class LineCount
1414{
15- public static async Task < ReportResult > Run ( string path , LineCountData data , string [ ] excludeDirectories , string [ ] excludeFiles )
15+ public static async Task < ReportResult ? > Run ( string path , LineCountData data , string [ ] excludeDirectories , string [ ] excludeFiles , CancellationToken cancellationToken = default )
1616 {
17- path = Path . TrimEndingDirectorySeparator ( path ) ;
18-
19- var excludeFilePatterns = PathPatterns . Create ( path , excludeFiles ) ;
20- var excludeDirectoryPatterns = PathPatterns . Create ( path , excludeDirectories ) ;
21-
22- return await GetLineCount ( path , data , excludeFilePatterns , excludeDirectoryPatterns ) ;
17+ try
18+ {
19+ path = Path . TrimEndingDirectorySeparator ( path ) ;
20+
21+ var excludeFilePatterns = PathPatterns . Create ( path , excludeFiles ) ;
22+ var excludeDirectoryPatterns = PathPatterns . Create ( path , excludeDirectories ) ;
23+
24+ return await GetLineCount ( path , data , excludeFilePatterns , excludeDirectoryPatterns , cancellationToken ) ;
25+ }
26+ catch ( OperationCanceledException )
27+ {
28+ return null ;
29+ }
2330 }
2431
25- static async Task < ReportResult > GetLineCount ( string path , LineCountData data , PathPatterns excludeFilePatterns , PathPatterns excludeDirectoryPatterns )
32+ static async Task < ReportResult > GetLineCount ( string path , LineCountData data , PathPatterns excludeFilePatterns , PathPatterns excludeDirectoryPatterns , CancellationToken cancellationToken = default )
2633 {
2734 try
2835 {
2936 FileAttributes attributes = File . GetAttributes ( path ) ;
3037
3138 if ( ! attributes . HasFlag ( FileAttributes . Directory ) )
3239 {
33- return await GetSingleFileLineCount ( path , data ) ;
40+ return await GetSingleFileLineCount ( path , data , cancellationToken ) ;
3441 }
3542
36- var filesReportResult = await CountInFiles ( path , data , excludeFilePatterns ) ;
43+ var filesReportResult = await CountInFiles ( path , data , excludeFilePatterns , cancellationToken ) ;
3744
3845 if ( ! filesReportResult . TryGetValue ( out var filesReport ) )
3946 {
4047 return filesReportResult ;
4148 }
4249
43- var directoriesReportResult = await CountInDirectories ( path , data , excludeFilePatterns , excludeDirectoryPatterns ) ;
50+ var directoriesReportResult = await CountInDirectories ( path , data , excludeFilePatterns , excludeDirectoryPatterns , cancellationToken ) ;
4451
4552 if ( ! directoriesReportResult . TryGetValue ( out var directoriesReport ) )
4653 {
@@ -75,7 +82,7 @@ static async Task<ReportResult> GetLineCount(string path, LineCountData data, Pa
7582 }
7683 }
7784
78- static async Task < ReportResult > CountInDirectories ( string path , LineCountData data , PathPatterns excludeFilePatterns , PathPatterns excludeDirectoryPatterns )
85+ static async Task < ReportResult > CountInDirectories ( string path , LineCountData data , PathPatterns excludeFilePatterns , PathPatterns excludeDirectoryPatterns , CancellationToken cancellationToken = default )
7986 {
8087 List < Task < ReportResult > > directorytasks = [ ] ;
8188
@@ -88,7 +95,7 @@ static async Task<ReportResult> CountInDirectories(string path, LineCountData da
8895 continue ;
8996 }
9097
91- var task = GetLineCount ( directory , data , excludeFilePatterns , excludeDirectoryPatterns ) ;
98+ var task = GetLineCount ( directory , data , excludeFilePatterns , excludeDirectoryPatterns , cancellationToken ) ;
9299 directorytasks . Add ( task ) ;
93100 }
94101 }
@@ -137,7 +144,7 @@ static async Task<ReportResult> CountInDirectories(string path, LineCountData da
137144 return new LineCountReport ( lineCount , fileCount ) ;
138145 }
139146
140- static async Task < ReportResult > CountInFiles ( string path , LineCountData data , PathPatterns excludeFilePatterns )
147+ static async Task < ReportResult > CountInFiles ( string path , LineCountData data , PathPatterns excludeFilePatterns , CancellationToken cancellationToken = default )
141148 {
142149 List < Task < Result < FileStats , IError > > > filetasks = [ ] ;
143150
@@ -152,7 +159,7 @@ static async Task<ReportResult> CountInFiles(string path, LineCountData data, Pa
152159 continue ;
153160 }
154161
155- Task < Result < FileStats , IError > > task = GetSingleFileLineCount ( file , data )
162+ Task < Result < FileStats , IError > > task = GetSingleFileLineCount ( file , data , cancellationToken )
156163 . ContinueWith ( task => task . Result . Map ( report => new FileStats ( file , report . Lines ) ) ) ;
157164 filetasks . Add ( task ) ;
158165 }
@@ -217,23 +224,23 @@ static IEnumerable<string> GetFilterFilePaths(string path, LineCountData data)
217224 return Directory . EnumerateFiles ( path , data . Filter ) . Select ( Path . GetFullPath ) ;
218225 }
219226
220- static Task < LineCountReport > GetSingleFileLineCountReport ( string path , LineCountData data )
227+ static Task < LineCountReport > GetSingleFileLineCountReport ( string path , LineCountData data , CancellationToken cancellationToken = default )
221228 {
222229 return ( data . FilterType switch
223230 {
224- FilterType . None => GetFileLineCount ( path ) ,
225- FilterType . Filtered => GetFilteredFileLineCount ( path , line => data . LineFilter ! . IsMatch ( line ) ) ,
226- FilterType . FilteredExcept => GetFilteredFileLineCount ( path , line => ! data . ExcludeLineFilter ! . IsMatch ( line ) ) ,
227- FilterType . FilteredBoth => GetFilteredFileLineCount ( path , line => data . LineFilter ! . IsMatch ( line ) && ! data . ExcludeLineFilter ! . IsMatch ( line ) ) ,
231+ FilterType . None => GetFileLineCount ( path , cancellationToken ) ,
232+ FilterType . Filtered => GetFilteredFileLineCount ( path , line => data . LineFilter ! . IsMatch ( line ) , cancellationToken ) ,
233+ FilterType . FilteredExcept => GetFilteredFileLineCount ( path , line => ! data . ExcludeLineFilter ! . IsMatch ( line ) , cancellationToken ) ,
234+ FilterType . FilteredBoth => GetFilteredFileLineCount ( path , line => data . LineFilter ! . IsMatch ( line ) && ! data . ExcludeLineFilter ! . IsMatch ( line ) , cancellationToken ) ,
228235 _ => throw new InvalidOperationException ( $ "CountType.{ data . FilterType } not recognized") ,
229236 } ) . ContinueWith ( task => new LineCountReport ( task . Result ) ) ;
230237 }
231238
232- static async Task < ReportResult > GetSingleFileLineCount ( string path , LineCountData data )
239+ static async Task < ReportResult > GetSingleFileLineCount ( string path , LineCountData data , CancellationToken cancellationToken = default )
233240 {
234241 try
235242 {
236- return await GetSingleFileLineCountReport ( path , data ) ;
243+ return await GetSingleFileLineCountReport ( path , data , cancellationToken ) ;
237244 }
238245 catch ( FileNotFoundException )
239246 {
@@ -277,41 +284,45 @@ static async Task<ReportResult> GetSingleFileLineCount(string path, LineCountDat
277284 }
278285 }
279286
280- public static async Task < int > GetFilteredFileLineCount ( string path , Predicate < string > filter )
287+ public static async Task < int > GetFilteredFileLineCount ( string path , Predicate < string > filter , CancellationToken cancellationToken = default )
281288 {
282289 using FileStream stream = File . OpenRead ( path ) ;
283290 using StreamReader reader = new StreamReader ( stream ) ;
284291
285- string ? line = await reader . ReadLineAsync ( ) ;
292+ string ? line = await reader . ReadLineAsync ( cancellationToken ) ;
286293 int count = 0 ;
287294
288- while ( line is not null )
295+ while ( line is not null && ! cancellationToken . IsCancellationRequested )
289296 {
290297 if ( filter ( line ) )
291298 {
292299 count ++ ;
293300 }
294301
295- line = await reader . ReadLineAsync ( ) ;
302+ line = await reader . ReadLineAsync ( cancellationToken ) ;
296303 }
297304
305+ cancellationToken . ThrowIfCancellationRequested ( ) ;
306+
298307 return count ;
299308 }
300309
301- public static async Task < int > GetFileLineCount ( string path )
310+ public static async Task < int > GetFileLineCount ( string path , CancellationToken cancellationToken = default )
302311 {
303312 using FileStream stream = File . OpenRead ( path ) ;
304313 using StreamReader reader = new StreamReader ( stream ) ;
305314
306- string ? line = await reader . ReadLineAsync ( ) ;
315+ string ? line = await reader . ReadLineAsync ( cancellationToken ) ;
307316 int count = 0 ;
308317
309- while ( line is not null )
318+ while ( line is not null && ! cancellationToken . IsCancellationRequested )
310319 {
311320 count ++ ;
312- line = await reader . ReadLineAsync ( ) ;
321+ line = await reader . ReadLineAsync ( cancellationToken ) ;
313322 }
314323
324+ cancellationToken . ThrowIfCancellationRequested ( ) ;
325+
315326 return count ;
316327 }
317328}
0 commit comments