Update emoji support

* Add constants for emojis
* Move emoji shortcode rendering to Markup
* Add documentation
* Add example
* Add tests
This commit is contained in:
Patrik Svensson
2020-09-18 01:58:55 +02:00
committed by Patrik Svensson
parent 090b30f731
commit eeb3f967b6
38 changed files with 17680 additions and 1878 deletions

View File

@ -55,5 +55,8 @@ namespace Generator.Commands
{
[CommandArgument(0, "<OUTPUT>")]
public string Output { get; set; }
[CommandOption("-i|--input <PATH>")]
public string Input { get; set; }
}
}

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -10,52 +11,72 @@ using Scriban.Runtime;
using Spectre.Cli;
using Spectre.IO;
using Path = Spectre.IO.Path;
using SpectreEnvironment = Spectre.IO.Environment;
namespace Generator.Commands
{
public sealed class EmojiGeneratorCommand : AsyncCommand<GeneratorCommandSettings>
{
private readonly IFileSystem _fileSystem;
private readonly IEnvironment _environment;
private readonly IHtmlParser _parser;
private readonly Dictionary<string, string> _templates = new Dictionary<string, string>
{
{ "Templates/Emoji.Generated.template", "Emoji.Generated.cs" },
{ "Templates/Emoji.Json.template", "emojis.json" },
};
public EmojiGeneratorCommand()
{
_fileSystem = new FileSystem();
_environment = new SpectreEnvironment();
_parser = new HtmlParser();
}
public override async Task<int> ExecuteAsync(CommandContext context, GeneratorCommandSettings settings)
{
var output = new DirectoryPath(settings.Output);
if (!_fileSystem.Directory.Exists(settings.Output))
{
_fileSystem.Directory.Create(settings.Output);
}
var templatePath = new FilePath("Templates/Emoji.Generated.template");
var stream = await FetchEmojis(settings);
var document = await _parser.ParseDocumentAsync(stream);
var emojis = Emoji.Parse(document).OrderBy(x => x.Name)
.Where(emoji => !emoji.HasCombinators)
.ToList();
var emojis = await FetchEmojis("http://www.unicode.org/emoji/charts/emoji-list.html");
// Render all templates
foreach (var (templateFilename, outputFilename) in _templates)
{
var result = await RenderTemplate(new FilePath(templateFilename), emojis);
var result = await RenderTemplate(templatePath, emojis);
var outputPath = output.CombineWithFilePath(templatePath.GetFilename().ChangeExtension(".cs"));
await File.WriteAllTextAsync(outputPath.FullPath, result);
var outputPath = output.CombineWithFilePath(outputFilename);
await File.WriteAllTextAsync(outputPath.FullPath, result);
}
return 0;
}
private async Task<IReadOnlyCollection<Emoji>> FetchEmojis(string url)
private async Task<Stream> FetchEmojis(GeneratorCommandSettings settings)
{
using var http = new HttpClient();
var input = string.IsNullOrEmpty(settings.Input)
? _environment.WorkingDirectory
: new DirectoryPath(settings.Input);
var htmlStream = await http.GetStreamAsync(url);
var file = _fileSystem.File.Retrieve(input.CombineWithFilePath("emoji-list.html"));
if (!file.Exists)
{
using var http = new HttpClient();
using var httpStream = await http.GetStreamAsync("http://www.unicode.org/emoji/charts/emoji-list.html");
using var outStream = file.OpenWrite();
var document = await _parser.ParseDocumentAsync(htmlStream);
await httpStream.CopyToAsync(outStream);
}
return Emoji.Parse(document).OrderBy(x => x.Name).ToList();
return file.OpenRead();
}
private static async Task<string> RenderTemplate(Path path, IReadOnlyCollection<Emoji> emojis)
@ -63,7 +84,6 @@ namespace Generator.Commands
var text = await File.ReadAllTextAsync(path.FullPath);
var template = Template.Parse(text);
var templateContext = new TemplateContext
{
// Because of the insane amount of Emojis,
@ -72,9 +92,7 @@ namespace Generator.Commands
};
var scriptObject = new ScriptObject();
scriptObject.Import(new { Emojis = emojis });
templateContext.PushGlobal(scriptObject);
return await template.RenderAsync(templateContext);

View File

@ -24,6 +24,9 @@
<None Update="Templates\ColorPalette.Generated.template">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Templates\Emoji.Json.template">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Templates\Emoji.Generated.template">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
@ -31,6 +34,7 @@
<ItemGroup>
<PackageReference Include="AngleSharp" Version="0.14.0" />
<PackageReference Include="Humanizer.Core" Version="2.8.26" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Scriban" Version="2.1.3" />
<PackageReference Include="Spectre.Cli" Version="0.36.1-preview.0.6" />

View File

@ -3,6 +3,7 @@ using System.Linq;
using System.Text;
using AngleSharp.Dom;
using AngleSharp.Html.Dom;
using Humanizer;
namespace Generator.Models
{
@ -10,15 +11,22 @@ namespace Generator.Models
{
private static readonly string[] _headers = { "count", "code", "sample", "name" };
private Emoji(string code, string name)
private Emoji(string identifier, string name, string code, string description)
{
Code = code;
Identifier = identifier;
Name = name;
Code = code;
Description = description;
NormalizedCode = Code.Replace("\\U", "U+");
HasCombinators = Code.Split(new[] { "\\U" }, System.StringSplitOptions.RemoveEmptyEntries).Length > 1;
}
public string Identifier { get; set; }
public string Code { get; }
public string NormalizedCode { get; }
public string Name { get; }
public string Description { get; set; }
public bool HasCombinators { get; set; }
public static IEnumerable<Emoji> Parse(IHtmlDocument document)
{
@ -30,13 +38,24 @@ namespace Generator.Models
foreach (var row in rows)
{
var dictionary = _headers
.Zip(row.Cells, (header, cell) => (header, cell.TextContent.Trim()))
.Zip(row.Cells, (header, cell) => (Header: header, cell.TextContent.Trim()))
.ToDictionary(x => x.Item1, x => x.Item2);
var code = TransformCode(dictionary["code"]);
var name = TransformName(dictionary["name"]);
var identifier = TransformName(dictionary["name"])
.Replace("-", "_")
.Replace("(", string.Empty)
.Replace(")", string.Empty);
yield return new Emoji(code, name);
var description = dictionary["name"].Humanize();
var name = identifier
.Replace("1st", "first")
.Replace("2nd", "second")
.Replace("3rd", "third")
.Pascalize();
yield return new Emoji(identifier, name, code, description);
}
}
@ -48,8 +67,14 @@ namespace Generator.Models
.Replace("\u201c", string.Empty)
.Replace("\u201d", string.Empty)
.Replace("\u229b", string.Empty)
.Trim()
.Replace(' ', '_')
.Replace("s", "s")
.Replace("", "_")
.Replace("&", "and")
.Replace("#", "hash")
.Replace("*", "star")
.Replace("!", string.Empty)
.Trim()
.ToLowerInvariant();
}

View File

@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Generated {{ date.now | date.to_string `%Y-%m-%d %k:%M` }}
// Generated {{ date.now | date.to_string `%F %R` }}
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.

View File

@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Generated {{ date.now | date.to_string `%Y-%m-%d %k:%M` }}
// Generated {{ date.now | date.to_string `%F %R` }}
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.

View File

@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Generated {{ date.now | date.to_string `%Y-%m-%d %k:%M` }}
// Generated {{ date.now | date.to_string `%F %R` }}
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.

View File

@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Generated {{ date.now | date.to_string `%Y-%m-%d %k:%M` }}
// Generated {{ date.now | date.to_string `%F %R` }}
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@ -10,20 +10,34 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console
{
/// <summary>
/// Utility class for working with emojis.
/// Utility for working with emojis.
/// </summary>
internal static partial class Emoji
public static partial class Emoji
{
private static readonly Dictionary<string, string> _emojis
= new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase)
{
{{~ for emoji in emojis }} { "{{ emoji.name }}", "{{ emoji.code }}" },
{{~ for emoji in emojis ~}}
{ "{{ emoji.identifier }}", Emoji.Known.{{ emoji.name }} },
{{~ end ~}}
};
/// <summary>
/// Contains all predefined emojis.
/// </summary>
public static class Known
{
{{- for emoji in emojis }}
/// <summary>
/// Gets the "{{ emoji.identifier }}" emoji.
/// Description: {{ emoji.description }}.
/// </summary>
public const string {{ emoji.name }} = "{{ emoji.code }}";
{{~ end ~}}
}
}
}

View File

@ -0,0 +1,10 @@
[
{{~ for x in 0..(emojis.size-1) ~}}
{
"id": "{{ emojis[x].identifier }}",
"name": "{{ emojis[x].name }}",
"description": "{{ emojis[x].description }}",
"code": "{{ emojis[x].normalized_code }}"
}{{ if x != (emojis.size-1) }},{{ end }}
{{~ end ~}}
]