mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-11-01 01:25:27 +08:00 
			
		
		
		
	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
					Patrik Svensson
				
			
				
					committed by
					
						 Patrik Svensson
						Patrik Svensson
					
				
			
			
				
	
			
			
			 Patrik Svensson
						Patrik Svensson
					
				
			
						parent
						
							090b30f731
						
					
				
				
					commit
					eeb3f967b6
				
			
							
								
								
									
										1
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -69,6 +69,7 @@ jobs: | ||||
|           dotnet example grid | ||||
|           dotnet example panel | ||||
|           dotnet example colors | ||||
|           dotnet example emojis | ||||
|  | ||||
|       - name: Build | ||||
|         shell: bash | ||||
|   | ||||
| @@ -22,11 +22,23 @@ | ||||
|     </None> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <None Remove="src\Data\emojis.json" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <EmbeddedResource Include="src\Data\emojis.json" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Statiq.Web" Version="1.0.0-beta.5" /> | ||||
|     <PackageReference Include="MinVer" PrivateAssets="All" Version="2.3.0" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <Folder Include="input\assets\images\emojis\" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <Target Name="Versioning" BeforeTargets="MinVer"> | ||||
|     <PropertyGroup Label="Build"> | ||||
|       <MinVerDefaultPreReleasePhase>preview</MinVerDefaultPreReleasePhase> | ||||
|   | ||||
| @@ -19,6 +19,7 @@ namespace Docs | ||||
|                 .ConfigureDeployment(deployBranch: "docs") | ||||
|                 .AddShortcode("Children", typeof(ChildrenShortcode)) | ||||
|                 .AddShortcode("ColorTable", typeof(ColorTableShortcode)) | ||||
|                 .AddShortcode("EmojiTable", typeof(EmojiTableShortcode)) | ||||
|                 .AddPipelines() | ||||
|                 .RunAsync(); | ||||
|  | ||||
|   | ||||
| @@ -128,14 +128,28 @@ | ||||
|                                         { | ||||
|                                             IDocument root = OutputPages["index.html"].First(); | ||||
|                                             <div class="sidebar-nav-item @(Document.IdEquals(root) ? "active" : null)"> | ||||
|                                                 @Html.DocumentLink(root) | ||||
|                                                     @if(root.ShowLink()) | ||||
|                                                     { | ||||
|                                                         @Html.DocumentLink(root) | ||||
|                                                     } | ||||
|                                                     else | ||||
|                                                     { | ||||
|                                                         @root.GetTitle() | ||||
|                                                     }  | ||||
|                                             </div> | ||||
|  | ||||
|                                             @foreach (IDocument document in OutputPages.GetChildrenOf(root).OnlyVisible()) | ||||
|                                             { | ||||
|                                                 DocumentList<IDocument> documentChildren = OutputPages.GetChildrenOf(document); | ||||
|                                                 <div class="sidebar-nav-item @(Document.IdEquals(document) ? "active" : null) @(documentChildren.Any() ? "has-children" : null)"> | ||||
|                                                     @Html.DocumentLink(document) | ||||
|                                                     @if(document.ShowLink()) | ||||
|                                                     { | ||||
|                                                         @Html.DocumentLink(document) | ||||
|                                                     } | ||||
|                                                     else | ||||
|                                                     { | ||||
|                                                         @document.GetTitle() | ||||
|                                                     }                                                    | ||||
|                                                 </div> | ||||
|  | ||||
|                                                 @if (documentChildren.OnlyVisible().Any()) | ||||
|   | ||||
							
								
								
									
										38
									
								
								docs/input/appendix/emojis.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								docs/input/appendix/emojis.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| Title: Emojis | ||||
| Order: 3 | ||||
| --- | ||||
|  | ||||
| Please note that what emojis that can be used is completely up to  | ||||
| the operating system and/or terminal you're using, and no guarantees | ||||
| can be made of how it will look. Calculating the width of emojis | ||||
| is also not an exact science in many ways, so milage might vary when | ||||
| used in tables, panels or grids. | ||||
|  | ||||
| To ensure best compatibility, consider only using emojis introduced | ||||
| before Unicode 13.0 that belongs in the `Emoji_Presentation` category | ||||
| in the official emoji list at  | ||||
| https://www.unicode.org/Public/UCD/latest/ucd/emoji/emoji-data.txt | ||||
|  | ||||
| # Usage | ||||
|  | ||||
| ```csharp | ||||
| // Markup | ||||
| AnsiConsole.MarkupLine("Hello :globe_showing_europe_africa:!"); | ||||
|  | ||||
| // Constant | ||||
| var hello = "Hello " + Emoji.Known.GlobeShowingEuropeAfrica; | ||||
| ``` | ||||
|  | ||||
| # Replacing emojis in text | ||||
|  | ||||
| ```csharp | ||||
| var phrase = "Mmmm :birthday_cake:"; | ||||
| var rendered = Emoji.Replace(phrase); | ||||
| ``` | ||||
|  | ||||
| # Emojis | ||||
|  | ||||
| _The images in the table below might not render correctly in your  | ||||
| browser for the same reasons mentioned in the `Compatibility` section._ | ||||
|  | ||||
| <?# EmojiTable /?> | ||||
| @@ -1,3 +1,10 @@ | ||||
| Title: Appendix | ||||
| Order: 10 | ||||
| --- | ||||
| --- | ||||
|  | ||||
| # Sections | ||||
|  | ||||
| * [Styles](xref:styles) | ||||
| * [Colors](xref:colors) | ||||
| * [Borders](xref:borders) | ||||
| * [Emojis](xref:emojis) | ||||
| @@ -2,8 +2,7 @@ Title: Markup | ||||
| Order: 2 | ||||
| --- | ||||
|  | ||||
| In `Spectre.Console` there's a class called `Markup` that | ||||
| allows you to output rich text to the console. | ||||
| The class `Markup` allows you to output rich text to the console. | ||||
|  | ||||
| # Syntax | ||||
|  | ||||
| @@ -54,6 +53,16 @@ You can set the background color in markup by prefixing the color with | ||||
| [default on blue]World[/] | ||||
| ``` | ||||
|  | ||||
| # Rendering emojis | ||||
|  | ||||
| To output an emoji as part of markup, you can use emoji shortcodes. | ||||
|  | ||||
| ```csharp | ||||
| AnsiConsole.MarkupLine("Hello :globe_showing_europe_africa:!"); | ||||
| ``` | ||||
|  | ||||
| For a list of emoji, see the [Emojis](xref:styles) appendix section. | ||||
|  | ||||
| # Colors | ||||
|  | ||||
| For a list of colors, see the [Colors](xref:colors) appendix section. | ||||
|   | ||||
| @@ -1,14 +1,20 @@ | ||||
| namespace Docs | ||||
| namespace Docs | ||||
| { | ||||
|     public static class Constants | ||||
|     { | ||||
|         public const string NoContainer = nameof(NoContainer); | ||||
|         public const string NoSidebar = nameof(NoSidebar); | ||||
|         public const string NoLink = nameof(NoLink); | ||||
|         public const string Topic = nameof(Topic); | ||||
|         public const string EditLink = nameof(EditLink); | ||||
|         public const string Description = nameof(Description); | ||||
|         public const string Hidden = nameof(Hidden); | ||||
|  | ||||
|         public static class Emojis | ||||
|         { | ||||
|             public const string Root = "EMOJIS_ROOT"; | ||||
|         } | ||||
|  | ||||
|         public static class Colors | ||||
|         { | ||||
|             public const string Url = "https://raw.githubusercontent.com/spectresystems/spectre.console/main/resources/scripts/Generator/Data/colors.json"; | ||||
|   | ||||
							
								
								
									
										7946
									
								
								docs/src/Data/emojis.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7946
									
								
								docs/src/Data/emojis.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,4 +1,4 @@ | ||||
| using Statiq.Common; | ||||
| using Statiq.Common; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
|  | ||||
| @@ -16,6 +16,11 @@ namespace Docs | ||||
|             return !document.GetBool(Constants.Hidden, false); | ||||
|         } | ||||
|  | ||||
|         public static bool ShowLink(this IDocument document) | ||||
|         { | ||||
|             return !document.GetBool(Constants.NoLink, false); | ||||
|         } | ||||
|  | ||||
|         public static IEnumerable<IDocument> OnlyVisible(this IEnumerable<IDocument> source) | ||||
|         { | ||||
|             return source.Where(x => x.IsVisible()); | ||||
|   | ||||
							
								
								
									
										20
									
								
								docs/src/Models/Emoji.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								docs/src/Models/Emoji.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Text; | ||||
| using Newtonsoft.Json; | ||||
|  | ||||
| namespace Docs.Models | ||||
| { | ||||
|     public sealed class Emoji | ||||
|     { | ||||
|         public string Id { get; set; } | ||||
|         public string Name { get; set; } | ||||
|         public string Description { get; set; } | ||||
|         public string Code { get; set; } | ||||
|  | ||||
|         public static List<Emoji> Parse(string json) | ||||
|         { | ||||
|             return JsonConvert.DeserializeObject<List<Emoji>>(json); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										41
									
								
								docs/src/Modules/ReadEmbedded.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								docs/src/Modules/ReadEmbedded.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Threading.Tasks; | ||||
| using Statiq.Common; | ||||
|  | ||||
| namespace Docs.Modules | ||||
| { | ||||
|     public sealed class ReadEmbedded : Module | ||||
|     { | ||||
|         private readonly System.Reflection.Assembly _assembly; | ||||
|         private readonly string _resource; | ||||
|  | ||||
|         public ReadEmbedded(System.Reflection.Assembly assembly, string resource) | ||||
|         { | ||||
|             _assembly = assembly ?? throw new ArgumentNullException(nameof(assembly)); | ||||
|             _resource = resource ?? throw new ArgumentNullException(nameof(resource)); | ||||
|         } | ||||
|  | ||||
|         protected override Task<IEnumerable<IDocument>> ExecuteContextAsync(IExecutionContext context) | ||||
|         { | ||||
|             return Task.FromResult((IEnumerable<IDocument>)new[] | ||||
|             { | ||||
|                 context.CreateDocument(ReadResource()), | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         private Stream ReadResource() | ||||
|         { | ||||
|             var resourceName = _resource.Replace("/", "."); | ||||
|  | ||||
|             var stream = _assembly.GetManifestResourceStream(resourceName); | ||||
|             if (stream == null) | ||||
|             { | ||||
|                 throw new InvalidOperationException("Could not load manifest resource stream."); | ||||
|             } | ||||
|  | ||||
|             return stream; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -26,13 +26,8 @@ namespace Docs.Pipelines | ||||
|                 new ExecuteConfig( | ||||
|                     Config.FromDocument(async (doc, ctx) => | ||||
|                     { | ||||
|                         var colors = Color.Parse(await doc.GetContentStringAsync()).ToList();  | ||||
|                         var definitions = new List<IDocument> { colors.ToDocument(Constants.Colors.Root) }; | ||||
|  | ||||
|                         return doc.Clone(new MetadataDictionary | ||||
|                         { | ||||
|                             [Constants.Colors.Root] = definitions | ||||
|                         }); | ||||
|                         var data = Color.Parse(await doc.GetContentStringAsync()).ToList();  | ||||
|                         return data.ToDocument(Constants.Colors.Root); | ||||
|                     })) | ||||
|             }; | ||||
|         } | ||||
|   | ||||
							
								
								
									
										34
									
								
								docs/src/Pipelines/EmojiPipeline.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								docs/src/Pipelines/EmojiPipeline.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| using System.Collections.Generic; | ||||
| using Docs.Models; | ||||
| using Docs.Modules; | ||||
| using Statiq.Common; | ||||
| using Statiq.Core; | ||||
|  | ||||
| namespace Docs.Pipelines | ||||
| { | ||||
|     public class EmojiPipeline : Pipeline | ||||
|     { | ||||
|         public EmojiPipeline() | ||||
|         { | ||||
|             InputModules = new ModuleList | ||||
|             { | ||||
|                 new ExecuteConfig( | ||||
|                     Config.FromContext(ctx => { | ||||
|                         return new ReadEmbedded( | ||||
|                             typeof(EmojiPipeline).Assembly, | ||||
|                             "Docs/src/Data/emojis.json"); | ||||
|                     })) | ||||
|             }; | ||||
|  | ||||
|             ProcessModules = new ModuleList | ||||
|             { | ||||
|                 new ExecuteConfig( | ||||
|                     Config.FromDocument(async (doc, ctx) => | ||||
|                     { | ||||
|                         var data = Emoji.Parse(await doc.GetContentStringAsync()); | ||||
|                         return data.ToDocument(Constants.Emojis.Root); | ||||
|                     })) | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -17,11 +17,10 @@ namespace Docs.Shortcodes | ||||
|             // Get the definition. | ||||
|             var colors = context.Outputs | ||||
|                 .FromPipeline(nameof(ColorsPipeline)) | ||||
|                 .First() | ||||
|                 .GetChildren(Constants.Colors.Root) | ||||
|                 .OfType<ObjectDocument<List<Color>>>() | ||||
|                 .First().Object; | ||||
|  | ||||
|             // Headers | ||||
|             var table = new XElement("table", new XAttribute("class", "table")); | ||||
|             var header = new XElement("tr", new XAttribute("class", "color-row")); | ||||
|             header.Add(new XElement("th", "")); | ||||
|   | ||||
							
								
								
									
										45
									
								
								docs/src/Shortcodes/EmojiTableShortcode.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								docs/src/Shortcodes/EmojiTableShortcode.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using Statiq.Common; | ||||
| using System.Xml.Linq; | ||||
| using Docs.Pipelines; | ||||
| using Docs.Models; | ||||
|  | ||||
| namespace Docs.Shortcodes | ||||
| { | ||||
|     public class EmojiTableShortcode : SyncShortcode | ||||
|     { | ||||
|         public override ShortcodeResult Execute(KeyValuePair<string, string>[] args, string content, IDocument document, IExecutionContext context) | ||||
|         { | ||||
|             var emojis = context.Outputs | ||||
|                 .FromPipeline(nameof(EmojiPipeline)) | ||||
|                 .OfType<ObjectDocument<List<Emoji>>>() | ||||
|                 .First().Object; | ||||
|  | ||||
|             // Headers | ||||
|             var table = new XElement("table", new XAttribute("class", "table")); | ||||
|             var header = new XElement("tr", new XAttribute("class", "emoji-row")); | ||||
|             header.Add(new XElement("th", "")); | ||||
|             header.Add(new XElement("th", "Markup")); | ||||
|             header.Add(new XElement("th", "Constant")); | ||||
|             table.Add(header); | ||||
|  | ||||
|             foreach (var emoji in emojis) | ||||
|             { | ||||
|                 var code = emoji.Code.Replace("U+0000", "U+").Replace("U+000", "U+"); | ||||
|                 var icon = string.Format("&#x{0};", emoji.Code.Replace("U+", string.Empty)); | ||||
|  | ||||
|                 var row = new XElement("tr"); | ||||
|                 row.Add(new XElement("td", icon)); | ||||
|                 row.Add(new XElement("td", new XElement("code", $":{emoji.Id}:"))); | ||||
|                 row.Add(new XElement("td", new XElement("code", emoji.Name))); | ||||
|  | ||||
|                 table.Add(row); | ||||
|             } | ||||
|  | ||||
|             return table.ToString() | ||||
|                 .Replace("&#x", "&#x"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										14
									
								
								examples/Emojis/Emojis.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								examples/Emojis/Emojis.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <OutputType>Exe</OutputType> | ||||
|     <TargetFramework>netcoreapp3.1</TargetFramework> | ||||
|     <IsPackable>false</IsPackable> | ||||
|     <Description>Demonstrates how to render emojis.</Description> | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
| </Project> | ||||
							
								
								
									
										17
									
								
								examples/Emojis/Program.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								examples/Emojis/Program.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| using System; | ||||
| using Spectre.Console; | ||||
|  | ||||
| namespace Emojis | ||||
| { | ||||
|     public static class Program | ||||
|     { | ||||
|         public static void Main(string[] args) | ||||
|         { | ||||
|             // Markup | ||||
|             AnsiConsole.Render( | ||||
|                 new Panel("[yellow]Hello :globe_showing_europe_africa:![/]") | ||||
|                     .RoundedBorder() | ||||
|                     .SetHeader("Markup")); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -12,8 +12,8 @@ namespace Info | ||||
|                 .AddRow("[b]:artist_palette: Color system[/]", $"{AnsiConsole.Capabilities.ColorSystem}") | ||||
|                 .AddRow("[b]:nail_polish: Supports ansi?[/]", $"{GetEmoji(AnsiConsole.Capabilities.SupportsAnsi)}") | ||||
|                 .AddRow("[b]:top_hat: Legacy console?[/]", $"{GetEmoji(AnsiConsole.Capabilities.LegacyConsole)}") | ||||
|                 .AddRow("[b]:left-right_arrow: Buffer width[/]", $"{AnsiConsole.Console.Width}") | ||||
|                 .AddRow("[b]:up-down_arrow: Buffer height[/]", $"{AnsiConsole.Console.Height}"); | ||||
|                 .AddRow("[b]:left_right_arrow: Buffer width[/]", $"{AnsiConsole.Console.Width}") | ||||
|                 .AddRow("[b]:up_down_arrow: Buffer height[/]", $"{AnsiConsole.Console.Height}"); | ||||
|  | ||||
|             AnsiConsole.Render( | ||||
|                 new Panel(grid) | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
|  | ||||
| $Output = Join-Path $PSScriptRoot "Temp" | ||||
| $Source = Join-Path $PSScriptRoot "/../../src/Spectre.Console" | ||||
| $Docs = Join-Path $PSScriptRoot "/../../docs/src/Data" | ||||
|  | ||||
| if(!(Test-Path $Output -PathType Container)) { | ||||
|     New-Item -ItemType Directory -Path $Output | Out-Null | ||||
| @@ -11,7 +12,7 @@ if(!(Test-Path $Output -PathType Container)) { | ||||
|  | ||||
| # Generate the files | ||||
| Push-Location Generator | ||||
| &dotnet run -- emoji "$Output" | ||||
| &dotnet run -- emoji "$Output" --input $Output | ||||
| if(!$?) { | ||||
|     Pop-Location | ||||
|     Throw "An error occured when generating code." | ||||
| @@ -19,4 +20,5 @@ if(!$?) { | ||||
| Pop-Location | ||||
|  | ||||
| # Copy the files to the correct location | ||||
| Copy-Item  (Join-Path "$Output" "Emoji.Generated.cs") -Destination "$Source/Emoji.Generated.cs" | ||||
| Copy-Item  (Join-Path "$Output" "Emoji.Generated.cs") -Destination "$Source/Emoji.Generated.cs" | ||||
| Copy-Item  (Join-Path "$Output" "emojis.json") -Destination "$Docs/emojis.json" | ||||
| @@ -55,5 +55,8 @@ namespace Generator.Commands | ||||
|     { | ||||
|         [CommandArgument(0, "<OUTPUT>")] | ||||
|         public string Output { get; set; } | ||||
|  | ||||
|         [CommandOption("-i|--input <PATH>")] | ||||
|         public string Input { get; set; } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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" /> | ||||
|   | ||||
| @@ -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(); | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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 ~}} | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										10
									
								
								resources/scripts/Generator/Templates/Emoji.Json.template
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								resources/scripts/Generator/Templates/Emoji.Json.template
									
									
									
									
									
										Normal 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 ~}} | ||||
| ] | ||||
							
								
								
									
										44
									
								
								src/Spectre.Console.Tests/Unit/EmojiTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/Spectre.Console.Tests/Unit/EmojiTests.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| using Shouldly; | ||||
| using Xunit; | ||||
|  | ||||
| namespace Spectre.Console.Tests.Unit | ||||
| { | ||||
|     public sealed class EmojiTests | ||||
|     { | ||||
|         [Fact] | ||||
|         public void Should_Substitute_Emoji_Shortcodes_In_Markdown() | ||||
|         { | ||||
|             // Given | ||||
|             var console = new TestableAnsiConsole(ColorSystem.Standard, AnsiSupport.Yes); | ||||
|  | ||||
|             // When | ||||
|             console.Markup("Hello :globe_showing_europe_africa:!"); | ||||
|  | ||||
|             // Then | ||||
|             console.Output.ShouldBe("Hello 🌍!"); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Contain_Predefined_Emojis() | ||||
|         { | ||||
|             // Given, When | ||||
|             const string result = "Hello " + Emoji.Known.GlobeShowingEuropeAfrica + "!"; | ||||
|  | ||||
|             // Then | ||||
|             result.ShouldBe("Hello 🌍!"); | ||||
|         } | ||||
|  | ||||
|         public sealed class TheReplaceMethod | ||||
|         { | ||||
|             [Fact] | ||||
|             public void Should_Replace_Emojis_In_Text() | ||||
|             { | ||||
|                 // Given, When | ||||
|                 var result = Emoji.Replace("Hello :globe_showing_europe_africa:!"); | ||||
|  | ||||
|                 // Then | ||||
|                 result.ShouldBe("Hello 🌍!"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -33,6 +33,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Borders", "..\examples\Bord | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Links", "..\examples\Links\Links.csproj", "{6AF8C93B-AA41-4F44-8B1B-B8D166576174}" | ||||
| EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emojis", "..\examples\Emojis\Emojis.csproj", "{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}" | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		Debug|Any CPU = Debug|Any CPU | ||||
| @@ -163,6 +165,18 @@ Global | ||||
| 		{6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Release|x64.Build.0 = Release|Any CPU | ||||
| 		{6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Release|x86.Build.0 = Release|Any CPU | ||||
| 		{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||
| 		{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Debug|x64.Build.0 = Debug|Any CPU | ||||
| 		{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Release|x64.ActiveCfg = Release|Any CPU | ||||
| 		{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Release|x64.Build.0 = Release|Any CPU | ||||
| 		{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Release|x86.Build.0 = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(SolutionProperties) = preSolution | ||||
| 		HideSolutionNode = FALSE | ||||
| @@ -176,6 +190,7 @@ Global | ||||
| 		{225CE0D4-06AB-411A-8D29-707504FE53B3} = {F0575243-121F-4DEE-9F6B-246E26DC0844} | ||||
| 		{094245E6-4C94-485D-B5AC-3153E878B112} = {F0575243-121F-4DEE-9F6B-246E26DC0844} | ||||
| 		{6AF8C93B-AA41-4F44-8B1B-B8D166576174} = {F0575243-121F-4DEE-9F6B-246E26DC0844} | ||||
| 		{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2} = {F0575243-121F-4DEE-9F6B-246E26DC0844} | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ExtensibilityGlobals) = postSolution | ||||
| 		SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C} | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| // <auto-generated> | ||||
| //     This code was generated by a tool. | ||||
| //     Generated 2020-08-03 15:17 | ||||
| //     Generated 2020-09-18 10:42 | ||||
| // | ||||
| //     Changes to this file may cause incorrect behavior and will be lost if | ||||
| //     the code is regenerated. | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -3,9 +3,9 @@ using System.Text.RegularExpressions; | ||||
| 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 Regex _emojiCode = new Regex(@"(:(\S*?):)", RegexOptions.Compiled); | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| // <auto-generated> | ||||
| //     This code was generated by a tool. | ||||
| //     Generated 2020-08-03 15:17 | ||||
| //     Generated 2020-09-18 10:42 | ||||
| // | ||||
| //     Changes to this file may cause incorrect behavior and will be lost if | ||||
| //     the code is regenerated. | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| // <auto-generated> | ||||
| //     This code was generated by a tool. | ||||
| //     Generated 2020-08-03 15:17 | ||||
| //     Generated 2020-09-18 10:42 | ||||
| // | ||||
| //     Changes to this file may cause incorrect behavior and will be lost if | ||||
| //     the code is regenerated. | ||||
|   | ||||
| @@ -8,6 +8,12 @@ namespace Spectre.Console.Internal | ||||
|     { | ||||
|         public static Paragraph Parse(string text, Style? style = null) | ||||
|         { | ||||
|             if (text is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(text)); | ||||
|             } | ||||
|  | ||||
|             text = Emoji.Replace(text); | ||||
|             style ??= Style.Plain; | ||||
|  | ||||
|             var result = new Paragraph(); | ||||
|   | ||||
| @@ -71,7 +71,7 @@ namespace Spectre.Console.Rendering | ||||
|                 throw new ArgumentNullException(nameof(text)); | ||||
|             } | ||||
|  | ||||
|             Text = Emoji.Replace(text).NormalizeLineEndings(); | ||||
|             Text = text.NormalizeLineEndings(); | ||||
|             Style = style; | ||||
|             IsLineBreak = lineBreak; | ||||
|             IsWhiteSpace = string.IsNullOrWhiteSpace(text); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user