mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-11-04 02:25:28 +08:00 
			
		
		
		
	Add Generator command to generate emoji lookup table
This commit is contained in:
		
				
					committed by
					
						
						Patrik Svensson
					
				
			
			
				
	
			
			
			
						parent
						
							11d331e31d
						
					
				
				
					commit
					a7b7d4e556
				
			@@ -0,0 +1,83 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using AngleSharp.Html.Parser;
 | 
			
		||||
using Generator.Models;
 | 
			
		||||
using Scriban;
 | 
			
		||||
using Scriban.Runtime;
 | 
			
		||||
using Spectre.Cli;
 | 
			
		||||
using Spectre.IO;
 | 
			
		||||
using Path = Spectre.IO.Path;
 | 
			
		||||
 | 
			
		||||
namespace Generator.Commands
 | 
			
		||||
{
 | 
			
		||||
    public sealed class EmojiGeneratorCommand : AsyncCommand<GeneratorCommandSettings>
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IFileSystem _fileSystem;
 | 
			
		||||
 | 
			
		||||
        private readonly IHtmlParser _parser;
 | 
			
		||||
 | 
			
		||||
        public EmojiGeneratorCommand()
 | 
			
		||||
        {
 | 
			
		||||
            _fileSystem = new FileSystem();
 | 
			
		||||
            _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 emojis = await FetchEmojis("http://www.unicode.org/emoji/charts/emoji-list.html");
 | 
			
		||||
 | 
			
		||||
            var result = await RenderTemplate(templatePath, emojis);
 | 
			
		||||
 | 
			
		||||
            var outputPath = output.CombineWithFilePath(templatePath.GetFilename().ChangeExtension(".cs"));
 | 
			
		||||
 | 
			
		||||
            await File.WriteAllTextAsync(outputPath.FullPath, result);
 | 
			
		||||
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task<IReadOnlyCollection<Emoji>> FetchEmojis(string url)
 | 
			
		||||
        {
 | 
			
		||||
            using var http = new HttpClient();
 | 
			
		||||
 | 
			
		||||
            var htmlStream = await http.GetStreamAsync(url);
 | 
			
		||||
 | 
			
		||||
            var document = await _parser.ParseDocumentAsync(htmlStream);
 | 
			
		||||
 | 
			
		||||
            return Emoji.Parse(document).OrderBy(x => x.Name).ToList();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static async Task<string> RenderTemplate(Path path, IReadOnlyCollection<Emoji> emojis)
 | 
			
		||||
        {
 | 
			
		||||
            var text = await File.ReadAllTextAsync(path.FullPath);
 | 
			
		||||
 | 
			
		||||
            var template = Template.Parse(text);
 | 
			
		||||
 | 
			
		||||
            var templateContext = new TemplateContext
 | 
			
		||||
            {
 | 
			
		||||
                // Because of the insane amount of Emojis,
 | 
			
		||||
                // we need to get rid of some secure defaults :P
 | 
			
		||||
                LoopLimit = int.MaxValue,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var scriptObject = new ScriptObject();
 | 
			
		||||
 | 
			
		||||
            scriptObject.Import(new { Emojis = emojis });
 | 
			
		||||
 | 
			
		||||
            templateContext.PushGlobal(scriptObject);
 | 
			
		||||
 | 
			
		||||
            return await template.RenderAsync(templateContext);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -24,9 +24,13 @@
 | 
			
		||||
    <None Update="Templates\ColorPalette.Generated.template">
 | 
			
		||||
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
 | 
			
		||||
    </None>
 | 
			
		||||
    <None Update="Templates\Emoji.Generated.template">
 | 
			
		||||
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
 | 
			
		||||
    </None>
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <PackageReference Include="AngleSharp" Version="0.14.0" />
 | 
			
		||||
    <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" />
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										70
									
								
								resources/scripts/Generator/Models/Emoji.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								resources/scripts/Generator/Models/Emoji.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using AngleSharp.Dom;
 | 
			
		||||
using AngleSharp.Html.Dom;
 | 
			
		||||
 | 
			
		||||
namespace Generator.Models
 | 
			
		||||
{
 | 
			
		||||
    public class Emoji
 | 
			
		||||
    {
 | 
			
		||||
        private static readonly string[] _headers = { "count", "code", "sample", "name" };
 | 
			
		||||
 | 
			
		||||
        private Emoji(string code, string name)
 | 
			
		||||
        {
 | 
			
		||||
            Code = code;
 | 
			
		||||
            Name = name;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string Code { get; }
 | 
			
		||||
 | 
			
		||||
        public string Name { get; }
 | 
			
		||||
 | 
			
		||||
        public static IEnumerable<Emoji> Parse(IHtmlDocument document)
 | 
			
		||||
        {
 | 
			
		||||
            var rows = document
 | 
			
		||||
                .GetNodes<IHtmlTableRowElement>(predicate: row =>
 | 
			
		||||
                    row.Cells.Length >= _headers.Length && // Filter out rows that don't have enough cells.
 | 
			
		||||
                    row.Cells.All(x => x.LocalName == TagNames.Td)); // We're only interested in td cells, not th.
 | 
			
		||||
 | 
			
		||||
            foreach (var row in rows)
 | 
			
		||||
            {
 | 
			
		||||
                var dictionary = _headers
 | 
			
		||||
                    .Zip(row.Cells, (header, cell) => (header, cell.TextContent.Trim()))
 | 
			
		||||
                    .ToDictionary(x => x.Item1, x => x.Item2);
 | 
			
		||||
 | 
			
		||||
                var code = TransformCode(dictionary["code"]);
 | 
			
		||||
                var name = TransformName(dictionary["name"]);
 | 
			
		||||
 | 
			
		||||
                yield return new Emoji(code, name);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static string TransformName(string name)
 | 
			
		||||
        {
 | 
			
		||||
            return name.Replace(":", string.Empty)
 | 
			
		||||
                .Replace(",", string.Empty)
 | 
			
		||||
                .Replace(".", string.Empty)
 | 
			
		||||
                .Replace("\u201c", string.Empty)
 | 
			
		||||
                .Replace("\u201d", string.Empty)
 | 
			
		||||
                .Replace("\u229b", string.Empty)
 | 
			
		||||
                .Trim()
 | 
			
		||||
                .Replace(' ', '_')
 | 
			
		||||
                .ToLowerInvariant();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static string TransformCode(string code)
 | 
			
		||||
        {
 | 
			
		||||
            var builder = new StringBuilder();
 | 
			
		||||
 | 
			
		||||
            foreach (var part in code.Split(' '))
 | 
			
		||||
            {
 | 
			
		||||
                builder.Append(part.Length == 6
 | 
			
		||||
                    ? part.Replace("+", "0000")
 | 
			
		||||
                    : part.Replace("+", "000"));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return builder.ToString().Replace("U", "\\U");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -11,6 +11,7 @@ namespace Generator
 | 
			
		||||
            app.Configure(config =>
 | 
			
		||||
            {
 | 
			
		||||
                config.AddCommand<ColorGeneratorCommand>("colors");
 | 
			
		||||
                config.AddCommand<EmojiGeneratorCommand>("emoji");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            return app.Run(args);
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,29 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
// <auto-generated>
 | 
			
		||||
//     This code was generated by a tool.
 | 
			
		||||
//     Generated {{ date.now | date.to_string `%Y-%m-%d %k:%M` }}
 | 
			
		||||
//
 | 
			
		||||
//     Changes to this file may cause incorrect behavior and will be lost if
 | 
			
		||||
//     the code is regenerated.
 | 
			
		||||
// </auto-generated>
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Diagnostics.CodeAnalysis;
 | 
			
		||||
 | 
			
		||||
namespace Spectre.Console
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Utility class for working with emojis.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    internal 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 }}" },
 | 
			
		||||
            {{~ end ~}}
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user