Remove Spectre.Console.Cli from repository

* Move Spectre.Console.Cli to its own repository
* Update build script to use Cake.Sdk and .NET Make
* Remove StyleCop (unmaintained)
* Add linting using dotnet format
* Fix generator which was broken
* Update dependencies
This commit is contained in:
Patrik Svensson
2025-10-26 22:32:22 +01:00
committed by Patrik Svensson
parent 1ec7b8ae8f
commit 45799107a3
856 changed files with 2416 additions and 24552 deletions

View File

@@ -25,154 +25,3 @@ indent_size = 2
[*.md] [*.md]
trim_trailing_whitespace = false trim_trailing_whitespace = false
[*.cs]
# Prefer file scoped namespace declarations
csharp_style_namespace_declarations = file_scoped:warning
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
dotnet_separate_import_directive_groups = false
# Avoid "this." and "Me." if not necessary
dotnet_style_qualification_for_field = false:refactoring
dotnet_style_qualification_for_property = false:refactoring
dotnet_style_qualification_for_method = false:refactoring
dotnet_style_qualification_for_event = false:refactoring
# Use language keywords instead of framework type names for type references
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# Suggest more modern language features when available
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
# Non-private static fields are PascalCase
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
# Non-private readonly fields are PascalCase
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = non_private_readonly_field_style
dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field
dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly
dotnet_naming_style.non_private_readonly_field_style.capitalization = pascal_case
# Constants are PascalCase
dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style
dotnet_naming_symbols.constants.applicable_kinds = field, local
dotnet_naming_symbols.constants.required_modifiers = const
dotnet_naming_style.constant_style.capitalization = pascal_case
# Instance fields are camelCase and start with _
dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
dotnet_naming_symbols.instance_fields.applicable_kinds = field
dotnet_naming_style.instance_field_style.capitalization = camel_case
dotnet_naming_style.instance_field_style.required_prefix = _
# Locals and parameters are camelCase
dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion
dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters
dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style
dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local
dotnet_naming_style.camel_case_style.capitalization = camel_case
# Local functions are PascalCase
dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
dotnet_naming_style.local_function_style.capitalization = pascal_case
# By default, name items with PascalCase
dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.all_members.applicable_kinds = *
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# Newline settings
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_switch_labels = true
csharp_indent_labels = flush_left
# Prefer "var" everywhere
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion
# Prefer method-like constructs to have a block body
csharp_style_expression_bodied_methods = false:none
csharp_style_expression_bodied_constructors = false:none
csharp_style_expression_bodied_operators = false:none
# Prefer property-like constructs to have an expression-body
csharp_style_expression_bodied_properties = true:none
csharp_style_expression_bodied_indexers = true:none
csharp_style_expression_bodied_accessors = true:none
# Suggest more modern language features when available
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = do_not_ignore
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Blocks are allowed
csharp_prefer_braces = true:silent
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
# warning RS0037: PublicAPI.txt is missing '#nullable enable'
dotnet_diagnostic.RS0037.severity = none

View File

@@ -28,12 +28,13 @@ jobs:
dotnet-version: | dotnet-version: |
8.0.x 8.0.x
9.0.x 9.0.x
10.0.x
- name: Build - name: Build
shell: bash shell: bash
run: | run: |
dotnet tool restore dotnet tool restore
dotnet cake dotnet make
- name: Upload Verify Test Results - name: Upload Verify Test Results
if: failure() if: failure()

View File

@@ -34,12 +34,13 @@ jobs:
dotnet-version: | dotnet-version: |
8.0.x 8.0.x
9.0.x 9.0.x
10.0.x
- name: Publish - name: Publish
shell: bash shell: bash
run: | run: |
dotnet tool restore dotnet tool restore
dotnet cake --target="publish" \ dotnet make publish \
--nuget-key="${{secrets.NUGET_API_KEY}}" \ --nuget-key="${{secrets.NUGET_API_KEY}}" \
--github-key="${{secrets.GITHUB_TOKEN}}" --github-key="${{secrets.GITHUB_TOKEN}}"

View File

