// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Build.Exceptions;
///
/// The release notes parser.
///
///
/// Original from Cake build tool source:
/// https://github.com/cake-build/cake/blob/9828d7b246d332054896e52ba56983822feb3f05/src/Cake.Common/ReleaseNotesParser.cs
///
public sealed class ReleaseNotesParser
{
private readonly Regex _versionRegex;
///
/// Initializes a new instance of the class.
///
public ReleaseNotesParser()
{
_versionRegex = new Regex(@"(?\d+(\s*\.\s*\d+){0,3})(?-[a-z][0-9a-z-]*)?");
}
///
/// Parses all release notes.
///
/// The content.
/// All release notes.
public IReadOnlyList Parse(string content)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
var lines = content.SplitLines();
if (lines.Length > 0)
{
var line = lines[0].Trim();
if (line.StartsWith("#", StringComparison.OrdinalIgnoreCase))
{
return ParseComplexFormat(lines);
}
if (line.StartsWith("*", StringComparison.OrdinalIgnoreCase))
{
return ParseSimpleFormat(lines);
}
}
throw new BuildAbortedException("Unknown release notes format.");
}
private IReadOnlyList ParseComplexFormat(string[] lines)
{
var lineIndex = 0;
var result = new List();
while (true)
{
if (lineIndex >= lines.Length)
{
break;
}
// Create release notes.
var semVer = SemVersion.Zero;
var version = SemVersion.TryParse(lines[lineIndex], out semVer);
if (!version)
{
throw new BuildAbortedException("Could not parse version from release notes header.");
}
var rawVersionLine = lines[lineIndex];
// Increase the line index.
lineIndex++;
// Parse content.
var notes = new List();
while (true)
{
// Sanity checks.
if (lineIndex >= lines.Length)
{
break;
}
if (lines[lineIndex].StartsWith("# ", StringComparison.OrdinalIgnoreCase))
{
break;
}
// Get the current line.
var line = (lines[lineIndex] ?? string.Empty).Trim();
if (!string.IsNullOrWhiteSpace(line))
{
notes.Add(line);
}
lineIndex++;
}
result.Add(new ReleaseNotes(semVer, notes, rawVersionLine));
}
return result.OrderByDescending(x => x.SemVersion).ToArray();
}
private IReadOnlyList ParseSimpleFormat(string[] lines)
{
var lineIndex = 0;
var result = new List();
while (true)
{
if (lineIndex >= lines.Length)
{
break;
}
// Trim the current line.
var line = (lines[lineIndex] ?? string.Empty);
if (string.IsNullOrWhiteSpace(line))
{
lineIndex++;
continue;
}
// Parse header.
var semVer = SemVersion.Zero;
var version = SemVersion.TryParse(lines[lineIndex], out semVer);
if (!version)
{
throw new BuildAbortedException("Could not parse version from release notes header.");
}
// Parse the description.
line = line.Substring(semVer.ToString().Length).Trim('-', ' ');
// Add the release notes to the result.
result.Add(new ReleaseNotes(semVer, new[] { line }, line));
lineIndex++;
}
return result.OrderByDescending(x => x.SemVersion).ToArray();
}
}