< Summary

Information
Class: DirectSight.Parser.FileReading.LocalFileReader
Assembly DirectSight
File(s): /home/runner/work/DirectSight/DirectSight/DirectSight/Parser/FileReading/LocalFileReader.cs
Line coverage
60%
Covered lines: 43
Uncovered lines: 28
Coverable lines: 71
Total lines: 152
Line coverage: 60.5%
Branch coverage
45%
Covered branches: 11
Total branches: 24
Branch coverage: 45.8%
Method coverage

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()75%44100%
.ctor()100%11100%
LoadFile(...)100%2273.33%
MapPath(...)50%8853.84%
MapPath(...)0%880%
ReadAllLinesPreserveTrailingEmpty(...)100%22100%

File(s)

/home/runner/work/DirectSight/DirectSight/DirectSight/Parser/FileReading/LocalFileReader.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.IO;
 4using System.Text.RegularExpressions;
 5using DirectSight.Common;
 6
 7namespace DirectSight.Parser.FileReading;
 8
 9/// <summary>
 10/// File reader for reading files from local disk.
 11/// </summary>
 12internal class LocalFileReader : IFileReader
 13{
 14    /// <summary>
 15    /// Regex to analyze if a path is a deterministic path.
 16    /// </summary>
 117    private static readonly Regex DeterministicPathRegex = new Regex("\\/_\\d?\\/", RegexOptions.Compiled);
 18
 19    /// <summary>
 20    /// The source directories for typical environments like Github Actions.
 21    /// </summary>
 22    private static readonly IReadOnlyList<string> DeterministicSourceDirectories;
 23
 24    static LocalFileReader()
 125    {
 126        var directories = new List<string>();
 27
 28        // Github Actions
 129        if ("true".Equals(Environment.GetEnvironmentVariable("GITHUB_ACTIONS"), StringComparison.OrdinalIgnoreCase)
 130            && Environment.GetEnvironmentVariable("GITHUB_WORKSPACE") != null)
 131        {
 132            directories.Add(Environment.GetEnvironmentVariable("GITHUB_WORKSPACE"));
 133        }
 34
 135        DeterministicSourceDirectories = directories;
 136    }
 37
 38    /// <summary>
 39    /// Initializes a new instance of the <see cref="LocalFileReader" /> class.
 40    /// </summary>
 3241    public LocalFileReader()
 3242    {
 3243    }
 44
 45    /// <summary>
 46    /// Loads the file with the given path.
 47    /// </summary>
 48    /// <param name="path">The path.</param>
 49    /// <param name="error">Error message if file reading failed, otherwise <code>null</code>.</param>
 50    /// <returns><code>null</code> if an error occurs, otherwise the lines of the file.</returns>
 51    public string[] LoadFile(string path, out string error)
 6652    {
 6653        string mappedPath = MapPath(path);
 54
 55        try
 6656        {
 6657            if (!File.Exists(mappedPath))
 258            {
 259                error = string.Format("File '{0}' does not exist (any more).", path);
 260                return null;
 61            }
 62
 6463            string[] lines = ReadAllLinesPreserveTrailingEmpty(mappedPath);
 64
 6465            error = null;
 6466            return lines;
 67        }
 068        catch (Exception ex)
 069        {
 070            error = string.Format("Error during reading file '{0}': {1}", path, ex.GetExceptionMessageForDisplay());
 071            return null;
 72        }
 6673    }
 74
 75    private static string MapPath(string path)
 6676    {
 6677        if (File.Exists(path))
 6478        {
 6479            return path;
 80        }
 81
 282        if (path.StartsWith("/_") && DeterministicPathRegex.IsMatch(path))
 083        {
 084            path = path[(path.IndexOf('/', 2) + 1)..];
 85
 086            if (File.Exists(path))
 087            {
 088                return path;
 89            }
 90
 091            return MapPath(path, DeterministicSourceDirectories);
 92        }
 93
 294        return path;
 6695    }
 96
 97    private static string MapPath(string path, IEnumerable<string> directories)
 098    {
 99        /*
 100         * Search in source dirctories
 101         *
 102         * E.g. with source directory 'C:\agent\1\work\s' the following locations will be searched:
 103         * C:\agent\1\work\s\_\some\directory\file.cs
 104         * C:\agent\1\work\s\some\directory\file.cs
 105         * C:\agent\1\work\s\directory\file.cs
 106         * C:\agent\1\work\s\file.cs
 107         */
 0108        string[] parts = path.Split('/', '\\');
 109
 0110        foreach (var sourceDirectory in directories)
 0111        {
 0112            for (int i = 0; i < parts.Length; i++)
 0113            {
 0114                string combinedPath = sourceDirectory;
 115
 0116                for (int j = i; j < parts.Length; j++)
 0117                {
 0118                    combinedPath = Path.Combine(combinedPath, parts[j]);
 0119                }
 120
 0121                if (File.Exists(combinedPath))
 0122                {
 0123                    return combinedPath;
 124                }
 0125            }
 0126        }
 127
 0128        return path;
 0129    }
 130
 131    /// <summary>
 132    /// Reads all lines of a file, preserving a trailing empty line if present.
 133    /// </summary>
 134    /// <param name="path">The path of the file.</param>
 135    /// <returns>The lines of the file.</returns>
 136    private static string[] ReadAllLinesPreserveTrailingEmpty(string path)
 64137    {
 64138        var lines = new List<string>();
 139
 64140        var encoding = FileHelper.GetEncoding(path);
 64141        using (var reader = new StreamReader(path, encoding))
 64142        {
 143            string line;
 3776144            while ((line = reader.ReadLine()) != null)
 3712145            {
 3712146                lines.Add(line);
 3712147            }
 64148        }
 149
 64150        return [.. lines];
 64151    }
 152}