This commit is contained in:
Patrik Svensson 2020-09-16 14:32:37 +02:00
Коммит ea97f296c0
46 изменённых файлов: 7357 добавлений и 0 удалений

175
.editorconfig Normal file
Просмотреть файл

@ -0,0 +1,175 @@
root = true
[*]
charset = utf-8
end_of_line = CRLF
indent_style = space
indent_size = 4
insert_final_newline = false
trim_trailing_whitespace = true
[*.sln]
indent_style = tab
[*.{csproj,vbproj,vcxproj,vcxproj.filters}]
indent_size = 2
[*.{xml,config,props,targets,nuspec,ruleset}]
indent_size = 2
[*.{yml,yaml}]
indent_size = 2
[*.json]
indent_size = 2
[*.md]
trim_trailing_whitespace = false
[*.sh]
end_of_line = lf
[*.cs]
# 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

1
.github/funding.yml поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
github: patriksvensson

73
.github/workflows/ci.yaml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,73 @@
name: Continuous Integration
on: pull_request
env:
# Set the DOTNET_SKIP_FIRST_TIME_EXPERIENCE environment variable to stop wasting time caching packages
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
# Disable sending usage data to Microsoft
DOTNET_CLI_TELEMETRY_OPTOUT: true
jobs:
###################################################
# DOCS
###################################################
docs:
name: Documentation
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
- name: Setup dotnet
uses: actions/setup-dotnet@v1
with:
dotnet-version: '3.1.301' # SDK Version to use.
- name: Build
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
cd docs
dotnet run --configuration Release
###################################################
# BUILD
###################################################
build:
name: Build
if: "!contains(github.event.head_commit.message, 'skip-ci')"
strategy:
matrix:
kind: ['linux', 'windows', 'macOS']
include:
- kind: linux
os: ubuntu-latest
- kind: windows
os: windows-latest
- kind: macOS
os: macos-latest
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: 'Get Git tags'
run: git fetch --tags
shell: bash
- name: Setup dotnet
uses: actions/setup-dotnet@v1
with:
dotnet-version: 3.1.301
- name: Build
shell: bash
run: |
dotnet tool restore
dotnet cake

113
.github/workflows/publish.yaml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,113 @@
name: Publish
on:
push:
tags:
- '*'
branches:
- main
paths:
- 'src/**'
env:
# Set the DOTNET_SKIP_FIRST_TIME_EXPERIENCE environment variable to stop wasting time caching packages
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
# Disable sending usage data to Microsoft
DOTNET_CLI_TELEMETRY_OPTOUT: true
jobs:
###################################################
# DOCS
###################################################
docs:
name: Documentation
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
- name: Setup dotnet
uses: actions/setup-dotnet@v1
with:
dotnet-version: '3.1.301' # SDK Version to use.
- name: Build
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
cd docs
dotnet run --configuration Release
###################################################
# BUILD
###################################################
build:
name: Build
if: "!contains(github.event.head_commit.message, 'skip-ci')"
strategy:
matrix:
kind: ['linux', 'windows', 'macOS']
include:
- kind: linux
os: ubuntu-latest
- kind: windows
os: windows-latest
- kind: macOS
os: macos-latest
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: 'Get Git tags'
run: git fetch --tags
shell: bash
- name: Setup dotnet
uses: actions/setup-dotnet@v1
with:
dotnet-version: 3.1.301
- name: Build
shell: bash
run: |
dotnet tool restore
dotnet cake
###################################################
# PUBLISH
###################################################
publish:
name: Publish
needs: [build]
if: "!contains(github.event.head_commit.message, 'skip-ci')"
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: 'Get Git tags'
run: git fetch --tags
shell: bash
- name: Setup dotnet
uses: actions/setup-dotnet@v1
with:
dotnet-version: 3.1.301
- name: Publish
shell: bash
run: |
dotnet tool restore
dotnet cake --target="publish" \
--nuget-key="${{secrets.NUGET_API_KEY}}" \
--github-key="${{secrets.GITHUB_TOKEN}}"

88
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,88 @@
# Misc folders
[Bb]in/
[Oo]bj/
[Tt]emp/
[Pp]ackages/
/.artifacts/
/[Tt]ools/
# Cakeup
cakeup-x86_64-latest.exe
# .NET Core CLI
/.dotnet/
/.packages/
dotnet-install.sh*
*.lock.json
# Visual Studio
.vs/
.vscode/
launchSettings.json
*.sln.ide/
# Rider
src/.idea/**/workspace.xml
src/.idea/**/tasks.xml
src/.idea/dictionaries
src/.idea/**/dataSources/
src/.idea/**/dataSources.ids
src/.idea/**/dataSources.xml
src/.idea/**/dataSources.local.xml
src/.idea/**/sqlDataSources.xml
src/.idea/**/dynamic.xml
src/.idea/**/uiDesigner.xml
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
*.userprefs
*.GhostDoc.xml
*StyleCop.Cache
# Build results
[Dd]ebug/
[Rr]elease/
x64/
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.log
*.vspscc
*.vssscc
.builds
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# ReSharper is a .NET coding add-in
_ReSharper*
# NCrunch
*.ncrunch*
.*crunch*.local.xml
_NCrunch_*
# NuGet Packages Directory
packages
# Windows
Thumbs.db

21
LICENSE.md Normal file
Просмотреть файл

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Spectre Systems AB
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

51
README.md Normal file
Просмотреть файл

