public async Task <IActionResult> Send(long id, [FromBody] SendMailDto sendMail) { Template template; try { template = await context.Set <Template>() .Include(x => x.Mail) .FirstAsync(x => x.User.Token == sendMail.Token && x.Id == id); } catch { throw new TemplateNotFoundException(); } var secret = configuration.GetValue <string>("PasswordSecret"); var stubble = new StubbleBuilder().Build(); var from = new MailAddress(template.Mail.EmailAddress, template.Mail.Name); var password = encryption.Decrypt(template.Mail.Password, secret); foreach (var recipient in sendMail.Recipients) { var subject = await stubble.RenderAsync(template.Subject, recipient.Args); var body = await stubble.RenderAsync(template.Content, recipient.Args); var to = new MailAddress(recipient.Email); var message = new MailMessage(from, to) { Subject = subject, Body = body, BodyEncoding = Encoding.UTF8, IsBodyHtml = template.IsHtml }; using var smtp = new SmtpClient(template.Mail.Host, template.Mail.Port) { DeliveryMethod = SmtpDeliveryMethod.Network, UseDefaultCredentials = false, EnableSsl = template.Mail.EnableSsl, Credentials = new NetworkCredential(template.Mail.EmailAddress, password) }; try { await smtp.SendMailAsync(message); } catch (Exception ex) { throw new BusinessException("smtp-error", $"Something happened when sending the email to {recipient.Email} via SMTP: {ex.Message}", ex); } } return(Ok()); }
public async Task <Message> Render <TModel>(string subject, string body, TModel data, CancellationToken cancellationToken) { var stubble = new StubbleBuilder().Build(); var renderedSubject = subject != null ? await stubble.RenderAsync(subject, data) : null; var renderedBody = body != null ? await stubble.RenderAsync(body, data) : null; var renderedHtmlBody = renderedBody != null?Markdig.Markdown.ToHtml(renderedBody) : null; return(new Message { Subject = renderedSubject, Body = renderedHtmlBody }); }
public async Task It_Can_Render_Literals_Async() { var stubble = new StubbleBuilder().Build(); var output = await stubble.RenderAsync("Literal test", null); Assert.Equal("Literal test", output); }
public async Task It_Can_Render_Inverted_Sections_Async() { var stubble = new StubbleBuilder().Build(); var output = await stubble.RenderAsync(@"{{^True}}Not True{{/True}}", new { True = false }); Assert.Equal("Not True", output); }
public async Task It_Can_Render_The_Template_Async() { var stubble = new StubbleBuilder() .Build(); var output = await stubble.RenderAsync("{{Foo}}", new { Foo = "Bar" }); Assert.Equal("Bar", output); }
public async Task <string> FormatAsync(string template, object data) { var stubble = new StubbleBuilder().Configure(s => { s.SetIgnoreCaseOnKeyLookup(true); }).Build(); return(await stubble.RenderAsync(template, data)); }
private async Task <string> RenderTemplateAsync(string template, object tags) { StubbleVisitorRenderer stubble = new StubbleBuilder().Configure(x => { x.SetIgnoreCaseOnKeyLookup(true); x.SetMaxRecursionDepth(512); }).Build(); return(await stubble.RenderAsync(template, tags)); }
public async Task <IActionResult> Test(TestTemplateDto template) { Mail mail; try { mail = context.Set <Mail>() .Where(x => x.User.Id == user.Id && x.Id == template.MailId) .First(); } catch (Exception ex) { throw new BusinessException("mail-not-found", "The provided mail was not found", ex); } var stubble = new StubbleBuilder().Build(); var from = new MailAddress(mail.EmailAddress); var to = new MailAddress(template.Recipient.Email); var password = encryption.Decrypt(mail.Password, template.Secret); var subject = await stubble.RenderAsync(template.Subject, template.Recipient.Args); var body = await stubble.RenderAsync(template.Content, template.Recipient.Args); var message = new MailMessage(from, to) { Subject = subject, Body = body, BodyEncoding = Encoding.UTF8, IsBodyHtml = template.IsHtml }; using var smtp = new SmtpClient(mail.Host, mail.Port) { DeliveryMethod = SmtpDeliveryMethod.Network, UseDefaultCredentials = false, EnableSsl = mail.EnableSsl, Credentials = new NetworkCredential(mail.EmailAddress, password) }; await smtp.SendMailAsync(message); return(Ok()); }
public async Task It_Can_Render_WithPartials_FromArgs_Async() { var stubble = new StubbleBuilder().Build(); var output = await stubble.RenderAsync("{{> foo}}", new { Foo = "Bar" }, new Dictionary <string, string> { { "foo", "{{Foo}} this" } }); Assert.Equal("Bar this", output); }
internal async Task <string> ApplyAsync(Dictionary <string, object> row) { if (string.IsNullOrWhiteSpace(template)) { template = ReadTemplate(); } var stubble = new StubbleBuilder().Build(); return(await stubble.RenderAsync(template, row)); }
/// <summary> /// Applies the moutache rendering to the given template string. /// </summary> /// <param name="template">The template.</param> /// <param name="variables">The prompt results.</param> /// <returns>The with moustache rendering applied</returns> public static async Task <string> ApplyMoustache(string template, IDictionary <string, object> variables) { var renderSettings = new RenderSettings { SkipHtmlEncoding = true }; var stubble = new StubbleBuilder().Build(); return(await stubble .RenderAsync(template, variables, renderSettings) .ConfigureAwait(false)); }
// Through the magic of System.CommandLine.Dragonfruit, this method is turned into a command-line API! // The XML docs become help text and the arguments become switches/args. /// <summary> /// Generates GitHub query lists from an input JSON file. /// </summary> /// <param name="inputFile">The JSON data file to use as input. Defaults to 'queries.json' in the current directory.</param> /// <param name="outputFile">The output markdown file to generate. Defaults to 'Queries.md' in the current directory.</param> static async Task <int> Main(string inputFile = null, string outputFile = null) { inputFile = inputFile ?? Path.Combine(Directory.GetCurrentDirectory(), "queries.json"); outputFile = outputFile ?? Path.Combine(Directory.GetCurrentDirectory(), "Queries.md"); if (!File.Exists(inputFile)) { Console.Error.WriteLine($"Could not find input file: {inputFile}"); return(1); } // Load the input file Console.WriteLine($"Loading input data: {inputFile} ..."); QueryList inputList; using (var inputStream = File.OpenRead(inputFile)) { var options = new JsonSerializerOptions() { PropertyNameCaseInsensitive = true, }; options.Converters.Add(new JsonStringEnumConverter()); inputList = await JsonSerializer.DeserializeAsync <QueryList>(inputStream, options); } // Generate queries Console.WriteLine("Computing queries..."); var querySet = new QuerySet(); foreach (var queryDefinition in inputList.Queries) { GenerateQuery(queryDefinition, inputList, querySet); } // Generate the template Console.WriteLine($"Generating output file: {outputFile} ..."); var stubble = new StubbleBuilder().Build(); using (var templateStream = typeof(Program).Assembly.GetManifestResourceStream("Internal.AspNetCore.QueryGenerator.Templates.querylist.mustache")) using (var templateReader = new StreamReader(templateStream)) { var template = await templateReader.ReadToEndAsync(); var output = await stubble.RenderAsync(template, querySet); await File.WriteAllTextAsync(outputFile, output); } return(0); }
private static async Task ExtractEventsFromAssemblies(string outputPath, IPlatform platform) { await platform.Extract().ConfigureAwait(false); Log.Debug("Extracting events from the following assemblies: {assemblies}", platform.Assemblies); Log.Debug("Using the following search directories: {assemblies}", platform.CecilSearchDirectories); var targetAssemblyDirs = platform.CecilSearchDirectories; var rp = new ReaderParameters { AssemblyResolver = new PathSearchAssemblyResolver(targetAssemblyDirs.ToArray()) }; var targetAssemblies = platform.Assemblies.Select(x => AssemblyDefinition.ReadAssembly(x, rp)).ToArray(); Log.Debug("Using {template} as the mustache template", _mustacheTemplate); var namespaceData = Array.Empty <Entities.NamespaceInfo>(); switch (platform.Platform) { case AutoPlatform.Essentials: namespaceData = StaticEventTemplateInformation.Create(targetAssemblies); break; default: namespaceData = EventTemplateInformation.Create(targetAssemblies); break; } var delegateData = DelegateTemplateInformation.Create(targetAssemblies); using (var streamReader = new StreamReader(_mustacheTemplate, Encoding.UTF8)) { var template = await streamReader.ReadToEndAsync().ConfigureAwait(false); var stubble = new StubbleBuilder().Build(); var result = (await stubble.RenderAsync(template, new { Namespaces = namespaceData, DelegateNamespaces = delegateData }).ConfigureAwait(false)) .Replace("System.String", "string") .Replace("System.Object", "object") .Replace("<", "<") .Replace(">", ">") .Replace("`1", string.Empty) .Replace("`2", string.Empty) .Replace("`3", string.Empty); await File.WriteAllTextAsync(outputPath, result).ConfigureAwait(false); } }
public async Task It_Can_Render_WithPartials_FromLoader_Async() { var stubble = new StubbleBuilder() .Configure(builder => { builder.SetPartialTemplateLoader(new DictionaryLoader(new Dictionary <string, string> { { "foo", "{{Foo}} this" } })); }).Build(); var output = await stubble.RenderAsync("{{> foo}}", new { Foo = "Bar" }); Assert.Equal("Bar this", output); }
private async Task <string> ProcessTemplate(object mustacheModel, string templateUri) { var stubble = new StubbleBuilder().Build(); var output = string.Empty; using (StreamReader streamReader = new StreamReader(templateUri, Encoding.UTF8)) { var content = await streamReader.ReadToEndAsync(); output = await stubble.RenderAsync(content, mustacheModel); } return(output); }
public async Task It_Should_Skip_Html_Encoding_With_Setting_Async() { var stubble = new StubbleBuilder() .Build(); var obj = new { Html = "<b>Html</b>" }; var result = await stubble.RenderAsync("{{Html}}\n{{{Html}}}", obj, new RenderSettings { SkipHtmlEncoding = true }); Assert.Equal("<b>Html</b>\n<b>Html</b>", result); }
public async Task Handle(HttpRequest req, HttpResponse res, object model, CancellationToken cancellationToken) { var source = viewLocator.GetView(model, res.HttpContext); if (string.IsNullOrEmpty(source)) { res.StatusCode = 500; res.ContentType = "text/plain"; await res.WriteAsync("View not found", cancellationToken); } var stubble = new StubbleBuilder().Build(); res.ContentType = "text/html"; res.StatusCode = (int)HttpStatusCode.OK; var output = await stubble.RenderAsync(source, model); await res.WriteAsync(output, cancellationToken); }
public async Task It_Should_Allow_An_NonAsync_Render_Function_In_Lambda() { var stubble = new StubbleBuilder() .Build(); var obj = new { Value = "a", ValueDictionary = new Dictionary <string, string> { { "a", "A is Cool" }, { "b", "B is Cool" }, }, ValueRender = new Func <string, Func <string, string>, object>((string text, Func <string, string> render) => "{{ValueDictionary." + render("{{Value}}") + "}}") }; var result = await stubble.RenderAsync("{{#ValueRender}}{{/ValueRender}}", obj); Assert.Equal("A is Cool", result); }
public async Task It_Should_Indent_Partials_When_Folding_Literals_Correctly() { var tpl = @"<div> {{> Body}} </div>"; var partial = @"<a href=""{{Url}}""> My Link </a>"; var stubble = new StubbleBuilder() .Configure(conf => { conf.SetPartialTemplateLoader(new DictionaryLoader(new Dictionary <string, string> { { "Body", partial } })); }) .Build(); var obj = new { Url = "MyUrl" }; var result = stubble.Render(tpl, obj); Assert.Equal(@"<div> <a href=""MyUrl""> My Link </a></div>", result); var resultAsync = await stubble.RenderAsync(tpl, obj); Assert.Equal(@"<div> <a href=""MyUrl""> My Link </a></div>", resultAsync); }
private async Task <string> RenderTemplate(string templateName, UserModel userModel, object model) { var fileIdentifier = new FileIdentifier { FileKey = $"{templateName}.mustache", FolderKey = ":templates", OrganizationKey = userModel.Identifier.OrganizationKey }; if (!(await IsFileAccessibleAsync(fileIdentifier))) { fileIdentifier.OrganizationKey = "system"; } if (!(await IsFileAccessibleAsync(fileIdentifier))) { throw new Exception($"Cannot load template for {templateName} at {fileIdentifier}"); } var stubble = new StubbleBuilder().Build(); // round-trip the model object back through // json so that we can get a dictionary readable by stubble model = JsonConvert.DeserializeObject <Dictionary <string, object> >(JsonConvert.SerializeObject(model)); string output = null; await API.File.DownloadAsync(fileIdentifier, async (stream, cancel) => { using (var streamReader = new StreamReader(stream, Encoding.UTF8)) { var content = await streamReader.ReadToEndAsync(); output = await stubble.RenderAsync(content, model); } }); return(output); }
public static async Task <string> ToConsoleOutput(this QuantifierResult quantifierResult) { var stubble = new StubbleBuilder() .Configure(settings => { settings.SetIgnoreCaseOnKeyLookup(true); }).Build(); using var stream = new StreamReader( Assembly.GetExecutingAssembly().GetManifestResourceStream( $"{typeof(QuantifierResultExtensions).Namespace}.ConsoleOutput.mustache") !); var consoleOutput = await stubble.RenderAsync( await stream.ReadToEndAsync(), new { quantifierResult.Label, quantifierResult.QuantifiedLinesAdded, quantifierResult.QuantifiedLinesDeleted, quantifierResult.PercentileAddition, quantifierResult.PercentileDeletion, quantifierResult.FormulaPercentile, Formula = quantifierResult.Formula.ToString(), }); return(consoleOutput); }
private async Task <DirectEmailHistory> InternalSendDirectAsync(Site site, DirectEmailHistory history, IDictionary <string, string> tags) { if (history == null) { throw new ArgumentNullException(nameof(history)); } var emailBase = await GetEmailBase(history.EmailBaseId, history.LanguageId); if (emailBase.EmailBaseText == null) { emailBase = await GetEmailBase(history.EmailBaseId, await _languageService.GetDefaultLanguageIdAsync()); } var stubble = new StubbleBuilder().Build(); using var message = new MimeMessage { Subject = history.Subject, Body = new BodyBuilder { TextBody = await stubble .RenderAsync(emailBase.EmailBaseText.TemplateText, tags), HtmlBody = await stubble .RenderAsync(emailBase.EmailBaseText.TemplateHtml, tags, new Stubble.Core.Settings.RenderSettings { SkipHtmlEncoding = true }) }.ToMessageBody() }; message.From.Add(new MailboxAddress(history.FromName, history.FromEmailAddress)); try { if (!string.IsNullOrEmpty(history.OverrideToEmailAddress)) { message.To.Add(MailboxAddress.Parse(history.OverrideToEmailAddress)); } else { message.To.Add(new MailboxAddress(history.ToName, history.ToEmailAddress)); } } catch (ParseException ex) { _logger.LogError("Unable to parse email address: {EmailAddress}", history.ToEmailAddress); throw new GraException($"Unable to parse email address: {history.ToEmailAddress}", ex); } using var client = new SmtpClient { // accept any STARTTLS certificate ServerCertificateValidationCallback = (_, __, ___, ____) => true }; client.MessageSent += (_, e) => { history.SentResponse = e.Response?.Length > 255 ? e.Response[..255]
public async Task <DirectEmailHistory> SendDirectAsync(DirectEmailDetails directEmailDetails) { if (directEmailDetails == null) { throw new ArgumentNullException(nameof(directEmailDetails)); } string toAddress; string toName; int languageId; Site site; if (directEmailDetails.ToUserId.HasValue) { var user = await _userRepository.GetByIdAsync(directEmailDetails.ToUserId ?? directEmailDetails.SendingUserId); if (string.IsNullOrEmpty(user.Email)) { _logger.LogError("Unable to send email to user id {UserId}: no email address configured.", directEmailDetails.ToUserId); throw new GraException($"User id {directEmailDetails.ToUserId} does not have an email address configured."); } site = await _siteLookupService.GetByIdAsync(user.SiteId); toAddress = user.Email; toName = user.FullName; languageId = directEmailDetails.LanguageId ?? (string.IsNullOrEmpty(user.Culture) ? await _languageService.GetDefaultLanguageIdAsync() : await _languageService.GetLanguageIdAsync(user.Culture)); } else { var user = await _userRepository.GetByIdAsync(directEmailDetails.SendingUserId); site = await _siteLookupService.GetByIdAsync(user.SiteId); toAddress = directEmailDetails.ToAddress; toName = directEmailDetails.ToName; languageId = directEmailDetails.LanguageId ?? await _languageService.GetLanguageIdAsync(CultureInfo.CurrentCulture.Name); } if (!SiteCanSendMail(site)) { throw new GraException("Unable to send mail, please ensure from name, from email, and outgoing mail server are configured in Site Management -> Configuration."); } var history = new DirectEmailHistory { CreatedBy = directEmailDetails.SendingUserId, FromEmailAddress = site.FromEmailAddress, FromName = site.FromEmailName, IsBulk = directEmailDetails.IsBulk, LanguageId = languageId, OverrideToEmailAddress = string .IsNullOrWhiteSpace(_config[ConfigurationKey.EmailOverride]) ? null : _config[ConfigurationKey.EmailOverride], ToEmailAddress = toAddress, ToName = toName }; if (directEmailDetails.ToUserId.HasValue) { history.UserId = directEmailDetails.ToUserId.Value; } DirectEmailTemplate directEmailTemplate = await GetDirectEmailTemplateAsync(directEmailDetails.DirectEmailSystemId, directEmailDetails.DirectEmailTemplateId, history.LanguageId); if (directEmailTemplate == null || directEmailTemplate.DirectEmailTemplateText == null) { // not available in the requested language, use the default language history.LanguageId = await _languageService.GetDefaultLanguageIdAsync(); directEmailTemplate = await GetDirectEmailTemplateAsync(directEmailDetails.DirectEmailSystemId, directEmailDetails.DirectEmailTemplateId, history.LanguageId); } history.DirectEmailTemplateId = directEmailTemplate.Id; history.EmailBaseId = directEmailTemplate.EmailBaseId; var stubble = new StubbleBuilder().Build(); history.Subject = await stubble .RenderAsync(directEmailTemplate.DirectEmailTemplateText.Subject, directEmailDetails.Tags); history.BodyText = await stubble .RenderAsync(directEmailTemplate.DirectEmailTemplateText.BodyCommonMark, directEmailDetails.Tags); history.BodyHtml = CommonMark.CommonMarkConverter.Convert(history.BodyText); string preview = await stubble .RenderAsync(directEmailTemplate.DirectEmailTemplateText.Preview, directEmailDetails.Tags); string title = await stubble .RenderAsync(directEmailTemplate.DirectEmailTemplateText.Title, directEmailDetails.Tags); string footer = CommonMark.CommonMarkConverter.Convert(await stubble .RenderAsync(directEmailTemplate.DirectEmailTemplateText.Footer, directEmailDetails.Tags)); history = await InternalSendDirectAsync(site, history, new Dictionary <string, string> { { "Footer", footer }, { "Preview", preview }, { "Title", title }, { "BodyHtml", history.BodyHtml }, { "BodyText", history.BodyText } }); if (directEmailDetails.IsBulk && !directEmailDetails.IsTest) { if (directEmailDetails.ToUserId.HasValue) { history.BodyHtml = null; history.BodyText = null; await _directEmailHistoryRepository.AddSaveNoAuditAsync(history); } if (!directEmailTemplate.SentBulk) { await _directEmailTemplateRepository .UpdateSentBulkAsync(directEmailTemplate.Id); } } else { if (!directEmailDetails.IsTest) { await _directEmailHistoryRepository.AddSaveNoAuditAsync(history); } await IncrementSentCountAsync(directEmailTemplate.Id); } return(history); }
public static async Task <string> ToMarkdownCommentAsync( this QuantifierResult quantifierResult, string repositoryLink, string contextFileLink, string pullRequestLink, string authorName, MarkdownCommentOptions markdownCommentOptions = null) { markdownCommentOptions ??= new MarkdownCommentOptions(); var stubble = new StubbleBuilder() .Configure(settings => { settings.SetIgnoreCaseOnKeyLookup(true); }).Build(); using var stream = new StreamReader( Assembly.GetExecutingAssembly().GetManifestResourceStream( $"{typeof(QuantifierResultExtensions).Namespace}.QuantifierComment.mustache") !); var feedbackLinkThumbsUp = CreateFeedbackLink( "ThumbsUp", authorName, repositoryLink, pullRequestLink); var feedbackLinkNeutral = CreateFeedbackLink( "Neutral", authorName, repositoryLink, pullRequestLink); var feedbackLinkThumbsDown = CreateFeedbackLink( "ThumbsDown", authorName, repositoryLink, pullRequestLink); var comment = await stubble.RenderAsync( await stream.ReadToEndAsync(), new { quantifierResult.Color, quantifierResult.Label, quantifierResult.QuantifiedLinesAdded, quantifierResult.QuantifiedLinesDeleted, quantifierResult.PercentileAddition, quantifierResult.PercentileDeletion, quantifierResult.FormulaPercentile, Formula = quantifierResult.Formula.ToString(), ContextFileLink = contextFileLink, FeedbackLinkUp = feedbackLinkThumbsUp, FeedbackLinkNeutral = feedbackLinkNeutral, FeedbackLinkDown = feedbackLinkThumbsDown, TotalFilesChanged = quantifierResult.QuantifierInput.Changes.Count, Details = string.Join( Environment.NewLine, quantifierResult.QuantifierInput.Changes .GroupBy(c => c.FileExtension) .Select( g => $"{g.Key} : +{g.Sum(v => v.QuantifiedLinesAdded)} -{g.Sum(v => v.QuantifiedLinesDeleted)}")), CollapseChangesSummarySection = markdownCommentOptions.CollapseChangesSummarySection ? string.Empty : "open", CollapsePullRequestQuantifiedSection = markdownCommentOptions.CollapsePullRequestQuantifiedSection ? string.Empty : "open" }); return(comment); }
public static async Task <string> ToMarkdownCommentAsync( this QuantifierResult quantifierResult, string repositoryLink, string contextFileLink, string pullRequestLink, string authorName, bool anonymous = true, MarkdownCommentOptions markdownCommentOptions = null) { markdownCommentOptions ??= new MarkdownCommentOptions(); var stubble = new StubbleBuilder() .Configure(settings => { settings.SetIgnoreCaseOnKeyLookup(true); }).Build(); using var stream = new StreamReader( Assembly.GetExecutingAssembly().GetManifestResourceStream( $"{typeof(QuantifierResultExtensions).Namespace}.Mustache.QuantifierComment.mustache") !); var feedbackLinkThumbsUp = CreateFeedbackLink( "ThumbsUp", authorName, repositoryLink, pullRequestLink, anonymous); var feedbackLinkNeutral = CreateFeedbackLink( "Neutral", authorName, repositoryLink, pullRequestLink, anonymous); var feedbackLinkThumbsDown = CreateFeedbackLink( "ThumbsDown", authorName, repositoryLink, pullRequestLink, anonymous); var contextFormulaPercentile = quantifierResult.Context.FormulaPercentile.First(f => f.Item1 == quantifierResult.Formula).Item2; var(idealSizeLowerBound, idealSizeUpperBound) = GetIdealChangeCountRange(contextFormulaPercentile); var detailsByFileExt = quantifierResult.QuantifierInput.Changes .Where(c => !string.IsNullOrEmpty(c.FileExtension)) .GroupBy(c => c.FileExtension) .Select( g => $"{g.Key} : +{g.Sum(v => v.QuantifiedLinesAdded)} -{g.Sum(v => v.QuantifiedLinesDeleted)}") .Concat( quantifierResult.QuantifierInput.Changes .Where(c => string.IsNullOrEmpty(c.FileExtension)) .Select(c => $"{c.FilePath} : +{c.QuantifiedLinesAdded} -{c.QuantifiedLinesDeleted}")); var comment = await stubble.RenderAsync( await stream.ReadToEndAsync(), new { // lower th color str because of a bug in shield service // https://img.shields.io/static/v1?label=Quantified&message=Extra%20Small&color=Red is not respecting the red color, // but https://img.shields.io/static/v1?label=Quantified&message=Extra%20Small&color=red is working Color = quantifierResult.Color.ToLower(), quantifierResult.Label, // encode the label in case contains space EncodedLabel = Uri.EscapeUriString(quantifierResult.Label), quantifierResult.QuantifiedLinesAdded, quantifierResult.QuantifiedLinesDeleted, quantifierResult.FormulaLinesChanged, quantifierResult.PercentileAddition, quantifierResult.PercentileDeletion, quantifierResult.FormulaPercentile, IdealSizeLowerBound = idealSizeLowerBound, IdealSizeUpperBound = idealSizeUpperBound, IsIdealSize = quantifierResult.FormulaLinesChanged >= idealSizeLowerBound && quantifierResult.FormulaLinesChanged <= idealSizeUpperBound, Formula = quantifierResult.Formula.ToString(), ContextFileLink = !string.IsNullOrWhiteSpace(contextFileLink) ? contextFileLink : "https://github.com/microsoft/PullRequestQuantifier/blob/main/docs/prquantifier-yaml.md", FeedbackLinkUp = feedbackLinkThumbsUp, FeedbackLinkNeutral = feedbackLinkNeutral, FeedbackLinkDown = feedbackLinkThumbsDown, TotalFilesChanged = quantifierResult.QuantifierInput.Changes.Count, Details = string.Join(Environment.NewLine, detailsByFileExt), CollapsePullRequestQuantifiedSection = markdownCommentOptions.CollapsePullRequestQuantifiedSection ? string.Empty : "open" }); return(comment); }