@@ -11,7 +11,6 @@ It is heavily inspired by the excellent Python library, [Rich](https://github.co
1. [Installing](#installing) 1. [Installing](#installing)
1. [Documentation](#documentation) 1. [Documentation](#documentation)
1. [Examples](#examples) 1. [Examples](#examples)
1. [Sponsors](#sponsors)
1. [Code of Conduct](#code-of-conduct) 1. [Code of Conduct](#code-of-conduct)
1. [.NET Foundation](#net-foundation) 1. [.NET Foundation](#net-foundation)
1. [License](#license) 1. [License](#license)
@@ -52,26 +51,6 @@ https://spectreconsole.net
To see `Spectre.Console` in action, please see the To see `Spectre.Console` in action, please see the
[examples repository](https://github.com/spectreconsole/examples). [examples repository](https://github.com/spectreconsole/examples).
## Sponsors
The following people are [sponsoring](https://github.com/sponsors/patriksvensson)
`Spectre.Console` to show their support and to ensure the longevity of the project.
* [Rodney Littles II](https://github.com/RLittlesII)
* [Martin Björkström](https://github.com/bjorkstromm)
* [Dave Glick](https://github.com/daveaglick)
* [Kim Gunnarsson](https://github.com/kimgunnarsson)
* [Andrew McClenaghan](https://github.com/andymac4182)
* [C. Augusto Proiete](https://github.com/augustoproiete)
* [Viktor Elofsson](https://github.com/vktr)
* [Steven Knox](https://github.com/stevenknox)
* [David Pendray](https://github.com/dpen2000)
* [Elmah.io](https://github.com/elmahio)
* [Tom Kerkhove](https://github.com/tomkerkhove)
We really appreciate it.
**Thank you very much!**
## Code of Conduct ## Code of Conduct
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.

View File

@@ -9,11 +9,10 @@ para Python.
## Índice de Conteúdo ## Índice de Conteúdo
1. [Funcionalidades](#funcionalidades) 1. [Funcionalidades](#funcionalidades)
2. [Instalação](#instalação) 1. [Instalação](#instalação)
3. [Documentação](#documentação) 1. [Documentação](#documentação)
4. [Exemplos](#exemplos) 1. [Exemplos](#exemplos)
5. [Patrocinadores](#patrocinadores) 1. [Licença](#licença)
5. [Licença](#licença)
## Funcionalidades ## Funcionalidades
@@ -46,25 +45,6 @@ https://spectreconsole.net/
To see `Spectre.Console` in action, please see the To see `Spectre.Console` in action, please see the
[examples repository](https://github.com/spectreconsole/examples). [examples repository](https://github.com/spectreconsole/examples).
## Patrocinadores
As seguintes pessoas estão [patrocinando](https://github.com/sponsors/patriksvensson)
o Spectre.Console para mostrar o seu apoio e garantir a longevidade do projeto.
* [Rodney Littles II](https://github.com/RLittlesII)
* [Martin Björkström](https://github.com/bjorkstromm)
* [Dave Glick](https://github.com/daveaglick)
* [Kim Gunanrsson](https://github.com/kimgunnarsson)
* [Andrew McClenaghan](https://github.com/andymac4182)
* [C. Augusto Proiete](https://github.com/augustoproiete)
* [Viktor Elofsson](https://github.com/vktr)
* [Steven Knox](https://github.com/stevenknox)
* [David Pendray](https://github.com/dpen2000)
* [Elmah.io](https://github.com/elmahio)
Eu estou muito agradecido.
**Muito obrigado!**
## Licença ## Licença
Copyright © Patrik Svensson, Phil Scott, Nils Andresen, Cédric Luthi, Frank Ray Copyright © Patrik Svensson, Phil Scott, Nils Andresen, Cédric Luthi, Frank Ray

View File

@@ -9,11 +9,10 @@ _[![Spectre.Console NuGet Version](https://img.shields.io/nuget/v/spectre.consol
## 目录 ## 目录
1. [功能](#功能) 1. [功能](#功能)
2. [安装](#安装) 1. [安装](#安装)
3. [文档](#文档) 1. [文档](#文档)
4. [例子](#例子) 1. [例子](#例子)
5. [Sponsors](#Sponsors) 1. [开源许可](#开源许可)
6. [开源许可](#开源许可)
## 功能 ## 功能
@@ -42,24 +41,6 @@ https://spectreconsole.net/
To see `Spectre.Console` in action, please see the To see `Spectre.Console` in action, please see the
[examples repository](https://github.com/spectreconsole/examples). [examples repository](https://github.com/spectreconsole/examples).
## Sponsors
下面这些用户正在[sponsor](https://github.com/sponsors/patriksvensson)上支持着Spectre.Console确保这个项目的持续维护。
* [Rodney Littles II](https://github.com/RLittlesII)
* [Martin Björkström](https://github.com/bjorkstromm)
* [Dave Glick](https://github.com/daveaglick)
* [Kim Gunanrsson](https://github.com/kimgunnarsson)
* [Andrew McClenaghan](https://github.com/andymac4182)
* [C. Augusto Proiete](https://github.com/augustoproiete)
* [Viktor Elofsson](https://github.com/vktr)
* [Steven Knox](https://github.com/stevenknox)
* [David Pendray](https://github.com/dpen2000)
* [Elmah.io](https://github.com/elmahio)
我对此表示十分感激
**非常感谢各位!**
## 开源许可 ## 开源许可
版权所有 © Patrik Svensson, Phil Scott, Nils Andresen, Cédric Luthi, Frank Ray 版权所有 © Patrik Svensson, Phil Scott, Nils Andresen, Cédric Luthi, Frank Ray

View File

@@ -1,3 +1,12 @@
#:sdk Cake.Sdk@5.1.25296.94-beta
var solution = "./src/Spectre.Console.slnx";
var generatorSolution = "./resources/scripts/Generator/Generator.slnx";
var testProject = "./src/Spectre.Console.Tests/Spectre.Console.Tests.csproj";
////////////////////////////////////////////////////////////////
// Arguments
var target = Argument("target", "Default"); var target = Argument("target", "Default");
var configuration = Argument("configuration", "Release"); var configuration = Argument("configuration", "Release");
@@ -5,33 +14,31 @@ var configuration = Argument("configuration", "Release");
// Tasks // Tasks
Task("Clean") Task("Clean")
.Does(context => .Does(ctx =>
{ {
context.CleanDirectory("./.artifacts"); ctx.CleanDirectory("./.artifacts");
});
Task("Lint")
.Does(ctx =>
{
ctx.DotNetFormat(solution, new DotNetFormatSettings
{
VerifyNoChanges = true,
});
}); });
Task("Build") Task("Build")
.IsDependentOn("Clean") .IsDependentOn("Clean")
.Does(context => .IsDependentOn("Lint")
.Does(ctx =>
{ {
Information("Compiling generator..."); ctx.DotNetBuild(solution, new DotNetBuildSettings
DotNetBuild("./resources/scripts/Generator/Generator.slnx", new DotNetBuildSettings
{ {
Configuration = configuration, Configuration = configuration,
Verbosity = DotNetVerbosity.Minimal, Verbosity = DotNetVerbosity.Minimal,
NoLogo = true, NoLogo = true,
NoIncremental = context.HasArgument("rebuild"), NoIncremental = ctx.HasArgument("rebuild"),
MSBuildSettings = new DotNetMSBuildSettings()
.TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error)
});
Information("\nCompiling Spectre.Console...");
DotNetBuild("./src/Spectre.Console.slnx", new DotNetBuildSettings
{
Configuration = configuration,
Verbosity = DotNetVerbosity.Minimal,
NoLogo = true,
NoIncremental = context.HasArgument("rebuild"),
MSBuildSettings = new DotNetMSBuildSettings() MSBuildSettings = new DotNetMSBuildSettings()
.TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error) .TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error)
}); });
@@ -39,17 +46,9 @@ Task("Build")
Task("Test") Task("Test")
.IsDependentOn("Build") .IsDependentOn("Build")
.Does(context => .Does(ctx =>
{ {
DotNetTest("./src/Tests/Spectre.Console.Tests/Spectre.Console.Tests.csproj", new DotNetTestSettings { ctx.DotNetTest(testProject, new DotNetTestSettings {
Configuration = configuration,
Verbosity = DotNetVerbosity.Minimal,
NoLogo = true,
NoRestore = true,
NoBuild = true,
});
DotNetTest("./src/Tests/Spectre.Console.Cli.Tests/Spectre.Console.Cli.Tests.csproj", new DotNetTestSettings {
Configuration = configuration, Configuration = configuration,
Verbosity = DotNetVerbosity.Minimal, Verbosity = DotNetVerbosity.Minimal,
NoLogo = true, NoLogo = true,
@@ -60,9 +59,9 @@ Task("Test")
Task("Package") Task("Package")
.IsDependentOn("Test") .IsDependentOn("Test")
.Does(context => .Does(ctx =>
{ {
context.DotNetPack($"./src/Spectre.Console.slnx", new DotNetPackSettings ctx.DotNetPack(solution, new DotNetPackSettings
{ {
Configuration = configuration, Configuration = configuration,
Verbosity = DotNetVerbosity.Minimal, Verbosity = DotNetVerbosity.Minimal,
@@ -78,17 +77,17 @@ Task("Package")
Task("Publish-NuGet") Task("Publish-NuGet")
.WithCriteria(ctx => BuildSystem.IsRunningOnGitHubActions, "Not running on GitHub Actions") .WithCriteria(ctx => BuildSystem.IsRunningOnGitHubActions, "Not running on GitHub Actions")
.IsDependentOn("Package") .IsDependentOn("Package")
.Does(context => .Does(ctx =>
{ {
var apiKey = Argument<string>("nuget-key", null); var apiKey = Argument<string?>("nuget-key", null);
if(string.IsNullOrWhiteSpace(apiKey)) { if(string.IsNullOrWhiteSpace(apiKey)) {
throw new CakeException("No NuGet API key was provided."); throw new CakeException("No NuGet API key was provided.");
} }
// Publish to GitHub Packages // Publish to GitHub Packages
foreach(var file in context.GetFiles("./.artifacts/*.nupkg")) foreach(var file in ctx.GetFiles("./.artifacts/*.nupkg"))
{ {
context.Information("Publishing {0}...", file.GetFilename().FullPath); ctx.Information("Publishing {0}...", file.GetFilename().FullPath);
DotNetNuGetPush(file.FullPath, new DotNetNuGetPushSettings DotNetNuGetPush(file.FullPath, new DotNetNuGetPushSettings
{ {
Source = "https://api.nuget.org/v3/index.json", Source = "https://api.nuget.org/v3/index.json",

View File

@@ -22,7 +22,6 @@ namespace Docs
.AddSetting(Constants.SourceFiles, new List<string> .AddSetting(Constants.SourceFiles, new List<string>
{ {
"../../src/Spectre.Console/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs", "../../src/Spectre.Console/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
"../../src/Spectre.Console.Cli/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
"../../src/Spectre.Console.Testing/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs", "../../src/Spectre.Console.Testing/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
"../../src/Extensions/Spectre.Console.ImageSharp/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs", "../../src/Extensions/Spectre.Console.ImageSharp/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
"../../src/Extensions/Spectre.Console.Json/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs" "../../src/Extensions/Spectre.Console.Json/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs"

View File

@@ -1,9 +1,6 @@
Title: CommandApp Title: CommandApp
Order: 2 Order: 2
Description: "**CommandApp** is the entry point for a *Spectre.Console.Cli* command line application. It is used to configure the settings and commands used for execution of the application." Description: "**CommandApp** is the entry point for a *Spectre.Console.Cli* command line application. It is used to configure the settings and commands used for execution of the application."
Reference:
- T:Spectre.Console.Cli.CommandApp
- T:Spectre.Console.Cli.CommandApp`1
--- ---
`CommandApp` is the entry point for a `Spectre.Console.Cli` command line application. It is used to configure the settings and commands used for execution of the application. Most `Spectre.Console.Cli` applications will need to specify a custom configuration using the `Configure` method. `CommandApp` is the entry point for a `Spectre.Console.Cli` command line application. It is used to configure the settings and commands used for execution of the application. Most `Spectre.Console.Cli` applications will need to specify a custom configuration using the `Configure` method.
@@ -76,7 +73,7 @@ return app.Run(args);
``` ```
<?# Alert ?> <?# Alert ?>
`MyTypeRegistrar` is a custom class that implements [ITypeRegistrar](xref:T:Spectre.Console.Cli.ITypeRegistrar) and must be provided by the user. `MyTypeRegistrar` is a custom class that implements `ITypeRegistrar` and must be provided by the user.
<?#/ Alert ?> <?#/ Alert ?>
There is a working [example of dependency injection](https://github.com/spectreconsole/examples/tree/main/examples/Cli/Injection) that uses `Microsoft.Extensions.DependencyInjection` as the container. Example implementations of `ITypeRegistrar` and `ITypeResolver` are provided, which you can copy and paste to your application for dependency injection. There is a working [example of dependency injection](https://github.com/spectreconsole/examples/tree/main/examples/Cli/Injection) that uses `Microsoft.Extensions.DependencyInjection` as the container. Example implementations of `ITypeRegistrar` and `ITypeResolver` are provided, which you can copy and paste to your application for dependency injection.

View File

@@ -1,9 +1,6 @@
Title: Creating Commands Title: Creating Commands
Order: 6 Order: 6
Description: "How to create commands for *Spectre.Console.Cli*" Description: "How to create commands for *Spectre.Console.Cli*"
Reference:
- T:Spectre.Console.Cli.AsyncCommand`1
- T:Spectre.Console.Cli.Command`1
--- ---
Commands in `Spectre.Console.Cli` are defined by creating a class that inherits from either `Command<TSettings>` or `AsyncCommand<TSettings>`. `Command<TSettings>` must implement an `Execute` method that returns an int where as `AsyncCommand<TSettings>` must implement `ExecuteAsync` returning `Task<int>`. Commands in `Spectre.Console.Cli` are defined by creating a class that inherits from either `Command<TSettings>` or `AsyncCommand<TSettings>`. `Command<TSettings>` must implement an `Execute` method that returns an int where as `AsyncCommand<TSettings>` must implement `ExecuteAsync` returning `Task<int>`.

View File

@@ -1,10 +1,6 @@
Title: Specifying Settings Title: Specifying Settings
Order: 5 Order: 5
Description: "How to define command line argument settings for your *Spectre.Console.Cli* Commands" Description: "How to define command line argument settings for your *Spectre.Console.Cli* Commands"
Reference:
- T:Spectre.Console.Cli.CommandSettings
- T:Spectre.Console.Cli.CommandArgumentAttribute
- T:Spectre.Console.Cli.CommandOptionAttribute
--- ---
Settings for `Spectre.Console.Cli` commands are defined via classes that inherit from `CommandSettings`. Attributes are used to indicate how the parser interprets the command line arguments and create a runtime instance of the settings to be used. Settings for `Spectre.Console.Cli` commands are defined via classes that inherit from `CommandSettings`. Attributes are used to indicate how the parser interprets the command line arguments and create a runtime instance of the settings to be used.

View File

@@ -1,10 +1,6 @@
Title: Unit Testing Title: Unit Testing
Order: 14 Order: 14
Description: Instructions for unit testing a Spectre.Console application. Description: Instructions for unit testing a Spectre.Console application.
Reference:
- T:Spectre.Console.Testing.CommandAppTester
- T:Spectre.Console.Testing.TestConsole
- T:Spectre.Console.Testing.TestConsoleInput
--- ---
`Spectre.Console` has a separate project that contains test harnesses for unit testing your own console applications. `Spectre.Console` has a separate project that contains test harnesses for unit testing your own console applications.

View File

@@ -2,17 +2,19 @@
"version": 1, "version": 1,
"isRoot": true, "isRoot": true,
"tools": { "tools": {
"cake.tool": {
"version": "5.1.0",
"commands": [
"dotnet-cake"
]
},
"verify.tool": { "verify.tool": {
"version": "0.6.0", "version": "0.6.0",
"commands": [ "commands": [
"dotnet-verify" "dotnet-verify"
] ],
"rollForward": false
},
"make": {
"version": "0.8.0",
"commands": [
"dotnet-make"
],
"rollForward": false
} }
} }
} }

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "http://json.schemastore.org/global", "$schema": "http://json.schemastore.org/global",
"sdk": { "sdk": {
"version": "9.0.306", "version": "10.0.100-rc.2.25502.107",
"rollForward": "latestFeature" "rollForward": "latestFeature"
} }
} }

View File

@@ -1,5 +0,0 @@
# `Spectre.Console.Cli`
`Spectre.Console.Cli` is a modern library for parsing command line arguments. While it's extremely opinionated in what it does, it tries to follow established industry conventions, and draws its inspiration from applications you use everyday.
Detailed instructions for using this library is located on the project website, https://spectreconsole.net

5
resources/scripts/Generate-Colors.ps1 Normal file → Executable file
View File

@@ -1,8 +1,11 @@
#!/usr/local/bin/pwsh
########################################################## ##########################################################
# Script that generates known colors and lookup tables. # Script that generates known colors and lookup tables.
########################################################## ##########################################################
$Output = Join-Path $PSScriptRoot "Temp" $Output = Join-Path $PSScriptRoot "Temp"
$Generator = Join-Path $PSScriptRoot "/../../src/Generator"
$Source = Join-Path $PSScriptRoot "/../../src/Spectre.Console" $Source = Join-Path $PSScriptRoot "/../../src/Spectre.Console"
if(!(Test-Path $Output -PathType Container)) { if(!(Test-Path $Output -PathType Container)) {
@@ -10,7 +13,7 @@ if(!(Test-Path $Output -PathType Container)) {
} }
# Generate the files # Generate the files
Push-Location Generator Push-Location $Generator
&dotnet run -- colors "$Output" &dotnet run -- colors "$Output"
if(!$?) { if(!$?) {
Pop-Location Pop-Location

5
resources/scripts/Generate-Emoji.ps1 Normal file → Executable file
View File

@@ -1,8 +1,11 @@
#!/usr/local/bin/pwsh
########################################################## ##########################################################
# Script that generates the emoji lookup table. # Script that generates the emoji lookup table.
########################################################## ##########################################################
$Output = Join-Path $PSScriptRoot "Temp" $Output = Join-Path $PSScriptRoot "Temp"
$Generator = Join-Path $PSScriptRoot "/../../src/Generator"
$Source = Join-Path $PSScriptRoot "/../../src/Spectre.Console" $Source = Join-Path $PSScriptRoot "/../../src/Spectre.Console"
$Docs = Join-Path $PSScriptRoot "/../../docs/src/Data" $Docs = Join-Path $PSScriptRoot "/../../docs/src/Data"
@@ -11,7 +14,7 @@ if(!(Test-Path $Output -PathType Container)) {
} }
# Generate the files # Generate the files
Push-Location Generator Push-Location $Generator
&dotnet run -- emoji "$Output" --input $Output &dotnet run -- emoji "$Output" --input $Output
if(!$?) { if(!$?) {
Pop-Location Pop-Location

15
resources/scripts/Generate-Samples.ps1 Normal file → Executable file
View File

@@ -1,11 +1,22 @@
#!/usr/local/bin/pwsh
[CmdletBinding(PositionalBinding=$false)]
Param(
[Parameter(ValueFromRemainingArguments)]
[string[]]$Remaining
)
# first arg is either the name of a single sample you want to run or leave # first arg is either the name of a single sample you want to run or leave
# blank if you want to run them all. the samples aren't going to run at the same # blank if you want to run them all. the samples aren't going to run at the same
# speed each time so if you run all of them you'll have everything as a change # speed each time so if you run all of them you'll have everything as a change
# for your commit so use this sparingly. # for your commit so use this sparingly.
$Generator = Join-Path $PSScriptRoot "/../../src/Generator"
$Output = Join-Path $PSScriptRoot "../../docs/input/assets/casts"
# Generate the files # Generate the files
Push-Location Generator Push-Location $Generator
dotnet run -- samples -o "../../../docs/input/assets/casts" $args[0] &dotnet run -- samples -o "$Output" $Remaining
if(!$?) { if(!$?) {
Pop-Location Pop-Location
Throw "An error occured when generating code." Throw "An error occured when generating code."

7
resources/scripts/Generate-Spinners.ps1 Normal file → Executable file
View File

@@ -1,8 +1,11 @@
#!/usr/local/bin/pwsh
########################################################## ##########################################################
# Script that generates progress spinners. # Script that generates progress spinners.
########################################################## ##########################################################
$Output = Join-Path $PSScriptRoot "Temp" $Output = Join-Path $PSScriptRoot "Temp"
$Generator = Join-Path $PSScriptRoot "/../../src/Generator"
$Source = Join-Path $PSScriptRoot "/../../src/Spectre.Console" $Source = Join-Path $PSScriptRoot "/../../src/Spectre.Console"
if(!(Test-Path $Output -PathType Container)) { if(!(Test-Path $Output -PathType Container)) {
@@ -10,8 +13,8 @@ if(!(Test-Path $Output -PathType Container)) {
} }
# Generate the files # Generate the files
Push-Location Generator Push-Location $Generator
&dotnet run -- spinners "$Output" --input $Output &dotnet run -- spinners "$Output"
if(!$?) { if(!$?) {
Pop-Location Pop-Location
Throw "An error occured when generating code." Throw "An error occured when generating code."

View File

@@ -1,33 +0,0 @@
using System;
using Spectre.Console;
using Generator.Commands;
using System.Threading;
namespace DocExampleGenerator
{
internal static class AnsiConsoleExtensions
{
/// <summary>
/// Displays something via AnsiConsole, waits a bit and then simulates typing based on the input. If the console
/// doesn't have the focus this will just type into whatever window does so watch the alt-tab.
/// </summary>
/// <param name="console"></param>
/// <param name="action">The display action.</param>
/// <param name="input">The characters to type. ↑ for an up arrow, ↓ for down arrow, ↲ for a return and ¦ for a pause.</param>
/// <param name="initialDelayMs">How long to delay before typing. This should be at least 100ms because we won't check if the prompt has displayed before simulating typing.</param>
/// <param name="keypressDelayMs">Delay between keypresses. There will be a bit of randomness between each keypress +/- 20% of this value.</param>
public static void DisplayThenType(this IAnsiConsole console, Action<IAnsiConsole> action, string input, int initialDelayMs = 500, int keypressDelayMs = 200)
{
if (console is not AsciiCastConsole asciiConsole)
{
throw new InvalidOperationException("Not an ASCII cast console");
}
asciiConsole.Input.PushText(input, keypressDelayMs);
Thread.Sleep(initialDelayMs);
action(console);
}
}
}

View File

@@ -1,40 +0,0 @@
using System;
using Spectre.Console;
using Spectre.Console.Rendering;
namespace Generator.Commands
{
public sealed class AsciiCastConsole : IAnsiConsole
{
private readonly IAnsiConsole _console;
private readonly AsciiCastInput _input;
public Profile Profile => _console.Profile;
public IAnsiConsoleCursor Cursor => _console.Cursor;
IAnsiConsoleInput IAnsiConsole.Input => _input;
public AsciiCastInput Input => _input;
public IExclusivityMode ExclusivityMode => _console.ExclusivityMode;
public RenderPipeline Pipeline => _console.Pipeline;
public AsciiCastConsole(IAnsiConsole console)
{
_console = console ?? throw new ArgumentNullException(nameof(console));
_input = new AsciiCastInput();
}
public void Clear(bool home)
{
_console.Clear(home);
}
public void Write(IRenderable renderable)
{
_console.Write(renderable);
}
}
}

View File

@@ -1,15 +0,0 @@
using Spectre.Console;
namespace Generator.Commands
{
public static class AsciiCastExtensions
{
public static AsciiCastOut WrapWithAsciiCastRecorder(this IAnsiConsole ansiConsole)
{
AsciiCastOut castRecorder = new(ansiConsole.Profile.Out);
ansiConsole.Profile.Out = castRecorder;
return castRecorder;
}
}
}

View File

@@ -1,92 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Spectre.Console;
namespace Generator.Commands
{
public sealed class AsciiCastInput : IAnsiConsoleInput
{
private readonly Queue<(ConsoleKeyInfo?, int)> _input;
private readonly Random _random = new Random();
public AsciiCastInput()
{
_input = new Queue<(ConsoleKeyInfo?, int)>();
}
public void PushText(string input, int keypressDelayMs)
{
if (input is null)
{
throw new ArgumentNullException(nameof(input));
}
foreach (var character in input)
{
PushCharacter(character, keypressDelayMs);
}
}
public void PushTextWithEnter(string input, int keypressDelayMs)
{
PushText(input, keypressDelayMs);
PushKey(ConsoleKey.Enter, keypressDelayMs);
}
public void PushCharacter(char input, int keypressDelayMs)
{
var delay = keypressDelayMs + _random.Next((int)(keypressDelayMs * -.2), (int)(keypressDelayMs * .2));
switch (input)
{
case '↑':
PushKey(ConsoleKey.UpArrow, keypressDelayMs);
break;
case '↓':
PushKey(ConsoleKey.DownArrow, keypressDelayMs);
break;
case '↲':
PushKey(ConsoleKey.Enter, keypressDelayMs);
break;
case '¦':
_input.Enqueue((null, delay));
break;
default:
var control = char.IsUpper(input);
_input.Enqueue((new ConsoleKeyInfo(input, (ConsoleKey)input, false, false, control), delay));
break;
}
}
public void PushKey(ConsoleKey input, int keypressDelayMs)
{
var delay = keypressDelayMs + _random.Next((int)(keypressDelayMs * -.2), (int)(keypressDelayMs * .2));
_input.Enqueue((new ConsoleKeyInfo((char)input, input, false, false, false), delay));
}
public bool IsKeyAvailable()
{
return _input.Count > 0;
}
public ConsoleKeyInfo? ReadKey(bool intercept)
{
if (_input.Count == 0)
{
throw new InvalidOperationException("No input available.");
}
var result = _input.Dequeue();
Thread.Sleep(result.Item2);
return result.Item1;
}
public Task<ConsoleKeyInfo?> ReadKeyAsync(bool intercept, CancellationToken cancellationToken)
{
return Task.FromResult(ReadKey(intercept));
}
}
}

View File

@@ -1,95 +0,0 @@
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.Json;
using Spectre.Console;
namespace Generator.Commands
{
public class AsciiCastOut : IAnsiConsoleOutput
{
private sealed class AsciiCastWriter : TextWriter
{
private readonly TextWriter _wrappedTextWriter;
private readonly StringBuilder _builder = new StringBuilder();
private int? _firstTick;
public AsciiCastWriter(TextWriter wrappedTextWriter)
{
_wrappedTextWriter = wrappedTextWriter;
}
public override void Write(string value)
{
if (value == null)
{
return;
}
Append(value);
_wrappedTextWriter.Write(value);
base.Write(value);
}
public override Encoding Encoding => _wrappedTextWriter.Encoding;
private void Append(string value)
{
var tick = 0m;
if (_firstTick.HasValue)
{
tick = Environment.TickCount - _firstTick.Value;
}
else
{
_firstTick = Environment.TickCount;
}
tick /= 1000m;
_builder.Append('[')
.AppendFormat(CultureInfo.InvariantCulture, "{0}", tick)
.Append(", \"o\", \"").Append(JsonEncodedText.Encode(value)).AppendLine("\"]");
}
public string GetJsonAndClearBuffer()
{
var json = _builder.ToString();
// reset the buffer and also reset the first tick count
_builder.Clear();
_firstTick = null;
return json;
}
}
private readonly IAnsiConsoleOutput _wrappedAnsiConsole;
private readonly AsciiCastWriter _asciiCastWriter;
public AsciiCastOut(IAnsiConsoleOutput wrappedAnsiConsole)
{
_wrappedAnsiConsole = wrappedAnsiConsole ?? throw new ArgumentNullException(nameof(wrappedAnsiConsole));
_asciiCastWriter = new AsciiCastWriter(_wrappedAnsiConsole.Writer);
}
public TextWriter Writer => _asciiCastWriter;
public bool IsTerminal => _wrappedAnsiConsole.IsTerminal;
public int Width => _wrappedAnsiConsole.Width;
public int Height => _wrappedAnsiConsole.Height;
public void SetEncoding(Encoding encoding)
{
_wrappedAnsiConsole.SetEncoding(encoding);
}
public string GetCastJson(string title, int? width = null, int? height = null)
{
var header = $"{{\"version\": 2, \"width\": {width ?? _wrappedAnsiConsole.Width}, \"height\": {height ?? _wrappedAnsiConsole.Height}, \"title\": \"{JsonEncodedText.Encode(title)}\", \"env\": {{\"TERM\": \"Spectre.Console\"}}}}";
return $"{header}{Environment.NewLine}{_asciiCastWriter.GetJsonAndClearBuffer()}{Environment.NewLine}";
}
}
}

View File

@@ -1,20 +0,0 @@
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class BarChartSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 5);
public override void Run(IAnsiConsole console)
{
console.Write(new BarChart()
.Width(60)
.Label("[green bold underline]Number of fruits[/]")
.CenterLabel()
.AddItem("Apple", 12, Color.Yellow)
.AddItem("Orange", 54, Color.Green)
.AddItem("Banana", 33, Color.Red));
}
}
}

View File

@@ -1,61 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using Spectre.Console;
namespace Generator.Commands.Samples
{
public abstract class BaseSample
{
public abstract void Run(IAnsiConsole console);
public virtual string Name() => PascalToKebab(GetType().Name.Replace("Sample",""));
public virtual (int Cols, int Rows) ConsoleSize => (82, 24);
public virtual IEnumerable<(string Name, Action<Capabilities> CapabilitiesAction)> GetCapabilities()
{
return new (string Name, Action<Capabilities> CapabilitiesAction)[]
{
("plain", capabilities =>
{
capabilities.Unicode = false;
capabilities.Ansi = true;
capabilities.Interactive = true;
capabilities.Legacy = false;
capabilities.Links = false;
capabilities.ColorSystem = ColorSystem.Legacy;
}),
("rich", capabilities =>
{
capabilities.Unicode = true;
capabilities.Ansi = true;
capabilities.Interactive = true;
capabilities.Legacy = false;
capabilities.Links = false;
capabilities.ColorSystem = ColorSystem.TrueColor;
}),
};
}
private string PascalToKebab(ReadOnlySpan<char> input)
{
var sb = new StringBuilder();
var previousUpper = true;
foreach (var chr in input)
{
if (char.IsUpper(chr) && previousUpper == false)
{
sb.Append('-');
previousUpper = true;
}
else
{
previousUpper = false;
}
sb.Append(char.ToLower(chr));
}
return sb.ToString();
}
}
}

View File

@@ -1,21 +0,0 @@
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class BreakdownChartSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 5);
public override void Run(IAnsiConsole console)
{
console.Write(new BreakdownChart()
.Width(60)
.AddItem("SCSS", 80, Color.Red)
.AddItem("HTML", 28.3, Color.Blue)
.AddItem("C#", 22.6, Color.Green)
.AddItem("JavaScript", 6, Color.Yellow)
.AddItem("Ruby", 6, Color.LightGreen)
.AddItem("Shell", 0.1, Color.Aqua));
}
}
}

View File

@@ -1,40 +0,0 @@
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal abstract class BaseCalendarSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 12);
}
internal class CalendarSample : BaseCalendarSample
{
public override void Run(IAnsiConsole console) => console.Write(new Calendar(2020,10));
}
internal class CalendarCultureSample : BaseCalendarSample
{
public override void Run(IAnsiConsole console) => console.Write(new Calendar(2020,10).Culture("sv-SE"));
}
internal class CalendarHeader : BaseCalendarSample
{
public override void Run(IAnsiConsole console)
{
var calendar = new Calendar(2020,10);
calendar.HeaderStyle(Style.Parse("blue bold"));
console.Write(calendar);
}
}
internal class CalendarHighlightSample : BaseCalendarSample
{
public override void Run(IAnsiConsole console)
{
var calendar = new Calendar(2020, 10).HighlightStyle(Style.Parse("yellow bold"));
calendar.AddCalendarEvent(2020, 10, 11);
console.Write(calendar);
}
}
}

View File

@@ -1,27 +0,0 @@
using SixLabors.ImageSharp.Processing;
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class CanvasImageSample : BaseSample
{
public override void Run(IAnsiConsole console)
{
var image = new CanvasImage("../../../examples/Console/Canvas/cake.png");
image.MaxWidth(16);
console.Write(image);
}
}
internal class CanvasImageManipulationSample : BaseSample
{
public override void Run(IAnsiConsole console)
{
var image = new CanvasImage("../../../examples/Console/Canvas/cake.png");
image.MaxWidth(24);
image.BilinearResampler();
image.Mutate(ctx => ctx.Grayscale().Rotate(-45).EntropyCrop());
console.Write(image);
}
}
}

View File

@@ -1,29 +0,0 @@
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class CanvasSample : BaseSample
{
public override void Run(IAnsiConsole console)
{
var canvas = new Canvas(16, 16);
// Draw some shapes
for(var i = 0; i < canvas.Width; i++)
{
// Cross
canvas.SetPixel(i, i, Color.White);
canvas.SetPixel(canvas.Width - i - 1, i, Color.White);
// Border
canvas.SetPixel(i, 0, Color.Red);
canvas.SetPixel(0, i, Color.Green);
canvas.SetPixel(i, canvas.Height - 1, Color.Blue);
canvas.SetPixel(canvas.Width - 1, i, Color.Yellow);
}
// Render the canvas
console.Write(canvas);
}
}
}

View File

@@ -1,81 +0,0 @@
using System;
using System.Security.Authentication;
using Generator.Commands.Samples;
using Spectre.Console;
// keep the namespace here short because it'll be used in the display of the exceptions
// and we want to keep that below 100 characters wide.
namespace Samples
{
public class Exceptions
{
internal abstract class BaseExceptionSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (100, 12);
protected readonly Exception Exception = null!;
protected BaseExceptionSample()
{
try
{
DoMagic(42, null);
}
catch (Exception ex)
{
Exception = ex;
}
}
}
internal class DefaultExceptionSample : BaseExceptionSample
{
public override void Run(IAnsiConsole console) => console.WriteException(Exception, ExceptionFormats.ShortenPaths);
}
internal class ShortenedExceptionSample : BaseExceptionSample
{
public override void Run(IAnsiConsole console) => console.WriteException(Exception, ExceptionFormats.ShortenEverything | ExceptionFormats.ShowLinks);
}
internal class CustomColorsExceptionSample : BaseExceptionSample
{
public override void Run(IAnsiConsole console)
{
console.WriteException(Exception, new ExceptionSettings
{
Format = ExceptionFormats.ShortenEverything | ExceptionFormats.ShowLinks,
Style = new ExceptionStyle
{
Exception = new Style().Foreground(Color.Grey),
Message = new Style().Foreground(Color.White),
NonEmphasized = new Style().Foreground(Color.Cornsilk1),
Parenthesis = new Style().Foreground(Color.Cornsilk1),
Method = new Style().Foreground(Color.Red),
ParameterName = new Style().Foreground(Color.Cornsilk1),
ParameterType = new Style().Foreground(Color.Red),
Path = new Style().Foreground(Color.Red),
LineNumber = new Style().Foreground(Color.Cornsilk1),
}
});
}
}
private static void DoMagic(int foo, string[,] bar)
{
try
{
CheckCredentials(foo, bar);
}
catch(Exception ex)
{
throw new InvalidOperationException("Whaaat?", ex);
}
}
private static void CheckCredentials(int qux, string[,] corgi)
{
throw new InvalidCredentialException("The credentials are invalid.");
}
}
}

View File

@@ -1,16 +0,0 @@
using Spectre.Console;
namespace Generator.Commands.Samples
{
public class FigletSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (100, 24);
public override void Run(IAnsiConsole console)
{
console.Write(new FigletText("Left aligned").LeftJustified().Color(Color.Red));
console.Write(new FigletText("Centered").Centered().Color(Color.Green));
console.Write(new FigletText("Right aligned").RightJustified().Color(Color.Blue));
}
}
}

View File

@@ -1,97 +0,0 @@
using DocExampleGenerator;
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class InputSample : BaseSample
{
public override void Run(IAnsiConsole console)
{
var age = 0;
var name = string.Empty;
var sport = string.Empty;
var password = string.Empty;
var color = string.Empty;
console.DisplayThenType(c => name = AskName(c), "Peter F↲");
console.DisplayThenType(c => sport = AskSport(c), "football↲¦¦¦¦Hockey↲");
console.DisplayThenType(c => age = AskAge(c), "Forty↲¦¦¦¦40↲");
console.DisplayThenType(c => password = AskPassword(c), "hunter2↲");
console.DisplayThenType(c => color = AskColor(c), "↲");
AnsiConsole.Write(new Rule("[yellow]Results[/]").RuleStyle("grey").LeftJustified());
AnsiConsole.Write(new Table().AddColumns("[grey]Question[/]", "[grey]Answer[/]")
.RoundedBorder()
.BorderColor(Color.Grey)
.AddRow("[grey]Name[/]", name)
.AddRow("[grey]Favorite sport[/]", sport)
.AddRow("[grey]Age[/]", age.ToString())
.AddRow("[grey]Password[/]", password)
.AddRow("[grey]Favorite color[/]", string.IsNullOrEmpty(color) ? "Unknown" : color));
}
private static string AskName(IAnsiConsole console)
{
console.WriteLine();
console.Write(new Rule("[yellow]Strings[/]").RuleStyle("grey").LeftJustified());
var name = console.Ask<string>("What's your [green]name[/]?");
return name;
}
private static string AskSport(IAnsiConsole console)
{
console.WriteLine();
console.Write(new Rule("[yellow]Choices[/]").RuleStyle("grey").LeftJustified());
return console.Prompt(
new TextPrompt<string>("What's your [green]favorite sport[/]?")
.InvalidChoiceMessage("[red]That's not a sport![/]")
.DefaultValue("Sport?")
.AddChoice("Soccer")
.AddChoice("Hockey")
.AddChoice("Basketball"));
}
private static int AskAge(IAnsiConsole console)
{
console.WriteLine();
console.Write(new Rule("[yellow]Integers[/]").RuleStyle("grey").LeftJustified());
return console.Prompt(
new TextPrompt<int>("How [green]old[/] are you?")
.PromptStyle("green")
.ValidationErrorMessage("[red]That's not a valid age[/]")
.Validate(age =>
{
return age switch
{
<= 0 => ValidationResult.Error("[red]You must at least be 1 years old[/]"),
>= 123 => ValidationResult.Error("[red]You must be younger than the oldest person alive[/]"),
_ => ValidationResult.Success(),
};
}));
}
private static string AskPassword(IAnsiConsole console)
{
console.WriteLine();
console.Write(new Rule("[yellow]Secrets[/]").RuleStyle("grey").LeftJustified());
return console.Prompt(
new TextPrompt<string>("Enter [green]password[/]?")
.PromptStyle("red")
.Secret());
}
private static string AskColor(IAnsiConsole console)
{
console.WriteLine();
console.Write(new Rule("[yellow]Optional[/]").RuleStyle("grey").LeftJustified());
return console.Prompt(
new TextPrompt<string>("[grey][[Optional]][/] What is your [green]favorite color[/]?")
.AllowEmpty());
}
}
}

View File

@@ -1,39 +0,0 @@
using Spectre.Console;
using Spectre.Console.Json;
namespace Generator.Commands.Samples
{
public class JsonSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (60, 20);
public override void Run(IAnsiConsole console)
{
var json = new JsonText(
"""
{
"hello": 32,
"world": {
"foo": 21,
"bar": 255,
"baz": [
0.32, 0.33e-32,
0.42e32, 0.55e+32,
{
"hello": "world",
"lol": null
}
]
}
}
""");
AnsiConsole.Write(
new Panel(json)
.Header("Some JSON in a panel")
.Collapse()
.RoundedBorder()
.BorderColor(Color.Yellow));
}
}
}

View File

@@ -1,30 +0,0 @@
using Spectre.Console;
using Spectre.Console.Json;
namespace Generator.Commands.Samples
{
public class LayoutSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (80, 24);
public override void Run(IAnsiConsole console)
{
var layout = new Layout("Root")
.SplitColumns(
new Layout("Left"),
new Layout("Right")
.SplitRows(
new Layout("Top"),
new Layout("Bottom")));
layout["Left"].Update(
new Panel(
Align.Center(
new Markup("Hello [blue]World![/]"),
VerticalAlignment.Middle))
.Expand());
AnsiConsole.Write(layout);
}
}
}

View File

@@ -1,84 +0,0 @@
using System;
using System.Threading;
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class LiveSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (100, 20);
public override void Run(IAnsiConsole console)
{
var table = new Table();
// Animate
console.Live(table)
.AutoClear(false)
.Overflow(VerticalOverflow.Ellipsis)
.Cropping(VerticalOverflowCropping.Top)
.Start(ctx =>
{
void Update(int delay, Action action)
{
action();
ctx.Refresh();
Thread.Sleep(delay);
}
// Columns
Update(230, () => table.AddColumn("Release date"));
Update(230, () => table.AddColumn("Title"));
Update(230, () => table.AddColumn("Budget"));
Update(230, () => table.AddColumn("Opening Weekend"));
Update(230, () => table.AddColumn("Box office"));
// Rows
Update(70, () => table.AddRow("May 25, 1977", "[yellow]Star Wars[/] [grey]Ep.[/] [u]IV[/]", "$11,000,000", "$1,554,475", "$775,398,007"));
Update(70, () => table.AddRow("May 21, 1980", "[yellow]Star Wars[/] [grey]Ep.[/] [u]V[/]", "$18,000,000", "$4,910,483", "$547,969,004"));
Update(70, () => table.AddRow("May 25, 1983", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VI[/]", "$32,500,000", "$23,019,618", "$475,106,177"));
Update(70, () => table.AddRow("May 19, 1999", "[yellow]Star Wars[/] [grey]Ep.[/] [u]I[/]", "$115,000,000", "$64,810,870", "$1,027,044,677"));
Update(70, () => table.AddRow("May 16, 2002", "[yellow]Star Wars[/] [grey]Ep.[/] [u]II[/]", "$115,000,000", "$80,027,814", "$649,436,358"));
Update(70, () => table.AddRow("May 19, 2005", "[yellow]Star Wars[/] [grey]Ep.[/] [u]III[/]", "$113,000,000", "$108,435,841", "$850,035,635"));
Update(70, () => table.AddRow("Dec 18, 2015", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VII[/]", "$245,000,000", "$247,966,675", "$2,068,223,624"));
Update(70, () => table.AddRow("Dec 15, 2017", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VIII[/]", "$317,000,000", "$220,009,584", "$1,333,539,889"));
Update(70, () => table.AddRow("Dec 20, 2019", "[yellow]Star Wars[/] [grey]Ep.[/] [u]IX[/]", "$245,000,000", "$177,383,864", "$1,074,114,248"));
// Column footer
Update(230, () => table.Columns[2].Footer("$1,633,000,000"));
Update(230, () => table.Columns[3].Footer("$928,119,224"));
Update(400, () => table.Columns[4].Footer("$10,318,030,576"));
// Column alignment
Update(230, () => table.Columns[2].RightAligned());
Update(230, () => table.Columns[3].RightAligned());
Update(400, () => table.Columns[4].RightAligned());
// Column titles
Update(70, () => table.Columns[0].Header("[bold]Release date[/]"));
Update(70, () => table.Columns[1].Header("[bold]Title[/]"));
Update(70, () => table.Columns[2].Header("[red bold]Budget[/]"));
Update(70, () => table.Columns[3].Header("[green bold]Opening Weekend[/]"));
Update(400, () => table.Columns[4].Header("[blue bold]Box office[/]"));
// Footers
Update(70, () => table.Columns[2].Footer("[red bold]$1,633,000,000[/]"));
Update(70, () => table.Columns[3].Footer("[green bold]$928,119,224[/]"));
Update(400, () => table.Columns[4].Footer("[blue bold]$10,318,030,576[/]"));
// Title
Update(500, () => table.Title("Star Wars Movies"));
Update(400, () => table.Title("[[ [yellow]Star Wars Movies[/] ]]"));
// Borders
Update(230, () => table.BorderColor(Color.Yellow));
Update(230, () => table.MinimalBorder());
Update(230, () => table.SimpleBorder());
Update(230, () => table.SimpleHeavyBorder());
// Caption
Update(400, () => table.Caption("[[ [blue]THE END[/] ]]"));
});
}
}
}

View File

@@ -1,40 +0,0 @@
using DocExampleGenerator;
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class MultiSelectionSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 14);
public override void Run(IAnsiConsole console)
{
console.DisplayThenType(AskFruit, "↓↓ ¦¦↑↑ ¦¦ ¦¦↓ ↓↓↓↓↓ ↓↓↓↓ ¦¦↲");
}
private static void AskFruit(IAnsiConsole console)
{
var favorites = console.Prompt(
new MultiSelectionPrompt<string>()
.PageSize(10)
.Title("What are your [green]favorite fruits[/]?")
.MoreChoicesText("[grey](Move up and down to reveal more fruits)[/]")
.InstructionsText("[grey](Press [blue]<space>[/] to toggle a fruit, [green]<enter>[/] to accept)[/]")
.AddChoiceGroup("Berries", new[]
{
"Blackcurrant", "Blueberry", "Cloudberry",
"Elderberry", "Honeyberry", "Mulberry"
})
.AddChoices(new[]
{
"Apple", "Apricot", "Avocado", "Banana",
"Cherry", "Cocunut", "Date", "Dragonfruit", "Durian",
"Egg plant", "Fig", "Grape", "Guava",
"Jackfruit", "Jambul", "Kiwano", "Kiwifruit", "Lime", "Lylo",
"Lychee", "Melon", "Nectarine", "Orange", "Olive"
}));
console.MarkupLine("Your selected: [yellow]{0}[/]", string.Join(',', favorites));
}
}
}

View File

@@ -1,16 +0,0 @@
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class PanelSample : BaseSample
{
public override void Run(IAnsiConsole console)
{
var panel = new Panel("[red]Spaghetti\nLinguini\nFettucine\nTortellini\nCapellini\nLasagna[/]");
panel.Header = new PanelHeader("[underline]Pasta Menu[/]", Justify.Center);
panel.Border = BoxBorder.Double;
panel.Padding = new Padding(2, 2, 2, 2);
console.Write(panel);
}
}
}

View File

@@ -1,87 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class ProgressSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 10);
public override IEnumerable<(string Name, Action<Capabilities> CapabilitiesAction)> GetCapabilities()
{
yield return ("non-interactive", capabilities =>
{
capabilities.Ansi = false;
capabilities.Interactive = false;
capabilities.Legacy = false;
capabilities.Unicode = true;
capabilities.ColorSystem = ColorSystem.TrueColor;
});
foreach (var capability in base.GetCapabilities())
{
yield return capability;
}
}
public override void Run(IAnsiConsole console)
{
// Show progress
console.Progress()
.AutoClear(false)
.Columns(new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn(), new RemainingTimeColumn(), new SpinnerColumn())
.Start(ctx =>
{
var random = new Random(122978);
// Create some tasks
var tasks = CreateTasks(ctx, random);
var warpTask = ctx.AddTask("Going to warp", autoStart: false).IsIndeterminate();
// Wait for all tasks (except the indeterminate one) to complete
while (!ctx.IsFinished)
{
// Increment progress
foreach (var (task, increment) in tasks)
{
task.Increment(random.NextDouble() * increment);
}
// Simulate some delay
Thread.Sleep(100);
}
// Now start the "warp" task
warpTask.StartTask();
warpTask.IsIndeterminate(false);
while (!ctx.IsFinished)
{
warpTask.Increment(12 * random.NextDouble());
// Simulate some delay
Thread.Sleep(100);
}
});
}
private static List<(ProgressTask Task, int Delay)> CreateTasks(ProgressContext progress, Random random)
{
var tasks = new List<(ProgressTask, int)>();
var names = new[]
{
"Retriculating algorithms", "Colliding splines", "Solving quarks", "Folding data structures",
"Rerouting capacitators "
};
for (var i = 0; i < 5; i++)
{
tasks.Add((progress.AddTask(names[i]), random.Next(2, 10)));
}
return tasks;
}
}
}

View File

@@ -1,21 +0,0 @@
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class RuleSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (82, 10);
public override void Run(IAnsiConsole console)
{
console.Write(new Rule());
console.WriteLine();
console.Write(new Rule("[blue]Left aligned[/]").LeftJustified().RuleStyle("red"));
console.WriteLine();
console.Write(new Rule("[green]Centered[/]").Centered().RuleStyle("green"));
console.WriteLine();
console.Write(new Rule("[red]Right aligned[/]").RightJustified().RuleStyle("blue"));
console.WriteLine();
}
}
}

View File

@@ -1,29 +0,0 @@
using DocExampleGenerator;
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class SelectionSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 14);
public override void Run(IAnsiConsole console)
{
console.DisplayThenType(AskFruit, "↓↓↓¦¦¦¦ ");
}
private static void AskFruit(IAnsiConsole console)
{
// Ask for the user's favorite fruit
var fruit = console.Prompt(
new SelectionPrompt<string>()
.Title("What's your [green]favorite fruit[/]?")
.PageSize(10)
.MoreChoicesText("[grey](Move up and down to reveal more fruits)[/]")
.AddChoices(new [] {"Apple", "Apricot", "Avocado", "Banana", "Blackcurrant", "Blueberry", "Cherry", "Cloudberry", "Cocunut"}));
// Echo the fruit back to the terminal
console.WriteLine($"I agree. {fruit} is tasty!");
}
}
}

View File

@@ -1,71 +0,0 @@
using System.Threading;
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class StatusSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 10);
private static void WriteLogMessage(string message)
{
AnsiConsole.MarkupLine($"[grey]LOG:[/] {message}[grey]...[/]");
}
public override void Run(IAnsiConsole console)
{
console.Status()
.AutoRefresh(true)
.Spinner(Spinner.Known.Default)
.Start("[yellow]Initializing warp drive[/]", ctx =>
{
// Initialize
Thread.Sleep(3000);
WriteLogMessage("Starting gravimetric field displacement manifold");
Thread.Sleep(1000);
WriteLogMessage("Warming up deuterium chamber");
Thread.Sleep(2000);
WriteLogMessage("Generating antideuterium");
// Warp nacelles
Thread.Sleep(3000);
ctx.Spinner(Spinner.Known.BouncingBar);
ctx.Status("[bold blue]Unfolding warp nacelles[/]");
WriteLogMessage("Unfolding left warp nacelle");
Thread.Sleep(2000);
WriteLogMessage("Left warp nacelle [green]online[/]");
WriteLogMessage("Unfolding right warp nacelle");
Thread.Sleep(1000);
WriteLogMessage("Right warp nacelle [green]online[/]");
// Warp bubble
Thread.Sleep(3000);
ctx.Spinner(Spinner.Known.Star2);
ctx.Status("[bold blue]Generating warp bubble[/]");
Thread.Sleep(3000);
ctx.Spinner(Spinner.Known.Star);
ctx.Status("[bold blue]Stabilizing warp bubble[/]");
// Safety
ctx.Spinner(Spinner.Known.Monkey);
ctx.Status("[bold blue]Performing safety checks[/]");
WriteLogMessage("Enabling interior dampening");
Thread.Sleep(2000);
WriteLogMessage("Interior dampening [green]enabled[/]");
// Warp!
Thread.Sleep(3000);
ctx.Spinner(Spinner.Known.Moon);
WriteLogMessage("Preparing for warp");
Thread.Sleep(1000);
for (var warp = 1; warp < 10; warp++)
{
ctx.Status($"[bold blue]Warp {warp}[/]");
Thread.Sleep(500);
}
});
// Done
AnsiConsole.MarkupLine("[bold green]Crusing at Warp 9.8[/]"); }
}
}

View File

@@ -1,46 +0,0 @@
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class TableSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (100, 30);
public override void Run(IAnsiConsole console)
{
var simple = new Table()
.Border(TableBorder.Square)
.BorderColor(Color.Red)
.AddColumn(new TableColumn("[u]CDE[/]").Footer("EDC").Centered())
.AddColumn(new TableColumn("[u]FED[/]").Footer("DEF"))
.AddColumn(new TableColumn("[u]IHG[/]").Footer("GHI"))
.AddRow("Hello", "[red]World![/]", "")
.AddRow("[blue]Bonjour[/]", "[white]le[/]", "[red]monde![/]")
.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", "");
var second = new Table()
.Border(TableBorder.Rounded)
.BorderColor(Color.Green)
.AddColumn(new TableColumn("[u]Foo[/]"))
.AddColumn(new TableColumn("[u]Bar[/]"))
.AddColumn(new TableColumn("[u]Baz[/]"))
.AddRow("Hello", "[red]World![/]", "")
.AddRow(simple, new Text("Whaaat"), new Text("Lolz"))
.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", "");
var table = new Table()
.Centered()
.Border(TableBorder.DoubleEdge)
.Title("TABLE [yellow]TITLE[/]")
.Caption("TABLE [yellow]CAPTION[/]")
.AddColumn(new TableColumn(new Panel("[u]ABC[/]").BorderColor(Color.Red)).Footer("[u]FOOTER 1[/]"))
.AddColumn(new TableColumn(new Panel("[u]DEF[/]").BorderColor(Color.Green)).Footer("[u]FOOTER 2[/]"))
.AddColumn(new TableColumn(new Panel("[u]GHI[/]").BorderColor(Color.Blue)).Footer("[u]FOOTER 3[/]"))
.AddRow(new Text("Hello").Centered(), new Markup("[red]World![/]"), Text.Empty)
.AddRow(second, new Text("Whaaat"), new Text("Lol"))
.AddRow(new Markup("[blue]Hej[/]").Centered(), new Markup("[yellow]Världen![/]"), Text.Empty);
console.Write(table);
}
}
}

View File

@@ -1,44 +0,0 @@
using DocExampleGenerator;
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class TextPathSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (40, 23);
public override void Run(IAnsiConsole console)
{
console.Write(
new Panel(
new Padder(new TextPath("C:/This/Is/A/Super/Long/Path/That/Will/Be/Truncated.txt"), new Padding(0,1)))
.BorderStyle(new Style(foreground: Color.Grey))
.Header("Windows path"));
console.Write(
new Panel(
new Padder(new TextPath("/This/Is/A/Super/Long/Path/That/Will/Be/Truncated.txt"), new Padding(0,1)))
.BorderStyle(new Style(foreground: Color.Grey))
.Header("Unix path"));
console.Write(
new Panel(
new Padder(new TextPath("/This/Is/A/Long/Path/That/Will/Be/Truncated.txt")
.RootColor(Color.Green)
.SeparatorColor(Color.Red)
.StemColor(Color.Yellow)
.LeafColor(Color.Blue), new Padding(0,1)))
.BorderStyle(new Style(foreground: Color.Grey))
.Header("Styling"));
console.Write(
new Panel(
new Padder(new Rows(
new TextPath("/This/Is/A/Long/Path/That/Will/Be/Truncated.txt").LeftJustified(),
new TextPath("/This/Is/A/Long/Path/That/Will/Be/Truncated.txt").Centered(),
new TextPath("/This/Is/A/Long/Path/That/Will/Be/Truncated.txt").RightJustified()), new Padding(0,1)))
.BorderStyle(new Style(foreground: Color.Grey))
.Header("Alignment"));
}
}
}

View File

@@ -1,39 +0,0 @@
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class TreeSample : BaseSample
{
public override void Run(IAnsiConsole console)
{
// Create the tree
var tree = new Tree("Root")
.Style(Style.Parse("red"))
.Guide(TreeGuide.Line);
// Add some nodes
var foo = tree.AddNode("[yellow]Nest objects like tables[/]");
var table = foo.AddNode(new Table()
.RoundedBorder()
.AddColumn("First")
.AddColumn("Second")
.AddRow("1", "2")
.AddRow("3", "4")
.AddRow("5", "6"));
table.AddNode("[blue]with[/]");
table.AddNode("[blue]multiple[/]");
table.AddNode("[blue]children too[/]");
var bar = tree.AddNode("Any IRenderable can be nested, such as [yellow]calendars[/]");
bar.AddNode(new Calendar(2020, 12)
.Border(TableBorder.Rounded)
.BorderStyle(new Style(Color.Green3_1))
.AddCalendarEvent(2020, 12, 12)
.HideHeader());
console.Write(tree);
}
}
}

View File

@@ -1,59 +0,0 @@
using System.IO;
using System.Threading;
using Generator.Models;
using Scriban;
using Spectre.Console.Cli;
using Spectre.IO;
namespace Generator.Commands
{
public sealed class ColorGeneratorCommand : Command<ColorGeneratorCommand.Settings>
{
private readonly IFileSystem _fileSystem;
public ColorGeneratorCommand()
{
_fileSystem = new FileSystem();
}
public sealed class Settings : GeneratorSettings
{
[CommandOption("-i|--input <PATH>")]
public string Input { get; set; }
}
public override int Execute(CommandContext context, Settings settings, CancellationToken cancellationToken)
{
var templates = new FilePath[]
{
"Templates/ColorPalette.Generated.template",
"Templates/Color.Generated.template",
"Templates/ColorTable.Generated.template"
};
// Read the color model.
var model = Color.Parse(File.ReadAllText("Data/colors.json"));
var output = new DirectoryPath(settings.Output);
if (!_fileSystem.Directory.Exists(settings.Output))
{
_fileSystem.Directory.Create(settings.Output);
}
foreach (var templatePath in templates)
{
// Parse the Scriban template.
var template = Template.Parse(File.ReadAllText(templatePath.FullPath));
// Render the template with the model.
var result = template.Render(new { Colors = model });
// Write output to file
var file = output.CombineWithFilePath(templatePath.GetFilename().ChangeExtension(".cs"));
File.WriteAllText(file.FullPath, result);
}
return 0;
}
}
}

View File

@@ -1,107 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using AngleSharp.Html.Parser;
using Generator.Models;
using Scriban;
using Scriban.Runtime;
using Spectre.Console.Cli;
using Spectre.IO;
using Path = Spectre.IO.Path;
using SpectreEnvironment = Spectre.IO.Environment;
namespace Generator.Commands
{
public sealed class EmojiGeneratorCommand : AsyncCommand<EmojiGeneratorCommand.Settings>
{
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" }, // For documentation
};
public sealed class Settings : GeneratorSettings
{
[CommandOption("-i|--input <PATH>")]
public string Input { get; set; }
}
public EmojiGeneratorCommand()
{
_fileSystem = new FileSystem();
_environment = new SpectreEnvironment();
_parser = new HtmlParser();
}
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings, CancellationToken cancellationToken)
{
var output = new DirectoryPath(settings.Output);
if (!_fileSystem.Directory.Exists(settings.Output))
{
_fileSystem.Directory.Create(settings.Output);
}
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();
// Render all templates
foreach (var (templateFilename, outputFilename) in _templates)
{
var result = await RenderTemplate(new FilePath(templateFilename), emojis);
var outputPath = output.CombineWithFilePath(outputFilename);
await File.WriteAllTextAsync(outputPath.FullPath, result);
}
return 0;
}
private async Task<Stream> FetchEmojis(Settings settings)
{
var input = string.IsNullOrEmpty(settings.Input)
? _environment.WorkingDirectory
: new DirectoryPath(settings.Input);
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();
await httpStream.CopyToAsync(outStream);
}
return file.OpenRead();
}
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);
}
}
}

View File

@@ -1,10 +0,0 @@
using Spectre.Console.Cli;
namespace Generator.Commands
{
public class GeneratorSettings : CommandSettings
{
[CommandArgument(0, "<OUTPUT>")]
public string Output { get; set; }
}
}

View File

@@ -1,101 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Generator.Commands.Samples;
using Spectre.Console;
using Spectre.Console.Cli;
namespace Generator.Commands
{
internal class SampleCommand : Command<SampleCommand.Settings>
{
public class Settings : CommandSettings
{
public Settings(string outputPath, string sample, bool list)
{
Sample = sample;
OutputPath = outputPath ?? Environment.CurrentDirectory;
List = list;
}
[CommandArgument(0, "[sample]")]
public string Sample { get; }
[CommandOption("-o|--output")]
public string OutputPath { get; }
[CommandOption("-l|--list")]
public bool List { get; }
}
private readonly IAnsiConsole _console;
public SampleCommand(IAnsiConsole console)
{
_console = new AsciiCastConsole(console);
}
public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings, CancellationToken cancellationToken)
{
var samples = typeof(BaseSample).Assembly
.GetTypes()
.Where(i => i.IsClass && i.IsAbstract == false && i.IsSubclassOf(typeof(BaseSample)))
.Select(Activator.CreateInstance)
.Cast<BaseSample>();
var selectedSample = settings.Sample;
if (settings.List)
{
selectedSample = AnsiConsole.Prompt(
new SelectionPrompt<string>()
.Title("Select an example to record")
.PageSize(25)
.AddChoices(samples.Select(x => x.Name())));
}
if (!string.IsNullOrWhiteSpace(selectedSample))
{
var desiredSample = samples.FirstOrDefault(i => i.Name().Equals(selectedSample, StringComparison.OrdinalIgnoreCase));
if (desiredSample == null)
{
_console.MarkupLine($"[red]Error:[/] could not find sample [blue]{selectedSample}[/]");
return -1;
}
samples = new List<BaseSample> { desiredSample };
}
// from here on out everything we write will be recorded.
var recorder = _console.WrapWithAsciiCastRecorder();
foreach (var sample in samples)
{
var sampleName = sample.Name();
var originalWidth = _console.Profile.Width;
var originalHeight = _console.Profile.Height;
_console.Profile.Encoding = Encoding.UTF8;
_console.Profile.Width = sample.ConsoleSize.Cols;
_console.Profile.Height = sample.ConsoleSize.Rows;
foreach (var (capabilityName, action) in sample.GetCapabilities())
{
action(_console.Profile.Capabilities);
sample.Run(_console);
var json = recorder.GetCastJson($"{sampleName} ({capabilityName})", sample.ConsoleSize.Cols + 2, sample.ConsoleSize.Rows);
File.WriteAllText(Path.Combine(settings.OutputPath, $"{sampleName}-{capabilityName}.cast"), json);
}
_console.Profile.Width = originalWidth;
_console.Profile.Height = originalHeight;
}
return 0;
}
}
}

View File

@@ -1,47 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Generator.Models;
using Scriban;
using Spectre.Console.Cli;
using Spectre.IO;
namespace Generator.Commands
{
public sealed class SpinnerGeneratorCommand : Command<GeneratorSettings>
{
private readonly IFileSystem _fileSystem;
public SpinnerGeneratorCommand()
{
_fileSystem = new FileSystem();
}
public override int Execute(CommandContext context, GeneratorSettings settings, CancellationToken cancellationToken)
{
// Read the spinner model.
var spinners = new List<Spinner>();
spinners.AddRange(Spinner.Parse(File.ReadAllText("Data/spinners_default.json")));
spinners.AddRange(Spinner.Parse(File.ReadAllText("Data/spinners_sindresorhus.json")));
var output = new DirectoryPath(settings.Output);
if (!_fileSystem.Directory.Exists(settings.Output))
{
_fileSystem.Directory.Create(settings.Output);
}
// Parse the Scriban template.
var templatePath = new FilePath("Templates/Spinner.Generated.template");
var template = Template.Parse(File.ReadAllText(templatePath.FullPath));
// Render the template with the model.
var result = template.Render(new { Spinners = spinners });
// Write output to file
var file = output.CombineWithFilePath(templatePath.GetFilename().ChangeExtension(".cs"));
File.WriteAllText(file.FullPath, result);
return 0;
}
}
}

View File

@@ -1,59 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>default</LangVersion>
</PropertyGroup>
<ItemGroup>
<Compile Remove="out\**" />
<EmbeddedResource Remove="out\**" />
<None Remove="out\**" />
</ItemGroup>
<ItemGroup>
<None Update="Data\colors.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Data\spinners_default.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Data\spinners_sindresorhus.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Templates\ColorTable.Generated.template">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Templates\Color.Generated.template">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Templates\ColorPalette.Generated.template">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Templates\Spinner.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>
</ItemGroup>
<ItemGroup>
<PackageReference Include="AngleSharp" Version="1.3.0" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="Scriban" Version="6.4.0" />
<PackageReference Include="Spectre.IO" Version="0.21.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Spectre.Console\Spectre.Console.csproj" />
<ProjectReference Include="..\..\..\src\Spectre.Console.Cli\Spectre.Console.Cli.csproj" />
<ProjectReference Include="..\..\..\src\Extensions\Spectre.Console.ImageSharp\Spectre.Console.ImageSharp.csproj" />
<ProjectReference Include="..\..\..\src\Extensions\Spectre.Console.Json\Spectre.Console.Json.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,79 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
namespace Generator.Models
{
public sealed class Color
{
public int Number { get; set; }
public string Hex { get; set; }
public string Name { get; set; }
public List<string> Aliases { get; set; } = new List<string>();
public Rgb Rgb { get; set; }
public int R => Rgb.R;
public int G => Rgb.G;
public int B => Rgb.B;
public static IEnumerable<Color> Parse(string json)
{
var source = JsonConvert.DeserializeObject<List<Color>>(json);
var check = new Dictionary<string, Color>(StringComparer.OrdinalIgnoreCase);
var colorAliases = source
.SelectMany(c => c.Aliases.Select(a => new { Alias = a, Color = c }))
.Select(a => new Color()
{
Hex = a.Color.Hex,
Name = a.Alias,
Number = a.Color.Number,
Rgb = a.Color.Rgb
})
.ToList();
var colors = source
.Union(colorAliases)
.OrderBy(c => c.Number);
foreach (var color in colors)
{
if (!check.ContainsKey(color.Name))
{
check.Add(color.Name, color);
}
else
{
var newName = (string)null;
for (int i = 1; i < 100; i++)
{
if (!check.ContainsKey($"{color.Name}_{i}"))
{
newName = $"{color.Name}_{i}";
break;
}
}
if (newName == null)
{
throw new InvalidOperationException("Impossible!");
}
check.Add(newName, color);
color.Name = newName;
}
}
return colors;
}
}
public sealed class Rgb
{
public int R { get; set; }
public int G { get; set; }
public int B { get; set; }
}
}

View File

@@ -1,14 +0,0 @@
using System.Collections.Generic;
namespace Generator.Models
{
public sealed class ColorModel
{
public List<Color> Colors { get; set; }
public ColorModel(IEnumerable<Color> colors)
{
Colors = new List<Color>(colors);
}
}
}

View File

@@ -1,95 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AngleSharp.Dom;
using AngleSharp.Html.Dom;
using Humanizer;
namespace Generator.Models
{
public class Emoji
{
private static readonly string[] _headers = { "count", "code", "sample", "name" };
private Emoji(string identifier, string name, string code, string description)
{
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)
{
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: header, cell.TextContent.Trim()))
.ToDictionary(x => x.Item1, x => x.Item2);
var code = TransformCode(dictionary["code"]);
var identifier = TransformName(dictionary["name"])
.Replace("-", "_")
.Replace("(", string.Empty)
.Replace(")", string.Empty);
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);
}
}
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)
.Replace(' ', '_')
.Replace("s", "s")
.Replace("", "_")
.Replace("&", "and")
.Replace("#", "hash")
.Replace("*", "star")
.Replace("!", string.Empty)
.Trim()
.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");
}
}
}

View File

@@ -1,6 +0,0 @@
namespace Generator.Models
{
public sealed class Palette
{
}
}

View File

@@ -1,31 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Humanizer;
using Newtonsoft.Json;
namespace Generator.Models
{
public sealed class Spinner
{
public string Name { get; set; }
public string NormalizedName { get; set; }
public int Interval { get; set; }
public bool Unicode { get; set; }
public List<string> Frames { get; set; }
public static IEnumerable<Spinner> Parse(string json)
{
var data = JsonConvert.DeserializeObject<Dictionary<string, Spinner>>(json);
foreach (var item in data)
{
item.Value.Name = item.Key;
item.Value.NormalizedName = item.Value.Name.Pascalize();
var frames = item.Value.Frames;
item.Value.Frames = frames.Select(f => f.Replace("\\", "\\\\")).ToList();
}
return data.Values;
}
}
}

View File

@@ -1,22 +0,0 @@
using Generator.Commands;
using Spectre.Console.Cli;
namespace Generator
{
public static class Program
{
public static int Main(string[] args)
{
var app = new CommandApp();
app.Configure(config =>
{
config.AddCommand<ColorGeneratorCommand>("colors");
config.AddCommand<EmojiGeneratorCommand>("emoji");
config.AddCommand<SpinnerGeneratorCommand>("spinners");
config.AddCommand<SampleCommand>("samples");
});
return app.Run(args);
}
}
}

View File

@@ -1,6 +1,156 @@
root = false root = false
[*.cs] [*.cs]
# Prefer file scoped namespace declarations
csharp_style_namespace_declarations = file_scoped:warning
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
dotnet_separate_import_directive_groups = false
# Avoid "this." and "Me." if not necessary
dotnet_style_qualification_for_field = false:refactoring
dotnet_style_qualification_for_property = false:refactoring
dotnet_style_qualification_for_method = false:refactoring
dotnet_style_qualification_for_event = false:refactoring
# Use language keywords instead of framework type names for type references
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# Suggest more modern language features when available
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
# Non-private static fields are PascalCase
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
# Non-private readonly fields are PascalCase
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = non_private_readonly_field_style
dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field
dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly
dotnet_naming_style.non_private_readonly_field_style.capitalization = pascal_case
# Constants are PascalCase
dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style
dotnet_naming_symbols.constants.applicable_kinds = field, local
dotnet_naming_symbols.constants.required_modifiers = const
dotnet_naming_style.constant_style.capitalization = pascal_case
# Instance fields are camelCase and start with _
dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
dotnet_naming_symbols.instance_fields.applicable_kinds = field
dotnet_naming_style.instance_field_style.capitalization = camel_case
dotnet_naming_style.instance_field_style.required_prefix = _
# Locals and parameters are camelCase
dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion
dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters
dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style
dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local
dotnet_naming_style.camel_case_style.capitalization = camel_case
# Local functions are PascalCase
dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
dotnet_naming_style.local_function_style.capitalization = pascal_case
# By default, name items with PascalCase
dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.all_members.applicable_kinds = *
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# Newline settings
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_switch_labels = true
csharp_indent_labels = flush_left
# Prefer "var" everywhere
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion
# Prefer method-like constructs to have a block body
csharp_style_expression_bodied_methods = false:none
csharp_style_expression_bodied_constructors = false:none
csharp_style_expression_bodied_operators = false:none
# Prefer property-like constructs to have an expression-body
csharp_style_expression_bodied_properties = true:none
csharp_style_expression_bodied_indexers = true:none
csharp_style_expression_bodied_accessors = true:none
# Suggest more modern language features when available
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = do_not_ignore
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Blocks are allowed
csharp_prefer_braces = true:silent
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
# RS0037: PublicAPI.txt is missing '#nullable enable'
dotnet_diagnostic.RS0037.severity = none
# IDE0055: Fix formatting # IDE0055: Fix formatting
dotnet_diagnostic.IDE0055.severity = warning dotnet_diagnostic.IDE0055.severity = warning

View File

@@ -50,17 +50,10 @@
<EmbedUntrackedSources>true</EmbedUntrackedSources> <EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)/stylecop.json" Link="Properties/stylecop.json"/>
</ItemGroup>
<!-- Allow folks to build with minimal dependencies (though they will need to provide their own Version data) --> <!-- Allow folks to build with minimal dependencies (though they will need to provide their own Version data) -->
<ItemGroup Label="Build Tools Package References" Condition="'$(UseBuildTimeTools)' != 'false'"> <ItemGroup Label="Build Tools Package References" Condition="'$(UseBuildTimeTools)' != 'false'">
<PackageReference Include="MinVer" PrivateAssets="All" /> <PackageReference Include="MinVer" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" /> <PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="Roslynator.Analyzers"> <PackageReference Include="Roslynator.Analyzers">
<PrivateAssets>All</PrivateAssets> <PrivateAssets>All</PrivateAssets>
</PackageReference> </PackageReference>

View File

@@ -1,8 +0,0 @@
<Project>
<Target Name="Versioning" BeforeTargets="MinVer">
<PropertyGroup Label="Build">
<MinVerDefaultPreReleaseIdentifiers>preview.0</MinVerDefaultPreReleaseIdentifiers>
<MinVerVerbosity>normal</MinVerVerbosity>
</PropertyGroup>
</Target>
</Project>

View File

@@ -3,18 +3,22 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="AngleSharp" Version="1.3.0" />
<PackageVersion Include="Humanizer.Core" Version="2.14.1" />
<PackageVersion Include="IsExternalInit" Version="1.0.3" /> <PackageVersion Include="IsExternalInit" Version="1.0.3" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.10" /> <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.10" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.0" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="8.0.0" /> <PackageVersion Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="8.0.0" />
<PackageVersion Include="MinVer" PrivateAssets="All" Version="6.0.0" /> <PackageVersion Include="MinVer" PrivateAssets="All" Version="6.0.0" />
<PackageVersion Include="OpenCli.Sources" Version="0.5.0" /> <PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="PolySharp" Version="1.15.0" /> <PackageVersion Include="PolySharp" Version="1.15.0" />
<PackageVersion Include="Roslynator.Analyzers" PrivateAssets="All" Version="4.14.1" /> <PackageVersion Include="Roslynator.Analyzers" PrivateAssets="All" Version="4.14.1" />
<PackageVersion Include="Scriban" Version="6.4.0" />
<PackageVersion Include="Shouldly" Version="4.3.0" /> <PackageVersion Include="Shouldly" Version="4.3.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.11" /> <PackageVersion Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageVersion Include="Spectre.Console.Cli" Version="0.53.0" />
<PackageVersion Include="Spectre.IO" Version="0.21.0" />
<PackageVersion Include="Spectre.Verify.Extensions" Version="28.16.0" /> <PackageVersion Include="Spectre.Verify.Extensions" Version="28.16.0" />
<PackageVersion Include="StyleCop.Analyzers" PrivateAssets="All" Version="1.2.0-beta.556" />
<PackageVersion Include="System.Memory" Version="4.6.3" /> <PackageVersion Include="System.Memory" Version="4.6.3" />
<PackageVersion Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.160" /> <PackageVersion Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.160" />
<PackageVersion Include="Verify.Xunit" Version="31.0.5" /> <PackageVersion Include="Verify.Xunit" Version="31.0.5" />

View File

@@ -1,5 +1,3 @@
using System.Text;
namespace Spectre.Console.Json; namespace Spectre.Console.Json;
internal static class JsonTokenizer internal static class JsonTokenizer

View File

@@ -0,0 +1,32 @@
using System;
using System.Threading;
using Generator.Commands;
using Spectre.Console;
namespace DocExampleGenerator;
internal static class AnsiConsoleExtensions
{
/// <summary>
/// Displays something via AnsiConsole, waits a bit and then simulates typing based on the input. If the console
/// doesn't have the focus this will just type into whatever window does so watch the alt-tab.
/// </summary>
/// <param name="console"></param>
/// <param name="action">The display action.</param>
/// <param name="input">The characters to type. ↑ for an up arrow, ↓ for down arrow, ↲ for a return and ¦ for a pause.</param>
/// <param name="initialDelayMs">How long to delay before typing. This should be at least 100ms because we won't check if the prompt has displayed before simulating typing.</param>
/// <param name="keypressDelayMs">Delay between keypresses. There will be a bit of randomness between each keypress +/- 20% of this value.</param>
public static void DisplayThenType(this IAnsiConsole console, Action<IAnsiConsole> action, string input, int initialDelayMs = 500, int keypressDelayMs = 200)
{
if (console is not AsciiCastConsole asciiConsole)
{
throw new InvalidOperationException("Not an ASCII cast console");
}
asciiConsole.Input.PushText(input, keypressDelayMs);
Thread.Sleep(initialDelayMs);
action(console);
}
}

View File

@@ -0,0 +1,39 @@
using System;
using Spectre.Console;
using Spectre.Console.Rendering;
namespace Generator.Commands;
public sealed class AsciiCastConsole : IAnsiConsole
{
private readonly IAnsiConsole _console;
private readonly AsciiCastInput _input;
public Profile Profile => _console.Profile;
public IAnsiConsoleCursor Cursor => _console.Cursor;
IAnsiConsoleInput IAnsiConsole.Input => _input;
public AsciiCastInput Input => _input;
public IExclusivityMode ExclusivityMode => _console.ExclusivityMode;
public RenderPipeline Pipeline => _console.Pipeline;
public AsciiCastConsole(IAnsiConsole console)
{
_console = console ?? throw new ArgumentNullException(nameof(console));
_input = new AsciiCastInput();
}
public void Clear(bool home)
{
_console.Clear(home);
}
public void Write(IRenderable renderable)
{
_console.Write(renderable);
}
}

View File

@@ -0,0 +1,14 @@
using Spectre.Console;
namespace Generator.Commands;
public static class AsciiCastExtensions
{
public static AsciiCastOut WrapWithAsciiCastRecorder(this IAnsiConsole ansiConsole)
{
AsciiCastOut castRecorder = new(ansiConsole.Profile.Out);
ansiConsole.Profile.Out = castRecorder;
return castRecorder;
}
}

View File

@@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Spectre.Console;
namespace Generator.Commands;
public sealed class AsciiCastInput : IAnsiConsoleInput
{
private readonly Queue<(ConsoleKeyInfo?, int)> _input;
private readonly Random _random = new Random();
public AsciiCastInput()
{
_input = new Queue<(ConsoleKeyInfo?, int)>();
}
public void PushText(string input, int keypressDelayMs)
{
if (input is null)
{
throw new ArgumentNullException(nameof(input));
}
foreach (var character in input)
{
PushCharacter(character, keypressDelayMs);
}
}
public void PushTextWithEnter(string input, int keypressDelayMs)
{
PushText(input, keypressDelayMs);
PushKey(ConsoleKey.Enter, keypressDelayMs);
}
public void PushCharacter(char input, int keypressDelayMs)
{
var delay = keypressDelayMs + _random.Next((int)(keypressDelayMs * -.2), (int)(keypressDelayMs * .2));
switch (input)
{
case '↑':
PushKey(ConsoleKey.UpArrow, keypressDelayMs);
break;
case '↓':
PushKey(ConsoleKey.DownArrow, keypressDelayMs);
break;
case '↲':
PushKey(ConsoleKey.Enter, keypressDelayMs);
break;
case '¦':
_input.Enqueue((null, delay));
break;
default:
var control = char.IsUpper(input);
_input.Enqueue((new ConsoleKeyInfo(input, (ConsoleKey)input, false, false, control), delay));
break;
}
}
public void PushKey(ConsoleKey input, int keypressDelayMs)
{
var delay = keypressDelayMs + _random.Next((int)(keypressDelayMs * -.2), (int)(keypressDelayMs * .2));
_input.Enqueue((new ConsoleKeyInfo((char)input, input, false, false, false), delay));
}
public bool IsKeyAvailable()
{
return _input.Count > 0;
}
public ConsoleKeyInfo? ReadKey(bool intercept)
{
if (_input.Count == 0)
{
throw new InvalidOperationException("No input available.");
}
var result = _input.Dequeue();
Thread.Sleep(result.Item2);
return result.Item1;
}
public Task<ConsoleKeyInfo?> ReadKeyAsync(bool intercept, CancellationToken cancellationToken)
{
return Task.FromResult(ReadKey(intercept));
}
}

View File

@@ -0,0 +1,94 @@
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.Json;
using Spectre.Console;
namespace Generator.Commands;
public class AsciiCastOut : IAnsiConsoleOutput
{
private sealed class AsciiCastWriter : TextWriter
{
private readonly TextWriter _wrappedTextWriter;
private readonly StringBuilder _builder = new StringBuilder();
private int? _firstTick;
public AsciiCastWriter(TextWriter wrappedTextWriter)
{
_wrappedTextWriter = wrappedTextWriter;
}
public override void Write(string? value)
{
if (value == null)
{
return;
}
Append(value);
_wrappedTextWriter.Write(value);
base.Write(value);
}
public override Encoding Encoding => _wrappedTextWriter.Encoding;
private void Append(string value)
{
var tick = 0m;
if (_firstTick.HasValue)
{
tick = Environment.TickCount - _firstTick.Value;
}
else
{
_firstTick = Environment.TickCount;
}
tick /= 1000m;
_builder.Append('[')
.AppendFormat(CultureInfo.InvariantCulture, "{0}", tick)
.Append(", \"o\", \"").Append(JsonEncodedText.Encode(value)).AppendLine("\"]");
}
public string GetJsonAndClearBuffer()
{
var json = _builder.ToString();
// reset the buffer and also reset the first tick count
_builder.Clear();
_firstTick = null;
return json;
}
}
private readonly IAnsiConsoleOutput _wrappedAnsiConsole;
private readonly AsciiCastWriter _asciiCastWriter;
public AsciiCastOut(IAnsiConsoleOutput wrappedAnsiConsole)
{
_wrappedAnsiConsole = wrappedAnsiConsole ?? throw new ArgumentNullException(nameof(wrappedAnsiConsole));
_asciiCastWriter = new AsciiCastWriter(_wrappedAnsiConsole.Writer);
}
public TextWriter Writer => _asciiCastWriter;
public bool IsTerminal => _wrappedAnsiConsole.IsTerminal;
public int Width => _wrappedAnsiConsole.Width;
public int Height => _wrappedAnsiConsole.Height;
public void SetEncoding(Encoding encoding)
{
_wrappedAnsiConsole.SetEncoding(encoding);
}
public string GetCastJson(string title, int? width = null, int? height = null)
{
var header = $"{{\"version\": 2, \"width\": {width ?? _wrappedAnsiConsole.Width}, \"height\": {height ?? _wrappedAnsiConsole.Height}, \"title\": \"{JsonEncodedText.Encode(title)}\", \"env\": {{\"TERM\": \"Spectre.Console\"}}}}";
return $"{header}{Environment.NewLine}{_asciiCastWriter.GetJsonAndClearBuffer()}{Environment.NewLine}";
}
}

View File

@@ -1,4 +1,4 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Generator.Commands.Samples; using Generator.Commands.Samples;
using Spectre.Console; using Spectre.Console;
using Spectre.Console.Extensions; using Spectre.Console.Extensions;

View File

@@ -0,0 +1,19 @@
using Spectre.Console;
namespace Generator.Commands.Samples;
internal class BarChartSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 5);
public override void Run(IAnsiConsole console)
{
console.Write(new BarChart()
.Width(60)
.Label("[green bold underline]Number of fruits[/]")
.CenterLabel()
.AddItem("Apple", 12, Color.Yellow)
.AddItem("Orange", 54, Color.Green)
.AddItem("Banana", 33, Color.Red));
}
}

View File

@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Text;
using Spectre.Console;
namespace Generator.Commands.Samples;
public abstract class BaseSample
{
public abstract void Run(IAnsiConsole console);
public virtual string Name() => PascalToKebab(GetType().Name.Replace("Sample", ""));
public virtual (int Cols, int Rows) ConsoleSize => (82, 24);
public virtual IEnumerable<(string Name, Action<Capabilities> CapabilitiesAction)> GetCapabilities()
{
return new (string Name, Action<Capabilities> CapabilitiesAction)[]
{
("plain", capabilities =>
{
capabilities.Unicode = false;
capabilities.Ansi = true;
capabilities.Interactive = true;
capabilities.Legacy = false;
capabilities.Links = false;
capabilities.ColorSystem = ColorSystem.Legacy;
}),
("rich", capabilities =>
{
capabilities.Unicode = true;
capabilities.Ansi = true;
capabilities.Interactive = true;
capabilities.Legacy = false;
capabilities.Links = false;
capabilities.ColorSystem = ColorSystem.TrueColor;
}),
};
}
private string PascalToKebab(ReadOnlySpan<char> input)
{
var sb = new StringBuilder();
var previousUpper = true;
foreach (var chr in input)
{
if (char.IsUpper(chr) && previousUpper == false)
{
sb.Append('-');
previousUpper = true;
}
else
{
previousUpper = false;
}
sb.Append(char.ToLower(chr));
}
return sb.ToString();
}
}

View File

@@ -0,0 +1,20 @@
using Spectre.Console;
namespace Generator.Commands.Samples;
internal class BreakdownChartSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 5);
public override void Run(IAnsiConsole console)
{
console.Write(new BreakdownChart()
.Width(60)
.AddItem("SCSS", 80, Color.Red)
.AddItem("HTML", 28.3, Color.Blue)
.AddItem("C#", 22.6, Color.Green)
.AddItem("JavaScript", 6, Color.Yellow)
.AddItem("Ruby", 6, Color.LightGreen)
.AddItem("Shell", 0.1, Color.Aqua));
}
}

View File

@@ -0,0 +1,39 @@
using Spectre.Console;
namespace Generator.Commands.Samples;
internal abstract class BaseCalendarSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 12);
}
internal class CalendarSample : BaseCalendarSample
{
public override void Run(IAnsiConsole console) => console.Write(new Calendar(2020, 10));
}
internal class CalendarCultureSample : BaseCalendarSample
{
public override void Run(IAnsiConsole console) => console.Write(new Calendar(2020, 10).Culture("sv-SE"));
}
internal class CalendarHeader : BaseCalendarSample
{
public override void Run(IAnsiConsole console)
{
var calendar = new Calendar(2020, 10);
calendar.HeaderStyle(Style.Parse("blue bold"));
console.Write(calendar);
}
}
internal class CalendarHighlightSample : BaseCalendarSample
{
public override void Run(IAnsiConsole console)
{
var calendar = new Calendar(2020, 10).HighlightStyle(Style.Parse("yellow bold"));
calendar.AddCalendarEvent(2020, 10, 11);
console.Write(calendar);
}
}

View File

@@ -0,0 +1,26 @@
using SixLabors.ImageSharp.Processing;
using Spectre.Console;
namespace Generator.Commands.Samples;
internal class CanvasImageSample : BaseSample
{
public override void Run(IAnsiConsole console)
{
var image = new CanvasImage("./Data/cake.png");
image.MaxWidth(16);
console.Write(image);
}
}
internal class CanvasImageManipulationSample : BaseSample
{
public override void Run(IAnsiConsole console)
{
var image = new CanvasImage("./Data/cake.png");
image.MaxWidth(24);
image.BilinearResampler();
image.Mutate(ctx => ctx.Grayscale().Rotate(-45).EntropyCrop());
console.Write(image);
}
}

View File

@@ -0,0 +1,28 @@
using Spectre.Console;
namespace Generator.Commands.Samples;
internal class CanvasSample : BaseSample
{
public override void Run(IAnsiConsole console)
{
var canvas = new Canvas(16, 16);
// Draw some shapes
for (var i = 0; i < canvas.Width; i++)
{
// Cross
canvas.SetPixel(i, i, Color.White);
canvas.SetPixel(canvas.Width - i - 1, i, Color.White);
// Border
canvas.SetPixel(i, 0, Color.Red);
canvas.SetPixel(0, i, Color.Green);
canvas.SetPixel(i, canvas.Height - 1, Color.Blue);
canvas.SetPixel(canvas.Width - 1, i, Color.Yellow);
}
// Render the canvas
console.Write(canvas);
}
}

View File

@@ -43,11 +43,11 @@ public class ColumnsSample : BaseSample
private sealed class Fruit private sealed class Fruit
{ {
public string Name { get; init; } public required string Name { get; init; }
public static List<Fruit> LoadFriuts() public static List<Fruit> LoadFriuts()
{ {
return new [] return new[]
{ {
"Apple", "Apple",
"Apricot", "Apricot",
@@ -87,7 +87,7 @@ public class ColumnsSample : BaseSample
"Ximenia", "Ximenia",
"Yuzu", "Yuzu",
} }
.Select(x => new Fruit{Name = x}) .Select(x => new Fruit { Name = x })
.ToList(); .ToList();
} }
} }

View File

@@ -0,0 +1,80 @@
using System;
using System.Security.Authentication;
using Generator.Commands.Samples;
using Spectre.Console;
// Keep the namespace here short because it'll be used in the display of the exceptions,
// and we want to keep that below 100 characters wide.
namespace Samples;
public static class Exceptions
{
internal abstract class BaseExceptionSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (100, 12);
protected readonly Exception Exception = null!;
protected BaseExceptionSample()
{
try
{
DoMagic(42, null!);
}
catch (Exception ex)
{
Exception = ex;
}
}
}
internal class DefaultExceptionSample : BaseExceptionSample
{
public override void Run(IAnsiConsole console) => console.WriteException(Exception, ExceptionFormats.ShortenPaths);
}
internal class ShortenedExceptionSample : BaseExceptionSample
{
public override void Run(IAnsiConsole console) => console.WriteException(Exception, ExceptionFormats.ShortenEverything | ExceptionFormats.ShowLinks);
}
internal class CustomColorsExceptionSample : BaseExceptionSample
{
public override void Run(IAnsiConsole console)
{
console.WriteException(Exception, new ExceptionSettings
{
Format = ExceptionFormats.ShortenEverything | ExceptionFormats.ShowLinks,
Style = new ExceptionStyle
{
Exception = new Style().Foreground(Color.Grey),
Message = new Style().Foreground(Color.White),
NonEmphasized = new Style().Foreground(Color.Cornsilk1),
Parenthesis = new Style().Foreground(Color.Cornsilk1),
Method = new Style().Foreground(Color.Red),
ParameterName = new Style().Foreground(Color.Cornsilk1),
ParameterType = new Style().Foreground(Color.Red),
Path = new Style().Foreground(Color.Red),
LineNumber = new Style().Foreground(Color.Cornsilk1),
}
});
}
}
private static void DoMagic(int foo, string[,] bar)
{
try
{
CheckCredentials(foo, bar);
}
catch (Exception ex)
{
throw new InvalidOperationException("Whaaat?", ex);
}
}
private static void CheckCredentials(int qux, string[,] corgi)
{
throw new InvalidCredentialException("The credentials are invalid.");
}
}

View File

@@ -0,0 +1,15 @@
using Spectre.Console;
namespace Generator.Commands.Samples;
public class FigletSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (100, 24);
public override void Run(IAnsiConsole console)
{
console.Write(new FigletText("Left aligned").LeftJustified().Color(Color.Red));
console.Write(new FigletText("Centered").Centered().Color(Color.Green));
console.Write(new FigletText("Right aligned").RightJustified().Color(Color.Blue));
}
}

View File

@@ -0,0 +1,96 @@
using DocExampleGenerator;
using Spectre.Console;
namespace Generator.Commands.Samples;
internal class InputSample : BaseSample
{
public override void Run(IAnsiConsole console)
{
var age = 0;
var name = string.Empty;
var sport = string.Empty;
var password = string.Empty;
var color = string.Empty;
console.DisplayThenType(c => name = AskName(c), "Peter F↲");
console.DisplayThenType(c => sport = AskSport(c), "football↲¦¦¦¦Hockey↲");
console.DisplayThenType(c => age = AskAge(c), "Forty↲¦¦¦¦40↲");
console.DisplayThenType(c => password = AskPassword(c), "hunter2↲");
console.DisplayThenType(c => color = AskColor(c), "↲");
AnsiConsole.Write(new Rule("[yellow]Results[/]").RuleStyle("grey").LeftJustified());
AnsiConsole.Write(new Table().AddColumns("[grey]Question[/]", "[grey]Answer[/]")
.RoundedBorder()
.BorderColor(Color.Grey)
.AddRow("[grey]Name[/]", name)
.AddRow("[grey]Favorite sport[/]", sport)
.AddRow("[grey]Age[/]", age.ToString())
.AddRow("[grey]Password[/]", password)
.AddRow("[grey]Favorite color[/]", string.IsNullOrEmpty(color) ? "Unknown" : color));
}
private static string AskName(IAnsiConsole console)
{
console.WriteLine();
console.Write(new Rule("[yellow]Strings[/]").RuleStyle("grey").LeftJustified());
var name = console.Ask<string>("What's your [green]name[/]?");
return name;
}
private static string AskSport(IAnsiConsole console)
{
console.WriteLine();
console.Write(new Rule("[yellow]Choices[/]").RuleStyle("grey").LeftJustified());
return console.Prompt(
new TextPrompt<string>("What's your [green]favorite sport[/]?")
.InvalidChoiceMessage("[red]That's not a sport![/]")
.DefaultValue("Sport?")
.AddChoice("Soccer")
.AddChoice("Hockey")
.AddChoice("Basketball"));
}
private static int AskAge(IAnsiConsole console)
{
console.WriteLine();
console.Write(new Rule("[yellow]Integers[/]").RuleStyle("grey").LeftJustified());
return console.Prompt(
new TextPrompt<int>("How [green]old[/] are you?")
.PromptStyle("green")
.ValidationErrorMessage("[red]That's not a valid age[/]")
.Validate(age =>
{
return age switch
{
<= 0 => ValidationResult.Error("[red]You must at least be 1 years old[/]"),
>= 123 => ValidationResult.Error("[red]You must be younger than the oldest person alive[/]"),
_ => ValidationResult.Success(),
};
}));
}
private static string AskPassword(IAnsiConsole console)
{
console.WriteLine();
console.Write(new Rule("[yellow]Secrets[/]").RuleStyle("grey").LeftJustified());
return console.Prompt(
new TextPrompt<string>("Enter [green]password[/]?")
.PromptStyle("red")
.Secret());
}
private static string AskColor(IAnsiConsole console)
{
console.WriteLine();
console.Write(new Rule("[yellow]Optional[/]").RuleStyle("grey").LeftJustified());
return console.Prompt(
new TextPrompt<string>("[grey][[Optional]][/] What is your [green]favorite color[/]?")
.AllowEmpty());
}
}

View File

@@ -0,0 +1,38 @@
using Spectre.Console;
using Spectre.Console.Json;
namespace Generator.Commands.Samples;
public class JsonSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (60, 20);
public override void Run(IAnsiConsole console)
{
var json = new JsonText(
"""
{
"hello": 32,
"world": {
"foo": 21,
"bar": 255,
"baz": [
0.32, 0.33e-32,
0.42e32, 0.55e+32,
{
"hello": "world",
"lol": null
}
]
}
}
""");
AnsiConsole.Write(
new Panel(json)
.Header("Some JSON in a panel")
.Collapse()
.RoundedBorder()
.BorderColor(Color.Yellow));
}
}

View File

@@ -0,0 +1,28 @@
using Spectre.Console;
namespace Generator.Commands.Samples;
public class LayoutSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (80, 24);
public override void Run(IAnsiConsole console)
{
var layout = new Layout("Root")
.SplitColumns(
new Layout("Left"),
new Layout("Right")
.SplitRows(
new Layout("Top"),
new Layout("Bottom")));
layout["Left"].Update(
new Panel(
Align.Center(
new Markup("Hello [blue]World![/]"),
VerticalAlignment.Middle))
.Expand());
AnsiConsole.Write(layout);
}
}

View File

@@ -0,0 +1,83 @@
using System;
using System.Threading;
using Spectre.Console;
namespace Generator.Commands.Samples;
internal class LiveSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (100, 20);
public override void Run(IAnsiConsole console)
{
var table = new Table();
// Animate
console.Live(table)
.AutoClear(false)
.Overflow(VerticalOverflow.Ellipsis)
.Cropping(VerticalOverflowCropping.Top)
.Start(ctx =>
{
void Update(int delay, Action action)
{
action();
ctx.Refresh();
Thread.Sleep(delay);
}
// Columns
Update(230, () => table.AddColumn("Release date"));
Update(230, () => table.AddColumn("Title"));
Update(230, () => table.AddColumn("Budget"));
Update(230, () => table.AddColumn("Opening Weekend"));
Update(230, () => table.AddColumn("Box office"));
// Rows
Update(70, () => table.AddRow("May 25, 1977", "[yellow]Star Wars[/] [grey]Ep.[/] [u]IV[/]", "$11,000,000", "$1,554,475", "$775,398,007"));
Update(70, () => table.AddRow("May 21, 1980", "[yellow]Star Wars[/] [grey]Ep.[/] [u]V[/]", "$18,000,000", "$4,910,483", "$547,969,004"));
Update(70, () => table.AddRow("May 25, 1983", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VI[/]", "$32,500,000", "$23,019,618", "$475,106,177"));
Update(70, () => table.AddRow("May 19, 1999", "[yellow]Star Wars[/] [grey]Ep.[/] [u]I[/]", "$115,000,000", "$64,810,870", "$1,027,044,677"));
Update(70, () => table.AddRow("May 16, 2002", "[yellow]Star Wars[/] [grey]Ep.[/] [u]II[/]", "$115,000,000", "$80,027,814", "$649,436,358"));
Update(70, () => table.AddRow("May 19, 2005", "[yellow]Star Wars[/] [grey]Ep.[/] [u]III[/]", "$113,000,000", "$108,435,841", "$850,035,635"));
Update(70, () => table.AddRow("Dec 18, 2015", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VII[/]", "$245,000,000", "$247,966,675", "$2,068,223,624"));
Update(70, () => table.AddRow("Dec 15, 2017", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VIII[/]", "$317,000,000", "$220,009,584", "$1,333,539,889"));
Update(70, () => table.AddRow("Dec 20, 2019", "[yellow]Star Wars[/] [grey]Ep.[/] [u]IX[/]", "$245,000,000", "$177,383,864", "$1,074,114,248"));
// Column footer
Update(230, () => table.Columns[2].Footer("$1,633,000,000"));
Update(230, () => table.Columns[3].Footer("$928,119,224"));
Update(400, () => table.Columns[4].Footer("$10,318,030,576"));
// Column alignment
Update(230, () => table.Columns[2].RightAligned());
Update(230, () => table.Columns[3].RightAligned());
Update(400, () => table.Columns[4].RightAligned());
// Column titles
Update(70, () => table.Columns[0].Header("[bold]Release date[/]"));
Update(70, () => table.Columns[1].Header("[bold]Title[/]"));
Update(70, () => table.Columns[2].Header("[red bold]Budget[/]"));
Update(70, () => table.Columns[3].Header("[green bold]Opening Weekend[/]"));
Update(400, () => table.Columns[4].Header("[blue bold]Box office[/]"));
// Footers
Update(70, () => table.Columns[2].Footer("[red bold]$1,633,000,000[/]"));
Update(70, () => table.Columns[3].Footer("[green bold]$928,119,224[/]"));
Update(400, () => table.Columns[4].Footer("[blue bold]$10,318,030,576[/]"));
// Title
Update(500, () => table.Title("Star Wars Movies"));
Update(400, () => table.Title("[[ [yellow]Star Wars Movies[/] ]]"));
// Borders
Update(230, () => table.BorderColor(Color.Yellow));
Update(230, () => table.MinimalBorder());
Update(230, () => table.SimpleBorder());
Update(230, () => table.SimpleHeavyBorder());
// Caption
Update(400, () => table.Caption("[[ [blue]THE END[/] ]]"));
});
}
}

View File

@@ -0,0 +1,39 @@
using DocExampleGenerator;
using Spectre.Console;
namespace Generator.Commands.Samples;
internal class MultiSelectionSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 14);
public override void Run(IAnsiConsole console)
{
console.DisplayThenType(AskFruit, "↓↓ ¦¦↑↑ ¦¦ ¦¦↓ ↓↓↓↓↓ ↓↓↓↓ ¦¦↲");
}
private static void AskFruit(IAnsiConsole console)
{
var favorites = console.Prompt(
new MultiSelectionPrompt<string>()
.PageSize(10)
.Title("What are your [green]favorite fruits[/]?")
.MoreChoicesText("[grey](Move up and down to reveal more fruits)[/]")
.InstructionsText("[grey](Press [blue]<space>[/] to toggle a fruit, [green]<enter>[/] to accept)[/]")
.AddChoiceGroup("Berries", new[]
{
"Blackcurrant", "Blueberry", "Cloudberry",
"Elderberry", "Honeyberry", "Mulberry"
})
.AddChoices(new[]
{
"Apple", "Apricot", "Avocado", "Banana",
"Cherry", "Cocunut", "Date", "Dragonfruit", "Durian",
"Egg plant", "Fig", "Grape", "Guava",
"Jackfruit", "Jambul", "Kiwano", "Kiwifruit", "Lime", "Lylo",
"Lychee", "Melon", "Nectarine", "Orange", "Olive"
}));
console.MarkupLine("Your selected: [yellow]{0}[/]", string.Join(',', favorites));
}
}

View File

@@ -0,0 +1,15 @@
using Spectre.Console;
namespace Generator.Commands.Samples;
internal class PanelSample : BaseSample
{
public override void Run(IAnsiConsole console)
{
var panel = new Panel("[red]Spaghetti\nLinguini\nFettucine\nTortellini\nCapellini\nLasagna[/]");
panel.Header = new PanelHeader("[underline]Pasta Menu[/]", Justify.Center);
panel.Border = BoxBorder.Double;
panel.Padding = new Padding(2, 2, 2, 2);
console.Write(panel);
}
}

View File

@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Spectre.Console;
namespace Generator.Commands.Samples;
internal class ProgressSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 10);
public override IEnumerable<(string Name, Action<Capabilities> CapabilitiesAction)> GetCapabilities()
{
yield return ("non-interactive", capabilities =>
{
capabilities.Ansi = false;
capabilities.Interactive = false;
capabilities.Legacy = false;
capabilities.Unicode = true;
capabilities.ColorSystem = ColorSystem.TrueColor;
}
);
foreach (var capability in base.GetCapabilities())
{
yield return capability;
}
}
public override void Run(IAnsiConsole console)
{
// Show progress
console.Progress()
.AutoClear(false)
.Columns(new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn(), new RemainingTimeColumn(), new SpinnerColumn())
.Start(ctx =>
{
var random = new Random(122978);
// Create some tasks
var tasks = CreateTasks(ctx, random);
var warpTask = ctx.AddTask("Going to warp", autoStart: false).IsIndeterminate();
// Wait for all tasks (except the indeterminate one) to complete
while (!ctx.IsFinished)
{
// Increment progress
foreach (var (task, increment) in tasks)
{
task.Increment(random.NextDouble() * increment);
}
// Simulate some delay
Thread.Sleep(100);
}
// Now start the "warp" task
warpTask.StartTask();
warpTask.IsIndeterminate(false);
while (!ctx.IsFinished)
{
warpTask.Increment(12 * random.NextDouble());
// Simulate some delay
Thread.Sleep(100);
}
});
}
private static List<(ProgressTask Task, int Delay)> CreateTasks(ProgressContext progress, Random random)
{
var tasks = new List<(ProgressTask, int)>();
var names = new[]
{
"Retriculating algorithms", "Colliding splines", "Solving quarks", "Folding data structures",
"Rerouting capacitators "
};
for (var i = 0; i < 5; i++)
{
tasks.Add((progress.AddTask(names[i]), random.Next(2, 10)));
}
return tasks;
}
}

View File

@@ -0,0 +1,20 @@
using Spectre.Console;
namespace Generator.Commands.Samples;
internal class RuleSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (82, 10);
public override void Run(IAnsiConsole console)
{
console.Write(new Rule());
console.WriteLine();
console.Write(new Rule("[blue]Left aligned[/]").LeftJustified().RuleStyle("red"));
console.WriteLine();
console.Write(new Rule("[green]Centered[/]").Centered().RuleStyle("green"));
console.WriteLine();
console.Write(new Rule("[red]Right aligned[/]").RightJustified().RuleStyle("blue"));
console.WriteLine();
}
}

View File

@@ -0,0 +1,28 @@
using DocExampleGenerator;
using Spectre.Console;
namespace Generator.Commands.Samples;
internal class SelectionSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 14);
public override void Run(IAnsiConsole console)
{
console.DisplayThenType(AskFruit, "↓↓↓¦¦¦¦ ");
}
private static void AskFruit(IAnsiConsole console)
{
// Ask for the user's favorite fruit
var fruit = console.Prompt(
new SelectionPrompt<string>()
.Title("What's your [green]favorite fruit[/]?")
.PageSize(10)
.MoreChoicesText("[grey](Move up and down to reveal more fruits)[/]")
.AddChoices(new[] { "Apple", "Apricot", "Avocado", "Banana", "Blackcurrant", "Blueberry", "Cherry", "Cloudberry", "Cocunut" }));
// Echo the fruit back to the terminal
console.WriteLine($"I agree. {fruit} is tasty!");
}
}

View File

@@ -0,0 +1,71 @@
using System.Threading;
using Spectre.Console;
namespace Generator.Commands.Samples;
internal class StatusSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 10);
private static void WriteLogMessage(string message)
{
AnsiConsole.MarkupLine($"[grey]LOG:[/] {message}[grey]...[/]");
}
public override void Run(IAnsiConsole console)
{
console.Status()
.AutoRefresh(true)
.Spinner(Spinner.Known.Default)
.Start("[yellow]Initializing warp drive[/]", ctx =>
{
// Initialize
Thread.Sleep(3000);
WriteLogMessage("Starting gravimetric field displacement manifold");
Thread.Sleep(1000);
WriteLogMessage("Warming up deuterium chamber");
Thread.Sleep(2000);
WriteLogMessage("Generating antideuterium");
// Warp nacelles
Thread.Sleep(3000);
ctx.Spinner(Spinner.Known.BouncingBar);
ctx.Status("[bold blue]Unfolding warp nacelles[/]");
WriteLogMessage("Unfolding left warp nacelle");
Thread.Sleep(2000);
WriteLogMessage("Left warp nacelle [green]online[/]");
WriteLogMessage("Unfolding right warp nacelle");
Thread.Sleep(1000);
WriteLogMessage("Right warp nacelle [green]online[/]");
// Warp bubble
Thread.Sleep(3000);
ctx.Spinner(Spinner.Known.Star2);
ctx.Status("[bold blue]Generating warp bubble[/]");
Thread.Sleep(3000);
ctx.Spinner(Spinner.Known.Star);
ctx.Status("[bold blue]Stabilizing warp bubble[/]");
// Safety
ctx.Spinner(Spinner.Known.Monkey);
ctx.Status("[bold blue]Performing safety checks[/]");
WriteLogMessage("Enabling interior dampening");
Thread.Sleep(2000);
WriteLogMessage("Interior dampening [green]enabled[/]");
// Warp!
Thread.Sleep(3000);
ctx.Spinner(Spinner.Known.Moon);
WriteLogMessage("Preparing for warp");
Thread.Sleep(1000);
for (var warp = 1; warp < 10; warp++)
{
ctx.Status($"[bold blue]Warp {warp}[/]");
Thread.Sleep(500);
}
});
// Done
AnsiConsole.MarkupLine("[bold green]Crusing at Warp 9.8[/]");
}
}

View File

@@ -0,0 +1,45 @@
using Spectre.Console;
namespace Generator.Commands.Samples;
internal class TableSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (100, 30);
public override void Run(IAnsiConsole console)
{
var simple = new Table()
.Border(TableBorder.Square)
.BorderColor(Color.Red)
.AddColumn(new TableColumn("[u]CDE[/]").Footer("EDC").Centered())
.AddColumn(new TableColumn("[u]FED[/]").Footer("DEF"))
.AddColumn(new TableColumn("[u]IHG[/]").Footer("GHI"))
.AddRow("Hello", "[red]World![/]", "")
.AddRow("[blue]Bonjour[/]", "[white]le[/]", "[red]monde![/]")
.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", "");
var second = new Table()
.Border(TableBorder.Rounded)
.BorderColor(Color.Green)
.AddColumn(new TableColumn("[u]Foo[/]"))
.AddColumn(new TableColumn("[u]Bar[/]"))
.AddColumn(new TableColumn("[u]Baz[/]"))
.AddRow("Hello", "[red]World![/]", "")
.AddRow(simple, new Text("Whaaat"), new Text("Lolz"))
.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", "");
var table = new Table()
.Centered()
.Border(TableBorder.DoubleEdge)
.Title("TABLE [yellow]TITLE[/]")
.Caption("TABLE [yellow]CAPTION[/]")
.AddColumn(new TableColumn(new Panel("[u]ABC[/]").BorderColor(Color.Red)).Footer("[u]FOOTER 1[/]"))
.AddColumn(new TableColumn(new Panel("[u]DEF[/]").BorderColor(Color.Green)).Footer("[u]FOOTER 2[/]"))
.AddColumn(new TableColumn(new Panel("[u]GHI[/]").BorderColor(Color.Blue)).Footer("[u]FOOTER 3[/]"))
.AddRow(new Text("Hello").Centered(), new Markup("[red]World![/]"), Text.Empty)
.AddRow(second, new Text("Whaaat"), new Text("Lol"))
.AddRow(new Markup("[blue]Hej[/]").Centered(), new Markup("[yellow]Världen![/]"), Text.Empty);
console.Write(table);
}
}

View File

@@ -0,0 +1,42 @@
using Spectre.Console;
namespace Generator.Commands.Samples;
internal class TextPathSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (40, 23);
public override void Run(IAnsiConsole console)
{
console.Write(
new Panel(
new Padder(new TextPath("C:/This/Is/A/Super/Long/Path/That/Will/Be/Truncated.txt"), new Padding(0, 1)))
.BorderStyle(new Style(foreground: Color.Grey))
.Header("Windows path"));
console.Write(
new Panel(
new Padder(new TextPath("/This/Is/A/Super/Long/Path/That/Will/Be/Truncated.txt"), new Padding(0, 1)))
.BorderStyle(new Style(foreground: Color.Grey))
.Header("Unix path"));
console.Write(
new Panel(
new Padder(new TextPath("/This/Is/A/Long/Path/That/Will/Be/Truncated.txt")
.RootColor(Color.Green)
.SeparatorColor(Color.Red)
.StemColor(Color.Yellow)
.LeafColor(Color.Blue), new Padding(0, 1)))
.BorderStyle(new Style(foreground: Color.Grey))
.Header("Styling"));
console.Write(
new Panel(
new Padder(new Rows(
new TextPath("/This/Is/A/Long/Path/That/Will/Be/Truncated.txt").LeftJustified(),
new TextPath("/This/Is/A/Long/Path/That/Will/Be/Truncated.txt").Centered(),
new TextPath("/This/Is/A/Long/Path/That/Will/Be/Truncated.txt").RightJustified()), new Padding(0, 1)))
.BorderStyle(new Style(foreground: Color.Grey))
.Header("Alignment"));
}
}

View File

@@ -0,0 +1,38 @@
using Spectre.Console;
namespace Generator.Commands.Samples;
internal class TreeSample : BaseSample
{
public override void Run(IAnsiConsole console)
{
// Create the tree
var tree = new Tree("Root")
.Style(Style.Parse("red"))
.Guide(TreeGuide.Line);
// Add some nodes
var foo = tree.AddNode("[yellow]Nest objects like tables[/]");
var table = foo.AddNode(new Table()
.RoundedBorder()
.AddColumn("First")
.AddColumn("Second")
.AddRow("1", "2")
.AddRow("3", "4")
.AddRow("5", "6"));
table.AddNode("[blue]with[/]");
table.AddNode("[blue]multiple[/]");
table.AddNode("[blue]children too[/]");
var bar = tree.AddNode("Any IRenderable can be nested, such as [yellow]calendars[/]");
bar.AddNode(new Calendar(2020, 12)
.Border(TableBorder.Rounded)
.BorderStyle(new Style(Color.Green3_1))
.AddCalendarEvent(2020, 12, 12)
.HideHeader());
console.Write(tree);
}
}

View File

@@ -0,0 +1,73 @@
using System.IO;
using System.Linq;
using System.Threading;
using Generator.Models;
using Scriban;
using Spectre.Console.Cli;
using Spectre.IO;
namespace Generator.Commands;
public sealed class ColorGeneratorCommand : Command<ColorGeneratorCommand.Settings>
{
private readonly IFileSystem _fileSystem;
public ColorGeneratorCommand()
{
_fileSystem = new FileSystem();
}
public sealed class Settings : GeneratorSettings
{
}
public override int Execute(CommandContext context, Settings settings, CancellationToken cancellationToken)
{
var templates = new FilePath[]
{
"Templates/ColorPalette.Generated.template", "Templates/Color.Generated.template",
"Templates/ColorTable.Generated.template"
};
// Read the color model.
var model = Color
.Parse(File.ReadAllText("Data/colors.json"))
.OrderBy(x => x.Number)
.ToArray();
// Palettes
var legacyPalette = model.Where(x => x.Number is >= 0 and < 8 && !x.IsAlias)
.OrderBy(x => x.Number).ToArray();
var standardPalette = model.Where(x => x.Number is >= 8 and < 16 && !x.IsAlias)
.OrderBy(x => x.Number).ToArray();
var eightBitPalette = model.Where(x => x.Number is >= 16 and < 256 && !x.IsAlias)
.OrderBy(x => x.Number).ToArray();
var output = new DirectoryPath(settings.Output);
if (!_fileSystem.Directory.Exists(settings.Output))
{
_fileSystem.Directory.Create(settings.Output);
}
foreach (var templatePath in templates)
{
// Parse the Scriban template.
var template = Template.Parse(File.ReadAllText(templatePath.FullPath));
// Render the template with the model.
var result = template.Render(new
{
Colors = model,
Legacy = legacyPalette,
Standard = standardPalette,
EightBit = eightBitPalette,
});
// Write output to file
var file = output.CombineWithFilePath(templatePath.GetFilename().ChangeExtension(".cs"));
File.WriteAllText(file.FullPath, result);
}
return 0;
}
}

View File

@@ -0,0 +1,106 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using AngleSharp.Html.Parser;
using Generator.Models;
using Scriban;
using Scriban.Runtime;
using Spectre.Console.Cli;
using Spectre.IO;
using Path = Spectre.IO.Path;
using SpectreEnvironment = Spectre.IO.Environment;
namespace Generator.Commands;
public sealed class EmojiGeneratorCommand : AsyncCommand<EmojiGeneratorCommand.Settings>
{
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" }, // For documentation
};
public sealed class Settings : GeneratorSettings
{
[CommandOption("-i|--input <PATH>")]
public string? Input { get; set; }
}
public EmojiGeneratorCommand()
{
_fileSystem = new FileSystem();
_environment = new SpectreEnvironment();
_parser = new HtmlParser();
}
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings, CancellationToken cancellationToken)
{
var output = new DirectoryPath(settings.Output);
if (!_fileSystem.Directory.Exists(settings.Output))
{
_fileSystem.Directory.Create(settings.Output);
}
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();
// Render all templates
foreach (var (templateFilename, outputFilename) in _templates)
{
var result = await RenderTemplate(new FilePath(templateFilename), emojis);
var outputPath = output.CombineWithFilePath(outputFilename);
await File.WriteAllTextAsync(outputPath.FullPath, result, cancellationToken);
}
return 0;
}
private async Task<Stream> FetchEmojis(Settings settings)
{
var input = string.IsNullOrEmpty(settings.Input)
? _environment.WorkingDirectory
: new DirectoryPath(settings.Input);
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();
await httpStream.CopyToAsync(outStream);
}
return file.OpenRead();
}
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);
}
}

View File

@@ -0,0 +1,9 @@
using Spectre.Console.Cli;
namespace Generator.Commands;
public class GeneratorSettings : CommandSettings
{
[CommandArgument(0, "<OUTPUT>")]
public string Output { get; set; } = null!;
}

View File

@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Generator.Commands.Samples;
using Spectre.Console;
using Spectre.Console.Cli;
namespace Generator.Commands;
internal class SampleCommand : Command<SampleCommand.Settings>
{
public class Settings : CommandSettings
{
public Settings(string outputPath, string sample, bool list)
{
Sample = sample;
OutputPath = outputPath ?? Environment.CurrentDirectory;
List = list;
}
[CommandArgument(0, "[sample]")]
public string Sample { get; }
[CommandOption("-o|--output")]
public string OutputPath { get; }
[CommandOption("-l|--list")]
public bool List { get; }
}
private readonly IAnsiConsole _console;
public SampleCommand(IAnsiConsole console)
{
_console = new AsciiCastConsole(console);
}
public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings, CancellationToken cancellationToken)
{
var samples = typeof(BaseSample).Assembly
.GetTypes()
.Where(i => i.IsClass && i.IsAbstract == false && i.IsSubclassOf(typeof(BaseSample)))
.Select(Activator.CreateInstance)
.Cast<BaseSample>();
var selectedSample = settings.Sample;
if (settings.List)
{
selectedSample = AnsiConsole.Prompt(
new SelectionPrompt<string>()
.Title("Select an example to record")
.PageSize(25)
.AddChoices(samples.Select(x => x.Name())));
}
if (!string.IsNullOrWhiteSpace(selectedSample))
{
var desiredSample = samples.FirstOrDefault(i => i.Name().Equals(selectedSample, StringComparison.OrdinalIgnoreCase));
if (desiredSample == null)
{
_console.MarkupLine($"[red]Error:[/] could not find sample [blue]{selectedSample}[/]");
return -1;
}
samples = new List<BaseSample> { desiredSample };
}
// from here on out everything we write will be recorded.
var recorder = _console.WrapWithAsciiCastRecorder();
foreach (var sample in samples)
{
var sampleName = sample.Name();
var originalWidth = _console.Profile.Width;
var originalHeight = _console.Profile.Height;
_console.Profile.Encoding = Encoding.UTF8;
_console.Profile.Width = sample.ConsoleSize.Cols;
_console.Profile.Height = sample.ConsoleSize.Rows;
foreach (var (capabilityName, action) in sample.GetCapabilities())
{
action(_console.Profile.Capabilities);
sample.Run(_console);
var json = recorder.GetCastJson($"{sampleName} ({capabilityName})", sample.ConsoleSize.Cols + 2, sample.ConsoleSize.Rows);
File.WriteAllText(Path.Combine(settings.OutputPath, $"{sampleName}-{capabilityName}.cast"), json);
}
_console.Profile.Width = originalWidth;
_console.Profile.Height = originalHeight;
}
return 0;
}
}

View File

@@ -0,0 +1,56 @@
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Generator.Models;
using Scriban;
using Scriban.Runtime;
using Spectre.Console.Cli;
using Spectre.IO;
namespace Generator.Commands;
public sealed class SpinnerGeneratorCommand : Command<GeneratorSettings>
{
private readonly IFileSystem _fileSystem;
public SpinnerGeneratorCommand()
{
_fileSystem = new FileSystem();
}
public override int Execute(CommandContext context, GeneratorSettings settings, CancellationToken cancellationToken)
{
// Read the spinner model.
var spinners = new List<Spinner>();
spinners.AddRange(Spinner.Parse(File.ReadAllText("Data/spinners_default.json")));
spinners.AddRange(Spinner.Parse(File.ReadAllText("Data/spinners_sindresorhus.json")));
var output = new DirectoryPath(settings.Output);
if (!_fileSystem.Directory.Exists(settings.Output))
{
_fileSystem.Directory.Create(settings.Output);
}
// Parse the Scriban template.
var templatePath = new FilePath("Templates/Spinner.Generated.template");
var template = Template.Parse(File.ReadAllText(templatePath.FullPath));
var scriptObject = new ScriptObject();
scriptObject.Import(new
{
Spinners = spinners
});
var templateContext = new TemplateContext(scriptObject);
templateContext.LoopLimit = 0;
// Render the template with the model.
var result = template.Render(templateContext);
// Write output to file
var file = output.CombineWithFilePath(templatePath.GetFilename().ChangeExtension(".cs"));
File.WriteAllText(file.FullPath, result);
return 0;
}
}

BIN
src/Generator/Data/cake.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

@@ -0,0 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<NoWarn>$(NoWarn);CS8002</NoWarn>
</PropertyGroup>
<ItemGroup>
<Compile Remove="out\**" />
<EmbeddedResource Remove="out\**" />
<None Remove="out\**" />
<None Update="Data\cake.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Update="Data\*.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Templates\*.template">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="AngleSharp" />
<PackageReference Include="Humanizer.Core" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="Scriban" />
<PackageReference Include="Spectre.Console.Cli" />
<PackageReference Include="Spectre.IO" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Spectre.Console\Spectre.Console.csproj" />
<ProjectReference Include="..\Extensions\Spectre.Console.ImageSharp\Spectre.Console.ImageSharp.csproj" />
<ProjectReference Include="..\Extensions\Spectre.Console.Json\Spectre.Console.Json.csproj" />
</ItemGroup>
</Project>

Some files were not shown because too many files have changed in this diff Show More