File: Internal\MatcherContext.cs
Web Access
Project: src\src\libraries\Microsoft.Extensions.FileSystemGlobbing\src\Microsoft.Extensions.FileSystemGlobbing.csproj (Microsoft.Extensions.FileSystemGlobbing)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.FileSystemGlobbing.Abstractions;
using Microsoft.Extensions.FileSystemGlobbing.Internal.PathSegments;
using Microsoft.Extensions.FileSystemGlobbing.Internal.PatternContexts;
using Microsoft.Extensions.FileSystemGlobbing.Util;
 
namespace Microsoft.Extensions.FileSystemGlobbing.Internal
{
    /// <summary>
    /// This API supports infrastructure and is not intended to be used
    /// directly from your code. This API may change or be removed in future releases.
    /// </summary>
    public class MatcherContext
    {
        private readonly DirectoryInfoBase _root;
        private readonly IPatternContext _patternContext;
        private readonly List<FilePatternMatch> _files;
 
        private readonly HashSet<string> _declaredLiteralFolderSegmentInString;
        private readonly HashSet<LiteralPathSegment> _declaredLiteralFileSegments = new HashSet<LiteralPathSegment>();
 
        private bool _declaredParentPathSegment;
        private bool _declaredWildcardPathSegment;
 
        public MatcherContext(IEnumerable<IPattern> includePatterns, IEnumerable<IPattern> excludePatterns, DirectoryInfoBase directoryInfo, StringComparison comparison)
        {
            _root = directoryInfo;
            _files = [];
            _declaredLiteralFolderSegmentInString = new HashSet<string>(StringComparisonHelper.GetStringComparer(comparison));
 
            IPatternContext[] includePatternContexts = includePatterns.Select(pattern => pattern.CreatePatternContextForInclude()).ToArray();
            IPatternContext[] excludePatternContexts = excludePatterns.Select(pattern => pattern.CreatePatternContextForExclude()).ToArray();
 
            _patternContext = new IncludesFirstCompositePatternContext(includePatternContexts, excludePatternContexts);
        }
 
        internal MatcherContext(List<IncludeOrExcludeValue<IPattern>> orderedPatterns, DirectoryInfoBase directoryInfo, StringComparison comparison)
        {
            _root = directoryInfo;
            _files = [];
            _declaredLiteralFolderSegmentInString = new HashSet<string>(StringComparisonHelper.GetStringComparer(comparison));
 
            IncludeOrExcludeValue<IPatternContext>[] includeOrExcludePatternContexts = orderedPatterns
                .Select(item => new IncludeOrExcludeValue<IPatternContext>
                {
                    Value = item.IsInclude ? item.Value.CreatePatternContextForInclude() : item.Value.CreatePatternContextForExclude(),
                    IsInclude = item.IsInclude
                })
                .ToArray();
 
            _patternContext = new PreserveOrderCompositePatternContext(includeOrExcludePatternContexts);
        }
 
        public PatternMatchingResult Execute()
        {
            _files.Clear();
 
            Match(_root, parentRelativePath: null);
 
            return new PatternMatchingResult(_files, _files.Count > 0);
        }
 
        private void Match(DirectoryInfoBase directory, string? parentRelativePath)
        {
            // Request all the including and excluding patterns to push current directory onto their status stack.
            _patternContext.PushDirectory(directory);
            Declare();
 
            var entities = new List<FileSystemInfoBase?>();
            if (_declaredWildcardPathSegment || _declaredLiteralFileSegments.Count != 0)
            {
                entities.AddRange(directory.EnumerateFileSystemInfos());
            }
            else
            {
                IEnumerable<FileSystemInfoBase> candidates = directory.EnumerateFileSystemInfos();
                foreach (FileSystemInfoBase candidate in candidates)
                {
                    if (candidate is DirectoryInfoBase &&
                        _declaredLiteralFolderSegmentInString.Contains(candidate.Name))
                    {
                        entities.Add(candidate);
                    }
                }
            }
 
            if (_declaredParentPathSegment)
            {
                entities.Add(directory.GetDirectory(".."));
            }
 
            // collect files and sub directories
            var subDirectories = new List<DirectoryInfoBase>();
            foreach (FileSystemInfoBase? entity in entities)
            {
                if (entity is FileInfoBase fileInfo)
                {
                    PatternTestResult result = _patternContext.Test(fileInfo);
                    if (result.IsSuccessful)
                    {
                        _files.Add(new FilePatternMatch(
                            path: CombinePath(parentRelativePath, fileInfo.Name),
                            stem: result.Stem));
                    }
 
                    continue;
                }
 
                if (entity is DirectoryInfoBase directoryInfo)
                {
                    if (_patternContext.Test(directoryInfo))
                    {
                        subDirectories.Add(directoryInfo);
                    }
 
                    continue;
                }
            }
 
            // Matches the sub directories recursively
            foreach (DirectoryInfoBase subDir in subDirectories)
            {
                string relativePath = CombinePath(parentRelativePath, subDir.Name);
 
                Match(subDir, relativePath);
            }
 
            // Request all the including and excluding patterns to pop their status stack.
            _patternContext.PopDirectory();
        }
 
        private void Declare()
        {
            _declaredLiteralFileSegments.Clear();
            _declaredParentPathSegment = false;
            _declaredWildcardPathSegment = false;
 
            _patternContext.Declare(DeclareInclude);
        }
 
        private void DeclareInclude(IPathSegment patternSegment, bool isLastSegment)
        {
            if (patternSegment is LiteralPathSegment literalSegment)
            {
                if (isLastSegment)
                {
                    _declaredLiteralFileSegments.Add(literalSegment);
                }
                else
                {
                    _declaredLiteralFolderSegmentInString.Add(literalSegment.Value);
                }
            }
            else if (patternSegment is ParentPathSegment)
            {
                _declaredParentPathSegment = true;
            }
            else if (patternSegment is WildcardPathSegment)
            {
                _declaredWildcardPathSegment = true;
            }
        }
 
        internal static string CombinePath(string? left, string right)
        {
            if (string.IsNullOrEmpty(left))
            {
                return right;
            }
            else
            {
                return $"{left}/{right}";
            }
        }
    }
}