Skip to content

Latest commit

 

History

History
67 lines (52 loc) · 3 KB

File metadata and controls

67 lines (52 loc) · 3 KB

Meziantou.Framework.CodeOwners

Meziantou.Framework.CodeOwners parses CODEOWNERS file. These files are common on GitHub and GitLab.

Parsing takes the dialect of the file, because the two hosts do not accept the same syntax: sections ([Name], ^[Name], [Name][2]) are a GitLab extension, and in the GitHub syntax a line starting with [ is a pattern, since [ opens a character class.

Each entry is one line of the file: a pattern and the owners it declares.

CodeOwnersFile file = CodeOwnersFile.Parse("* @user1 docs@example.com", CodeOwnersDialect.GitHub);
// file.Entries[0].Pattern: "*"
// file.Entries[0].Owners[0]: Type=Username, Name="user1"
// file.Entries[0].Owners[1]: Type=EmailAddress, Name="docs@example.com"

Entries are returned in file order, and CODEOWNERS resolution is last-match-wins, so the owners of a path are those of the last entry whose pattern matches it:

CodeOwnersEntry? owningEntry = file.Entries.LastOrDefault(entry => Matches(entry.Pattern, path));
IReadOnlyList<CodeOwner> owners = owningEntry?.Owners ?? [];
// An empty Owners list means the entry explicitly leaves the pattern unowned

Parse throws a CodeOwnersParseException when the file is invalid. The exception reports the first error and where it is:

try
{
    CodeOwnersFile.Parse("[Section\n* @user1", CodeOwnersDialect.GitLab);
}
catch (CodeOwnersParseException ex)
{
    // ex.Error.Kind: CodeOwnersParseErrorKind.UnterminatedSectionHeader
    // ex.Error.LineNumber: 1
    // ex.Error.LinePosition: 1
    Console.WriteLine(ex.Message);
}

Use TryParse when an invalid file should not throw. An overload reports the same error without allocating an exception:

if (CodeOwnersFile.TryParse(content, CodeOwnersDialect.GitHub, out CodeOwnersFile? file, out CodeOwnersParseError error))
{
    // ...
}
else
{
    Console.WriteLine(error); // line 1, position 1: the section header is not terminated by ']'
}

Parse and both TryParse overloads also accept a ReadOnlySpan<char>, so a file read into a buffer does not have to be turned into a string first. The parsed CodeOwnersFile never references the span: every value it exposes is copied out of it, and reported error positions are relative to the start of the span.

char[] buffer = ...;
CodeOwnersFile file = CodeOwnersFile.Parse(buffer.AsSpan(0, length), CodeOwnersDialect.GitHub);

A CodeOwnersFile only exists for a valid file: neither method hands back a partially parsed one. Entries, Owners and DefaultOwners are read-only views that cannot be cast back to a mutable list.

In the GitLab dialect an owner can also be a role, written @@developer, @@maintainer or @@owner (plural accepted). Anything else after @@ is an error, as is a @ inside a username.

CodeOwnersFile file = CodeOwnersFile.Parse("*.md @@maintainers", CodeOwnersDialect.GitLab);
// file.Entries[0].Owners[0]: Type=Role, Name="maintainers"