Files
octokit.net/Octokit.Generators/AsyncPaginationExtensionsGenerator.cs

120 lines
4.4 KiB
C#

using System.Linq;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Data;
using System.Text;
namespace Octokit.Generators
{
/// <summary>
/// AsyncPaginationExtensionsGenerator for generating pagination extensions for Octokit.net Clients that return collections.
/// </summary>
/// <remarks>
/// This generator originally appeared in https://github.com/octokit/octokit.net/pull/2516
/// The generator solves a small part of a larger effort that is being discussed:
/// https://github.com/octokit/octokit.net/discussions/2499
/// https://github.com/octokit/octokit.net/discussions/2495
/// https://github.com/octokit/octokit.net/issues/2517
/// In the future, we should be able to unify generation for
/// * models (request and response)
/// * clients
/// * routing and related helpers
/// TODO: Convert to use Rosyln source generators
/// </remarks>
class AsyncPaginationExtensionsGenerator
{
private const string HEADER = (
@"using System;
using System.Collections.Generic;
namespace Octokit.AsyncPaginationExtension
{
/// <summary>
/// Provides all extensions for pagination.
/// </summary>
/// <remarks>
/// The <code>pageSize</code> parameter at the end of all methods allows for specifying the amount of elements to be fetched per page.
/// Only useful to optimize the amount of API calls made.
/// </remarks>
public static class Extensions
{
private const int DEFAULT_PAGE_SIZE = 30;
");
private const string FOOTER = (
@"
}
}");
/// <summary>
/// GenerateAsync static entry point for generating pagination extensions.
/// </summary>
/// <remarks>
/// This defaults the search path to the root of the project
/// This expects to generate the resulting code and put it in Octokit.AsyncPaginationExtension
/// This does a wholesale overwrite on ./Octokit.AsyncPaginationExtension/Extensions.cs
/// </remarks>
public static async Task GenerateAsync(string root = "./")
{
var sb = new StringBuilder(HEADER);
var enumOptions = new EnumerationOptions { RecurseSubdirectories = true };
var paginatedCallRegex = new Regex(@".*Task<IReadOnlyList<(?<returnType>\w+)>>\s*(?<name>\w+)(?<template><.*>)?\((?<arg>.*?)(, )?ApiOptions \w*\);");
foreach (var file in Directory.EnumerateFiles(root, "I*.cs", enumOptions)) {
var type = Path.GetFileNameWithoutExtension(file);
foreach (var line in File.ReadAllLines(file)) {
var match = paginatedCallRegex.Match(line);
if (!match.Success) { continue; }
sb.Append(BuildBodyFromTemplate(match, type));
}
}
sb.Append(FOOTER);
await File.WriteAllTextAsync("./Octokit.AsyncPaginationExtension/Extensions.cs", sb.ToString());
}
/// <summary>
/// BuildBodyFromTemplate uses the match from the regex search and parses values from the given source
/// to use to generate the paging implementations.
/// </summary>
/// <remarks>
/// TODO: This should be reworked to use source templates
/// </remarks>
private static string BuildBodyFromTemplate(Match match, string type)
{
var argSplitRegex = new Regex(@" (?![^<]*>)");
var returnType = match.Groups["returnType"].Value;
var name = match.Groups["name"].Value;
var arg = match.Groups["arg"].Value;
var template = match.Groups["template"];
var templateStr = template.Success ? template.Value : string.Empty;
var splitArgs = argSplitRegex.Split(arg).ToArray();
var lambda = arg.Length == 0
? $"t.{name}{templateStr}"
: $"options => t.{name}{templateStr}({string.Join(' ', splitArgs.Where((_, i) => i % 2 == 1))}, options)";
var docArgs = string.Join(", ", splitArgs.Where((_, i) => i % 2 == 0)).Replace('<', '{').Replace('>', '}');
if (docArgs.Length != 0) {
docArgs += ", ";
}
if (arg.Length != 0) {
arg += ", ";
}
return ($@"
/// <inheritdoc cref=""{type}.{name}({docArgs}ApiOptions)""/>
public static IPaginatedList<{returnType}> {name}Async{templateStr}(this {type} t, {arg}int pageSize = DEFAULT_PAGE_SIZE)
=> pageSize > 0 ? new PaginatedList<{returnType}>({lambda}, pageSize) : throw new ArgumentOutOfRangeException(nameof(pageSize), pageSize, ""The page size must be positive."");
");
}
}
}