File: StaticFileRegeneration\TpnSectionHeader.cs
Web Access
Project: src\runtime\src\tasks\installer.tasks\installer.tasks.csproj (installer.tasks)
// 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 System.Text.RegularExpressions;

namespace Microsoft.DotNet.Build.Tasks
{
    public class TpnSectionHeader
    {
        private static readonly char[] SectionSeparatorChars = { '-', '=' };
        private static readonly Regex NumberListPrefix = new Regex(@"^[0-9]+\.\t(?<name>.*)$");

        public static bool IsSeparatorLine(string line)
        {
            return line.Length > 2 && line.All(c => SectionSeparatorChars.Contains(c));
        }

        public static IEnumerable<TpnSectionHeader> ParseAll(string[] lines)
        {
            // A separator line can't represent a section if it's on the first or last few lines.
            for (int i = 1; i < lines.Length - 2; i++)
            {
                string lineAbove = lines[i - 1].Trim();
                string line = lines[i].Trim();
                string lineBelow = lines[i + 1].Trim();

                if (line.Length > 2 &&
                    IsSeparatorLine(line) &&
                    string.IsNullOrEmpty(lineBelow))
                {
                    // 'line' is a separator line. Check around to see what kind it is.

                    if (string.IsNullOrEmpty(lineAbove))
                    {
                        var header = ParseSeparatedOrNull(lines, i);
                        if (header != null)
                        {
                            yield return header;
                        }
                    }
                    else
                    {
                        var header = ParseUnderlined(lines, i);
                        yield return header;
                    }
                }

                var numberedHeader = ParseNumberedOrNull(lines, i);
                if (numberedHeader != null)
                {
                    yield return numberedHeader;
                }
            }
        }

        public string Name { get; set; }
        public string SeparatorLine { get; set; }

        public TpnSectionHeaderFormat Format { get; set; }

        public int StartLine { get; set; }
        public int LineLength { get; set; }

        public string SingleLineName => Name.Replace('\n', ' ').Replace('\r', ' ');

        public override string ToString()
        {
            switch (Format)
            {
                case TpnSectionHeaderFormat.Separated:
                    return
                        SeparatorLine + Environment.NewLine +
                        Environment.NewLine +
                        Name;

                case TpnSectionHeaderFormat.Underlined:
                    return
                        Name + Environment.NewLine +
                        SeparatorLine;

                case TpnSectionHeaderFormat.Numbered:
                    return SeparatorLine;

                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        private static TpnSectionHeader ParseSeparatedOrNull(string[] lines, int i)
        {
            string[] nameLines = lines
                .Skip(i + 2)
                .TakeWhile(s => !string.IsNullOrWhiteSpace(s))
                .Select(s => s.Trim())
                .ToArray();

            string name = string.Join(Environment.NewLine, nameLines);

            // If there's a separator line as the last line in the name, this line doesn't indicate
            // a section. It needs to be handled by ParseUnderlined instead.
            if (nameLines.Any(IsSeparatorLine))
            {
                if (nameLines.Take(nameLines.Length - 1).Any(IsSeparatorLine))
                {
                    throw new ArgumentException(
                        $"Separator line detected inside name '{name}'");
                }
            }
            else
            {
                return new TpnSectionHeader
                {
                    Name = name,

                    SeparatorLine = lines[i],
                    Format = TpnSectionHeaderFormat.Separated,

                    StartLine = i,
                    LineLength = 2 + nameLines.Length
                };
            }

            return null;
        }

        private static TpnSectionHeader ParseUnderlined(string[] lines, int i)
        {
            string[] nameLines = lines
                .Take(i)
                .Reverse()
                .TakeWhile(s => !string.IsNullOrWhiteSpace(s))
                .Reverse()
                .Select(s => s.Trim())
                .ToArray();

            int nameStartLine = i - nameLines.Length;

            return new TpnSectionHeader
            {
                Name = string.Join(Environment.NewLine, nameLines),

                SeparatorLine = lines[i],
                Format = TpnSectionHeaderFormat.Underlined,

                StartLine = nameStartLine,
                LineLength = nameLines.Length + 1
            };
        }
        private static TpnSectionHeader ParseNumberedOrNull(string[] lines, int i)
        {
            Match numberListMatch;

            if (string.IsNullOrWhiteSpace(lines[i - 1]) &&
                string.IsNullOrWhiteSpace(lines[i + 1]) &&
                (numberListMatch = NumberListPrefix.Match(lines[i])).Success)
            {
                return new TpnSectionHeader
                {
                    Name = numberListMatch.Groups["name"].Value,

                    SeparatorLine = lines[i],
                    Format = TpnSectionHeaderFormat.Numbered,

                    StartLine = i,
                    LineLength = 1
                };
            }

            return null;
        }
    }
}