mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-14 16:02:50 +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:
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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user