@ -0,0 +1,51 @@
# Wcwidth
This is a port of the [Python port](https://github.com/jquast/wcwidth)
written by Jeff Quast, which originally was written by Markus Kuhn.
* Python port: https://github.com/jquast/wcwidth (MIT)
* Original: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
## Usage
```csharp
using Wcwidth;
// Get the width
var width = Wcwidth.GetWidth('コ');
// It should be 2 cells wide
Debug.Assert(width == 2);
```
## Building
We're using [Cake](https://github.com/cake-build/cake) as a
[dotnet tool](https://docs.microsoft.com/en-us/dotnet/core/tools/global-tools)
for building. So make sure that you've restored Cake by running
the following in the repository root:
```
> dotnet tool restore
```
After that, running the build is as easy as writing:
```
> dotnet cake
```
## Acknowledgement
This code is a port of https://github.com/jquast/wcwidth,
licensed under [MIT](https://github.com/jquast/wcwidth/blob/dc720a9a4c3c6ae6c5b16a552cfe5186dde22551/LICENSE).
This code was originally derived directly from C code of the same name,
whose latest version is available at http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c:
```
* Markus Kuhn -- 2007-05-26 (Unicode 5.0)
* Permission to use, copy, modify, and distribute this software
* for any purpose and without fee is hereby granted. The author
* disclaims all warranties with regard to this software.
```

112
build.cake Normal file
Просмотреть файл

@ -0,0 +1,112 @@
var target = Argument("target", "Default");
var configuration = Argument("configuration", "Release");
////////////////////////////////////////////////////////////////
// Tasks
Task("Build")
.Does(context =>
{
DotNetCoreBuild("./src/Wcwidth.sln", new DotNetCoreBuildSettings {
Configuration = configuration,
NoIncremental = context.HasArgument("rebuild"),
MSBuildSettings = new DotNetCoreMSBuildSettings()
.TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error)
});
});
Task("Test")
.IsDependentOn("Build")
.Does(context =>
{
DotNetCoreTest("./src/Wcwidth.sln", new DotNetCoreTestSettings {
Configuration = configuration,
NoRestore = true,
NoBuild = true,
});
});
Task("Package")
.IsDependentOn("Test")
.Does(context =>
{
context.CleanDirectory("./.artifacts");
context.DotNetCorePack($"./src/Wcwidth.sln", new DotNetCorePackSettings {
Configuration = configuration,
NoRestore = true,
NoBuild = true,
OutputDirectory = "./.artifacts",
MSBuildSettings = new DotNetCoreMSBuildSettings()
.TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error)
});
});
Task("Publish-GitHub")
.WithCriteria(ctx => BuildSystem.IsRunningOnGitHubActions, "Not running on GitHub Actions")
.IsDependentOn("Package")
.Does(context =>
{
var apiKey = Argument<string>("github-key", null);
if(string.IsNullOrWhiteSpace(apiKey)) {
throw new CakeException("No GitHub API key was provided.");
}
// Publish to GitHub Packages
var exitCode = 0;
foreach(var file in context.GetFiles("./.artifacts/*.nupkg"))
{
context.Information("Publishing {0}...", file.GetFilename().FullPath);
exitCode += StartProcess("dotnet",
new ProcessSettings {
Arguments = new ProcessArgumentBuilder()
.Append("gpr")
.Append("push")
.AppendQuoted(file.FullPath)
.AppendSwitchSecret("-k", " ", apiKey)
}
);
}
if(exitCode != 0)
{
throw new CakeException("Could not push GitHub packages.");
}
});
Task("Publish-NuGet")
.WithCriteria(ctx => BuildSystem.IsRunningOnGitHubActions, "Not running on GitHub Actions")
.IsDependentOn("Package")
.Does(context =>
{
var apiKey = Argument<string>("nuget-key", null);
if(string.IsNullOrWhiteSpace(apiKey)) {
throw new CakeException("No NuGet API key was provided.");
}
// Publish to GitHub Packages
foreach(var file in context.GetFiles("./.artifacts/*.nupkg"))
{
context.Information("Publishing {0}...", file.GetFilename().FullPath);
DotNetCoreNuGetPush(file.FullPath, new DotNetCoreNuGetPushSettings
{
Source = "https://api.nuget.org/v3/index.json",
ApiKey = apiKey,
});
}
});
////////////////////////////////////////////////////////////////
// Targets
Task("Publish")
.IsDependentOn("Publish-GitHub")
.IsDependentOn("Publish-NuGet");
Task("Default")
.IsDependentOn("Package");
////////////////////////////////////////////////////////////////
// Execution
RunTarget(target)

24
dotnet-tools.json Normal file
Просмотреть файл

@ -0,0 +1,24 @@
{
"version": 1,
"isRoot": true,
"tools": {
"cake.tool": {
"version": "0.38.4",
"commands": [
"dotnet-cake"
]
},
"gpr": {
"version": "0.1.224",
"commands": [
"gpr"
]
},
"dotnet-example": {
"version": "0.8.0",
"commands": [
"dotnet-example"
]
}
}
}

7
global.json Normal file
Просмотреть файл

@ -0,0 +1,7 @@
{
"projects": [ "src" ],
"sdk": {
"version": "3.1.301",
"rollForward": "latestMajor"
}
}

2
scripts/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,2 @@
Output
Data

28
scripts/Generate.ps1 Normal file
Просмотреть файл

@ -0,0 +1,28 @@
##########################################################
# Script that generates known colors and lookup tables.
##########################################################
$Output = Join-Path $PSScriptRoot "Output"
$Data = Join-Path $PSScriptRoot "Data"
$Source = Join-Path $PSScriptRoot "/../src/Wcwidth/Tables"
if(!(Test-Path $Output -PathType Container)) {
New-Item -ItemType Directory -Path $Output | Out-Null
}
if(!(Test-Path $Data -PathType Container)) {
New-Item -ItemType Directory -Path $Data | Out-Null
}
# Generate the files
Push-Location Generator
&dotnet run -- "$Output" --data "$Data"
if(!$?) {
Pop-Location
Throw "An error occured when generating code."
}
Pop-Location
# Copy the files to the correct location
Copy-Item (Join-Path "$Output" "ZeroTable.Generated.cs") -Destination "$Source/ZeroTable.Generated.cs"
Copy-Item (Join-Path "$Output" "WideTable.Generated.cs") -Destination "$Source/WideTable.Generated.cs"

Просмотреть файл

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Spectre.Cli;
using Spectre.Console;
using Spectre.IO;
using Wcwidth;
namespace Generator.Commands
{
public sealed class TableGeneratorCommand : AsyncCommand<TableGeneratorSettings>
{
private readonly IFileSystem _fileSystem;
private readonly List<TableGenerator> _generators;
public TableGeneratorCommand()
{
_fileSystem = new FileSystem();
_generators = new List<TableGenerator>
{
new ZeroTableGenerator(),
new WideTableGenerator(),
};
}
public override async Task<int> ExecuteAsync(CommandContext context, TableGeneratorSettings settings)
{
// Get the output path
var output = new DirectoryPath(settings.Output);
if (!_fileSystem.Directory.Exists(settings.Output))
{
_fileSystem.Directory.Create(settings.Output);
}
// Get the data path
var data = settings.Data != null
? new DirectoryPath(settings.Data)
: output;
// Get all versions
var versions = GetVersions();
foreach (var generator in _generators)
{
// Generate the source
AnsiConsole.MarkupLine("⏳ Generating [yellow]{0}[/]...", generator.ClassName);
var result = await generator.Build(data, versions);
// Write the generated source to disk
var file = output.CombineWithFilePath($"{generator.ClassName}.Generated.cs");
AnsiConsole.MarkupLine("💾 Saving [yellow]{0}[/]...", file.GetFilename().FullPath);
File.WriteAllText(file.FullPath, result);
}
return 0;
}
private List<string> GetVersions()
{
var result = new List<string>();
foreach (var field in typeof(Unicode).GetFields().Where(x => x.IsStatic))
{
var attr = field.GetCustomAttribute<DescriptionAttribute>();
if (attr == null || string.IsNullOrWhiteSpace(attr.Description))
{
throw new InvalidOperationException("Unicode version enum is missing version attribute.");
}
result.Add(attr.Description);
}
return result;
}
}
}

Просмотреть файл

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Text;
using Spectre.Cli;
namespace Generator.Commands
{
public sealed class TableGeneratorSettings : CommandSettings
{
[CommandArgument(0, "<OUTPUT>")]
public string Output { get; set; }
[CommandOption("-d|--data <PATH>")]
public string Data { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Spectre.Cli" Version="0.43.0" />
<PackageReference Include="Scriban" Version="2.1.3" />
<PackageReference Include="Spectre.IO" Version="0.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Wcwidth\Wcwidth.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Templates\Table.template">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30413.136
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Generator", "Generator.csproj", "{64A7A6EB-C4EA-4537-A181-B0C11CF21CE4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{64A7A6EB-C4EA-4537-A181-B0C11CF21CE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{64A7A6EB-C4EA-4537-A181-B0C11CF21CE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{64A7A6EB-C4EA-4537-A181-B0C11CF21CE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{64A7A6EB-C4EA-4537-A181-B0C11CF21CE4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {37F50EC9-9829-4283-B046-E72EA93276CC}
EndGlobalSection
EndGlobal

Просмотреть файл

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Generator.Models;
using Generator.Templating;
using Generator.Utilities;
using Scriban;
using Scriban.Runtime;
using Spectre.Console;
using Spectre.IO;
using ScribanTemplate = Scriban.Template;
namespace Generator
{
public abstract class TableGenerator
{
public abstract string ClassName { get; }
protected abstract string DataFilename { get; }
protected virtual string Template { get; } = "Templates/Table.template";
public async Task<string> Build(DirectoryPath data, IEnumerable<string> versions)
{
// Compile the template
var template = ScribanTemplate.Parse(File.ReadAllText(Template));
// Get data
var model = new ScriptObject();
model["data"] = await GetData(data, versions, Filter);
model["name"] = ClassName;
// Prepare context
var context = new TemplateContext();
context.PushGlobal(model);
context.PushGlobal(new ScribanHelpers());
context.LoopLimit = 100000;
// Render template
return template.Render(context);
}
protected abstract string GetUrl(string version);
protected abstract bool Filter(string category);
private async Task<IEnumerable<UnicodeDataTable>> GetData(
DirectoryPath data,
IEnumerable<string> versions,
Func<string, bool> predicate)
{
var result = new List<UnicodeDataTable>();
foreach (var version in versions)
{
var url = GetUrl(version);
var filename = data.CombineWithFilePath($"{DataFilename}_{version}.txt");
using var stream = await UnicodeDataRetriever.GetData(filename, url);
result.Add(UnicodeDataParser.Parse(url, version, stream, predicate));
}
return result;
}
}
}

Просмотреть файл

@ -0,0 +1,21 @@
using System;
namespace Generator
{
public sealed class WideTableGenerator : TableGenerator
{
public override string ClassName => "WideTable";
protected override string DataFilename => "EastAsianWidth";
protected override string GetUrl(string version)
{
return $"http://www.unicode.org/Public/{version}/ucd/EastAsianWidth.txt";
}
protected override bool Filter(string category)
{
return category.Equals("W", StringComparison.OrdinalIgnoreCase) ||
category.Equals("F", StringComparison.OrdinalIgnoreCase);
}
}
}

Просмотреть файл

@ -0,0 +1,21 @@
using System;
namespace Generator
{
public sealed class ZeroTableGenerator : TableGenerator
{
public override string ClassName => "ZeroTable";
protected override string DataFilename => "DerivedGeneralCategory";
protected override string GetUrl(string version)
{
return $"http://www.unicode.org/Public/{version}/ucd/extracted/DerivedGeneralCategory.txt";
}
protected override bool Filter(string category)
{
return category.Equals("Me", StringComparison.OrdinalIgnoreCase) ||
category.Equals("Mn", StringComparison.OrdinalIgnoreCase);
}
}
}

Просмотреть файл

@ -0,0 +1,15 @@
using System;
using Generator.Commands;
using Spectre.Cli;
namespace Generator
{
public static class Program
{
public static int Main(string[] args)
{
var app = new CommandApp<TableGeneratorCommand>();
return app.Run(args);
}
}
}

Просмотреть файл

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
using Scriban.Runtime;
namespace Generator.Templating
{
public class ScribanHelpers : ScriptObject
{
public static string StringVer(string version)
{
return version.Replace(".", "_");
}
public static string HexPad(int value, int pad)
{
return string.Format("0x{0}", value.ToString("X").PadLeft(pad, '0'));
}
}
}

Просмотреть файл

@ -0,0 +1,41 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Generated {{ date.now | date.to_string `%F %R` }}
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
namespace Wcwidth
{
internal static partial class {{ name }}
{
private static uint[,] GenerateTable(Unicode version)
{
return version switch
{
{{- for item in data }}
Unicode.Version_{{ item.version | string_ver }} => Unicode_{{ item.version | string_ver }}(),
{{- end }}
_ => throw new InvalidOperationException("Unknown Unicode version"),
};
}
{{~ for item in data }}
// Source: {{ item.source }}
private static uint[,] Unicode_{{ item.version | string.replace "." "_" }}()
{
return new uint[,]
{
{{~ for range in item.ranges ~}}
{%{{}%} {{ range.start | hex_pad 6 }}, {{ range.end | hex_pad 6 }} {%{}}%},
{{~ end ~}}
};
}
{{~ end ~}}
}
}

Просмотреть файл

@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Generator.Models;
namespace Generator.Utilities
{
public static class UnicodeDataParser
{
private static readonly Regex _regex;
static UnicodeDataParser()
{
_regex = new Regex("^(?<start>[0-9A-F]{4,6})(..(?<end>[0-9A-F]{4,6})?)?\\s*;(?<property>[A-Za-z ]*)#(?<comment>.*$)");
}
public static UnicodeDataTable Parse(string source, string version, Stream stream,
Func<string, bool> predicate)
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}
if (version is null)
{
throw new ArgumentNullException(nameof(version));
}
if (stream is null)
{
throw new ArgumentNullException(nameof(stream));
}
var result = new List<int>();
using var reader = new StreamReader(stream);
while (true)
{
var line = reader.ReadLine();
if (line == null)
{
break;
}
if (line.StartsWith("#", StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
var range = ParseLine(line, predicate);
if (range != null)
{
result.AddRange(range);
}
}
return UnicodeDataTableBuilder.Build(source, version, result);
}
private static IEnumerable<int> ParseLine(string line, Func<string, bool> predicate)
{
var match = _regex.Match(line);
if (!match.Success)
{
throw new InvalidOperationException("Could not parse line");
}
var property = match.GetGroupValue("property", string.Empty).Trim();
if (predicate(property))
{
var (start, end) = GetStartAndEnd(match);
return Enumerable.Range(start, end - start + 1);
}
return null;
}
private static (int, int) GetStartAndEnd(Match match)
{
var start = match.GetGroupValue("start")?.Trim();
if (start == null)
{
throw new InvalidOperationException("Missing start code point");
}
var end = match.GetGroupValue("end", start).Trim();
return (
int.Parse(start, NumberStyles.HexNumber),
int.Parse(end, NumberStyles.HexNumber));
}
}
}

Просмотреть файл

@ -0,0 +1,8 @@
namespace Generator.Models
{
public sealed class UnicodeDataRange
{
public int Start { get; set; }
public int End { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,25 @@
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Spectre.Console;
using Spectre.IO;
namespace Generator.Utilities
{
public static class UnicodeDataRetriever
{
public static async Task<Stream> GetData(
FilePath filename, string url)
{
if (!File.Exists(filename.FullPath))
{
AnsiConsole.MarkupLine($"🌍 Downloading [yellow]{url}[/]...");
using var client = new HttpClient();
var content = await client.GetStringAsync(url);
await File.WriteAllTextAsync(filename.FullPath, content);
}
return File.OpenRead(filename.FullPath);
}
}
}

Просмотреть файл

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Generator.Models
{
public sealed class UnicodeDataTable
{
public string Version { get; set; }
public string Source { get; set; }
public List<UnicodeDataRange> Ranges { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Generator.Models;
using Spectre.Console;
namespace Generator.Utilities
{
public static class UnicodeDataTableBuilder
{
public static UnicodeDataTable Build(
string source,
string version,
List<int> values)
{
var ranges = CollapseRanges(values);
return new UnicodeDataTable
{
Source = source,
Version = version,
Ranges = ranges.OrderBy(x => x.Item1).Select((value) =>
{
return new UnicodeDataRange
{
Start = value.Item1,
End = value.Item2
};
}).ToList()
};
}
private static Deque<(int, int)> CollapseRanges(List<int> values)
{
var queue = new Deque<(int, int)>();
var start = values[0];
var end = values[0];
foreach (var (index, value) in values.Enumerate())
{
if (index == 0)
{
queue.Append((value, value));
continue;
}
(start, end) = queue.Pop();
if (end == value - 1)
{
queue.Append((start, value));
}
else
{
queue.Append((start, end));
queue.Append((value, value));
}
}
return queue;
}
}
}

Просмотреть файл

@ -0,0 +1,40 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Generator.Utilities
{
public sealed class Deque<T> : IEnumerable<T>
{
private readonly List<T> _items;
public Deque()
{
_items = new List<T>();
}
public void Append(T item)
{
_items.Add(item);
}
public T Pop()
{
var last = _items[_items.Count - 1];
_items.RemoveAt(_items.Count - 1);
return last;
}
public IEnumerator<T> GetEnumerator()
{
return _items.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

Просмотреть файл

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
namespace Generator
{
public static class EnumerableExtensions
{
public static IEnumerable<(int Index, T Item)> Enumerate<T>(this IEnumerable<T> source)
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}
return Enumerate(source.GetEnumerator());
}
public static IEnumerable<(int Index, T Item)> Enumerate<T>(this IEnumerator<T> source)
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}
var last = !source.MoveNext();
T current;
for (var index = 0; !last; index++)
{
current = source.Current;
last = !source.MoveNext();
yield return (index, current);
}
}
}
}

Просмотреть файл

@ -0,0 +1,14 @@
using System.Text.RegularExpressions;
namespace Generator
{
public static class MatchExtensions
{
public static string GetGroupValue(this Match match, string group, string defaultValue = null)
{
return match.Groups[group].Success
? match.Groups[group].Value
: defaultValue;
}
}
}

95
src/.editorconfig Normal file
Просмотреть файл

@ -0,0 +1,95 @@
root = false
[*.cs]
# IDE0055: Fix formatting
dotnet_diagnostic.IDE0055.severity = warning
# SA1101: Prefix local calls with this
dotnet_diagnostic.SA1101.severity = none
# SA1633: File should have header
dotnet_diagnostic.SA1633.severity = none
# SA1201: Elements should appear in the correct order
dotnet_diagnostic.SA1201.severity = none
# SA1202: Public members should come before private members
dotnet_diagnostic.SA1202.severity = none
# SA1309: Field names should not begin with underscore
dotnet_diagnostic.SA1309.severity = none
# SA1404: Code analysis suppressions should have justification
dotnet_diagnostic.SA1404.severity = none
# SA1516: Elements should be separated by a blank line
dotnet_diagnostic.SA1516.severity = none
# CA1303: Do not pass literals as localized parameters
dotnet_diagnostic.CA1303.severity = none
# CSA1204: Static members should appear before non-static members
dotnet_diagnostic.SA1204.severity = none
# IDE0052: Remove unread private members
dotnet_diagnostic.IDE0052.severity = warning
# IDE0063: Use simple 'using' statement
csharp_prefer_simple_using_statement = false:suggestion
# IDE0018: Variable declaration can be inlined
dotnet_diagnostic.IDE0018.severity = warning
# SA1625: Element documenation should not be copied and pasted
dotnet_diagnostic.SA1625.severity = none
# IDE0005: Using directive is unnecessary
dotnet_diagnostic.IDE0005.severity = warning
# SA1117: Parameters should be on same line or separate lines
dotnet_diagnostic.SA1117.severity = none
# SA1404: Code analysis suppression should have justification
dotnet_diagnostic.SA1404.severity = none
# SA1101: Prefix local calls with this
dotnet_diagnostic.SA1101.severity = none
# SA1633: File should have header
dotnet_diagnostic.SA1633.severity = none
# SA1649: File name should match first type name
dotnet_diagnostic.SA1649.severity = none
# SA1402: File may only contain a single type
dotnet_diagnostic.SA1402.severity = none
# CA1814: Prefer jagged arrays over multidimensional
dotnet_diagnostic.CA1814.severity = none
# RCS1194: Implement exception constructors.
dotnet_diagnostic.RCS1194.severity = none
# CA1032: Implement standard exception constructors
dotnet_diagnostic.CA1032.severity = none
# CA1826: Do not use Enumerable methods on indexable collections. Instead use the collection directly
dotnet_diagnostic.CA1826.severity = none
# RCS1079: Throwing of new NotImplementedException.
dotnet_diagnostic.RCS1079.severity = warning
# RCS1057: Add empty line between declarations.
dotnet_diagnostic.RCS1057.severity = none
# IDE0004: Remove Unnecessary Cast
dotnet_diagnostic.IDE0004.severity = warning
# CA1810: Initialize reference type static fields inline
dotnet_diagnostic.CA1810.severity = none
# SA1636: File header copyright text should match
dotnet_diagnostic.SA1636.severity = none
# CA1724: Namespace conflict
dotnet_diagnostic.CA1724.severity = none

45
src/Directory.Build.props Normal file
Просмотреть файл

@ -0,0 +1,45 @@
<Project>
<PropertyGroup Label="Settings">
<Deterministic>true</Deterministic>
<LangVersion>8.0</LangVersion>
<DebugSymbols>true</DebugSymbols>
<DebugType>embedded</DebugType>
<MinVerSkip Condition="'$(Configuration)' == 'Debug'">true</MinVerSkip>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Label="Deterministic Build" Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<PropertyGroup Label="Package Information">
<Description>A .NET library that calculates the width of Unicode characters.</Description>
<Authors>Patrik Svensson</Authors>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/patriksvensson/wcwidth</RepositoryUrl>
<PackageProjectUrl>https://github.com/patriksvensson/wcwidth</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReleaseNotes>https://github.com/patriksvensson/wcwidth/releases</PackageReleaseNotes>
</PropertyGroup>
<PropertyGroup Label="Source Link">
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
</PropertyGroup>
<ItemGroup Label="Package References">
<PackageReference Include="MinVer" PrivateAssets="All" Version="2.3.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="1.0.0" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.113">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="Roslynator.Analyzers" Version="2.3.0">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

Просмотреть файл

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

Просмотреть файл

@ -0,0 +1,14 @@
root = false
[*.cs]
# CS1591: Missing XML comment for publicly visible type or member
dotnet_diagnostic.CS1591.severity = none
# SA1600: Elements should be documented
dotnet_diagnostic.SA1600.severity = none
# SA1633: File should have header
dotnet_diagnostic.SA1633.severity = none
# CA1707: Identifiers should not contain underscores
dotnet_diagnostic.CA1707.severity = none

Просмотреть файл

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="Shouldly" Version="3.0.2" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Wcwidth\Wcwidth.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,120 @@
using System.Linq;
using Shouldly;
using Xunit;
namespace Wcwidth.Tests
{
public sealed class WcwidthTests
{
[Fact]
public void Test_Favorite_Emoji()
{
// Given
var length = "💩".Select(c => Wcwidth.GetWidth(c)).Sum();
// Then
length.ShouldBe(length);
}
[Fact]
public void Test_Hello_Japanese()
{
char c2 = 'コ';
var foo = Wcwidth.GetWidth(c2);
// Given
const string phrase = "コンニチハ, セカイ!";
var expected = new[] { 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 1 };
// When
var length = phrase.Select(c => Wcwidth.GetWidth(c));
// Then
length.ShouldBe(expected);
}
[Fact]
public void Test_Null_Width()
{
// Given
const string phrase = "abc\x0000def";
var expected = new[] { 1, 1, 1, 0, 1, 1, 1 };
// When
var length = phrase.Select(c => Wcwidth.GetWidth(c));
// Then
length.ShouldBe(expected);
}
[Fact]
public void Test_Control_C0_Width()
{
// Given
const string phrase = "\x1b[0m";
var expected = new[] { -1, 1, 1, 1 };
// When
var length = phrase.Select(c => Wcwidth.GetWidth(c));
// Then
length.ShouldBe(expected);
}
[Fact]
public void Test_Combining_Width()
{
// Given
const string phrase = "--\u05bf--";
var expected = new[] { 1, 1, 0, 1, 1 };
// When
var length = phrase.Select(c => Wcwidth.GetWidth(c));
// Then
length.ShouldBe(expected);
}
[Fact]
public void Test_Combining_Cafe_Width()
{
// Given
const string phrase = "cafe\u0301";
var expected = new[] { 1, 1, 1, 1, 0 };
// When
var length = phrase.Select(c => Wcwidth.GetWidth(c));
// Then
length.ShouldBe(expected);
}
[Fact]
public void Test_Combining_Enclosing()
{
// Given
const string phrase = "\u0410\u0488";
var expected = new[] { 1, 0 };
// When
var length = phrase.Select(c => Wcwidth.GetWidth(c));
// Then
length.ShouldBe(expected);
}
[Fact]
public void Test_Combining_Spacing()
{
// Given
const string phrase = "\u1B13\u1B28\u1B2E\u1B44";
var expected = new[] { 1, 1, 1, 1 };
// When
var length = phrase.Select(c => Wcwidth.GetWidth(c));
// Then
length.ShouldBe(expected);
}
}
}

59
src/Wcwidth.sln Normal file
Просмотреть файл

@ -0,0 +1,59 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30330.147
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wcwidth", "Wcwidth\Wcwidth.csproj", "{97277350-2069-4863-B0DE-07E929629958}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wcwidth.Tests", "Wcwidth.Tests\Wcwidth.Tests.csproj", "{A90C8C46-58DF-41B8-B273-B24396E8424C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9C3DA7B2-0055-453E-BFF7-F4D24B438433}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets
..\README.md = ..\README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{97277350-2069-4863-B0DE-07E929629958}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{97277350-2069-4863-B0DE-07E929629958}.Debug|Any CPU.Build.0 = Debug|Any CPU
{97277350-2069-4863-B0DE-07E929629958}.Debug|x64.ActiveCfg = Debug|Any CPU
{97277350-2069-4863-B0DE-07E929629958}.Debug|x64.Build.0 = Debug|Any CPU
{97277350-2069-4863-B0DE-07E929629958}.Debug|x86.ActiveCfg = Debug|Any CPU
{97277350-2069-4863-B0DE-07E929629958}.Debug|x86.Build.0 = Debug|Any CPU
{97277350-2069-4863-B0DE-07E929629958}.Release|Any CPU.ActiveCfg = Release|Any CPU
{97277350-2069-4863-B0DE-07E929629958}.Release|Any CPU.Build.0 = Release|Any CPU
{97277350-2069-4863-B0DE-07E929629958}.Release|x64.ActiveCfg = Release|Any CPU
{97277350-2069-4863-B0DE-07E929629958}.Release|x64.Build.0 = Release|Any CPU
{97277350-2069-4863-B0DE-07E929629958}.Release|x86.ActiveCfg = Release|Any CPU
{97277350-2069-4863-B0DE-07E929629958}.Release|x86.Build.0 = Release|Any CPU
{A90C8C46-58DF-41B8-B273-B24396E8424C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A90C8C46-58DF-41B8-B273-B24396E8424C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A90C8C46-58DF-41B8-B273-B24396E8424C}.Debug|x64.ActiveCfg = Debug|Any CPU
{A90C8C46-58DF-41B8-B273-B24396E8424C}.Debug|x64.Build.0 = Debug|Any CPU
{A90C8C46-58DF-41B8-B273-B24396E8424C}.Debug|x86.ActiveCfg = Debug|Any CPU
{A90C8C46-58DF-41B8-B273-B24396E8424C}.Debug|x86.Build.0 = Debug|Any CPU
{A90C8C46-58DF-41B8-B273-B24396E8424C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A90C8C46-58DF-41B8-B273-B24396E8424C}.Release|Any CPU.Build.0 = Release|Any CPU
{A90C8C46-58DF-41B8-B273-B24396E8424C}.Release|x64.ActiveCfg = Release|Any CPU
{A90C8C46-58DF-41B8-B273-B24396E8424C}.Release|x64.Build.0 = Release|Any CPU
{A90C8C46-58DF-41B8-B273-B24396E8424C}.Release|x86.ActiveCfg = Release|Any CPU
{A90C8C46-58DF-41B8-B273-B24396E8424C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {91DC3E44-F01D-459A-A956-418FE23E6723}
EndGlobalSection
EndGlobal

14
src/Wcwidth/Interval.cs Normal file
Просмотреть файл

@ -0,0 +1,14 @@
namespace Wcwidth
{
internal readonly struct Interval
{
public int Start { get; }
public int End { get; }
public Interval(int start, int end)
{
Start = start;
End = end;
}
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,37 @@
using System.Collections.Generic;
namespace Wcwidth
{
internal static partial class WideTable
{
private static readonly Dictionary<Unicode, uint[,]> _lookup;
private static readonly object _lock;
static WideTable()
{
_lookup = new Dictionary<Unicode, uint[,]>();
_lock = new object();
}
public static uint[,] GetTable(Unicode version)
{
if (!_lookup.TryGetValue(version, out var table))
{
lock (_lock)
{
if (_lookup.TryGetValue(version, out table))
{
return table;
}
// Generate the table for the version dynamically
// since we don't want to load everything into memory.
table = GenerateTable(version);
_lookup[version] = table;
}
}
return table;
}
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,37 @@
using System.Collections.Generic;
namespace Wcwidth
{
internal static partial class ZeroTable
{
private static readonly Dictionary<Unicode, uint[,]> _lookup;
private static readonly object _lock;
static ZeroTable()
{
_lookup = new Dictionary<Unicode, uint[,]>();
_lock = new object();
}
public static uint[,] GetTable(Unicode version)
{
if (!_lookup.TryGetValue(version, out var table))
{
lock (_lock)
{
if (_lookup.TryGetValue(version, out table))
{
return table;
}
// Generate the table for the version dynamically
// since we don't want to load everything into memory.
table = GenerateTable(version);
_lookup[version] = table;
}
}
return table;
}
}
}

108
src/Wcwidth/Unicode.cs Normal file
Просмотреть файл

@ -0,0 +1,108 @@
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
namespace Wcwidth
{
/// <summary>
/// Represents a Unicode version.
/// </summary>
[SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")]
public enum Unicode
{
/// <summary>
/// Unicode version 4.1.0.
/// </summary>
[Description("4.1.0")]
Version_4_1_0,
/// <summary>
/// Unicode version 5.0.0.
/// </summary>
[Description("5.0.0")]
Version_5_0_0,
/// <summary>
/// Unicode version 5.1.0.
/// </summary>
[Description("5.1.0")]
Version_5_1_0,
/// <summary>
/// Unicode version 5.2.0.
/// </summary>
[Description("5.2.0")]
Version_5_2_0,
/// <summary>
/// Unicode version 6.0.0.
/// </summary>
[Description("6.0.0")]
Version_6_0_0,
/// <summary>
/// Unicode version 6.1.0.
/// </summary>
[Description("6.1.0")]
Version_6_1_0,
/// <summary>
/// Unicode version 6.2.0.
/// </summary>
[Description("6.2.0")]
Version_6_2_0,
/// <summary>
/// Unicode version 6.3.0.
/// </summary>
[Description("6.3.0")]
Version_6_3_0,
/// <summary>
/// Unicode version 7.0.0.
/// </summary>
[Description("7.0.0")]
Version_7_0_0,
/// <summary>
/// Unicode version 8.0.0.
/// </summary>
[Description("8.0.0")]
Version_8_0_0,
/// <summary>
/// Unicode version 9.0.0.
/// </summary>
[Description("9.0.0")]
Version_9_0_0,
/// <summary>
/// Unicode version 10.0.0.
/// </summary>
[Description("10.0.0")]
Version_10_0_0,
/// <summary>
/// Unicode version 11.0.0.
/// </summary>
[Description("11.0.0")]
Version_11_0_0,
/// <summary>
/// Unicode version 12.0.0.
/// </summary>
[Description("12.0.0")]
Version_12_0_0,
/// <summary>
/// Unicode version 12.1.0.
/// </summary>
[Description("12.1.0")]
Version_12_1_0,
/// <summary>
/// Unicode version 13.0.0.
/// </summary>
[Description("13.0.0")]
Version_13_0_0,
}
}

146
src/Wcwidth/Wcwidth.cs Normal file
Просмотреть файл

@ -0,0 +1,146 @@
// This is a port of the Python version of wcwidth() written by Jeff Quast,
// which originally was written by Markus Kuhn.
//
// https://github.com/jquast/wcwidth
// http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
//
// In fixed-width output devices, Latin characters all occupy a single "cell" position
// of equal width, whereas ideographic CJK characters occupy two such cells. Interoperability
// between terminal-line applications and (teletype-style) character terminals using the
// UTF-8 encoding requires agreement on which character should advance the cursor by how
// many cell positions. No established formal standards exist at present on which Unicode
// character shall occupy how many cell positions on character terminals. These routines
// are a first attempt of defining such behavior based on simple rules applied to data
// provided by the Unicode Consortium.
//
// For some graphical characters, the Unicode standard explicitly defines a character-cell
// width via the definition of the East Asian FullWidth (F), Wide (W), Half-width (H),
// and Narrow (Na) classes. In all these cases, there is no ambiguity about which width
// a terminal shall use. For characters in the East Asian Ambiguous (A) class, the width
// choice depends purely on a preference of backward compatibility with either historic
// CJK or Western practice. Choosing single-width for these characters is easy to justify
// as the appropriate long-term solution, as the CJK practice of displaying these characters
// as double-width comes from historic implementation simplicity (8-bit encoded characters
// were displayed single-width and 16-bit ones double-width, even for Greek, Cyrillic, etc.)
// and not any typographic considerations.
//
// Much less clear is the choice of width for the Not East Asian (Neutral) class.
// Existing practice does not dictate a width for any of these characters. It would
// nevertheless make sense typographically to allocate two character cells to characters
// such as for instance EM SPACE or VOLUME INTEGRAL, which cannot be represented adequately
// with a single-width glyph. The following routines at present merely assign a single-cell
// width to all neutral characters, in the interest of simplicity. This is not entirely
// satisfactory and should be reconsidered before establishing a formal standard in this area.
// At the moment, the decision which Not East Asian (Neutral) characters should be represented
// by double-width glyphs cannot yet be answered by applying a simple rule from the Unicode
// database content. Setting up a proper standard for the behavior of UTF-8 character
// terminals will require a careful analysis not only of each Unicode character, but also
// of each presentation form, something the author of these routines has avoided to do so far.
//
// http://www.unicode.org/unicode/reports/tr11/
using System.Collections.Generic;
namespace Wcwidth
{
/// <summary>
/// A utility for calculating the width of Unicode characters.
/// </summary>
public static class Wcwidth
{
/// <summary>
/// Gets the latest unicode version.
/// </summary>
public static Unicode Latest { get; } = Unicode.Version_13_0_0;
// NOTE: created by hand, there isn't anything identifiable other than
// general Cf category code to identify these, and some characters in Cf
// category code are of non-zero width.
// Also includes some Cc, Mn, Zl, and Zp characters
private static readonly HashSet<uint> ZeroWidthCf = new HashSet<uint>
{
0, // Null (Cc)
0x034F, // Combining grapheme joiner (Mn)
0x200B, // Zero width space
0x200C, // Zero width non-joiner
0x200D, // Zero width joiner
0x200E, // Left-to-right mark
0x200F, // Right-to-left mark
0x2028, // Line separator (Zl)
0x2029, // Paragraph separator (Zp)
0x202A, // Left-to-right embedding
0x202B, // Right-to-left embedding
0x202C, // Pop directional formatting
0x202D, // Left-to-right override
0x202E, // Right-to-left override
0x2060, // Word joiner
0x2061, // Function application
0x2062, // Invisible times
0x2063, // Invisible separator
};
/// <summary>
/// Gets the width for a Unicode character.
/// </summary>
/// <param name="value">The charachter to calculate the width for.</param>
/// <param name="version">The Unicode version to use.</param>
/// <returns>The width of the character.</returns>
public static int GetWidth(char value, Unicode? version = null)
{
version ??= Latest;
// NOTE: created by hand, there isn't anything identifiable other than
// general Cf category code to identify these, and some characters in Cf
// category code are of non-zero width.
if (ZeroWidthCf.Contains(value))
{
return 0;
}
// C0/C1 control characters
if (value < 32 || (value >= 0x07F && value < 0x0A0))
{
return -1;
}
// Combining characters with zero width?
if (BinarySearch(value, ZeroTable.GetTable(version.Value)) != 0)
{
return 0;
}
return 1 + BinarySearch(value, WideTable.GetTable(version.Value));
}
private static int BinarySearch(uint rune, uint[,] table)
{
var min = 0;
var max = table.GetUpperBound(0);
int mid;
if (rune < table[0, 0] || rune > table[max, 1])
{
return 0;
}
while (max >= min)
{
mid = (min + max) / 2;
if (rune > table[mid, 1])
{
min = mid + 1;
}
else if (rune < table[mid, 0])
{
max = mid - 1;
}
else
{
return 1;
}
}
return 0;
}
}
}

Просмотреть файл

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" />
</ItemGroup>
</Project>

27
src/stylecop.json Normal file
Просмотреть файл

@ -0,0 +1,27 @@
{
"$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
"settings": {
"documentationRules": {
"xmlHeader": false,
"documentExposedElements": true,
"documentInternalElements": false,
"documentPrivateElements": false,
"documentPrivateFields": false
},
"layoutRules": {
"newlineAtEndOfFile": "allow",
"allowConsecutiveUsings": true
},
"orderingRules": {
"usingDirectivesPlacement": "outsideNamespace",
"systemUsingDirectivesFirst": true,
"elementOrder": [
"kind",
"accessibility",
"constant",
"static",
"readonly"
]
}
}
}