chore: switching to mfgames-project-setup-flake + reformat

This commit is contained in:
D. Moonfire 2024-03-05 23:15:03 -06:00
parent 32be8aad6e
commit 5702d4f9b8
385 changed files with 15399 additions and 17472 deletions

View file

@ -1,121 +1,123 @@
root=true
root = true
["*"]
charset = "utf-8"
csharp_new_line_before_members_in_object_initializers = false
csharp_preferred_modifier_order = "public, override, protected, internal, file, new, virtual, abstract, private, sealed, readonly, static, extern, unsafe, volatile, async, required:suggestion"
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = false
csharp_space_after_cast = false
csharp_style_var_elsewhere = "false:hint"
csharp_style_var_for_built_in_types = "false:hint"
csharp_style_var_when_type_is_apparent = "true:hint"
curly_bracket_next_line = true
dotnet_style_predefined_type_for_locals_parameters_members = "true:hint"
dotnet_style_predefined_type_for_member_access = "true:hint"
dotnet_style_qualification_for_event = "true:hint"
dotnet_style_qualification_for_field = "true:hint"
dotnet_style_qualification_for_method = "true:hint"
dotnet_style_qualification_for_property = "true:hint"
dotnet_style_require_accessibility_modifiers = "for_non_interface_members:hint"
end_of_line = "lf"
indent_brace_style = "K&R"
indent_size = 4
indent_style = "space"
insert_final_newline = true
max_line_length = 80
resharper_alignment_tab_fill_style = "optimal_fill"
resharper_apply_on_completion = true
resharper_blank_lines_after_control_transfer_statements = 1
resharper_blank_lines_around_single_line_auto_property = 1
resharper_blank_lines_around_single_line_property = 1
resharper_blank_lines_before_block_statements = 1
resharper_blank_lines_before_control_transfer_statements = 1
resharper_blank_lines_before_single_line_comment = 1
resharper_blank_lines_between_using_groups = 1
resharper_braces_for_for = "required"
resharper_braces_for_foreach = "required"
resharper_braces_for_ifelse = "required"
resharper_braces_for_while = "required"
resharper_can_use_global_alias = false
resharper_check_namespace_highlighting = "none"
resharper_convert_to_auto_property_highlighting = "none"
resharper_csharp_blank_lines_around_single_line_field = 1
resharper_csharp_blank_lines_around_single_line_invocable = 1
resharper_csharp_empty_block_style = "together_same_line"
resharper_csharp_indent_style = "tab"
resharper_csharp_insert_final_newline = true
resharper_csharp_keep_blank_lines_in_code = 1
resharper_csharp_keep_blank_lines_in_declarations = 1
resharper_csharp_max_line_length = 100
resharper_csharp_new_line_before_while = true
resharper_csharp_use_indent_invocation_rpar = true
resharper_csharp_wrap_extends_list_style = "chop_if_long"
resharper_csharp_wrap_parameters_style = "chop_if_long"
resharper_css_insert_final_newline = false
resharper_enforce_line_ending_style = true
resharper_html_insert_final_newline = false
resharper_indent_nested_fixed_stmt = true
resharper_js_indent_style = "spaces"
resharper_js_insert_final_newline = true
resharper_js_keep_blank_lines_in_code = 1
resharper_js_stick_comment = false
resharper_js_use_indent_from_vs = false
resharper_js_wrap_before_binary_opsign = true
resharper_js_wrap_chained_method_calls = "chop_if_long"
resharper_keep_blank_lines_between_declarations = 1
resharper_localizable_element_highlighting = "none"
resharper_min_blank_lines_after_imports = 1
resharper_place_attribute_on_same_line = false
resharper_place_constructor_initializer_on_same_line = false
resharper_place_expr_property_on_single_line = true
resharper_place_simple_initializer_on_single_line = false
resharper_place_type_constraints_on_same_line = false
resharper_protobuf_insert_final_newline = false
resharper_qualified_using_at_nested_scope = true
resharper_redundant_comma_in_attribute_list_highlighting = "none"
resharper_redundant_comma_in_enum_declaration_highlighting = "none"
resharper_redundant_comma_in_initializer_highlighting = "none"
resharper_resx_insert_final_newline = false
resharper_space_within_single_line_array_initializer_braces = true
resharper_string_compare_to_is_culture_specific_highlighting = "none"
resharper_string_index_of_is_culture_specific_1_highlighting = "none"
resharper_use_indents_from_main_language_in_file = false
resharper_use_null_propagation_highlighting = "none"
resharper_use_object_or_collection_initializer_highlighting = "hint"
resharper_use_string_interpolation_highlighting = "hint"
resharper_vb_insert_final_newline = false
resharper_wrap_after_declaration_lpar = true
resharper_wrap_after_invocation_lpar = true
resharper_wrap_before_extends_colon = true
resharper_wrap_before_first_type_parameter_constraint = true
resharper_wrap_before_type_parameter_langle = true
resharper_xml_insert_final_newline = false
resharper_xmldoc_indent_child_elements = "ZeroIndent"
resharper_xmldoc_indent_text = "ZeroIndent"
resharper_xmldoc_insert_final_newline = false
tab_width = 4
trim_trailing_whitespace = true
[*]
charset=utf-8
csharp_new_line_before_members_in_object_initializers=false
csharp_preferred_modifier_order=public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion
csharp_preserve_single_line_blocks=true
csharp_preserve_single_line_statements=false
csharp_space_after_cast=false
csharp_style_var_elsewhere=false:hint
csharp_style_var_for_built_in_types=false:hint
csharp_style_var_when_type_is_apparent=true:hint
curly_bracket_next_line=true
dotnet_style_predefined_type_for_locals_parameters_members=true:hint
dotnet_style_predefined_type_for_member_access=true:hint
dotnet_style_qualification_for_event=true:hint
dotnet_style_qualification_for_field=true:hint
dotnet_style_qualification_for_method=true:hint
dotnet_style_qualification_for_property=true:hint
dotnet_style_require_accessibility_modifiers=for_non_interface_members:hint
end_of_line=lf
indent_brace_style=K&R
indent_size=4
indent_style=space
insert_final_newline=true
max_line_length=80
resharper_alignment_tab_fill_style=optimal_fill
resharper_apply_on_completion=true
resharper_blank_lines_after_control_transfer_statements=1
resharper_blank_lines_around_single_line_auto_property=1
resharper_blank_lines_around_single_line_property=1
resharper_blank_lines_before_single_line_comment=1
resharper_blank_lines_between_using_groups=1
resharper_braces_for_for=required
resharper_braces_for_foreach=required
resharper_braces_for_ifelse=required
resharper_braces_for_while=required
resharper_can_use_global_alias=false
resharper_check_namespace_highlighting=none
resharper_convert_to_auto_property_highlighting=none
resharper_csharp_blank_lines_around_single_line_field=1
resharper_csharp_blank_lines_around_single_line_invocable=1
resharper_csharp_indent_style=tab
resharper_csharp_insert_final_newline=true
resharper_csharp_keep_blank_lines_in_code=1
resharper_csharp_keep_blank_lines_in_declarations=1
resharper_csharp_new_line_before_while=true
resharper_csharp_use_indent_from_vs=false
resharper_csharp_wrap_arguments_style=chop_if_long
resharper_csharp_wrap_extends_list_style=chop_if_long
resharper_csharp_wrap_parameters_style=chop_if_long
resharper_css_insert_final_newline=false
resharper_enforce_line_ending_style=true
resharper_html_insert_final_newline=false
resharper_indent_nested_fixed_stmt=true
resharper_js_indent_style=tab
resharper_js_insert_final_newline=true
resharper_js_keep_blank_lines_in_code=1
resharper_js_stick_comment=false
resharper_js_use_indent_from_vs=false
resharper_js_wrap_before_binary_opsign=true
resharper_js_wrap_chained_method_calls=chop_if_long
resharper_keep_blank_lines_between_declarations=1
resharper_localizable_element_highlighting=none
resharper_min_blank_lines_after_imports=1
resharper_place_attribute_on_same_line=false
resharper_place_constructor_initializer_on_same_line=false
resharper_place_type_constraints_on_same_line=false
resharper_protobuf_insert_final_newline=false
resharper_qualified_using_at_nested_scope=true
resharper_redundant_comma_in_attribute_list_highlighting=none
resharper_redundant_comma_in_enum_declaration_highlighting=none
resharper_redundant_comma_in_initializer_highlighting=none
resharper_resx_insert_final_newline=false
resharper_space_within_single_line_array_initializer_braces=true
resharper_string_compare_to_is_culture_specific_highlighting=none
resharper_string_index_of_is_culture_specific_1_highlighting=none
resharper_use_indents_from_main_language_in_file=false
resharper_use_null_propagation_highlighting=none
resharper_use_object_or_collection_initializer_highlighting=hint
resharper_use_string_interpolation_highlighting=hint
resharper_vb_insert_final_newline=false
resharper_wrap_after_declaration_lpar=true
resharper_wrap_after_invocation_lpar=true
resharper_wrap_before_extends_colon=true
resharper_wrap_before_first_type_parameter_constraint=true
resharper_wrap_before_type_parameter_langle=true
resharper_xml_insert_final_newline=false
resharper_xmldoc_indent_child_elements=ZeroIndent
resharper_xmldoc_indent_text=ZeroIndent
resharper_xmldoc_insert_final_newline=false
tab_width=4
trim_trailing_whitespace=true
["*.cs"]
indent_size = 4
indent_style = "space"
tab_width = 4
[*.md]
max_line_length=off
trim_trailing_whitespace=false
["*.md"]
max_line_length = "off"
[*.{appxmanifest,build,config,csproj,dbml,discomap,dtd,jsproj,lsproj,njsproj,nuspec,proj,props,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}]
indent_size=2
indent_style=space
tab_width=2
["*.{appxmanifest,build,config,csproj,dbml,discomap,dtd,jsproj,lsproj,njsproj,nuspec,proj,props,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}]"]
indent_size = 2
indent_style = "space"
tab_width = 2
[*.{diff,patch}]
end_of_line=unset
indent_size=unset
insert_final_newline=unset
trim_trailing_whitespace=unset
["package.json"]
indent_size = 2
indent_style = "space"
tab_width = 2
[package.json]
indent_size=2
indent_style=space
tab_width=2
[{LICENSES/**,LICENSE}]
charset=unset
end_of_line=unset
indent_size=unset
indent_style=unset
insert_final_newline=unset
trim_trailing_whitespace=unset
["{LICENSES/**,LICENSE}"]
charset = "unset"
end_of_line = "unset"
indent_size = "unset"
indent_style = "unset"
insert_final_newline = "unset"
trim_trailing_whitespace = "unset"

3
.gitignore vendored
View file

@ -23,6 +23,7 @@ TestResults/
tests/artifacts/
# nixago: ignore-linked-files
/.prettierrc.json
/lefthook.yml
/.conform.yaml
/treefmt.toml
/treefmt.toml

View file

@ -17,24 +17,24 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall
community
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of
any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address,
without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
- The use of sexualized language or imagery, and sexual attention or advances of
any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address,
without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities

133
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,133 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of
any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address,
without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official email address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
contact@mfgames.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

33
DCO.md Normal file
View file

@ -0,0 +1,33 @@
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.

View file

@ -539,7 +539,7 @@ II.2.12 <HandlesEvent />
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseVarWhenEvident</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseVarWhenEvident</s:String>
<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/EnableStyleCopSupport/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue"></s:String>
<s:Boolean x:Key="/Default/CodeStyle/LiveTemplatesUseVar/UseVar/@EntryValue">False</s:Boolean>
@ -663,6 +663,7 @@ II.2.12 &lt;HandlesEvent /&gt;&#xD;
<s:String x:Key="/Default/Environment/PerformanceGuide/SwitchBehaviour/=VCS/@EntryIndexedValue">LIVE_MONITOR</s:String>
<s:String x:Key="/Default/Environment/PerformanceGuide/SwitchBehaviour/=VsBulb/@EntryIndexedValue">DO_NOTHING</s:String>
<s:String x:Key="/Default/Environment/PerformanceGuide/SwitchBehaviour/=XAML_0020Designer/@EntryIndexedValue">LIVE_MONITOR</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECodeCleanup_002EFileHeader_002EFileHeaderSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpFileLayoutPatternsUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>

View file

@ -2,5 +2,5 @@
## Libraries
- [Gallium](./gallium/)
- [Nitride](./nitride/)
- [Gallium](./gallium/)
- [Nitride](./nitride/)

View file

@ -4,17 +4,16 @@ namespace NitrideCopyFiles;
public class CopyFilesModule : Module
{
/// <inheritdoc />
protected override void Load(ContainerBuilder builder)
{
// This just registers all the non-static classes as singletons
// within the system. We use lifetimes in other components depending
// on how they are used, but in this case, we don't need it.
builder.RegisterAssemblyTypes(
this.GetType()
.Assembly)
.AsSelf()
.AsImplementedInterfaces()
.SingleInstance();
}
/// <inheritdoc />
protected override void Load(ContainerBuilder builder)
{
// This just registers all the non-static classes as singletons
// within the system. We use lifetimes in other components depending
// on how they are used, but in this case, we don't need it.
builder
.RegisterAssemblyTypes(this.GetType().Assembly)
.AsSelf()
.AsImplementedInterfaces()
.SingleInstance();
}
}

View file

@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using MfGames.Gallium;
using MfGames.Nitride;
using MfGames.Nitride.IO.Contents;
@ -16,97 +15,99 @@ namespace NitrideCopyFiles;
/// </summary>
public class CopyFilesPipeline : PipelineBase
{
private readonly AddPathPrefix addPathPrefix;
private readonly AddPathPrefix addPathPrefix;
private readonly ClearDirectory clearDirectory;
private readonly ClearDirectory clearDirectory;
private readonly ReadFiles readFiles;
private readonly ReadFiles readFiles;
private readonly RemovePathPrefix removePathPrefix;
private readonly RemovePathPrefix removePathPrefix;
private readonly WriteFiles writeFiles;
private readonly WriteFiles writeFiles;
public CopyFilesPipeline(
ReadFiles readFiles,
ClearDirectory clearDirectory,
WriteFiles writeFiles,
RemovePathPrefix removePathPrefix,
AddPathPrefix addPathPrefix)
{
// While we can configure these during runtime, it seems cleaner to
// build them up during the constructor to call out the ones that
// require runtime data.
this.readFiles = readFiles.WithPattern("/input/**/*.txt");
this.clearDirectory = clearDirectory.WithPath("/output");
this.writeFiles = writeFiles;
this.removePathPrefix = removePathPrefix.WithPathPrefix("/input");
this.addPathPrefix = addPathPrefix.WithPathPrefix("/output");
}
public CopyFilesPipeline(
ReadFiles readFiles,
ClearDirectory clearDirectory,
WriteFiles writeFiles,
RemovePathPrefix removePathPrefix,
AddPathPrefix addPathPrefix
)
{
// While we can configure these during runtime, it seems cleaner to
// build them up during the constructor to call out the ones that
// require runtime data.
this.readFiles = readFiles.WithPattern("/input/**/*.txt");
this.clearDirectory = clearDirectory.WithPath("/output");
this.writeFiles = writeFiles;
this.removePathPrefix = removePathPrefix.WithPathPrefix("/input");
this.addPathPrefix = addPathPrefix.WithPathPrefix("/output");
}
/// <inheritdoc />
public override IAsyncEnumerable<Entity> RunAsync(
IEnumerable<Entity> _,
CancellationToken cancellationToken = default)
{
// We don't care about the incoming entities which means we can
// ignore them and use the entities from the ReadFiles operation
// or we can union the entities pass in with the ReadFiles in case
// we want to merge them.
//
// This will read all the files of the given pattern and return them
// as an IEnumerable<Entity> with the Zio components (UPath and
// binary contents) set. As a note, this doesn't actually read the
// file, just create a pointer to where the file could be read from.
//
// The path component will always be the relative path to the root,
// so `/input/a.txt` in this case since we only have one file.
IEnumerable<Entity> entities = this.readFiles.Run();
/// <inheritdoc />
public override IAsyncEnumerable<Entity> RunAsync(
IEnumerable<Entity> _,
CancellationToken cancellationToken = default
)
{
// We don't care about the incoming entities which means we can
// ignore them and use the entities from the ReadFiles operation
// or we can union the entities pass in with the ReadFiles in case
// we want to merge them.
//
// This will read all the files of the given pattern and return them
// as an IEnumerable<Entity> with the Zio components (UPath and
// binary contents) set. As a note, this doesn't actually read the
// file, just create a pointer to where the file could be read from.
//
// The path component will always be the relative path to the root,
// so `/input/a.txt` in this case since we only have one file.
IEnumerable<Entity> entities = this.readFiles.Run();
// Change the path. The path (Zio.UPath component) stays with the
// entity which is why there is no root directory in the writeFiles
// operation. Instead, we need to remove the `/input` and replace it
// with the `/output`.
//
// We can do that with a RemovePathPrefix followed by an
// AddPathPrefix, or use the more complex version of ChangePaths.
// In this case, we are going for easy to learn, so we'll do the
// pair.
//
// We are going to use the chain extension to make it easier to
// read. Coming out of this, we will have one entity that fulfills:
//
// entity.Get<UPath> == "/output/a.txt"
entities = entities
.Run(this.removePathPrefix, cancellationToken)
.Run(this.addPathPrefix, cancellationToken);
// Change the path. The path (Zio.UPath component) stays with the
// entity which is why there is no root directory in the writeFiles
// operation. Instead, we need to remove the `/input` and replace it
// with the `/output`.
//
// We can do that with a RemovePathPrefix followed by an
// AddPathPrefix, or use the more complex version of ChangePaths.
// In this case, we are going for easy to learn, so we'll do the
// pair.
//
// We are going to use the chain extension to make it easier to
// read. Coming out of this, we will have one entity that fulfills:
//
// entity.Get<UPath> == "/output/a.txt"
entities = entities
.Run(this.removePathPrefix, cancellationToken)
.Run(this.addPathPrefix, cancellationToken);
// Then we write out the files to the output. First we make sure we
// clear out the output. This operation performs an action when it
// it is first entered, but otherwise passes all the inputs through.
//
// We can call the clear directory in three ways. The first is to call
// the operation with:
//
// this.clearDirectory.Run()
//
// The other is to pass in the entities and get a new list of
// modified ones out. In this case, clear doesn't make any changes,
// but Entity objects are immutable, so we always work on the list
// returned.
//
// entities = this.clearDirectory.Run(entity)
//
// The third way is to use an extension on entities which lets us
// chain calls, ala Gulp's pipelines. The below code does this along
// with writing the files to the output.
entities = entities
.Run(this.clearDirectory, cancellationToken)
.Run(this.writeFiles, cancellationToken);
// Then we write out the files to the output. First we make sure we
// clear out the output. This operation performs an action when it
// it is first entered, but otherwise passes all the inputs through.
//
// We can call the clear directory in three ways. The first is to call
// the operation with:
//
// this.clearDirectory.Run()
//
// The other is to pass in the entities and get a new list of
// modified ones out. In this case, clear doesn't make any changes,
// but Entity objects are immutable, so we always work on the list
// returned.
//
// entities = this.clearDirectory.Run(entity)
//
// The third way is to use an extension on entities which lets us
// chain calls, ala Gulp's pipelines. The below code does this along
// with writing the files to the output.
entities = entities
.Run(this.clearDirectory, cancellationToken)
.Run(this.writeFiles, cancellationToken);
// If we are chaining this pipeline into another, we return the
// entities. Otherwise, we can just return an empty list. The
// pipeline is async, so it is wrapped in a task, but most
// operations are not (or are both).
return entities.ToAsyncEnumerable();
}
// If we are chaining this pipeline into another, we return the
// entities. Otherwise, we can just return an empty list. The
// pipeline is async, so it is wrapped in a task, but most
// operations are not (or are both).
return entities.ToAsyncEnumerable();
}
}

View file

@ -1,8 +1,6 @@
using System.IO;
using System.Threading.Tasks;
using Autofac;
using MfGames.IO.Extensions;
using MfGames.Nitride;
using MfGames.Nitride.IO.Setup;
@ -15,41 +13,41 @@ namespace NitrideCopyFiles;
/// </summary>
public static class CopyFilesProgram
{
public static async Task<int> Main(string[] args)
{
// All of the builder methods are fluent in that they return the
// builder to allow them to be chained. However, for documentation
// purposes, we are going to split them apart to explain the details.
var builder = new NitrideBuilder(args, ConfigureNitride);
public static async Task<int> Main(string[] args)
{
// All of the builder methods are fluent in that they return the
// builder to allow them to be chained. However, for documentation
// purposes, we are going to split them apart to explain the details.
var builder = new NitrideBuilder(args, ConfigureNitride);
// Filesystem access is provided by Zio, which allows for mutliple
// folders to be combined together into a single unified file
// system. At the moment, we set the "root" directory which will
// contains all the paths, both input and output.
//
// Like Serilog, we use a number of extension methods on the builder
// to inject functionality such as plugins and extensions. In this
// case, we only need the Nitride.IO module so we use the `UseIO`
// to inject the requisite modules and configure it.
DirectoryInfo rootDir = typeof(CopyFilesProgram)
.GetDirectory()!
.FindGitRoot()!
.GetDirectory("examples/NitrideCopyFiles");
// Filesystem access is provided by Zio, which allows for mutliple
// folders to be combined together into a single unified file
// system. At the moment, we set the "root" directory which will
// contains all the paths, both input and output.
//
// Like Serilog, we use a number of extension methods on the builder
// to inject functionality such as plugins and extensions. In this
// case, we only need the Nitride.IO module so we use the `UseIO`
// to inject the requisite modules and configure it.
DirectoryInfo rootDir = typeof(CopyFilesProgram)
.GetDirectory()!
.FindGitRoot()!
.GetDirectory("examples/NitrideCopyFiles");
builder.UseIO(rootDir);
builder.UseIO(rootDir);
// We use Autofac for the bulk of our registration handling. This
// was mainly because we are more comfortable with Autofac, but it
// also has a clean interface for handling some of the more esoteric
// problems we've encountered.
builder.ConfigureContainer(x => x.RegisterModule<CopyFilesModule>());
// We use Autofac for the bulk of our registration handling. This
// was mainly because we are more comfortable with Autofac, but it
// also has a clean interface for handling some of the more esoteric
// problems we've encountered.
builder.ConfigureContainer(x => x.RegisterModule<CopyFilesModule>());
// Finally, we build the site generator object and run it.
return await builder.RunAsync();
}
// Finally, we build the site generator object and run it.
return await builder.RunAsync();
}
private static void ConfigureNitride(NitrideConfiguration config)
{
config.AddLogPipelineCommandLineOption = true;
}
private static void ConfigureNitride(NitrideConfiguration config)
{
config.AddLogPipelineCommandLineOption = true;
}
}

View file

@ -4,16 +4,16 @@ namespace NitridePipelines;
public class NitridePipelinesModule : Module
{
/// <inheritdoc />
protected override void Load(ContainerBuilder builder)
{
// This just registers all the non-static classes as singletons
// within the system. We use lifetimes in other components depending
// on how they are used, but in this case, we don't need it.
builder
.RegisterAssemblyTypes(this.GetType().Assembly)
.AsSelf()
.AsImplementedInterfaces()
.SingleInstance();
}
/// <inheritdoc />
protected override void Load(ContainerBuilder builder)
{
// This just registers all the non-static classes as singletons
// within the system. We use lifetimes in other components depending
// on how they are used, but in this case, we don't need it.
builder
.RegisterAssemblyTypes(this.GetType().Assembly)
.AsSelf()
.AsImplementedInterfaces()
.SingleInstance();
}
}

View file

@ -1,8 +1,6 @@
using System.IO;
using System.Threading.Tasks;
using Autofac;
using MfGames.IO.Extensions;
using MfGames.Nitride;
using MfGames.Nitride.IO.Setup;
@ -15,22 +13,21 @@ namespace NitridePipelines;
/// </summary>
public static class NitridePipelinesProgram
{
public static async Task<int> Main(string[] args)
{
DirectoryInfo rootDir = typeof(NitridePipelinesProgram)
.GetDirectory()!
.FindGitRoot()!
.GetDirectory("examples/NitridePipelines");
public static async Task<int> Main(string[] args)
{
DirectoryInfo rootDir = typeof(NitridePipelinesProgram)
.GetDirectory()!
.FindGitRoot()!
.GetDirectory("examples/NitridePipelines");
return await new NitrideBuilder(args, ConfigureNitride)
.UseIO(rootDir)
.ConfigureContainer(
x => x.RegisterModule<NitridePipelinesModule>())
.RunAsync();
}
return await new NitrideBuilder(args, ConfigureNitride)
.UseIO(rootDir)
.ConfigureContainer(x => x.RegisterModule<NitridePipelinesModule>())
.RunAsync();
}
private static void ConfigureNitride(NitrideConfiguration config)
{
config.AddLogPipelineCommandLineOption = true;
}
private static void ConfigureNitride(NitrideConfiguration config)
{
config.AddLogPipelineCommandLineOption = true;
}
}

View file

@ -2,46 +2,38 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MfGames.Gallium;
using MfGames.Nitride.Pipelines;
using Serilog;
using Zio;
namespace NitridePipelines.Pipelines;
public class DelayPipeline1 : PipelineBase
{
private readonly ILogger logger;
private readonly ILogger logger;
public DelayPipeline1(
ILogger logger,
InputPipeline1 input1)
{
this.logger = logger.ForContext<DelayPipeline1>();
this.AddDependency(input1);
}
public DelayPipeline1(ILogger logger, InputPipeline1 input1)
{
this.logger = logger.ForContext<DelayPipeline1>();
this.AddDependency(input1);
}
/// <inheritdoc />
public override IAsyncEnumerable<Entity> RunAsync(
IEnumerable<Entity> entities,
CancellationToken cancellationToken = default)
{
entities = entities
.Select(
entity =>
{
Task.Delay(1000, cancellationToken).Wait(cancellationToken);
/// <inheritdoc />
public override IAsyncEnumerable<Entity> RunAsync(
IEnumerable<Entity> entities,
CancellationToken cancellationToken = default
)
{
entities = entities.Select(entity =>
{
Task.Delay(1000, cancellationToken).Wait(cancellationToken);
this.logger.Information(
"Delayed {Value}",
entity.Get<UPath>());
this.logger.Information("Delayed {Value}", entity.Get<UPath>());
return entity;
});
return entity;
});
return entities.ToAsyncEnumerable();
}
return entities.ToAsyncEnumerable();
}
}

View file

@ -2,53 +2,43 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MfGames.Gallium;
using MfGames.Nitride.IO;
using MfGames.Nitride.IO.Contents;
using Serilog;
using Zio;
namespace NitridePipelines.Pipelines;
public class InputPipeline1 : FileSystemWatchablePipelineBase
{
private readonly ReadFiles readFiles;
private readonly ReadFiles readFiles;
public InputPipeline1(
ILogger logger,
IFileSystem fileSystem,
ReadFiles readFiles)
: base(logger, fileSystem)
{
this.readFiles = readFiles
.WithPattern("/input/input1/*.txt");
}
public InputPipeline1(ILogger logger, IFileSystem fileSystem, ReadFiles readFiles)
: base(logger, fileSystem)
{
this.readFiles = readFiles.WithPattern("/input/input1/*.txt");
}
/// <inheritdoc />
protected override UPath WatchPath => "/input/input1";
/// <inheritdoc />
protected override UPath WatchPath => "/input/input1";
/// <inheritdoc />
public override IAsyncEnumerable<Entity> RunAsync(
IEnumerable<Entity> _,
CancellationToken cancellationToken = default)
{
IEnumerable<Entity> entities = this.readFiles
.Run()
.Select(
entity =>
{
Task.Delay(1000, cancellationToken).Wait(cancellationToken);
/// <inheritdoc />
public override IAsyncEnumerable<Entity> RunAsync(
IEnumerable<Entity> _,
CancellationToken cancellationToken = default
)
{
IEnumerable<Entity> entities = this.readFiles.Run()
.Select(entity =>
{
Task.Delay(1000, cancellationToken).Wait(cancellationToken);
this.Logger.Information(
"Read {Value}",
entity.Get<UPath>());
this.Logger.Information("Read {Value}", entity.Get<UPath>());
return entity;
});
return entity;
});
return entities.ToAsyncEnumerable();
}
return entities.ToAsyncEnumerable();
}
}

View file

@ -2,56 +2,46 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MfGames.Gallium;
using MfGames.Nitride.IO;
using MfGames.Nitride.IO.Contents;
using Serilog;
using Zio;
namespace NitridePipelines.Pipelines;
public class InputPipeline2 : FileSystemWatchablePipelineBase
{
private readonly ILogger logger;
private readonly ILogger logger;
private readonly ReadFiles readFiles;
private readonly ReadFiles readFiles;
public InputPipeline2(
ILogger logger,
IFileSystem fileSystem,
ReadFiles readFiles)
: base(logger, fileSystem)
{
this.logger = logger.ForContext<InputPipeline2>();
this.readFiles = readFiles
.WithPattern("/input/input2/*.txt");
}
public InputPipeline2(ILogger logger, IFileSystem fileSystem, ReadFiles readFiles)
: base(logger, fileSystem)
{
this.logger = logger.ForContext<InputPipeline2>();
this.readFiles = readFiles.WithPattern("/input/input2/*.txt");
}
/// <inheritdoc />
protected override UPath WatchPath => "/input/input2";
/// <inheritdoc />
protected override UPath WatchPath => "/input/input2";
/// <inheritdoc />
public override IAsyncEnumerable<Entity> RunAsync(
IEnumerable<Entity> _,
CancellationToken cancellationToken = default)
{
IEnumerable<Entity> entities = this.readFiles
.Run()
.Select(
entity =>
{
Task.Delay(1000, cancellationToken).Wait(cancellationToken);
/// <inheritdoc />
public override IAsyncEnumerable<Entity> RunAsync(
IEnumerable<Entity> _,
CancellationToken cancellationToken = default
)
{
IEnumerable<Entity> entities = this.readFiles.Run()
.Select(entity =>
{
Task.Delay(1000, cancellationToken).Wait(cancellationToken);
this.logger.Information(
"Read {Value}",
entity.Get<UPath>());
this.logger.Information("Read {Value}", entity.Get<UPath>());
return entity;
});
return entity;
});
return entities.ToAsyncEnumerable();
}
return entities.ToAsyncEnumerable();
}
}

View file

@ -2,47 +2,38 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MfGames.Gallium;
using MfGames.Nitride.Pipelines;
using Serilog;
using Zio;
namespace NitridePipelines.Pipelines;
public class OutputPipeline1 : PipelineBase
{
private readonly ILogger logger;
private readonly ILogger logger;
public OutputPipeline1(
ILogger logger,
DelayPipeline1 delay1,
InputPipeline2 input2)
{
this.logger = logger.ForContext<OutputPipeline1>();
this.AddDependency(delay1, input2);
}
public OutputPipeline1(ILogger logger, DelayPipeline1 delay1, InputPipeline2 input2)
{
this.logger = logger.ForContext<OutputPipeline1>();
this.AddDependency(delay1, input2);
}
/// <inheritdoc />
public override IAsyncEnumerable<Entity> RunAsync(
IEnumerable<Entity> entities,
CancellationToken cancellationToken = default)
{
entities = entities
.Select(
entity =>
{
Task.Delay(1000, cancellationToken).Wait(cancellationToken);
/// <inheritdoc />
public override IAsyncEnumerable<Entity> RunAsync(
IEnumerable<Entity> entities,
CancellationToken cancellationToken = default
)
{
entities = entities.Select(entity =>
{
Task.Delay(1000, cancellationToken).Wait(cancellationToken);
this.logger.Information(
"Pretended to write {Value}",
entity.Get<UPath>());
this.logger.Information("Pretended to write {Value}", entity.Get<UPath>());
return entity;
});
return entity;
});
return entities.ToAsyncEnumerable();
}
return entities.ToAsyncEnumerable();
}
}

View file

@ -2,46 +2,38 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MfGames.Gallium;
using MfGames.Nitride.Pipelines;
using Serilog;
using Zio;
namespace NitridePipelines.Pipelines;
public class OutputPipeline2 : PipelineBase
{
private readonly ILogger logger;
private readonly ILogger logger;
public OutputPipeline2(
ILogger logger,
InputPipeline2 input2)
{
this.logger = logger.ForContext<OutputPipeline2>();
this.AddDependency(input2);
}
public OutputPipeline2(ILogger logger, InputPipeline2 input2)
{
this.logger = logger.ForContext<OutputPipeline2>();
this.AddDependency(input2);
}
/// <inheritdoc />
public override IAsyncEnumerable<Entity> RunAsync(
IEnumerable<Entity> entities,
CancellationToken cancellationToken = default)
{
entities = entities
.Select(
entity =>
{
Task.Delay(1000, cancellationToken).Wait(cancellationToken);
/// <inheritdoc />
public override IAsyncEnumerable<Entity> RunAsync(
IEnumerable<Entity> entities,
CancellationToken cancellationToken = default
)
{
entities = entities.Select(entity =>
{
Task.Delay(1000, cancellationToken).Wait(cancellationToken);
this.logger.Information(
"Pretended to write {Value}",
entity.Get<UPath>());
this.logger.Information("Pretended to write {Value}", entity.Get<UPath>());
return entity;
});
return entity;
});
return entities.ToAsyncEnumerable();
}
return entities.ToAsyncEnumerable();
}
}

View file

@ -2,88 +2,77 @@ using System;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.Threading.Tasks;
using MfGames.ToolBuilder;
using MfGames.ToolBuilder.Config;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace SampleTool.Commands;
public class ConfigCommand : Command, ICommandHandler, ITopCommand
{
private readonly ILogger<ConfigCommand> logger;
private readonly ILogger<ConfigCommand> logger;
private readonly ConfigToolService service;
private readonly ConfigToolService service;
private readonly Option<string> setOption;
private readonly Option<string> setOption;
/// <inheritdoc />
public ConfigCommand(
ILoggerFactory loggerFactory,
ConfigToolService service)
: base(
"config",
"Sets and displays the configuration settings")
{
this.logger = loggerFactory.CreateLogger<ConfigCommand>();
this.service = service;
this.Handler = this;
/// <inheritdoc />
public ConfigCommand(ILoggerFactory loggerFactory, ConfigToolService service)
: base("config", "Sets and displays the configuration settings")
{
this.logger = loggerFactory.CreateLogger<ConfigCommand>();
this.service = service;
this.Handler = this;
this.setOption = new Option<string>(
"--set",
"Sets the text value in the setting");
this.setOption = new Option<string>("--set", "Sets the text value in the setting");
this.AddOption(this.setOption);
}
this.AddOption(this.setOption);
}
/// <inheritdoc />
public int Invoke(InvocationContext context)
{
return this.InvokeAsync(context).Result;
}
/// <inheritdoc />
public int Invoke(InvocationContext context)
{
return this.InvokeAsync(context).Result;
}
/// <inheritdoc />
public Task<int> InvokeAsync(InvocationContext context)
{
// Get the settings and report that it was being created.
ConfigCommandSettings settings =
this.service.ReadDefaultConfigFile<ConfigCommandSettings>();
/// <inheritdoc />
public Task<int> InvokeAsync(InvocationContext context)
{
// Get the settings and report that it was being created.
ConfigCommandSettings settings =
this.service.ReadDefaultConfigFile<ConfigCommandSettings>();
if (settings == null)
{
this.logger.LogInformation("Creating configuration file");
settings = new ConfigCommandSettings();
}
if (settings == null)
{
this.logger.LogInformation("Creating configuration file");
settings = new ConfigCommandSettings();
}
// If we have a set command, then provide it.
string value = context.ParseResult
.GetValueForOption(this.setOption);
// If we have a set command, then provide it.
string value = context.ParseResult.GetValueForOption(this.setOption);
if (value != null)
{
settings.Value = value;
}
if (value != null)
{
settings.Value = value;
}
// Increment the counter.
settings.TimesRead++;
// Increment the counter.
settings.TimesRead++;
// Report the values.
Console.WriteLine(
JsonConvert.SerializeObject(settings, Formatting.Indented));
// Report the values.
Console.WriteLine(JsonConvert.SerializeObject(settings, Formatting.Indented));
// Write out the settings.
this.service.WriteDefaultConfigFile(settings);
// Write out the settings.
this.service.WriteDefaultConfigFile(settings);
return Task.FromResult(0);
}
return Task.FromResult(0);
}
private class ConfigCommandSettings
{
public int TimesRead { get; set; }
private class ConfigCommandSettings
{
public int TimesRead { get; set; }
public string Value { get; set; }
}
public string Value { get; set; }
}
}

View file

@ -2,42 +2,40 @@ using System;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.Threading.Tasks;
using MfGames.ToolBuilder;
namespace SampleTool.Commands;
public class CrashCommand : Command, ICommandHandler, ITopCommand
{
private readonly Option<bool> messyOption;
private readonly Option<bool> messyOption;
/// <inheritdoc />
public CrashCommand()
: base("crash", "Crash the application with an exception")
{
this.Handler = this;
this.messyOption = new Option<bool>("--messy");
/// <inheritdoc />
public CrashCommand()
: base("crash", "Crash the application with an exception")
{
this.Handler = this;
this.messyOption = new Option<bool>("--messy");
this.AddOption(this.messyOption);
}
this.AddOption(this.messyOption);
}
/// <inheritdoc />
public int Invoke(InvocationContext context)
{
return this.InvokeAsync(context).Result;
}
/// <inheritdoc />
public int Invoke(InvocationContext context)
{
return this.InvokeAsync(context).Result;
}
/// <inheritdoc />
public Task<int> InvokeAsync(InvocationContext context)
{
bool messy = context.ParseResult.GetValueForOption(this.messyOption);
/// <inheritdoc />
public Task<int> InvokeAsync(InvocationContext context)
{
bool messy = context.ParseResult.GetValueForOption(this.messyOption);
if (messy)
{
throw new Exception(
"This command crashed messily as requested.");
}
if (messy)
{
throw new Exception("This command crashed messily as requested.");
}
throw new ToolException("This command crashed as requested.");
}
throw new ToolException("This command crashed as requested.");
}
}

View file

@ -1,97 +1,84 @@
using System.CommandLine;
using System.CommandLine.Invocation;
using System.Threading.Tasks;
using MfGames.ToolBuilder;
using Microsoft.Extensions.Logging;
using ILogger = Serilog.ILogger;
namespace SampleTool.Commands;
public class LogCommand : Command, ICommandHandler, ITopCommand
{
private readonly ILogger<LogCommand> extensionLogger;
private readonly ILogger<LogCommand> extensionLogger;
private readonly ILogger serilogContextLogger;
private readonly ILogger serilogContextLogger;
private readonly ILogger serilogLogger;
private readonly ILogger serilogLogger;
/// <inheritdoc />
public LogCommand(
ILoggerFactory loggerFactory,
ILogger serilogLogger)
: base(
"log",
"Shows various logging messages using Serilog and Microsoft")
{
this.serilogLogger = serilogLogger;
this.serilogContextLogger = serilogLogger.ForContext<LogCommand>();
this.extensionLogger = loggerFactory.CreateLogger<LogCommand>();
this.Handler = this;
}
/// <inheritdoc />
public LogCommand(ILoggerFactory loggerFactory, ILogger serilogLogger)
: base("log", "Shows various logging messages using Serilog and Microsoft")
{
this.serilogLogger = serilogLogger;
this.serilogContextLogger = serilogLogger.ForContext<LogCommand>();
this.extensionLogger = loggerFactory.CreateLogger<LogCommand>();
this.Handler = this;
}
/// <inheritdoc />
public int Invoke(InvocationContext context)
{
return this.InvokeAsync(context).Result;
}
/// <inheritdoc />
public int Invoke(InvocationContext context)
{
return this.InvokeAsync(context).Result;
}
/// <inheritdoc />
public Task<int> InvokeAsync(InvocationContext context)
{
// Show the serilog logging.
this.serilogLogger.Verbose("Serilog Verbose()");
this.serilogLogger.Debug("Serilog Debug()");
this.serilogLogger.Information("Serilog Information()");
this.serilogLogger.Warning("Serilog Warning()");
this.serilogLogger.Error("Serilog Error()");
this.serilogLogger.Fatal("Serilog Fatal()");
/// <inheritdoc />
public Task<int> InvokeAsync(InvocationContext context)
{
// Show the serilog logging.
this.serilogLogger.Verbose("Serilog Verbose()");
this.serilogLogger.Debug("Serilog Debug()");
this.serilogLogger.Information("Serilog Information()");
this.serilogLogger.Warning("Serilog Warning()");
this.serilogLogger.Error("Serilog Error()");
this.serilogLogger.Fatal("Serilog Fatal()");
// Show serilog with context.
this.serilogContextLogger.Information(
"Serilog Information() with context");
// Show serilog with context.
this.serilogContextLogger.Information("Serilog Information() with context");
// Show the extension logging.
this.extensionLogger.LogTrace(
"System.Extension.Logging LogTrace");
this.extensionLogger.LogDebug(
"System.Extension.Logging LogDebug");
this.extensionLogger.LogInformation(
"System.Extension.Logging LogInformation");
this.extensionLogger.LogWarning(
"System.Extension.Logging LogWarning");
this.extensionLogger.LogError(
"System.Extension.Logging LogError");
this.extensionLogger.LogCritical(
"System.Extension.Logging LogCritical");
// Show the extension logging.
this.extensionLogger.LogTrace("System.Extension.Logging LogTrace");
this.extensionLogger.LogDebug("System.Extension.Logging LogDebug");
this.extensionLogger.LogInformation("System.Extension.Logging LogInformation");
this.extensionLogger.LogWarning("System.Extension.Logging LogWarning");
this.extensionLogger.LogError("System.Extension.Logging LogError");
this.extensionLogger.LogCritical("System.Extension.Logging LogCritical");
// Show Serilog working through logging extensions.
var hash = new
{
Number = 1,
String = "String",
Inner = new { Nested = true }
};
// Show Serilog working through logging extensions.
var hash = new
{
Number = 1,
String = "String",
Inner = new { Nested = true }
};
this.serilogLogger.Information(
"Serilog Contextual parameters {Name} and {Quotes:l}",
"extension logger",
"without quotes");
this.serilogLogger.Information(
"Serilog Contextual nested object {@Nested}",
hash);
this.serilogLogger.Information(
"Serilog Contextual parameters {Name} and {Quotes:l}",
"extension logger",
"without quotes"
);
this.serilogLogger.Information("Serilog Contextual nested object {@Nested}", hash);
this.extensionLogger.LogInformation(
"System.Extension.Logging parameters {Name} and {Quotes:l}",
"extension logger",
"without quotes");
this.extensionLogger.LogInformation(
"System.Extension.Logging nested object {@Nested}",
hash);
this.extensionLogger.LogInformation(
"System.Extension.Logging parameters {Name} and {Quotes:l}",
"extension logger",
"without quotes"
);
this.extensionLogger.LogInformation(
"System.Extension.Logging nested object {@Nested}",
hash
);
// We're good.
return Task.FromResult(0);
}
// We're good.
return Task.FromResult(0);
}
}

View file

@ -11,81 +11,77 @@ using System.CommandLine;
using System.CommandLine.Invocation;
using System.Threading;
using System.Threading.Tasks;
using MfGames.ToolBuilder;
using Microsoft.Extensions.Logging;
using Spectre.Console;
namespace SampleTool.Commands;
public class SpectreCommand : Command, ICommandHandler, ITopCommand
{
private readonly ILogger<LogCommand> logger;
private readonly ILogger<LogCommand> logger;
/// <inheritdoc />
public SpectreCommand(ILoggerFactory loggerFactory)
: base("spectre", "Shows various SpectreConsole features")
{
this.logger = loggerFactory.CreateLogger<LogCommand>();
this.Handler = this;
}
/// <inheritdoc />
public SpectreCommand(ILoggerFactory loggerFactory)
: base("spectre", "Shows various SpectreConsole features")
{
this.logger = loggerFactory.CreateLogger<LogCommand>();
this.Handler = this;
}
/// <inheritdoc />
public int Invoke(InvocationContext context)
{
return this.InvokeAsync(context).Result;
}
/// <inheritdoc />
public int Invoke(InvocationContext context)
{
return this.InvokeAsync(context).Result;
}
/// <inheritdoc />
public async Task<int> InvokeAsync(InvocationContext context)
{
// Display a message ahead of this.
this.logger.LogInformation("Before things happened.");
/// <inheritdoc />
public async Task<int> InvokeAsync(InvocationContext context)
{
// Display a message ahead of this.
this.logger.LogInformation("Before things happened.");
// Show a progress bar.
CancellationToken cancellationToken = context.GetCancellationToken();
DateTime last = DateTime.UtcNow;
// Show a progress bar.
CancellationToken cancellationToken = context.GetCancellationToken();
DateTime last = DateTime.UtcNow;
await AnsiConsole.Progress()
.AutoClear(true)
.StartAsync(
async ctx =>
{
ProgressTask task1 = ctx.AddTask("[green]Fast Task[/]");
ProgressTask task2 = ctx.AddTask("[green]Slow Task[/]");
await AnsiConsole
.Progress()
.AutoClear(true)
.StartAsync(async ctx =>
{
ProgressTask task1 = ctx.AddTask("[green]Fast Task[/]");
ProgressTask task2 = ctx.AddTask("[green]Slow Task[/]");
while (!ctx.IsFinished)
{
// See if we're cancelled.
if (cancellationToken.IsCancellationRequested)
{
break;
}
while (!ctx.IsFinished)
{
// See if we're cancelled.
if (cancellationToken.IsCancellationRequested)
{
break;
}
// Simulate some work
await Task.Delay(25, cancellationToken);
// Simulate some work
await Task.Delay(25, cancellationToken);
if (DateTime.UtcNow - last
> TimeSpan.FromMilliseconds(500))
{
this.logger.LogWarning("Working...");
last = DateTime.UtcNow;
}
if (DateTime.UtcNow - last > TimeSpan.FromMilliseconds(500))
{
this.logger.LogWarning("Working...");
last = DateTime.UtcNow;
}
await Task.Delay(25, cancellationToken);
await Task.Delay(25, cancellationToken);
// Increment
task1.Increment(2.0);
task2.Increment(1.0);
}
});
// Increment
task1.Increment(2.0);
task2.Increment(1.0);
}
});
// Report we're done.
this.logger.LogInformation("Done");
// Report we're done.
this.logger.LogInformation("Done");
return 0;
}
return 0;
}
}
#endif

View file

@ -3,7 +3,6 @@ using System.CommandLine;
using System.CommandLine.Invocation;
using System.Data;
using System.Threading.Tasks;
using MfGames.ToolBuilder;
using MfGames.ToolBuilder.Tables;
@ -11,49 +10,46 @@ namespace SampleTool.Commands;
public class TableCommand : Command, ICommandHandler, ITopCommand
{
private readonly DataTable table;
private readonly DataTable table;
private readonly TableToolService tableService;
private readonly TableToolService tableService;
/// <inheritdoc />
public TableCommand(TableToolService.Factory tableService)
: base("table", "Display a Markdown table")
{
// Create the table structure.
this.table = new DataTable();
this.table.Columns.Add("DefaultString", typeof(string));
this.table.Columns.Add("DefaultInt32", typeof(int));
this.table.Columns.Add("HiddenString", typeof(string));
/// <inheritdoc />
public TableCommand(TableToolService.Factory tableService)
: base("table", "Display a Markdown table")
{
// Create the table structure.
this.table = new DataTable();
this.table.Columns.Add("DefaultString", typeof(string));
this.table.Columns.Add("DefaultInt32", typeof(int));
this.table.Columns.Add("HiddenString", typeof(string));
// Create the table service for formatting and displaying results.
this.tableService = tableService(
this.table,
new List<string>
{
"DefaultString",
"DefaultInt32",
})
.Attach(this);
// Create the table service for formatting and displaying results.
this.tableService = tableService(
this.table,
new List<string> { "DefaultString", "DefaultInt32", }
)
.Attach(this);
// This class handles the command.
this.Handler = this;
}
// This class handles the command.
this.Handler = this;
}
/// <inheritdoc />
public int Invoke(InvocationContext context)
{
return this.InvokeAsync(context).Result;
}
/// <inheritdoc />
public int Invoke(InvocationContext context)
{
return this.InvokeAsync(context).Result;
}
/// <inheritdoc />
public Task<int> InvokeAsync(InvocationContext context)
{
this.table.Rows.Add("Row 1", 1, "Hidden 1");
this.table.Rows.Add("Row 2", 10, "Hidden 2");
this.table.Rows.Add("Row 3", 100, "Hidden 3");
/// <inheritdoc />
public Task<int> InvokeAsync(InvocationContext context)
{
this.table.Rows.Add("Row 1", 1, "Hidden 1");
this.table.Rows.Add("Row 2", 10, "Hidden 2");
this.table.Rows.Add("Row 3", 100, "Hidden 3");
this.tableService.Write(context);
this.tableService.Write(context);
return Task.FromResult(0);
}
return Task.FromResult(0);
}
}

View file

@ -1,7 +1,5 @@
using System.Threading.Tasks;
using Autofac;
using MfGames.ToolBuilder;
using MfGames.ToolBuilder.Config;
using MfGames.ToolBuilder.Tables;
@ -10,19 +8,19 @@ namespace SampleTool;
public static class Program
{
public static async Task<int> Main(string[] args)
{
return await ToolBoxBuilder
.Create(args)
.UseUserConfiguration("mfgames-toolbuilder-sample")
.ConfigureContainer(ConfigureContainer)
.Build()
.RunAsync();
}
public static async Task<int> Main(string[] args)
{
return await ToolBoxBuilder
.Create(args)
.UseUserConfiguration("mfgames-toolbuilder-sample")
.ConfigureContainer(ConfigureContainer)
.Build()
.RunAsync();
}
private static void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterModule<SampleToolModule>();
builder.RegisterModule<ToolBuilderTablesModule>();
}
private static void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterModule<SampleToolModule>();
builder.RegisterModule<ToolBuilderTablesModule>();
}
}

View file

@ -2,9 +2,7 @@ using System;
using System.Collections.Generic;
using System.CommandLine;
using System.Linq;
using Autofac;
using MfGames.ToolBuilder;
namespace SampleTool;
@ -14,44 +12,38 @@ namespace SampleTool;
/// </summary>
public class SampleToolModule : Module
{
/// <inheritdoc />
protected override void Load(ContainerBuilder builder)
{
builder
.RegisterAssemblyTypes(this.GetType().Assembly)
.AsSelf()
.AsImplementedInterfaces();
/// <inheritdoc />
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(this.GetType().Assembly).AsSelf().AsImplementedInterfaces();
builder
.Register(
c =>
{
// Create the top-level command.
var root = new RootCommand
{
Name = "sample-tool",
Description =
"A sample tool that demonstrates functionality",
};
builder
.Register(c =>
{
// Create the top-level command.
var root = new RootCommand
{
Name = "sample-tool",
Description = "A sample tool that demonstrates functionality",
};
// Add in the top-level commands.
var commandList = c.Resolve<IList<ITopCommand>>()
.Cast<Command>()
.ToList();
// Add in the top-level commands.
var commandList = c.Resolve<IList<ITopCommand>>().Cast<Command>().ToList();
if (commandList.Count == 0)
{
throw new InvalidOperationException(
"Cannot create a tool without at least one command extending System.CommandLine.Command");
}
if (commandList.Count == 0)
{
throw new InvalidOperationException(
"Cannot create a tool without at least one command extending System.CommandLine.Command"
);
}
foreach (Command command in commandList)
{
root.AddCommand(command);
}
foreach (Command command in commandList)
{
root.AddCommand(command);
}
return root;
})
.AsSelf();
}
return root;
})
.AsSelf();
}
}

View file

@ -1,136 +1,12 @@
{
"nodes": {
"blank": {
"locked": {
"lastModified": 1625557891,
"narHash": "sha256-O8/MWsPBGhhyPoPLHZAuoZiiHo9q6FLlEeIDEXuj6T4=",
"owner": "divnix",
"repo": "blank",
"rev": "5a5d2684073d9f563072ed07c871d577a6c614a8",
"type": "github"
},
"original": {
"owner": "divnix",
"repo": "blank",
"type": "github"
}
},
"crane": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils_2",
"nixpkgs": [
"std",
"paisano-mdbook-preprocessor",
"nixpkgs"
],
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1676162383,
"narHash": "sha256-krUCKdz7ebHlFYm/A7IbKDnj2ZmMMm3yIEQcooqm7+E=",
"owner": "ipetkov",
"repo": "crane",
"rev": "6fb400ec631b22ccdbc7090b38207f7fb5cfb5f2",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"devshell": {
"inputs": {
"flake-utils": [
"std",
"flake-utils"
],
"nixpkgs": [
"std",
"nixpkgs"
]
},
"locked": {
"lastModified": 1682700442,
"narHash": "sha256-qjaAAcCYgp1pBBG7mY9z95ODUBZMtUpf0Qp3Gt/Wha0=",
"owner": "numtide",
"repo": "devshell",
"rev": "fb6673fe9fe4409e3f43ca86968261e970918a83",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"dmerge": {
"inputs": {
"haumea": "haumea",
"nixlib": "nixlib",
"yants": [
"std",
"yants"
]
},
"locked": {
"lastModified": 1686862774,
"narHash": "sha256-ojGtRQ9pIOUrxsQEuEPerUkqIJEuod9hIflfNkY+9CE=",
"owner": "divnix",
"repo": "dmerge",
"rev": "9f7f7a8349d33d7bd02e0f2b484b1f076e503a96",
"type": "github"
},
"original": {
"owner": "divnix",
"ref": "0.2.1",
"repo": "dmerge",
"type": "github"
}
},
"fenix": {
"inputs": {
"nixpkgs": "nixpkgs_2",
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1677306201,
"narHash": "sha256-VZ9x7qdTosFvVsrpgFHrtYfT6PU3yMIs7NRYn9ELapI=",
"owner": "nix-community",
"repo": "fenix",
"rev": "0923f0c162f65ae40261ec940406049726cfeab4",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"locked": {
"lastModified": 1659877975,
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
"lastModified": 1653893745,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github"
},
"original": {
@ -141,11 +17,11 @@
},
"flake-utils_2": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"lastModified": 1653893745,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github"
},
"original": {
@ -154,97 +30,296 @@
"type": "github"
}
},
"haumea": {
"inputs": {
"nixpkgs": [
"std",
"dmerge",
"nixlib"
]
},
"flake-utils_3": {
"locked": {
"lastModified": 1685133229,
"narHash": "sha256-FePm/Gi9PBSNwiDFq3N+DWdfxFq0UKsVVTJS3cQPn94=",
"owner": "nix-community",
"repo": "haumea",
"rev": "34dd58385092a23018748b50f9b23de6266dffc2",
"lastModified": 1653893745,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "v0.2.2",
"repo": "haumea",
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"incl": {
"inputs": {
"nixlib": [
"std",
"dmerge",
"nixlib"
]
},
"flake-utils_4": {
"locked": {
"lastModified": 1669263024,
"narHash": "sha256-E/+23NKtxAqYG/0ydYgxlgarKnxmDbg6rCMWnOBqn9Q=",
"owner": "divnix",
"repo": "incl",
"rev": "ce7bebaee048e4cd7ebdb4cee7885e00c4e2abca",
"lastModified": 1653893745,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github"
},
"original": {
"owner": "divnix",
"repo": "incl",
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"n2c": {
"inputs": {
"flake-utils": [
"std",
"flake-utils"
],
"nixpkgs": [
"std",
"nixpkgs"
]
},
"flake-utils_5": {
"locked": {
"lastModified": 1685771919,
"narHash": "sha256-3lVKWrhNXjHJB6QkZ2SJaOs4X/mmYXtY6ovPVpDMOHc=",
"owner": "nlewo",
"repo": "nix2container",
"rev": "95e2220911874064b5d809f8d35f7835184c4ddf",
"lastModified": 1653893745,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github"
},
"original": {
"owner": "nlewo",
"repo": "nix2container",
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_6": {
"locked": {
"lastModified": 1653893745,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_7": {
"locked": {
"lastModified": 1653893745,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_8": {
"locked": {
"lastModified": 1653893745,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_9": {
"locked": {
"lastModified": 1653893745,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"mfgames-project-setup": {
"inputs": {
"nixago": "nixago",
"nixago-exts": "nixago-exts_3",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1709701991,
"narHash": "sha256-aioBatPisIMfmVUq50g9M74GidTqgRpA+PVXuL6NYAo=",
"ref": "refs/heads/main",
"rev": "9c50fe4984c346cec825c2875e1ba81ec385f5b1",
"revCount": 15,
"type": "git",
"url": "https://src.mfgames.com/nixos-contrib/mfgames-project-setup-flake.git"
},
"original": {
"type": "git",
"url": "https://src.mfgames.com/nixos-contrib/mfgames-project-setup-flake.git"
}
},
"nixago": {
"inputs": {
"flake-utils": [
"std",
"flake-utils"
],
"nixago-exts": [
"std",
"blank"
],
"flake-utils": "flake-utils",
"nixago-exts": "nixago-exts",
"nixpkgs": [
"std",
"mfgames-project-setup",
"nixpkgs"
]
},
"locked": {
"lastModified": 1683210100,
"narHash": "sha256-bhGDOlkWtlhVECpoOog4fWiFJmLCpVEg09a40aTjCbw=",
"lastModified": 1687381756,
"narHash": "sha256-IUMIlYfrvj7Yli4H2vvyig8HEPpfCeMaE6+kBGPzFyk=",
"owner": "jmgilman",
"repo": "nixago",
"rev": "dacceb10cace103b3e66552ec9719fa0d33c0dc9",
"type": "github"
},
"original": {
"owner": "jmgilman",
"repo": "nixago",
"type": "github"
}
},
"nixago-exts": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixago": "nixago_2",
"nixpkgs": [
"mfgames-project-setup",
"nixago",
"nixpkgs"
]
},
"locked": {
"lastModified": 1676070308,
"narHash": "sha256-QaJ65oc2l8iwQIGWUJ0EKjCeSuuCM/LqR8RauxZUUkc=",
"owner": "nix-community",
"repo": "nixago-extensions",
"rev": "e5380cb0456f4ea3c86cf94e3039eb856bf07d0b",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixago-extensions",
"type": "github"
}
},
"nixago-exts_2": {
"inputs": {
"flake-utils": "flake-utils_4",
"nixago": "nixago_3",
"nixpkgs": [
"mfgames-project-setup",
"nixago",
"nixago-exts",
"nixago",
"nixpkgs"
]
},
"locked": {
"lastModified": 1655508669,
"narHash": "sha256-BDDdo5dZQMmwNH/GNacy33nPBnCpSIydWFPZs0kkj/g=",
"owner": "nix-community",
"repo": "nixago-extensions",
"rev": "3022a932ce109258482ecc6568c163e8d0b426aa",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixago-extensions",
"type": "github"
}
},
"nixago-exts_3": {
"inputs": {
"flake-utils": "flake-utils_6",
"nixago": "nixago_4",
"nixpkgs": [
"mfgames-project-setup",
"nixpkgs"
]
},
"locked": {
"lastModified": 1676070308,
"narHash": "sha256-QaJ65oc2l8iwQIGWUJ0EKjCeSuuCM/LqR8RauxZUUkc=",
"owner": "nix-community",
"repo": "nixago-extensions",
"rev": "e5380cb0456f4ea3c86cf94e3039eb856bf07d0b",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixago-extensions",
"type": "github"
}
},
"nixago-exts_4": {
"inputs": {
"flake-utils": "flake-utils_8",
"nixago": "nixago_5",
"nixpkgs": [
"mfgames-project-setup",
"nixago-exts",
"nixago",
"nixpkgs"
]
},
"locked": {
"lastModified": 1655508669,
"narHash": "sha256-BDDdo5dZQMmwNH/GNacy33nPBnCpSIydWFPZs0kkj/g=",
"owner": "nix-community",
"repo": "nixago-extensions",
"rev": "3022a932ce109258482ecc6568c163e8d0b426aa",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixago-extensions",
"type": "github"
}
},
"nixago_2": {
"inputs": {
"flake-utils": "flake-utils_3",
"nixago-exts": "nixago-exts_2",
"nixpkgs": [
"mfgames-project-setup",
"nixago",
"nixago-exts",
"nixpkgs"
]
},
"locked": {
"lastModified": 1676070010,
"narHash": "sha256-iYzJIWptE1EUD8VINAg66AAMUajizg8JUYN3oBmb8no=",
"owner": "nix-community",
"repo": "nixago",
"rev": "1da60ad9412135f9ed7a004669fdcf3d378ec630",
"rev": "d480ba6c0c16e2c5c0bd2122852d6a0c9ad1ed0e",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "rename-config-data",
"repo": "nixago",
"type": "github"
}
},
"nixago_3": {
"inputs": {
"flake-utils": "flake-utils_5",
"nixpkgs": [
"mfgames-project-setup",
"nixago",
"nixago-exts",
"nixago",
"nixago-exts",
"nixpkgs"
]
},
"locked": {
"lastModified": 1655405483,
"narHash": "sha256-Crd49aZWNrpczlRTOwWGfwBMsTUoG9vlHDKQC7cx264=",
"owner": "nix-community",
"repo": "nixago",
"rev": "e6a9566c18063db5b120e69e048d3627414e327d",
"type": "github"
},
"original": {
@ -253,284 +328,88 @@
"type": "github"
}
},
"nixlib": {
"nixago_4": {
"inputs": {
"flake-utils": "flake-utils_7",
"nixago-exts": "nixago-exts_4",
"nixpkgs": [
"mfgames-project-setup",
"nixago-exts",
"nixpkgs"
]
},
"locked": {
"lastModified": 1681001314,
"narHash": "sha256-5sDnCLdrKZqxLPK4KA8+f4A3YKO/u6ElpMILvX0g72c=",
"lastModified": 1676070010,
"narHash": "sha256-iYzJIWptE1EUD8VINAg66AAMUajizg8JUYN3oBmb8no=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "367c0e1086a4eb4502b24d872cea2c7acdd557f4",
"repo": "nixago",
"rev": "d480ba6c0c16e2c5c0bd2122852d6a0c9ad1ed0e",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"ref": "rename-config-data",
"repo": "nixago",
"type": "github"
}
},
"nixago_5": {
"inputs": {
"flake-utils": "flake-utils_9",
"nixpkgs": [
"mfgames-project-setup",
"nixago-exts",
"nixago",
"nixago-exts",
"nixpkgs"
]
},
"locked": {
"lastModified": 1655405483,
"narHash": "sha256-Crd49aZWNrpczlRTOwWGfwBMsTUoG9vlHDKQC7cx264=",
"owner": "nix-community",
"repo": "nixago",
"rev": "e6a9566c18063db5b120e69e048d3627414e327d",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixago",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1693565476,
"narHash": "sha256-ya00zHt7YbPo3ve/wNZ/6nts61xt7wK/APa6aZAfey0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "aa8aa7e2ea35ce655297e8322dc82bf77a31d04b",
"type": "github"
"lastModified": 1706098335,
"narHash": "sha256-r3dWjT8P9/Ah5m5ul4WqIWD8muj5F+/gbCdjiNVBKmU=",
"rev": "a77ab169a83a4175169d78684ddd2e54486ac651",
"revCount": 554858,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2311.554858%2Brev-a77ab169a83a4175169d78684ddd2e54486ac651/018d46f0-798f-71dc-a8c5-4689c46f7d12/source.tar.gz"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-unstable",
"type": "indirect"
"type": "tarball",
"url": "https://flakehub.com/f/NixOS/nixpkgs/%2A.tar.gz"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1677063315,
"narHash": "sha256-qiB4ajTeAOVnVSAwCNEEkoybrAlA+cpeiBxLobHndE8=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "988cc958c57ce4350ec248d2d53087777f9e1949",
"type": "github"
"lastModified": 1709569716,
"narHash": "sha256-iOR44RU4jQ+YPGrn+uQeYAp7Xo7Z/+gT+wXJoGxxLTY=",
"rev": "617579a787259b9a6419492eaac670a5f7663917",
"revCount": 556422,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2311.556422%2Brev-617579a787259b9a6419492eaac670a5f7663917/018e0df2-b0f7-7a27-af1a-04150ef0f2c7/source.tar.gz"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nosys": {
"locked": {
"lastModified": 1668010795,
"narHash": "sha256-JBDVBnos8g0toU7EhIIqQ1If5m/nyBqtHhL3sicdPwI=",
"owner": "divnix",
"repo": "nosys",
"rev": "feade0141487801c71ff55623b421ed535dbdefa",
"type": "github"
},
"original": {
"owner": "divnix",
"repo": "nosys",
"type": "github"
}
},
"paisano": {
"inputs": {
"nixpkgs": [
"std",
"nixpkgs"
],
"nosys": "nosys",
"yants": [
"std",
"yants"
]
},
"locked": {
"lastModified": 1686862844,
"narHash": "sha256-m8l/HpRBJnZ3c0F1u0IyQ3nYGWE0R9V5kfORuqZPzgk=",
"owner": "paisano-nix",
"repo": "core",
"rev": "6674b3d3577212c1eeecd30d62d52edbd000e726",
"type": "github"
},
"original": {
"owner": "paisano-nix",
"ref": "0.1.1",
"repo": "core",
"type": "github"
}
},
"paisano-actions": {
"inputs": {
"nixpkgs": [
"std",
"paisano-mdbook-preprocessor",
"nixpkgs"
]
},
"locked": {
"lastModified": 1677306424,
"narHash": "sha256-H9/dI2rGEbKo4KEisqbRPHFG2ajF8Tm111NPdKGIf28=",
"owner": "paisano-nix",
"repo": "actions",
"rev": "65ec4e080b3480167fc1a748c89a05901eea9a9b",
"type": "github"
},
"original": {
"owner": "paisano-nix",
"repo": "actions",
"type": "github"
}
},
"paisano-mdbook-preprocessor": {
"inputs": {
"crane": "crane",
"fenix": "fenix",
"nixpkgs": [
"std",
"nixpkgs"
],
"paisano-actions": "paisano-actions",
"std": [
"std"
]
},
"locked": {
"lastModified": 1680654400,
"narHash": "sha256-Qdpio+ldhUK3zfl22Mhf8HUULdUOJXDWDdO7MIK69OU=",
"owner": "paisano-nix",
"repo": "mdbook-paisano-preprocessor",
"rev": "11a8fc47f574f194a7ae7b8b98001f6143ba4cf1",
"type": "github"
},
"original": {
"owner": "paisano-nix",
"repo": "mdbook-paisano-preprocessor",
"type": "github"
}
},
"paisano-tui": {
"inputs": {
"nixpkgs": [
"std",
"blank"
],
"std": [
"std"
]
},
"locked": {
"lastModified": 1681847764,
"narHash": "sha256-mdd7PJW1BZvxy0cIKsPfAO+ohVl/V7heE5ZTAHzTdv8=",
"owner": "paisano-nix",
"repo": "tui",
"rev": "3096bad91cae73ab8ab3367d31f8a143d248a244",
"type": "github"
},
"original": {
"owner": "paisano-nix",
"ref": "0.1.1",
"repo": "tui",
"type": "github"
"type": "tarball",
"url": "https://flakehub.com/f/NixOS/nixpkgs/%2A.tar.gz"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"std": "std"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1677221702,
"narHash": "sha256-1M+58rC4eTCWNmmX0hQVZP20t3tfYNunl9D/PrGUyGE=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "f5401f620699b26ed9d47a1d2e838143a18dbe3b",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": [
"std",
"paisano-mdbook-preprocessor",
"crane",
"flake-utils"
],
"nixpkgs": [
"std",
"paisano-mdbook-preprocessor",
"crane",
"nixpkgs"
]
},
"locked": {
"lastModified": 1675391458,
"narHash": "sha256-ukDKZw922BnK5ohL9LhwtaDAdCsJL7L6ScNEyF1lO9w=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "383a4acfd11d778d5c2efcf28376cbd845eeaedf",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"std": {
"inputs": {
"arion": [
"std",
"blank"
],
"blank": "blank",
"devshell": "devshell",
"dmerge": "dmerge",
"flake-utils": "flake-utils",
"incl": "incl",
"makes": [
"std",
"blank"
],
"microvm": [
"std",
"blank"
],
"n2c": "n2c",
"nixago": "nixago",
"nixpkgs": [
"nixpkgs"
],
"paisano": "paisano",
"paisano-mdbook-preprocessor": "paisano-mdbook-preprocessor",
"paisano-tui": "paisano-tui",
"yants": "yants"
},
"locked": {
"lastModified": 1686877329,
"narHash": "sha256-A/SU8KqlLP2MBuhi9wmt6gDhXyp+chCeDZ4OBxfSWBI=",
"owner": "divnix",
"repo": "std",
"rev": "aa6d423b82b7b7c2a4545693dea9ed1db14676e7",
"type": "github"
},
"original": {
"owner": "divnix",
"ref": "v0.23.2",
"repo": "std",
"type": "github"
}
},
"yants": {
"inputs": {
"nixpkgs": [
"std",
"dmerge",
"nixlib"
]
},
"locked": {
"lastModified": 1686863218,
"narHash": "sha256-kooxYm3/3ornWtVBNHM3Zh020gACUyFX2G0VQXnB+mk=",
"owner": "divnix",
"repo": "yants",
"rev": "8f0da0dba57149676aa4817ec0c880fbde7a648d",
"type": "github"
},
"original": {
"owner": "divnix",
"repo": "yants",
"type": "github"
"mfgames-project-setup": "mfgames-project-setup",
"nixpkgs": "nixpkgs_2"
}
}
},

View file

@ -1,26 +1,52 @@
{
description = "A variety of .NET core libraries used for development";
inputs = {
std.url = "github:divnix/std/v0.23.2";
std.inputs.nixpkgs.follows = "nixpkgs";
nixpkgs.url = "nixpkgs/nixos-unstable";
nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/*.tar.gz";
mfgames-project-setup.url = "git+https://src.mfgames.com/nixos-contrib/mfgames-project-setup-flake.git";
};
outputs = inputs @ {
self,
std,
...
}:
std.growOn {
inherit inputs;
systems = ["x86_64-linux"];
cellsFrom = ./nix;
cellBlocks = with std.blockTypes; [
(devshells "shells")
(nixago "configs")
];
} {
devShells = std.harvest self ["common" "shells"];
outputs = inputs @ { self, nixpkgs, mfgames-project-setup, ... }:
let
# Helpers for producing system-specific outputs
supportedSystems = [ "x86_64-linux" ];
forEachSupportedSystem = f: nixpkgs.lib.genAttrs supportedSystems (system: f {
inherit system;
pkgs = import nixpkgs { inherit system; };
});
in
rec
{
# Set up the developer shell.
devShells = forEachSupportedSystem ({ system, pkgs }:
let
project-config = mfgames-project-setup.lib.mkConfig {
inherit system pkgs;
contributorCovenant.enable = true;
contributorCovenant.contact = "contact@mfgames.com";
developerCertificateOfOrigin.enable = true;
dotnet.enable = true;
};
in
{
# Shell
default = pkgs.mkShell {
packages = [
pkgs.gnugrep
pkgs.gawk
pkgs.diffutils
pkgs.fd
pkgs.just
pkgs.lefthook
pkgs.jq
pkgs.dotnet-sdk
pkgs.git
]
++ project-config.packages;
shellHook = project-config.shellHook;
};
});
# Formatting for the Nix files
formatter = forEachSupportedSystem ({ pkgs, ... }: pkgs.nixpkgs-fmt);
};
}

View file

@ -1,262 +0,0 @@
{
inputs,
cell,
}: let
inherit (inputs) nixpkgs;
inherit (inputs.cells) std presets;
l = nixpkgs.lib // builtins;
in {
conform = {
data = {
commit = {
header = {length = 89;};
conventional = {
# Only allow these types of conventional commits (inspired by Angular)
types = [
"build"
"chore"
"ci"
"docs"
"feat"
"fix"
"perf"
"refactor"
"style"
"test"
];
};
};
};
};
editorconfig = {
hook.mode = "copy"; # already useful before entering the devshell
data = {
root = true;
"*" = {
# Common
end_of_line = "lf";
insert_final_newline = true;
trim_trailing_whitespace = true;
charset = "utf-8";
indent_style = "space";
indent_size = 4;
indent_brace_style = "K&R";
max_line_length = 80;
tab_width = 4;
curly_bracket_next_line = true;
# Microsoft .NET properties
csharp_new_line_before_members_in_object_initializers = false;
csharp_preferred_modifier_order = "public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion";
csharp_space_after_cast = false;
csharp_style_var_elsewhere = "false:hint";
csharp_style_var_for_built_in_types = "false:hint";
csharp_style_var_when_type_is_apparent = "true:hint";
csharp_preserve_single_line_statements = false;
csharp_preserve_single_line_blocks = true;
dotnet_style_predefined_type_for_locals_parameters_members = "true:hint";
dotnet_style_predefined_type_for_member_access = "true:hint";
dotnet_style_qualification_for_event = "true:hint";
dotnet_style_qualification_for_field = "true:hint";
dotnet_style_qualification_for_method = "true:hint";
dotnet_style_qualification_for_property = "true:hint";
dotnet_style_require_accessibility_modifiers = "for_non_interface_members:hint";
# ReSharper properties
resharper_alignment_tab_fill_style = "optimal_fill";
resharper_apply_on_completion = true;
resharper_blank_lines_after_control_transfer_statements = 1;
resharper_blank_lines_around_single_line_auto_property = 1;
resharper_blank_lines_around_single_line_property = 1;
resharper_blank_lines_before_single_line_comment = 1;
resharper_blank_lines_between_using_groups = 1;
resharper_braces_for_for = "required";
resharper_braces_for_foreach = "required";
resharper_braces_for_ifelse = "required";
resharper_braces_for_while = "required";
resharper_can_use_global_alias = false;
resharper_csharp_blank_lines_around_single_line_field = 1;
resharper_csharp_blank_lines_around_single_line_invocable = 1;
resharper_csharp_indent_style = "tab";
resharper_csharp_insert_final_newline = true;
resharper_csharp_keep_blank_lines_in_code = 1;
resharper_csharp_keep_blank_lines_in_declarations = 1;
resharper_csharp_new_line_before_while = true;
resharper_csharp_use_indent_from_vs = false;
resharper_csharp_wrap_arguments_style = "chop_if_long";
resharper_csharp_wrap_extends_list_style = "chop_if_long";
resharper_csharp_wrap_parameters_style = "chop_if_long";
resharper_css_insert_final_newline = false;
resharper_enforce_line_ending_style = true;
resharper_html_insert_final_newline = false;
resharper_indent_nested_fixed_stmt = true;
resharper_js_indent_style = "tab";
resharper_js_insert_final_newline = true;
resharper_js_keep_blank_lines_in_code = 1;
resharper_js_stick_comment = false;
resharper_js_use_indent_from_vs = false;
resharper_js_wrap_before_binary_opsign = true;
resharper_js_wrap_chained_method_calls = "chop_if_long";
resharper_keep_blank_lines_between_declarations = 1;
resharper_min_blank_lines_after_imports = 1;
resharper_place_attribute_on_same_line = false;
resharper_place_constructor_initializer_on_same_line = false;
resharper_place_type_constraints_on_same_line = false;
resharper_protobuf_insert_final_newline = false;
resharper_qualified_using_at_nested_scope = true;
resharper_resx_insert_final_newline = false;
resharper_space_within_single_line_array_initializer_braces = true;
resharper_use_indents_from_main_language_in_file = false;
resharper_vb_insert_final_newline = false;
resharper_wrap_after_declaration_lpar = true;
resharper_wrap_after_invocation_lpar = true;
resharper_wrap_before_extends_colon = true;
resharper_wrap_before_first_type_parameter_constraint = true;
resharper_wrap_before_type_parameter_langle = true;
resharper_xmldoc_indent_child_elements = "ZeroIndent";
resharper_xmldoc_indent_text = "ZeroIndent";
resharper_xmldoc_insert_final_newline = false;
resharper_xml_insert_final_newline = false;
# ReSharper inspection severities
resharper_check_namespace_highlighting = "none";
resharper_convert_to_auto_property_highlighting = "none";
resharper_localizable_element_highlighting = "none";
resharper_redundant_comma_in_attribute_list_highlighting = "none";
resharper_redundant_comma_in_enum_declaration_highlighting = "none";
resharper_redundant_comma_in_initializer_highlighting = "none";
resharper_string_compare_to_is_culture_specific_highlighting = "none";
resharper_string_index_of_is_culture_specific_1_highlighting = "none";
resharper_use_null_propagation_highlighting = "none";
resharper_use_object_or_collection_initializer_highlighting = "hint";
resharper_use_string_interpolation_highlighting = "hint";
};
"*.{diff,patch}" = {
end_of_line = "unset";
insert_final_newline = "unset";
trim_trailing_whitespace = "unset";
indent_size = "unset";
};
"*.md" = {
max_line_length = "off";
trim_trailing_whitespace = false;
};
"package.json" = {
indent_style = "space";
indent_size = 2;
tab_width = 2;
};
"*.{appxmanifest,build,config,csproj,dbml,discomap,dtd,jsproj,lsproj,njsproj,nuspec,proj,props,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}" = {
indent_style = "space";
indent_size = 2;
tab_width = 2;
};
"{LICENSES/**,LICENSE}" = {
end_of_line = "unset";
insert_final_newline = "unset";
trim_trailing_whitespace = "unset";
charset = "unset";
indent_style = "unset";
indent_size = "unset";
};
};
};
lefthook = {
data = {
commit-msg = {
commands = {
# Runs conform on commit-msg hook to ensure commit messages are
# compliant.
conform = {
run = "${nixpkgs.conform}/bin/conform enforce --commit-msg-file {1}";
};
};
};
pre-commit = {
commands = {
# Runs treefmt on pre-commit hook to ensure checked-in source code is
# properly formatted.
treefmt = {
run = "${nixpkgs.treefmt}/bin/treefmt {staged_files}";
};
};
};
};
};
prettier = {
data = {
printWidth = 80;
proseWrap = "always";
};
output = ".prettierrc";
format = "json";
};
treefmt = {
data = {
formatter = {
dotnet = {
command = "dotnet";
options = ["jb" "cleanupcode"];
includes = ["*.cs"];
};
nix = {
command = "alejandra";
includes = ["*.nix" "*.nix.hbs"];
};
prettier = {
command = "prettier";
# 2023-09-02 DREM: Removed "--plugin" "prettier-plugin-toml"
options = ["--write"];
includes = [
"*.css"
"*.html"
"*.js"
"*.json"
"*.jsx"
"*.md"
"*.mdx"
"*.scss"
"*.ts"
"*.yaml"
#"*.toml"
];
};
shell = {
command = "shfmt";
options = ["-i" "4" "-s" "-w"];
includes = ["*.sh"];
};
# We mainly use it here to format the Markdown in our README.
prettier = {
excludes = ["**.min.js"];
};
};
};
packages = [
nixpkgs.alejandra
nixpkgs.nodePackages.prettier
nixpkgs.nodePackages.prettier-plugin-toml
nixpkgs.shfmt
nixpkgs.go
];
#devshell.startup.prettier-plugin-toml = l.stringsWithDeps.noDepEntry ''
# export NODE_PATH=${nixpkgs.nodePackages.prettier-plugin-toml}/lib/node_modules:$NODE_PATH
#'';
};
}

View file

@ -1,45 +0,0 @@
{
inputs,
cell,
}: let
inherit (inputs.std) std lib;
inherit (inputs) nixpkgs;
inherit (inputs.cells) cli;
l = nixpkgs.lib // builtins;
dev = lib.dev.mkShell {
packages = [
# Linux
nixpkgs.gnugrep
nixpkgs.gawk
nixpkgs.diffutils
nixpkgs.fd
# Building
nixpkgs.just
nixpkgs.lefthook
nixpkgs.jq
# .NET
nixpkgs.dotnet-sdk
# Nix
nixpkgs.nixfmt
nixpkgs.alejandra
# Git
nixpkgs.git
];
nixago = [
(lib.cfg.conform cell.configs.conform)
(lib.cfg.treefmt cell.configs.treefmt)
(lib.cfg.editorconfig cell.configs.editorconfig)
(lib.cfg.lefthook cell.configs.lefthook)
];
};
in {
inherit dev;
default = dev;
}

View file

@ -2,18 +2,18 @@ namespace MfGames.Crypto;
public enum ByteStringFormat
{
/// <summary>
/// Indicates that the format should be lowercase hex characters.
/// </summary>
LowercaseHex,
/// <summary>
/// Indicates that the format should be lowercase hex characters.
/// </summary>
LowercaseHex,
/// <summary>
/// Indicates that the format should be uppercase hex characters.
/// </summary>
UppercaseHex,
/// <summary>
/// Indicates that the format should be uppercase hex characters.
/// </summary>
UppercaseHex,
/// <summary>
/// Indicates that the format should be Base64.
/// </summary>
Base64,
/// <summary>
/// Indicates that the format should be Base64.
/// </summary>
Base64,
}

View file

@ -7,67 +7,62 @@ namespace MfGames.Crypto.Extensions;
/// </summary>
public static class CryptoByteArrayExtensions
{
/// <summary>
/// Converts the input into a hash string, such as hex or base64.
/// </summary>
/// <param name="input"></param>
/// <param name="format"></param>
/// <returns></returns>
public static string? ToByteString(
this byte[]? input,
ByteStringFormat format = ByteStringFormat.LowercaseHex)
{
if (input == null)
{
return null;
}
/// <summary>
/// Converts the input into a hash string, such as hex or base64.
/// </summary>
/// <param name="input"></param>
/// <param name="format"></param>
/// <returns></returns>
public static string? ToByteString(
this byte[]? input,
ByteStringFormat format = ByteStringFormat.LowercaseHex
)
{
if (input == null)
{
return null;
}
switch (format)
{
case ByteStringFormat.LowercaseHex:
return Convert.ToHexString(input).ToLowerInvariant();
switch (format)
{
case ByteStringFormat.LowercaseHex:
return Convert.ToHexString(input).ToLowerInvariant();
case ByteStringFormat.UppercaseHex:
return Convert.ToHexString(input);
case ByteStringFormat.UppercaseHex:
return Convert.ToHexString(input);
case ByteStringFormat.Base64:
return Convert.ToBase64String(input);
case ByteStringFormat.Base64:
return Convert.ToBase64String(input);
default:
throw new ArgumentOutOfRangeException(
nameof(format),
format,
null);
}
}
default:
throw new ArgumentOutOfRangeException(nameof(format), format, null);
}
}
/// <summary>
/// Hashes the given input and returns the results.
/// </summary>
/// <param name="input">A byte array or null.</param>
/// <param name="hash">The type of hash requested.</param>
/// <returns>Null if input is null, otherwise the hash value.</returns>
public static byte[]? ToHashBytes(
this byte[]? input,
HashType hash = HashType.Sha512)
{
return input == null
? null
: hash.CreateHash().ComputeHash(input);
}
/// <summary>
/// Hashes the given input and returns the results.
/// </summary>
/// <param name="input">A byte array or null.</param>
/// <param name="hash">The type of hash requested.</param>
/// <returns>Null if input is null, otherwise the hash value.</returns>
public static byte[]? ToHashBytes(this byte[]? input, HashType hash = HashType.Sha512)
{
return input == null ? null : hash.CreateHash().ComputeHash(input);
}
/// <summary>
/// Hashes the given input and returns the results as a string.
/// </summary>
/// <param name="input">A byte array or null.</param>
/// <param name="hash">The type of hash requested.</param>
/// <param name="format">The format of the requested string.</param>
/// <returns>Null if input is null, otherwise the hash value.</returns>
public static string? ToHashString(
this byte[]? input,
HashType hash = HashType.Sha512,
ByteStringFormat format = ByteStringFormat.LowercaseHex)
{
return input?.ToHashBytes(hash)?.ToByteString(format);
}
/// <summary>
/// Hashes the given input and returns the results as a string.
/// </summary>
/// <param name="input">A byte array or null.</param>
/// <param name="hash">The type of hash requested.</param>
/// <param name="format">The format of the requested string.</param>
/// <returns>Null if input is null, otherwise the hash value.</returns>
public static string? ToHashString(
this byte[]? input,
HashType hash = HashType.Sha512,
ByteStringFormat format = ByteStringFormat.LowercaseHex
)
{
return input?.ToHashBytes(hash)?.ToByteString(format);
}
}

View file

@ -1,5 +1,4 @@
using System.Text;
using MfGames.Crypto.Hashes;
namespace MfGames.Crypto.Extensions;
@ -9,54 +8,50 @@ namespace MfGames.Crypto.Extensions;
/// </summary>
public static class CryptoStringExtensions
{
/// <summary>
/// Gets the encoded byte array of the given string.
/// </summary>
/// <param name="input">The input string or null.</param>
/// <param name="encoding">The encoding to use, defaults to UTF-8.</param>
/// <returns>Null if the input was null, otherwise the byte array.</returns>
public static byte[]? ToBytes(
this string? input,
Encoding? encoding = null)
{
encoding ??= Encoding.UTF8;
/// <summary>
/// Gets the encoded byte array of the given string.
/// </summary>
/// <param name="input">The input string or null.</param>
/// <param name="encoding">The encoding to use, defaults to UTF-8.</param>
/// <returns>Null if the input was null, otherwise the byte array.</returns>
public static byte[]? ToBytes(this string? input, Encoding? encoding = null)
{
encoding ??= Encoding.UTF8;
return input == null
? null
: encoding.GetBytes(input);
}
return input == null ? null : encoding.GetBytes(input);
}
/// <summary>
/// Hashes the given input and returns the results.
/// </summary>
/// <param name="input">A byte array or null.</param>
/// <param name="hash">The type of hash requested.</param>
/// <param name="encoding">The encoding to use, defaults to UTF-8.</param>
/// <returns>Null if input is null, otherwise the hash value.</returns>
public static byte[]? ToHashBytes(
this string? input,
HashType hash = HashType.Sha512,
Encoding? encoding = null)
{
return input == null
? null
: hash.CreateHash().ComputeHash(input.ToBytes(encoding)!);
}
/// <summary>
/// Hashes the given input and returns the results.
/// </summary>
/// <param name="input">A byte array or null.</param>
/// <param name="hash">The type of hash requested.</param>
/// <param name="encoding">The encoding to use, defaults to UTF-8.</param>
/// <returns>Null if input is null, otherwise the hash value.</returns>
public static byte[]? ToHashBytes(
this string? input,
HashType hash = HashType.Sha512,
Encoding? encoding = null
)
{
return input == null ? null : hash.CreateHash().ComputeHash(input.ToBytes(encoding)!);
}
/// <summary>
/// Hashes the given input and returns the results as a string.
/// </summary>
/// <param name="input">A byte array or null.</param>
/// <param name="hash">The type of hash requested.</param>
/// <param name="format">The format of the requested string.</param>
/// <param name="encoding">The encoding to use, defaults to UTF-8.</param>
/// <returns>Null if input is null, otherwise the hash value.</returns>
public static string? ToHashString(
this string? input,
HashType hash = HashType.Sha512,
ByteStringFormat format = ByteStringFormat.LowercaseHex,
Encoding? encoding = null)
{
return input?.ToHashBytes(hash, encoding)?.ToByteString(format);
}
/// <summary>
/// Hashes the given input and returns the results as a string.
/// </summary>
/// <param name="input">A byte array or null.</param>
/// <param name="hash">The type of hash requested.</param>
/// <param name="format">The format of the requested string.</param>
/// <param name="encoding">The encoding to use, defaults to UTF-8.</param>
/// <returns>Null if input is null, otherwise the hash value.</returns>
public static string? ToHashString(
this string? input,
HashType hash = HashType.Sha512,
ByteStringFormat format = ByteStringFormat.LowercaseHex,
Encoding? encoding = null
)
{
return input?.ToHashBytes(hash, encoding)?.ToByteString(format);
}
}

View file

@ -5,23 +5,23 @@ namespace MfGames.Crypto.Hashes;
/// </summary>
public enum HashType
{
/// <summary>
/// Indicates a SHA-512 hash.
/// </summary>
Sha512,
/// <summary>
/// Indicates a SHA-512 hash.
/// </summary>
Sha512,
/// <summary>
/// Indicates a SHA-256 hash.
/// </summary>
Sha256,
/// <summary>
/// Indicates a SHA-256 hash.
/// </summary>
Sha256,
/// <summary>
/// Indicates a SHA-1 hash.
/// </summary>
Sha1,
/// <summary>
/// Indicates a SHA-1 hash.
/// </summary>
Sha1,
/// <summary>
/// Indicates a MD5 hash.
/// </summary>
Md5,
/// <summary>
/// Indicates a MD5 hash.
/// </summary>
Md5,
}

View file

@ -4,30 +4,30 @@ namespace MfGames.Crypto.Hashes;
public static class HashTypeExtensions
{
/// <summary>
/// Constructs a hash of the given type.
/// </summary>
/// <param name="type">The type of hash to create.</param>
/// <returns>A hash algorithm of the given type.</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static HashAlgorithm CreateHash(this HashType type)
{
switch (type)
{
case HashType.Sha512:
return SHA512.Create();
/// <summary>
/// Constructs a hash of the given type.
/// </summary>
/// <param name="type">The type of hash to create.</param>
/// <returns>A hash algorithm of the given type.</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static HashAlgorithm CreateHash(this HashType type)
{
switch (type)
{
case HashType.Sha512:
return SHA512.Create();
case HashType.Sha256:
return SHA256.Create();
case HashType.Sha256:
return SHA256.Create();
case HashType.Sha1:
return SHA1.Create();
case HashType.Sha1:
return SHA1.Create();
case HashType.Md5:
return MD5.Create();
case HashType.Md5:
return MD5.Create();
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
}

View file

@ -7,517 +7,497 @@ namespace MfGames.Gallium;
/// </summary>
public record Entity
{
public Entity()
: this(Interlocked.Increment(ref nextId))
{
}
public Entity()
: this(Interlocked.Increment(ref nextId)) { }
private Entity(int id)
{
this.Id = id;
this.Components = ImmutableDictionary.Create<Type, object>();
}
private Entity(int id)
{
this.Id = id;
this.Components = ImmutableDictionary.Create<Type, object>();
}
/// <summary>
/// Gets or sets the optional formatting for an entity. This is used to
/// override the ToString functionality to provide additional information.
/// If this is null, then the default "ToString" will be called.
/// </summary>
/// <remarks>
/// This must be a thread-safe function.
/// </remarks>
public static Func<Entity, string>? ToStringFormatter { get; set; }
/// <summary>
/// Gets or sets the optional formatting for an entity. This is used to
/// override the ToString functionality to provide additional information.
/// If this is null, then the default "ToString" will be called.
/// </summary>
/// <remarks>
/// This must be a thread-safe function.
/// </remarks>
public static Func<Entity, string>? ToStringFormatter { get; set; }
/// <inheritdoc />
public virtual bool Equals(Entity? other)
{
if (ReferenceEquals(null, other))
{
return false;
}
/// <inheritdoc />
public virtual bool Equals(Entity? other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
if (ReferenceEquals(this, other))
{
return true;
}
return this.Id == other.Id;
}
return this.Id == other.Id;
}
/// <inheritdoc />
public override int GetHashCode()
{
return this.Id.GetHashCode();
}
/// <inheritdoc />
public override int GetHashCode()
{
return this.Id.GetHashCode();
}
private ImmutableDictionary<Type, object> Components { get; init; }
private ImmutableDictionary<Type, object> Components { get; init; }
/// <summary>
/// The internal ID to ensure the entities are unique. Since we are not
/// worried about serialization or using the identifiers from one call
/// to another, we can use a simple interlocked identifier instead of
/// a factory or provider method.
/// </summary>
private static int nextId;
/// <summary>
/// The internal ID to ensure the entities are unique. Since we are not
/// worried about serialization or using the identifiers from one call
/// to another, we can use a simple interlocked identifier instead of
/// a factory or provider method.
/// </summary>
private static int nextId;
/// <summary>
/// Gets a value indicating whether the entity has a specific type of
/// component registered.
/// </summary>
/// <typeparam name="T1">The component type.</typeparam>
/// <returns>True if the type exists, otherwise false.</returns>
public bool Has<T1>()
{
return this.Has(typeof(T1));
}
/// <summary>
/// Gets a value indicating whether the entity has a specific type of
/// component registered.
/// </summary>
/// <typeparam name="T1">The component type.</typeparam>
/// <returns>True if the type exists, otherwise false.</returns>
public bool Has<T1>()
{
return this.Has(typeof(T1));
}
/// <summary>
/// Gets a value indicating whether the entity has components of the given types
/// registered.
/// </summary>
/// <typeparam name="T1">The first component type.</typeparam>
/// <typeparam name="T2">The second component type.</typeparam>
/// <returns>
/// True if there are components of the given type exists, otherwise
/// false.
/// </returns>
public bool HasAll<T1, T2>()
{
return this.HasAll(typeof(T1), typeof(T2));
}
/// <summary>
/// Gets a value indicating whether the entity has components of the given types
/// registered.
/// </summary>
/// <typeparam name="T1">The first component type.</typeparam>
/// <typeparam name="T2">The second component type.</typeparam>
/// <returns>
/// True if there are components of the given type exists, otherwise
/// false.
/// </returns>
public bool HasAll<T1, T2>()
{
return this.HasAll(typeof(T1), typeof(T2));
}
/// <summary>
/// Gets a value indicating whether the entity has components of the given types
/// registered.
/// </summary>
/// <typeparam name="T1">The first component type.</typeparam>
/// <typeparam name="T2">The second component type.</typeparam>
/// <typeparam name="T3">The third component type.</typeparam>
/// <returns>
/// True if there are components of the given type exists, otherwise
/// false.
/// </returns>
public bool HasAll<T1, T2, T3>()
{
return this.HasAll(typeof(T1), typeof(T2), typeof(T3));
}
/// <summary>
/// Gets a value indicating whether the entity has components of the given types
/// registered.
/// </summary>
/// <typeparam name="T1">The first component type.</typeparam>
/// <typeparam name="T2">The second component type.</typeparam>
/// <typeparam name="T3">The third component type.</typeparam>
/// <returns>
/// True if there are components of the given type exists, otherwise
/// false.
/// </returns>
public bool HasAll<T1, T2, T3>()
{
return this.HasAll(typeof(T1), typeof(T2), typeof(T3));
}
/// <summary>
/// Gets a value indicating whether the entity has components of the given types
/// registered.
/// </summary>
/// <typeparam name="T1">The first component type.</typeparam>
/// <typeparam name="T2">The second component type.</typeparam>
/// <typeparam name="T3">The third component type.</typeparam>
/// <typeparam name="T4">The third component type.</typeparam>
/// <returns>
/// True if there are components of the given type exists, otherwise
/// false.
/// </returns>
public bool HasAll<T1, T2, T3, T4>()
{
return this.HasAll(typeof(T1), typeof(T2), typeof(T3), typeof(T4));
}
/// <summary>
/// Gets a value indicating whether the entity has components of the given types
/// registered.
/// </summary>
/// <typeparam name="T1">The first component type.</typeparam>
/// <typeparam name="T2">The second component type.</typeparam>
/// <typeparam name="T3">The third component type.</typeparam>
/// <typeparam name="T4">The third component type.</typeparam>
/// <returns>
/// True if there are components of the given type exists, otherwise
/// false.
/// </returns>
public bool HasAll<T1, T2, T3, T4>()
{
return this.HasAll(typeof(T1), typeof(T2), typeof(T3), typeof(T4));
}
/// <summary>
/// Gets a value indicating whether the entity has a specific type of
/// component registered.
/// </summary>
/// <param name="type">The component type.</param>
/// <returns>True if the type exists, otherwise false.</returns>
public bool Has(Type type)
{
return this.Components.ContainsKey(type);
}
/// <summary>
/// Gets a value indicating whether the entity has a specific type of
/// component registered.
/// </summary>
/// <param name="type">The component type.</param>
/// <returns>True if the type exists, otherwise false.</returns>
public bool Has(Type type)
{
return this.Components.ContainsKey(type);
}
/// <summary>
/// Gets a value indicating whether the entity has components for all the given
/// types.
/// </summary>
/// <param name="t1">The component type.</param>
/// <param name="t2">The component type.</param>
/// <returns>True if the type exists, otherwise false.</returns>
public bool HasAll(
Type t1,
Type t2)
{
return this.Has(t1) && this.Components.ContainsKey(t2);
}
/// <summary>
/// Gets a value indicating whether the entity has components for all the given
/// types.
/// </summary>
/// <param name="t1">The component type.</param>
/// <param name="t2">The component type.</param>
/// <returns>True if the type exists, otherwise false.</returns>
public bool HasAll(Type t1, Type t2)
{
return this.Has(t1) && this.Components.ContainsKey(t2);
}
/// <summary>
/// Gets a value indicating whether the entity has components for all the given
/// types.
/// </summary>
/// <param name="t1">The component type.</param>
/// <param name="t2">The component type.</param>
/// <param name="t3">The component type.</param>
/// <returns>True if the type exists, otherwise false.</returns>
public bool HasAll(
Type t1,
Type t2,
Type t3)
{
return this.HasAll(t1, t2) && this.Components.ContainsKey(t3);
}
/// <summary>
/// Gets a value indicating whether the entity has components for all the given
/// types.
/// </summary>
/// <param name="t1">The component type.</param>
/// <param name="t2">The component type.</param>
/// <param name="t3">The component type.</param>
/// <returns>True if the type exists, otherwise false.</returns>
public bool HasAll(Type t1, Type t2, Type t3)
{
return this.HasAll(t1, t2) && this.Components.ContainsKey(t3);
}
/// <summary>
/// Gets a value indicating whether the entity has components for all the given
/// types.
/// </summary>
/// <param name="t1">The component type.</param>
/// <param name="t2">The component type.</param>
/// <param name="t3">The component type.</param>
/// <param name="t4">The component type.</param>
/// <returns>True if the type exists, otherwise false.</returns>
public bool HasAll(
Type t1,
Type t2,
Type t3,
Type t4)
{
return this.HasAll(t1, t2, t3) && this.Components.ContainsKey(t4);
}
/// <summary>
/// Gets a value indicating whether the entity has components for all the given
/// types.
/// </summary>
/// <param name="t1">The component type.</param>
/// <param name="t2">The component type.</param>
/// <param name="t3">The component type.</param>
/// <param name="t4">The component type.</param>
/// <returns>True if the type exists, otherwise false.</returns>
public bool HasAll(Type t1, Type t2, Type t3, Type t4)
{
return this.HasAll(t1, t2, t3) && this.Components.ContainsKey(t4);
}
/// <summary>
/// Retrieves a registered component of the given type.
/// </summary>
/// <typeparam name="TType">The component type.</typeparam>
/// <returns>The registered object.</returns>
public TType Get<TType>()
{
return (TType)this.Components[typeof(TType)];
}
/// <summary>
/// Retrieves a registered component of the given type.
/// </summary>
/// <typeparam name="TType">The component type.</typeparam>
/// <returns>The registered object.</returns>
public TType Get<TType>()
{
return (TType)this.Components[typeof(TType)];
}
/// <summary>
/// Retrieves a registered component of the given type and casts it to
/// TType.
/// </summary>
/// <param name="type">The component key.</param>
/// <typeparam name="TType">The component type.</typeparam>
/// <returns>The registered object.</returns>
public TType Get<TType>(Type type)
{
return (TType)this.Components[type];
}
/// <summary>
/// Retrieves a registered component of the given type and casts it to
/// TType.
/// </summary>
/// <param name="type">The component key.</param>
/// <typeparam name="TType">The component type.</typeparam>
/// <returns>The registered object.</returns>
public TType Get<TType>(Type type)
{
return (TType)this.Components[type];
}
/// <summary>
/// Gets the number of components registered in the entity.
/// </summary>
public int Count => this.Components.Count;
/// <summary>
/// Gets the number of components registered in the entity.
/// </summary>
public int Count => this.Components.Count;
/// <summary>
/// Gets the given component type if inside the entity, otherwise the
/// default value.
/// </summary>
/// <typeparam name="TType">The component type.</typeparam>
/// <returns>The found component or default (typically null).</returns>
public TType? GetOptional<TType>()
{
return this.Has<TType>() ? this.Get<TType>() : default;
}
/// <summary>
/// Gets the given component type if inside the entity, otherwise the
/// default value.
/// </summary>
/// <typeparam name="TType">The component type.</typeparam>
/// <returns>The found component or default (typically null).</returns>
public TType? GetOptional<TType>()
{
return this.Has<TType>() ? this.Get<TType>() : default;
}
/// <summary>
/// Attempts to get the value, if present. If not, this returns false
/// and the value is undefined. Otherwise, this method returns true
/// and the actual value inside that variable.
/// </summary>
/// <param name="value">The value if contained in the entity.</param>
/// <typeparam name="T1">The component type.</typeparam>
/// <returns>True if found, otherwise false.</returns>
public bool TryGet<T1>(out T1 value)
{
if (this.Has<T1>())
{
value = this.Get<T1>();
/// <summary>
/// Attempts to get the value, if present. If not, this returns false
/// and the value is undefined. Otherwise, this method returns true
/// and the actual value inside that variable.
/// </summary>
/// <param name="value">The value if contained in the entity.</param>
/// <typeparam name="T1">The component type.</typeparam>
/// <returns>True if found, otherwise false.</returns>
public bool TryGet<T1>(out T1 value)
{
if (this.Has<T1>())
{
value = this.Get<T1>();
return true;
}
return true;
}
value = default!;
value = default!;
return false;
}
return false;
}
/// <summary>
/// Attempts to get the values, if present. If not, this returns false
/// and the value is undefined. Otherwise, this method returns true
/// and the actual value inside that variable.
/// </summary>
/// <param name="value1">The value if contained in the entity.</param>
/// <param name="value2">The value if contained in the entity.</param>
/// <typeparam name="T1">The first component type.</typeparam>
/// <typeparam name="T2">The second component type.</typeparam>
/// <returns>True if found, otherwise false.</returns>
public bool TryGet<T1, T2>(
out T1 value1,
out T2 value2)
{
if (this.HasAll<T1, T2>())
{
value1 = this.Get<T1>();
value2 = this.Get<T2>();
/// <summary>
/// Attempts to get the values, if present. If not, this returns false
/// and the value is undefined. Otherwise, this method returns true
/// and the actual value inside that variable.
/// </summary>
/// <param name="value1">The value if contained in the entity.</param>
/// <param name="value2">The value if contained in the entity.</param>
/// <typeparam name="T1">The first component type.</typeparam>
/// <typeparam name="T2">The second component type.</typeparam>
/// <returns>True if found, otherwise false.</returns>
public bool TryGet<T1, T2>(out T1 value1, out T2 value2)
{
if (this.HasAll<T1, T2>())
{
value1 = this.Get<T1>();
value2 = this.Get<T2>();
return true;
}
return true;
}
value1 = default!;
value2 = default!;
value1 = default!;
value2 = default!;
return false;
}
return false;
}
/// <summary>
/// Attempts to get the values, if present. If not, this returns false
/// and the value is undefined. Otherwise, this method returns true
/// and the actual value inside that variable.
/// </summary>
/// <param name="value1">The value if contained in the entity.</param>
/// <param name="value2">The value if contained in the entity.</param>
/// <param name="value3">The value if contained in the entity.</param>
/// <typeparam name="T1">The first component type.</typeparam>
/// <typeparam name="T2">The second component type.</typeparam>
/// <typeparam name="T3">The third component type.</typeparam>
/// <returns>True if found, otherwise false.</returns>
public bool TryGet<T1, T2, T3>(
out T1 value1,
out T2 value2,
out T3 value3)
{
if (this.HasAll<T1, T2, T3>())
{
value1 = this.Get<T1>();
value2 = this.Get<T2>();
value3 = this.Get<T3>();
/// <summary>
/// Attempts to get the values, if present. If not, this returns false
/// and the value is undefined. Otherwise, this method returns true
/// and the actual value inside that variable.
/// </summary>
/// <param name="value1">The value if contained in the entity.</param>
/// <param name="value2">The value if contained in the entity.</param>
/// <param name="value3">The value if contained in the entity.</param>
/// <typeparam name="T1">The first component type.</typeparam>
/// <typeparam name="T2">The second component type.</typeparam>
/// <typeparam name="T3">The third component type.</typeparam>
/// <returns>True if found, otherwise false.</returns>
public bool TryGet<T1, T2, T3>(out T1 value1, out T2 value2, out T3 value3)
{
if (this.HasAll<T1, T2, T3>())
{
value1 = this.Get<T1>();
value2 = this.Get<T2>();
value3 = this.Get<T3>();
return true;
}
return true;
}
value1 = default!;
value2 = default!;
value3 = default!;
value1 = default!;
value2 = default!;
value3 = default!;
return false;
}
return false;
}
/// <summary>
/// Attempts to get the values, if present. If not, this returns false
/// and the value is undefined. Otherwise, this method returns true
/// and the actual value inside that variable.
/// </summary>
/// <param name="value1">The value if contained in the entity.</param>
/// <param name="value2">The value if contained in the entity.</param>
/// <param name="value3">The value if contained in the entity.</param>
/// <param name="value4">The value if contained in the entity.</param>
/// <typeparam name="T1">The first component type.</typeparam>
/// <typeparam name="T2">The second component type.</typeparam>
/// <typeparam name="T3">The third component type.</typeparam>
/// <typeparam name="T4">The fourth component type.</typeparam>
/// <returns>True if found, otherwise false.</returns>
public bool TryGet<T1, T2, T3, T4>(
out T1 value1,
out T2 value2,
out T3 value3,
out T4 value4)
{
if (this.HasAll<T1, T2, T3, T4>())
{
value1 = this.Get<T1>();
value2 = this.Get<T2>();
value3 = this.Get<T3>();
value4 = this.Get<T4>();
/// <summary>
/// Attempts to get the values, if present. If not, this returns false
/// and the value is undefined. Otherwise, this method returns true
/// and the actual value inside that variable.
/// </summary>
/// <param name="value1">The value if contained in the entity.</param>
/// <param name="value2">The value if contained in the entity.</param>
/// <param name="value3">The value if contained in the entity.</param>
/// <param name="value4">The value if contained in the entity.</param>
/// <typeparam name="T1">The first component type.</typeparam>
/// <typeparam name="T2">The second component type.</typeparam>
/// <typeparam name="T3">The third component type.</typeparam>
/// <typeparam name="T4">The fourth component type.</typeparam>
/// <returns>True if found, otherwise false.</returns>
public bool TryGet<T1, T2, T3, T4>(out T1 value1, out T2 value2, out T3 value3, out T4 value4)
{
if (this.HasAll<T1, T2, T3, T4>())
{
value1 = this.Get<T1>();
value2 = this.Get<T2>();
value3 = this.Get<T3>();
value4 = this.Get<T4>();
return true;
}
return true;
}
value1 = default!;
value2 = default!;
value3 = default!;
value4 = default!;
value1 = default!;
value2 = default!;
value3 = default!;
value4 = default!;
return false;
}
return false;
}
/// <summary>
/// Sets the component in the entity, regardless if there was a
/// component already registered.
/// </summary>
/// <param name="component">The component to register.</param>
/// <typeparam name="T1">The component type.</typeparam>
/// <returns>The entity for chaining.</returns>
/// <exception cref="ArgumentNullException"></exception>
public Entity Set<T1>(T1 component)
{
if (component == null)
{
throw new ArgumentNullException(nameof(component));
}
/// <summary>
/// Sets the component in the entity, regardless if there was a
/// component already registered.
/// </summary>
/// <param name="component">The component to register.</param>
/// <typeparam name="T1">The component type.</typeparam>
/// <returns>The entity for chaining.</returns>
/// <exception cref="ArgumentNullException"></exception>
public Entity Set<T1>(T1 component)
{
if (component == null)
{
throw new ArgumentNullException(nameof(component));
}
if (this.Components.TryGetValue(typeof(T1), out object? value)
&& value is T1
&& value.Equals(component))
{
return this;
}
if (
this.Components.TryGetValue(typeof(T1), out object? value)
&& value is T1
&& value.Equals(component)
)
{
return this;
}
return this with
{
Components = this.Components.SetItem(typeof(T1), component),
};
}
return this with
{
Components = this.Components.SetItem(typeof(T1), component),
};
}
/// <summary>
/// Sets zero or more components into an entity in a single call. This does
/// not allow for specifying the data type; each item will be added with
/// the result of `component.GetType()`.
/// </summary>
/// <param name="components">
/// The components to add to the entity. Any null objects
/// will be ignored.
/// </param>
/// <returns>
/// A new Entity with the modified component collection if there is at
/// least one component to set, otherwise the same entity.
/// </returns>
public Entity SetAll(params object?[] components)
{
if (components.Length == 0)
{
return this;
}
/// <summary>
/// Sets zero or more components into an entity in a single call. This does
/// not allow for specifying the data type; each item will be added with
/// the result of `component.GetType()`.
/// </summary>
/// <param name="components">
/// The components to add to the entity. Any null objects
/// will be ignored.
/// </param>
/// <returns>
/// A new Entity with the modified component collection if there is at
/// least one component to set, otherwise the same entity.
/// </returns>
public Entity SetAll(params object?[] components)
{
if (components.Length == 0)
{
return this;
}
ImmutableDictionary<Type, object> collection = this.Components;
ImmutableDictionary<Type, object> collection = this.Components;
foreach (object? component in components)
{
if (component != null)
{
collection = collection.SetItem(component.GetType(), component);
}
}
foreach (object? component in components)
{
if (component != null)
{
collection = collection.SetItem(component.GetType(), component);
}
}
return this with
{
Components = collection,
};
}
return this with
{
Components = collection,
};
}
/// <summary>
/// Adds a component to the entity.
/// </summary>
/// <param name="component">The component to register.</param>
/// <typeparam name="T1">The component type.</typeparam>
/// <returns>
/// The same entity if the component is already registered, otherwise a
/// cloned entity with the new component.
/// </returns>
/// <exception cref="ArgumentNullException"></exception>
public Entity Add<T1>(T1 component)
{
if (component == null)
{
throw new ArgumentNullException(nameof(component));
}
/// <summary>
/// Adds a component to the entity.
/// </summary>
/// <param name="component">The component to register.</param>
/// <typeparam name="T1">The component type.</typeparam>
/// <returns>
/// The same entity if the component is already registered, otherwise a
/// cloned entity with the new component.
/// </returns>
/// <exception cref="ArgumentNullException"></exception>
public Entity Add<T1>(T1 component)
{
if (component == null)
{
throw new ArgumentNullException(nameof(component));
}
if (this.Has<T1>())
{
throw new ArgumentException(
"An element with the same type ("
+ typeof(T1).FullName
+ ") already exists.",
nameof(component));
}
if (this.Has<T1>())
{
throw new ArgumentException(
"An element with the same type (" + typeof(T1).FullName + ") already exists.",
nameof(component)
);
}
if (this.Components.TryGetValue(typeof(T1), out object? value)
&& value is T1
&& value.Equals(component))
{
return this;
}
if (
this.Components.TryGetValue(typeof(T1), out object? value)
&& value is T1
&& value.Equals(component)
)
{
return this;
}
return this with
{
Components = this.Components.Add(typeof(T1), component)
};
}
return this with
{
Components = this.Components.Add(typeof(T1), component)
};
}
/// <summary>
/// Removes a component to the entity.
/// </summary>
/// <typeparam name="TType">The component type.</typeparam>
/// <returns>
/// The same entity if the component is already removed, otherwise a
/// cloned entity without the new component.
/// </returns>
/// <exception cref="ArgumentNullException"></exception>
public Entity Remove<TType>()
{
return this.Remove(typeof(TType));
}
/// <summary>
/// Removes a component to the entity.
/// </summary>
/// <typeparam name="TType">The component type.</typeparam>
/// <returns>
/// The same entity if the component is already removed, otherwise a
/// cloned entity without the new component.
/// </returns>
/// <exception cref="ArgumentNullException"></exception>
public Entity Remove<TType>()
{
return this.Remove(typeof(TType));
}
/// <summary>
/// Removes a component to the entity.
/// </summary>
/// <returns>
/// The same entity if the component is already removed, otherwise a
/// cloned entity without the new component.
/// </returns>
/// <param name="type">The component type to remove.</param>
public Entity Remove(Type type)
{
if (!this.Has(type))
{
return this;
}
/// <summary>
/// Removes a component to the entity.
/// </summary>
/// <returns>
/// The same entity if the component is already removed, otherwise a
/// cloned entity without the new component.
/// </returns>
/// <param name="type">The component type to remove.</param>
public Entity Remove(Type type)
{
if (!this.Has(type))
{
return this;
}
return this with
{
Components = this.Components.Remove(type)
};
}
return this with
{
Components = this.Components.Remove(type)
};
}
/// <summary>
/// Gets the identifier of the entity. This should be treated as an
/// opaque field.
/// </summary>
public int Id { get; private init; }
/// <summary>
/// Gets the identifier of the entity. This should be treated as an
/// opaque field.
/// </summary>
public int Id { get; private init; }
/// <summary>
/// Creates a copy of the entity, including copying the identifier.
/// </summary>
/// <returns></returns>
public Entity ExactCopy()
{
return this with { };
}
/// <summary>
/// Creates a copy of the entity, including copying the identifier.
/// </summary>
/// <returns></returns>
public Entity ExactCopy()
{
return this with { };
}
/// <summary>
/// Creates a copy of the entity, including components, but with a new
/// identifier.
/// </summary>
/// <returns></returns>
public Entity Copy()
{
return this with
{
Id = Interlocked.Increment(ref nextId)
};
}
/// <summary>
/// Creates a copy of the entity, including components, but with a new
/// identifier.
/// </summary>
/// <returns></returns>
public Entity Copy()
{
return this with { Id = Interlocked.Increment(ref nextId) };
}
/// <summary>
/// Retrieves a list of the component types currently registered in the
/// Entity.
/// </summary>
/// <returns>An enumerable of the various component keys.</returns>
public IEnumerable<Type> GetComponentTypes()
{
return this.Components.Keys;
}
/// <summary>
/// Retrieves a list of the component types currently registered in the
/// Entity.
/// </summary>
/// <returns>An enumerable of the various component keys.</returns>
public IEnumerable<Type> GetComponentTypes()
{
return this.Components.Keys;
}
/// <inheritdoc />
public override string ToString()
{
return ToStringFormatter == null
? $"Entity {this.Id} (Components {this.Components.Count:N0})"
: ToStringFormatter(this);
}
/// <inheritdoc />
public override string ToString()
{
return ToStringFormatter == null
? $"Entity {this.Id} (Components {this.Components.Count:N0})"
: ToStringFormatter(this);
}
}

View file

@ -2,23 +2,24 @@ namespace MfGames.Gallium;
public static class JoinEntityExtensions
{
/// <summary>
/// Merges two sets of entities using the identifier to determine which
/// entities are the same. The `merge` function takes both of the
/// entities with the Entity from the `input` first and the one from
/// `other` second. The returning entity is put into the collection. If
/// an entity from the input is not found in other, then it is just
/// passed on.
/// </summary>
/// <param name="input">The enumerable of entities to merge to.</param>
/// <param name="other">The collection of entities to merge from.</param>
/// <param name="merge">The callback to merge the two.</param>
/// <returns>An sequence of entities, merged and unmerged.</returns>
public static IEnumerable<Entity> JoinEntity(
this IEnumerable<Entity> input,
ICollection<Entity> other,
Func<Entity, Entity, Entity> merge)
{
return input.Join(other, a => a.Id, a => a.Id, merge);
}
/// <summary>
/// Merges two sets of entities using the identifier to determine which
/// entities are the same. The `merge` function takes both of the
/// entities with the Entity from the `input` first and the one from
/// `other` second. The returning entity is put into the collection. If
/// an entity from the input is not found in other, then it is just
/// passed on.
/// </summary>
/// <param name="input">The enumerable of entities to merge to.</param>
/// <param name="other">The collection of entities to merge from.</param>
/// <param name="merge">The callback to merge the two.</param>
/// <returns>An sequence of entities, merged and unmerged.</returns>
public static IEnumerable<Entity> JoinEntity(
this IEnumerable<Entity> input,
ICollection<Entity> other,
Func<Entity, Entity, Entity> merge
)
{
return input.Join(other, a => a.Id, a => a.Id, merge);
}
}

View file

@ -5,43 +5,40 @@ namespace MfGames.Gallium;
/// </summary>
public static class SelectComponentExtensions
{
/// <summary>
/// Retrieves a component from an entity and return it. If the entity does not have
/// the component, it will be
/// filtered out.
/// </summary>
/// <param name="entities">The entities to process.</param>
/// <typeparam name="T1">The component type being searched.</typeparam>
/// <returns>A sequence of T1.</returns>
public static IEnumerable<T1> SelectComponent<T1>(
this IEnumerable<Entity> entities)
{
foreach (Entity entity in entities)
{
if (entity.TryGet(out T1 v1))
{
yield return v1;
}
}
}
/// <summary>
/// Retrieves a component from an entity and return it. If the entity does not have
/// the component, it will be
/// filtered out.
/// </summary>
/// <param name="entities">The entities to process.</param>
/// <typeparam name="T1">The component type being searched.</typeparam>
/// <returns>A sequence of T1.</returns>
public static IEnumerable<T1> SelectComponent<T1>(this IEnumerable<Entity> entities)
{
foreach (Entity entity in entities)
{
if (entity.TryGet(out T1 v1))
{
yield return v1;
}
}
}
/// <summary>
/// Retrieves a component from an entity and return it. If the entity does not have
/// the component, it will be filtered out.
/// </summary>
/// <param name="entities">The entities to process.</param>
/// <param name="t1">The component type being searched.</param>
/// <returns>A sequence of T1.</returns>
public static IEnumerable<object> SelectComponent(
IEnumerable<Entity> entities,
Type t1)
{
foreach (Entity entity in entities)
{
if (entity.Has(t1))
{
yield return entity.Get<object>(t1);
}
}
}
/// <summary>
/// Retrieves a component from an entity and return it. If the entity does not have
/// the component, it will be filtered out.
/// </summary>
/// <param name="entities">The entities to process.</param>
/// <param name="t1">The component type being searched.</param>
/// <returns>A sequence of T1.</returns>
public static IEnumerable<object> SelectComponent(IEnumerable<Entity> entities, Type t1)
{
foreach (Entity entity in entities)
{
if (entity.Has(t1))
{
yield return entity.Get<object>(t1);
}
}
}
}

View file

@ -5,46 +5,43 @@ namespace MfGames.Gallium;
/// </summary>
public static class SelectComponentOrDefaultExtensions
{
/// <summary>
/// Retrieves a component from an entity and return it. If the entity does not have
/// the component, then null will be returned.
/// </summary>
/// <param name="entities">The entities to process.</param>
/// <param name="t1">The component type being searched.</param>
/// <returns>A sequence of T1 or nulls.</returns>
public static IEnumerable<object?> SelectComponent(
IEnumerable<Entity> entities,
Type t1)
{
foreach (Entity entity in entities)
{
if (entity.Has(t1))
{
yield return entity.Get<object>(t1);
}
/// <summary>
/// Retrieves a component from an entity and return it. If the entity does not have
/// the component, then null will be returned.
/// </summary>
/// <param name="entities">The entities to process.</param>
/// <param name="t1">The component type being searched.</param>
/// <returns>A sequence of T1 or nulls.</returns>
public static IEnumerable<object?> SelectComponent(IEnumerable<Entity> entities, Type t1)
{
foreach (Entity entity in entities)
{
if (entity.Has(t1))
{
yield return entity.Get<object>(t1);
}
yield return null;
}
}
yield return null;
}
}
/// <summary>
/// Retrieves a component from an entity and return it. If the entity does not have
/// the component, then the default value will be returned.
/// </summary>
/// <param name="entities">The entities to process.</param>
/// <typeparam name="T1">The component type being searched.</typeparam>
/// <returns>A sequence of T1.</returns>
public static IEnumerable<T1?> SelectComponentOrDefault<T1>(
this IEnumerable<Entity> entities)
{
foreach (Entity entity in entities)
{
if (entity.TryGet(out T1 v1))
{
yield return v1;
}
/// <summary>
/// Retrieves a component from an entity and return it. If the entity does not have
/// the component, then the default value will be returned.
/// </summary>
/// <param name="entities">The entities to process.</param>
/// <typeparam name="T1">The component type being searched.</typeparam>
/// <returns>A sequence of T1.</returns>
public static IEnumerable<T1?> SelectComponentOrDefault<T1>(this IEnumerable<Entity> entities)
{
foreach (Entity entity in entities)
{
if (entity.TryGet(out T1 v1))
{
yield return v1;
}
yield return default;
}
}
yield return default;
}
}
}

View file

@ -2,276 +2,279 @@ namespace MfGames.Gallium;
public static class SelectEntityExtensions
{
/// <summary>
/// Selects an entity from the given list, filtering on entities with
/// the given components.
/// </summary>
/// <param name="entities">The entities to parse.</param>
/// <param name="selectWithComponents">
/// The transformation function for the entity and selected components. If this
/// returns null, then the entity
/// will be filtered out.
/// </param>
/// <param name="includeEntitiesWithoutComponents">
/// If true, then entities without all the components are included. Otherwise, they
/// are excluded.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <returns>An enumeration of transformed entities.</returns>
public static IEnumerable<Entity> SelectEntity<T1>(
this IEnumerable<Entity> entities,
Func<Entity, T1, Entity?> selectWithComponents,
bool includeEntitiesWithoutComponents = true)
{
return entities.SelectEntity(
selectWithComponents,
includeEntitiesWithoutComponents ? a => a : a => null);
}
/// <summary>
/// Selects an entity from the given list, filtering on entities with
/// the given components.
/// </summary>
/// <param name="entities">The entities to parse.</param>
/// <param name="selectWithComponents">
/// The transformation function for the entity and selected components. If this
/// returns null, then the entity
/// will be filtered out.
/// </param>
/// <param name="includeEntitiesWithoutComponents">
/// If true, then entities without all the components are included. Otherwise, they
/// are excluded.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <returns>An enumeration of transformed entities.</returns>
public static IEnumerable<Entity> SelectEntity<T1>(
this IEnumerable<Entity> entities,
Func<Entity, T1, Entity?> selectWithComponents,
bool includeEntitiesWithoutComponents = true
)
{
return entities.SelectEntity(
selectWithComponents,
includeEntitiesWithoutComponents ? a => a : a => null
);
}
/// <summary>
/// Selects an entity from the given list, filtering on entities with
/// the given components.
/// </summary>
/// <param name="entities">The entities to parse.</param>
/// <param name="selectWithComponents">
/// The transformation function for the entity and selected components. If this
/// returns null, then the entity
/// will be filtered out.
/// </param>
/// <param name="includeEntitiesWithoutComponents">
/// If true, then entities without all the components are included. Otherwise, they
/// are excluded.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <typeparam name="T2">The type of the second component.</typeparam>
/// <returns>An enumeration of transformed entities.</returns>
public static IEnumerable<Entity> SelectEntity<T1, T2>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, Entity?> selectWithComponents,
bool includeEntitiesWithoutComponents = true)
{
return entities.SelectEntity(
selectWithComponents,
includeEntitiesWithoutComponents ? a => a : a => null);
}
/// <summary>
/// Selects an entity from the given list, filtering on entities with
/// the given components.
/// </summary>
/// <param name="entities">The entities to parse.</param>
/// <param name="selectWithComponents">
/// The transformation function for the entity and selected components. If this
/// returns null, then the entity
/// will be filtered out.
/// </param>
/// <param name="includeEntitiesWithoutComponents">
/// If true, then entities without all the components are included. Otherwise, they
/// are excluded.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <typeparam name="T2">The type of the second component.</typeparam>
/// <returns>An enumeration of transformed entities.</returns>
public static IEnumerable<Entity> SelectEntity<T1, T2>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, Entity?> selectWithComponents,
bool includeEntitiesWithoutComponents = true
)
{
return entities.SelectEntity(
selectWithComponents,
includeEntitiesWithoutComponents ? a => a : a => null
);
}
/// <summary>
/// Selects an entity from the given list, filtering on entities with
/// the given components.
/// </summary>
/// <param name="entities">The entities to parse.</param>
/// <param name="selectWithComponents">
/// The transformation function for the entity and selected components. If this
/// returns null, then the entity
/// will be filtered out.
/// </param>
/// <param name="includeEntitiesWithoutComponents">
/// If true, then entities without all the components are included. Otherwise, they
/// are excluded.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <typeparam name="T2">The type of the second component.</typeparam>
/// <typeparam name="T3">The type of the third component.</typeparam>
/// <returns>An enumeration of transformed entities.</returns>
public static IEnumerable<Entity> SelectEntity<T1, T2, T3>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, T3, Entity?> selectWithComponents,
bool includeEntitiesWithoutComponents = true)
{
return entities.SelectEntity(
selectWithComponents,
includeEntitiesWithoutComponents ? a => a : a => null);
}
/// <summary>
/// Selects an entity from the given list, filtering on entities with
/// the given components.
/// </summary>
/// <param name="entities">The entities to parse.</param>
/// <param name="selectWithComponents">
/// The transformation function for the entity and selected components. If this
/// returns null, then the entity
/// will be filtered out.
/// </param>
/// <param name="includeEntitiesWithoutComponents">
/// If true, then entities without all the components are included. Otherwise, they
/// are excluded.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <typeparam name="T2">The type of the second component.</typeparam>
/// <typeparam name="T3">The type of the third component.</typeparam>
/// <returns>An enumeration of transformed entities.</returns>
public static IEnumerable<Entity> SelectEntity<T1, T2, T3>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, T3, Entity?> selectWithComponents,
bool includeEntitiesWithoutComponents = true
)
{
return entities.SelectEntity(
selectWithComponents,
includeEntitiesWithoutComponents ? a => a : a => null
);
}
/// <summary>
/// Selects an entity from the given list, filtering on entities with
/// the given components.
/// </summary>
/// <param name="entities">The entities to parse.</param>
/// <param name="selectWithComponents">
/// The transformation function for the entity and selected components. If this
/// returns null, then the entity
/// will be filtered out.
/// </param>
/// <param name="includeEntitiesWithoutComponents">
/// If true, then entities without all the components are included. Otherwise, they
/// are excluded.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <typeparam name="T2">The type of the second component.</typeparam>
/// <typeparam name="T3">The type of the third component.</typeparam>
/// <typeparam name="T4">The type of the fourth component.</typeparam>
/// <returns>An enumeration of transformed entities.</returns>
public static IEnumerable<Entity> SelectEntity<T1, T2, T3, T4>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, T3, T4, Entity?> selectWithComponents,
bool includeEntitiesWithoutComponents = true)
{
return entities.SelectEntity(
selectWithComponents,
includeEntitiesWithoutComponents ? a => a : a => null);
}
/// <summary>
/// Selects an entity from the given list, filtering on entities with
/// the given components.
/// </summary>
/// <param name="entities">The entities to parse.</param>
/// <param name="selectWithComponents">
/// The transformation function for the entity and selected components. If this
/// returns null, then the entity
/// will be filtered out.
/// </param>
/// <param name="includeEntitiesWithoutComponents">
/// If true, then entities without all the components are included. Otherwise, they
/// are excluded.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <typeparam name="T2">The type of the second component.</typeparam>
/// <typeparam name="T3">The type of the third component.</typeparam>
/// <typeparam name="T4">The type of the fourth component.</typeparam>
/// <returns>An enumeration of transformed entities.</returns>
public static IEnumerable<Entity> SelectEntity<T1, T2, T3, T4>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, T3, T4, Entity?> selectWithComponents,
bool includeEntitiesWithoutComponents = true
)
{
return entities.SelectEntity(
selectWithComponents,
includeEntitiesWithoutComponents ? a => a : a => null
);
}
/// <summary>
/// Selects an entity from the given list, filtering on entities with
/// the given components.
/// </summary>
/// <param name="entities">The entities to parse.</param>
/// <param name="selectWithComponents">
/// The transformation function for the entity and selected components. If this
/// returns null, then the entity
/// will be filtered out.
/// </param>
/// <param name="selectWithoutComponents">
/// The optional transformation function for entities that do not have all the
/// components. If returns null,
/// then the entity will not be included.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <returns>An enumeration of transformed entities.</returns>
public static IEnumerable<Entity> SelectEntity<T1>(
this IEnumerable<Entity> entities,
Func<Entity, T1, Entity?> selectWithComponents,
Func<Entity, Entity?> selectWithoutComponents)
{
foreach (Entity entity in entities)
{
Entity? result = entity.TryGet(out T1 value1)
? selectWithComponents?.Invoke(entity, value1)
: selectWithoutComponents?.Invoke(entity);
/// <summary>
/// Selects an entity from the given list, filtering on entities with
/// the given components.
/// </summary>
/// <param name="entities">The entities to parse.</param>
/// <param name="selectWithComponents">
/// The transformation function for the entity and selected components. If this
/// returns null, then the entity
/// will be filtered out.
/// </param>
/// <param name="selectWithoutComponents">
/// The optional transformation function for entities that do not have all the
/// components. If returns null,
/// then the entity will not be included.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <returns>An enumeration of transformed entities.</returns>
public static IEnumerable<Entity> SelectEntity<T1>(
this IEnumerable<Entity> entities,
Func<Entity, T1, Entity?> selectWithComponents,
Func<Entity, Entity?> selectWithoutComponents
)
{
foreach (Entity entity in entities)
{
Entity? result = entity.TryGet(out T1 value1)
? selectWithComponents?.Invoke(entity, value1)
: selectWithoutComponents?.Invoke(entity);
if (result != null)
{
yield return result;
}
}
}
if (result != null)
{
yield return result;
}
}
}
/// <summary>
/// Selects an entity from the given list, filtering on entities with
/// the given components.
/// </summary>
/// <param name="entities">The entities to parse.</param>
/// <param name="selectWithComponents">
/// The transformation function for the entity and selected components. If this
/// returns null, then the entity
/// will be filtered out.
/// </param>
/// <param name="selectWithoutComponents">
/// The optional transformation function for entities that do not have all the
/// components. If returns null,
/// then the entity will not be included.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <typeparam name="T2">The type of the second component.</typeparam>
/// <returns>An enumeration of transformed entities.</returns>
public static IEnumerable<Entity> SelectEntity<T1, T2>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, Entity?> selectWithComponents,
Func<Entity, Entity?> selectWithoutComponents)
{
foreach (Entity entity in entities)
{
Entity? result = entity.TryGet(out T1 value1)
&& entity.TryGet(out T2 value2)
? selectWithComponents?.Invoke(entity, value1, value2)
: selectWithoutComponents?.Invoke(entity);
/// <summary>
/// Selects an entity from the given list, filtering on entities with
/// the given components.
/// </summary>
/// <param name="entities">The entities to parse.</param>
/// <param name="selectWithComponents">
/// The transformation function for the entity and selected components. If this
/// returns null, then the entity
/// will be filtered out.
/// </param>
/// <param name="selectWithoutComponents">
/// The optional transformation function for entities that do not have all the
/// components. If returns null,
/// then the entity will not be included.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <typeparam name="T2">The type of the second component.</typeparam>
/// <returns>An enumeration of transformed entities.</returns>
public static IEnumerable<Entity> SelectEntity<T1, T2>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, Entity?> selectWithComponents,
Func<Entity, Entity?> selectWithoutComponents
)
{
foreach (Entity entity in entities)
{
Entity? result =
entity.TryGet(out T1 value1) && entity.TryGet(out T2 value2)
? selectWithComponents?.Invoke(entity, value1, value2)
: selectWithoutComponents?.Invoke(entity);
if (result != null)
{
yield return result;
}
}
}
if (result != null)
{
yield return result;
}
}
}
/// <summary>
/// Selects an entity from the given list, filtering on entities with
/// the given components.
/// </summary>
/// <param name="entities">The entities to parse.</param>
/// <param name="selectWithComponents">
/// The transformation function for the entity and selected components. If this
/// returns null, then the entity
/// will be filtered out.
/// </param>
/// <param name="selectWithoutComponents">
/// The optional transformation function for entities that do not have all the
/// components. If returns null,
/// then the entity will not be included.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <typeparam name="T2">The type of the second component.</typeparam>
/// <typeparam name="T3">The type of the third component.</typeparam>
/// <returns>An enumeration of transformed entities.</returns>
public static IEnumerable<Entity> SelectEntity<T1, T2, T3>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, T3, Entity?> selectWithComponents,
Func<Entity, Entity?> selectWithoutComponents)
{
foreach (Entity entity in entities)
{
Entity? result =
entity.TryGet(out T1 value1)
&& entity.TryGet(out T2 value2)
&& entity.TryGet(out T3 value3)
? selectWithComponents?.Invoke(
entity,
value1,
value2,
value3)
: selectWithoutComponents?.Invoke(entity);
/// <summary>
/// Selects an entity from the given list, filtering on entities with
/// the given components.
/// </summary>
/// <param name="entities">The entities to parse.</param>
/// <param name="selectWithComponents">
/// The transformation function for the entity and selected components. If this
/// returns null, then the entity
/// will be filtered out.
/// </param>
/// <param name="selectWithoutComponents">
/// The optional transformation function for entities that do not have all the
/// components. If returns null,
/// then the entity will not be included.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <typeparam name="T2">The type of the second component.</typeparam>
/// <typeparam name="T3">The type of the third component.</typeparam>
/// <returns>An enumeration of transformed entities.</returns>
public static IEnumerable<Entity> SelectEntity<T1, T2, T3>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, T3, Entity?> selectWithComponents,
Func<Entity, Entity?> selectWithoutComponents
)
{
foreach (Entity entity in entities)
{
Entity? result =
entity.TryGet(out T1 value1)
&& entity.TryGet(out T2 value2)
&& entity.TryGet(out T3 value3)
? selectWithComponents?.Invoke(entity, value1, value2, value3)
: selectWithoutComponents?.Invoke(entity);
if (result != null)
{
yield return result;
}
}
}
if (result != null)
{
yield return result;
}
}
}
/// <summary>
/// Selects an entity from the given list, filtering on entities with
/// the given components.
/// </summary>
/// <param name="entities">The entities to parse.</param>
/// <param name="selectWithComponents">
/// The transformation function for the entity and selected components. If this
/// returns null, then the entity
/// will be filtered out.
/// </param>
/// <param name="selectWithoutComponents">
/// The optional transformation function for entities that do not have all the
/// components. If returns null,
/// then the entity will not be included.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <typeparam name="T2">The type of the second component.</typeparam>
/// <typeparam name="T3">The type of the third component.</typeparam>
/// <typeparam name="T4">The type of the third component.</typeparam>
/// <returns>An enumeration of transformed entities.</returns>
public static IEnumerable<Entity> SelectEntity<T1, T2, T3, T4>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, T3, T4, Entity?> selectWithComponents,
Func<Entity, Entity?> selectWithoutComponents)
{
foreach (Entity entity in entities)
{
Entity? result =
entity.TryGet(out T1 value1)
&& entity.TryGet(out T2 value2)
&& entity.TryGet(out T3 value3)
&& entity.TryGet(out T4 value4)
? selectWithComponents?.Invoke(
entity,
value1,
value2,
value3,
value4)
: selectWithoutComponents?.Invoke(entity);
/// <summary>
/// Selects an entity from the given list, filtering on entities with
/// the given components.
/// </summary>
/// <param name="entities">The entities to parse.</param>
/// <param name="selectWithComponents">
/// The transformation function for the entity and selected components. If this
/// returns null, then the entity
/// will be filtered out.
/// </param>
/// <param name="selectWithoutComponents">
/// The optional transformation function for entities that do not have all the
/// components. If returns null,
/// then the entity will not be included.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <typeparam name="T2">The type of the second component.</typeparam>
/// <typeparam name="T3">The type of the third component.</typeparam>
/// <typeparam name="T4">The type of the third component.</typeparam>
/// <returns>An enumeration of transformed entities.</returns>
public static IEnumerable<Entity> SelectEntity<T1, T2, T3, T4>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, T3, T4, Entity?> selectWithComponents,
Func<Entity, Entity?> selectWithoutComponents
)
{
foreach (Entity entity in entities)
{
Entity? result =
entity.TryGet(out T1 value1)
&& entity.TryGet(out T2 value2)
&& entity.TryGet(out T3 value3)
&& entity.TryGet(out T4 value4)
? selectWithComponents?.Invoke(entity, value1, value2, value3, value4)
: selectWithoutComponents?.Invoke(entity);
if (result != null)
{
yield return result;
}
}
}
if (result != null)
{
yield return result;
}
}
}
}

View file

@ -9,141 +9,145 @@ namespace MfGames.Gallium;
/// </summary>
public static class SelectManyEntityExtensions
{
/// <summary>
/// Pulls out all the entities that match the given components into an enumeration,
/// passes it into the callback
/// function, and then optionally merges the entities that did not match before
/// returning.
/// </summary>
/// <param name="entities">The entities to process</param>
/// <param name="selectMany">
/// The callback function to manipulate the list of
/// entities.
/// </param>
/// <param name="includeEntitiesWithoutComponents">
/// If true, the include entities
/// without components.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <returns>An enumeration of entities.</returns>
public static IEnumerable<Entity> SelectManyEntity<T1>(
this IEnumerable<Entity> entities,
Func<IEnumerable<Entity>, IEnumerable<Entity>> selectMany,
bool includeEntitiesWithoutComponents = true)
{
SplitEntityEnumerations split = entities.SplitEntity<T1>();
IEnumerable<Entity> results = selectMany(split.HasAll);
/// <summary>
/// Pulls out all the entities that match the given components into an enumeration,
/// passes it into the callback
/// function, and then optionally merges the entities that did not match before
/// returning.
/// </summary>
/// <param name="entities">The entities to process</param>
/// <param name="selectMany">
/// The callback function to manipulate the list of
/// entities.
/// </param>
/// <param name="includeEntitiesWithoutComponents">
/// If true, the include entities
/// without components.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <returns>An enumeration of entities.</returns>
public static IEnumerable<Entity> SelectManyEntity<T1>(
this IEnumerable<Entity> entities,
Func<IEnumerable<Entity>, IEnumerable<Entity>> selectMany,
bool includeEntitiesWithoutComponents = true
)
{
SplitEntityEnumerations split = entities.SplitEntity<T1>();
IEnumerable<Entity> results = selectMany(split.HasAll);
if (includeEntitiesWithoutComponents)
{
results = results.Union(split.NotHasAll);
}
if (includeEntitiesWithoutComponents)
{
results = results.Union(split.NotHasAll);
}
return results;
}
return results;
}
/// <summary>
/// Pulls out all the entities that match the given components into an enumeration,
/// passes it into the callback
/// function, and then optionally merges the entities that did not match before
/// returning.
/// </summary>
/// <param name="entities">The entities to process</param>
/// <param name="selectMany">
/// The callback function to manipulate the list of
/// entities.
/// </param>
/// <param name="includeEntitiesWithoutComponents">
/// If true, the include entities
/// without components.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <typeparam name="T2">The type of the second component.</typeparam>
/// <returns>An enumeration of entities.</returns>
public static IEnumerable<Entity> SelectManyEntity<T1, T2>(
this IEnumerable<Entity> entities,
Func<IEnumerable<Entity>, IEnumerable<Entity>> selectMany,
bool includeEntitiesWithoutComponents = true)
{
SplitEntityEnumerations split = entities.SplitEntity<T1, T2>();
IEnumerable<Entity> results = selectMany(split.HasAll);
/// <summary>
/// Pulls out all the entities that match the given components into an enumeration,
/// passes it into the callback
/// function, and then optionally merges the entities that did not match before
/// returning.
/// </summary>
/// <param name="entities">The entities to process</param>
/// <param name="selectMany">
/// The callback function to manipulate the list of
/// entities.
/// </param>
/// <param name="includeEntitiesWithoutComponents">
/// If true, the include entities
/// without components.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <typeparam name="T2">The type of the second component.</typeparam>
/// <returns>An enumeration of entities.</returns>
public static IEnumerable<Entity> SelectManyEntity<T1, T2>(
this IEnumerable<Entity> entities,
Func<IEnumerable<Entity>, IEnumerable<Entity>> selectMany,
bool includeEntitiesWithoutComponents = true
)
{
SplitEntityEnumerations split = entities.SplitEntity<T1, T2>();
IEnumerable<Entity> results = selectMany(split.HasAll);
if (includeEntitiesWithoutComponents)
{
results = results.Union(split.NotHasAll);
}
if (includeEntitiesWithoutComponents)
{
results = results.Union(split.NotHasAll);
}
return results;
}
return results;
}
/// <summary>
/// Pulls out all the entities that match the given components into an enumeration,
/// passes it into the callback
/// function, and then optionally merges the entities that did not match before
/// returning.
/// </summary>
/// <param name="entities">The entities to process</param>
/// <param name="selectMany">
/// The callback function to manipulate the list of
/// entities.
/// </param>
/// <param name="includeEntitiesWithoutComponents">
/// If true, the include entities
/// without components.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <typeparam name="T2">The type of the second component.</typeparam>
/// <typeparam name="T3">The type of the second component.</typeparam>
/// <returns>An enumeration of entities.</returns>
public static IEnumerable<Entity> SelectManyEntity<T1, T2, T3>(
this IEnumerable<Entity> entities,
Func<IEnumerable<Entity>, IEnumerable<Entity>> selectMany,
bool includeEntitiesWithoutComponents = true)
{
SplitEntityEnumerations split = entities.SplitEntity<T1, T2, T3>();
IEnumerable<Entity> results = selectMany(split.HasAll);
/// <summary>
/// Pulls out all the entities that match the given components into an enumeration,
/// passes it into the callback
/// function, and then optionally merges the entities that did not match before
/// returning.
/// </summary>
/// <param name="entities">The entities to process</param>
/// <param name="selectMany">
/// The callback function to manipulate the list of
/// entities.
/// </param>
/// <param name="includeEntitiesWithoutComponents">
/// If true, the include entities
/// without components.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <typeparam name="T2">The type of the second component.</typeparam>
/// <typeparam name="T3">The type of the second component.</typeparam>
/// <returns>An enumeration of entities.</returns>
public static IEnumerable<Entity> SelectManyEntity<T1, T2, T3>(
this IEnumerable<Entity> entities,
Func<IEnumerable<Entity>, IEnumerable<Entity>> selectMany,
bool includeEntitiesWithoutComponents = true
)
{
SplitEntityEnumerations split = entities.SplitEntity<T1, T2, T3>();
IEnumerable<Entity> results = selectMany(split.HasAll);
if (includeEntitiesWithoutComponents)
{
results = results.Union(split.NotHasAll);
}
if (includeEntitiesWithoutComponents)
{
results = results.Union(split.NotHasAll);
}
return results;
}
return results;
}
/// <summary>
/// Pulls out all the entities that match the given components into an enumeration,
/// passes it into the callback
/// function, and then optionally merges the entities that did not match before
/// returning.
/// </summary>
/// <param name="entities">The entities to process</param>
/// <param name="selectMany">
/// The callback function to manipulate the list of
/// entities.
/// </param>
/// <param name="includeEntitiesWithoutComponents">
/// If true, the include entities
/// without components.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <typeparam name="T2">The type of the second component.</typeparam>
/// <typeparam name="T3">The type of the second component.</typeparam>
/// <typeparam name="T4">The type of the second component.</typeparam>
/// <returns>An enumeration of entities.</returns>
public static IEnumerable<Entity> SelectManyEntity<T1, T2, T3, T4>(
this IEnumerable<Entity> entities,
Func<IEnumerable<Entity>, IEnumerable<Entity>> selectMany,
bool includeEntitiesWithoutComponents = true)
{
SplitEntityEnumerations split = entities.SplitEntity<T1, T2, T3, T4>();
IEnumerable<Entity> results = selectMany(split.HasAll);
/// <summary>
/// Pulls out all the entities that match the given components into an enumeration,
/// passes it into the callback
/// function, and then optionally merges the entities that did not match before
/// returning.
/// </summary>
/// <param name="entities">The entities to process</param>
/// <param name="selectMany">
/// The callback function to manipulate the list of
/// entities.
/// </param>
/// <param name="includeEntitiesWithoutComponents">
/// If true, the include entities
/// without components.
/// </param>
/// <typeparam name="T1">The type of the first component.</typeparam>
/// <typeparam name="T2">The type of the second component.</typeparam>
/// <typeparam name="T3">The type of the second component.</typeparam>
/// <typeparam name="T4">The type of the second component.</typeparam>
/// <returns>An enumeration of entities.</returns>
public static IEnumerable<Entity> SelectManyEntity<T1, T2, T3, T4>(
this IEnumerable<Entity> entities,
Func<IEnumerable<Entity>, IEnumerable<Entity>> selectMany,
bool includeEntitiesWithoutComponents = true
)
{
SplitEntityEnumerations split = entities.SplitEntity<T1, T2, T3, T4>();
IEnumerable<Entity> results = selectMany(split.HasAll);
if (includeEntitiesWithoutComponents)
{
results = results.Union(split.NotHasAll);
}
if (includeEntitiesWithoutComponents)
{
results = results.Union(split.NotHasAll);
}
return results;
}
return results;
}
}

View file

@ -1,16 +1,14 @@
namespace MfGames.Gallium;
public record SplitEntityEnumerations(
IEnumerable<Entity> HasAll,
IEnumerable<Entity> NotHasAll)
public record SplitEntityEnumerations(IEnumerable<Entity> HasAll, IEnumerable<Entity> NotHasAll)
{
/// <summary>
/// Gets a sequence of all entities that have all the given components.
/// </summary>
public IEnumerable<Entity> HasAll { get; } = HasAll;
/// <summary>
/// Gets a sequence of all entities that have all the given components.
/// </summary>
public IEnumerable<Entity> HasAll { get; } = HasAll;
/// <summary>
/// Gets the sequence of all entities that do not have all the given components.
/// </summary>
public IEnumerable<Entity> NotHasAll { get; } = NotHasAll;
/// <summary>
/// Gets the sequence of all entities that do not have all the given components.
/// </summary>
public IEnumerable<Entity> NotHasAll { get; } = NotHasAll;
}

View file

@ -7,290 +7,259 @@ namespace MfGames.Gallium;
/// </summary>
public static class SplitEntityExtensions
{
/// <summary>
/// Splits the enumeration of entities into two separate enumerations, ones that
/// have the given generic components
/// and those which do not.
/// </summary>
/// <param name="entities">The entities to split into two lists.</param>
/// <param name="test">
/// An additional test function to determine if the entity is
/// included in the has list. If null, then entities with all the components will
/// be included.
/// </param>
/// <typeparam name="T1">
/// A component to require to be in included in the first
/// list.
/// </typeparam>
/// <returns>A pair of enumerations, ones with the components and ones without.</returns>
public static SplitEntityEnumerations SplitEntity
<T1>(
this IEnumerable<Entity> entities,
Func<Entity, T1, bool>? test = null)
{
test ??= (
e,
v1) => true;
/// <summary>
/// Splits the enumeration of entities into two separate enumerations, ones that
/// have the given generic components
/// and those which do not.
/// </summary>
/// <param name="entities">The entities to split into two lists.</param>
/// <param name="test">
/// An additional test function to determine if the entity is
/// included in the has list. If null, then entities with all the components will
/// be included.
/// </param>
/// <typeparam name="T1">
/// A component to require to be in included in the first
/// list.
/// </typeparam>
/// <returns>A pair of enumerations, ones with the components and ones without.</returns>
public static SplitEntityEnumerations SplitEntity<T1>(
this IEnumerable<Entity> entities,
Func<Entity, T1, bool>? test = null
)
{
test ??= (e, v1) => true;
return entities.SplitEntity(
typeof(T1),
(
e,
v1) => test(e, (T1)v1));
}
return entities.SplitEntity(typeof(T1), (e, v1) => test(e, (T1)v1));
}
/// <summary>
/// Splits the enumeration of entities into two separate enumerations, ones that
/// have the given generic components
/// and those which do not.
/// </summary>
/// <param name="entities">The entities to split into two lists.</param>
/// <param name="test">
/// An additional test function to determine if the entity is
/// included in the has list. If null, then entities with all the components will
/// be included.
/// </param>
/// <typeparam name="T1">
/// A component to require to be in included in the first list.
/// </typeparam>
/// <typeparam name="T2">
/// A component to require to be in included in the first list.
/// </typeparam>
/// <returns>A pair of enumerations, ones with the components and ones without.</returns>
public static SplitEntityEnumerations SplitEntity
<T1, T2>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, bool>? test = null)
{
test ??= (
e,
v1,
v2) => true;
/// <summary>
/// Splits the enumeration of entities into two separate enumerations, ones that
/// have the given generic components
/// and those which do not.
/// </summary>
/// <param name="entities">The entities to split into two lists.</param>
/// <param name="test">
/// An additional test function to determine if the entity is
/// included in the has list. If null, then entities with all the components will
/// be included.
/// </param>
/// <typeparam name="T1">
/// A component to require to be in included in the first list.
/// </typeparam>
/// <typeparam name="T2">
/// A component to require to be in included in the first list.
/// </typeparam>
/// <returns>A pair of enumerations, ones with the components and ones without.</returns>
public static SplitEntityEnumerations SplitEntity<T1, T2>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, bool>? test = null
)
{
test ??= (e, v1, v2) => true;
return entities.SplitEntity(
typeof(T1),
typeof(T2),
(
e,
v1,
v2) => test(e, (T1)v1, (T2)v2));
}
return entities.SplitEntity(typeof(T1), typeof(T2), (e, v1, v2) => test(e, (T1)v1, (T2)v2));
}
/// <summary>
/// Splits the enumeration of entities into two separate enumerations, ones that
/// have the given generic components
/// and those which do not.
/// </summary>
/// <param name="entities">The entities to split into two lists.</param>
/// <param name="test">
/// An additional test function to determine if the entity is
/// included in the has list. If null, then entities with all the components will
/// be included.
/// </param>
/// <typeparam name="T1">
/// A component to require to be in included in the first list.
/// </typeparam>
/// <typeparam name="T2">
/// A component to require to be in included in the first list.
/// </typeparam>
/// <typeparam name="T3">
/// A component to require to be in included in the first list.
/// </typeparam>
/// <returns>A pair of enumerations, ones with the components and ones without.</returns>
public static SplitEntityEnumerations SplitEntity
<T1, T2, T3>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, T3, bool>? test = null)
{
test ??= (
e,
v1,
v2,
v3) => true;
/// <summary>
/// Splits the enumeration of entities into two separate enumerations, ones that
/// have the given generic components
/// and those which do not.
/// </summary>
/// <param name="entities">The entities to split into two lists.</param>
/// <param name="test">
/// An additional test function to determine if the entity is
/// included in the has list. If null, then entities with all the components will
/// be included.
/// </param>
/// <typeparam name="T1">
/// A component to require to be in included in the first list.
/// </typeparam>
/// <typeparam name="T2">
/// A component to require to be in included in the first list.
/// </typeparam>
/// <typeparam name="T3">
/// A component to require to be in included in the first list.
/// </typeparam>
/// <returns>A pair of enumerations, ones with the components and ones without.</returns>
public static SplitEntityEnumerations SplitEntity<T1, T2, T3>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, T3, bool>? test = null
)
{
test ??= (e, v1, v2, v3) => true;
return entities.SplitEntity(
typeof(T1),
typeof(T2),
typeof(T3),
(
e,
v1,
v2,
v3) => test(e, (T1)v1, (T2)v2, (T3)v3));
}
return entities.SplitEntity(
typeof(T1),
typeof(T2),
typeof(T3),
(e, v1, v2, v3) => test(e, (T1)v1, (T2)v2, (T3)v3)
);
}
/// <summary>
/// Splits the enumeration of entities into two separate enumerations, ones that
/// have the given generic components
/// and those which do not.
/// </summary>
/// <param name="entities">The entities to split into two lists.</param>
/// <param name="test">
/// An additional test function to determine if the entity is
/// included in the has list. If null, then entities with all the components will
/// be included.
/// </param>
/// <typeparam name="T1">
/// A component to require to be in included in the first list.
/// </typeparam>
/// <typeparam name="T2">
/// A component to require to be in included in the first list.
/// </typeparam>
/// <typeparam name="T3">
/// A component to require to be in included in the first list.
/// </typeparam>
/// <typeparam name="T4">
/// A component to require to be in included in the first list.
/// </typeparam>
/// <returns>A pair of enumerations, ones with the components and ones without.</returns>
public static SplitEntityEnumerations SplitEntity
<T1, T2, T3, T4>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, T3, T4, bool>? test = null)
{
test ??= (
e,
v1,
v2,
v3,
v4) => true;
/// <summary>
/// Splits the enumeration of entities into two separate enumerations, ones that
/// have the given generic components
/// and those which do not.
/// </summary>
/// <param name="entities">The entities to split into two lists.</param>
/// <param name="test">
/// An additional test function to determine if the entity is
/// included in the has list. If null, then entities with all the components will
/// be included.
/// </param>
/// <typeparam name="T1">
/// A component to require to be in included in the first list.
/// </typeparam>
/// <typeparam name="T2">
/// A component to require to be in included in the first list.
/// </typeparam>
/// <typeparam name="T3">
/// A component to require to be in included in the first list.
/// </typeparam>
/// <typeparam name="T4">
/// A component to require to be in included in the first list.
/// </typeparam>
/// <returns>A pair of enumerations, ones with the components and ones without.</returns>
public static SplitEntityEnumerations SplitEntity<T1, T2, T3, T4>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, T3, T4, bool>? test = null
)
{
test ??= (e, v1, v2, v3, v4) => true;
return entities.SplitEntity(
typeof(T1),
typeof(T2),
typeof(T3),
typeof(T4),
(
e,
v1,
v2,
v3,
v4) => test(e, (T1)v1, (T2)v2, (T3)v3, (T4)v4));
}
return entities.SplitEntity(
typeof(T1),
typeof(T2),
typeof(T3),
typeof(T4),
(e, v1, v2, v3, v4) => test(e, (T1)v1, (T2)v2, (T3)v3, (T4)v4)
);
}
/// <summary>
/// Splits the enumeration of entities into two separate enumerations, ones that
/// have the given component types and those which do not.
/// </summary>
/// <param name="entities">The entities to split into two lists.</param>
/// <param name="t1">The type of a required component.</param>
/// <param name="test">
/// An additional test function to determine if the entity is
/// included in the has list. If null, then entities with all the components will
/// be included.
/// </param>
/// <returns>A pair of enumerations, ones with the components and ones without.</returns>
public static SplitEntityEnumerations SplitEntity(
this IEnumerable<Entity> entities,
Type t1,
Func<Entity, object, bool> test)
{
return SplitEntity(
entities,
a => a.Has(t1) && test(a, a.Get<object>(t1)));
}
/// <summary>
/// Splits the enumeration of entities into two separate enumerations, ones that
/// have the given component types and those which do not.
/// </summary>
/// <param name="entities">The entities to split into two lists.</param>
/// <param name="t1">The type of a required component.</param>
/// <param name="test">
/// An additional test function to determine if the entity is
/// included in the has list. If null, then entities with all the components will
/// be included.
/// </param>
/// <returns>A pair of enumerations, ones with the components and ones without.</returns>
public static SplitEntityEnumerations SplitEntity(
this IEnumerable<Entity> entities,
Type t1,
Func<Entity, object, bool> test
)
{
return SplitEntity(entities, a => a.Has(t1) && test(a, a.Get<object>(t1)));
}
/// <summary>
/// Splits the enumeration of entities into two separate enumerations, ones that
/// have the given component types and those which do not.
/// </summary>
/// <param name="entities">The entities to split into two lists.</param>
/// <param name="t1">The type of a required component.</param>
/// <param name="t2">The type of a required component.</param>
/// <param name="test">
/// An additional test function to determine if the entity is
/// included in the has list. If null, then entities with all the components will
/// be included.
/// </param>
/// <returns>A pair of enumerations, ones with the components and ones without.</returns>
public static SplitEntityEnumerations SplitEntity(
this IEnumerable<Entity> entities,
Type t1,
Type t2,
Func<Entity, object, object, bool> test)
{
return SplitEntity(entities, a => a.HasAll(t1, t2));
}
/// <summary>
/// Splits the enumeration of entities into two separate enumerations, ones that
/// have the given component types and those which do not.
/// </summary>
/// <param name="entities">The entities to split into two lists.</param>
/// <param name="t1">The type of a required component.</param>
/// <param name="t2">The type of a required component.</param>
/// <param name="test">
/// An additional test function to determine if the entity is
/// included in the has list. If null, then entities with all the components will
/// be included.
/// </param>
/// <returns>A pair of enumerations, ones with the components and ones without.</returns>
public static SplitEntityEnumerations SplitEntity(
this IEnumerable<Entity> entities,
Type t1,
Type t2,
Func<Entity, object, object, bool> test
)
{
return SplitEntity(entities, a => a.HasAll(t1, t2));
}
/// <summary>
/// Splits the enumeration of entities into two separate enumerations, ones that
/// have the given component types and those which do not.
/// </summary>
/// <param name="entities">The entities to split into two lists.</param>
/// <param name="t1">The type of a required component.</param>
/// <param name="t2">The type of a required component.</param>
/// <param name="t3">The type of a required component.</param>
/// <param name="test">
/// An additional test function to determine if the entity is
/// included in the has list. If null, then entities with all the components will
/// be included.
/// </param>
/// <returns>A pair of enumerations, ones with the components and ones without.</returns>
public static SplitEntityEnumerations SplitEntity(
this IEnumerable<Entity> entities,
Type t1,
Type t2,
Type t3,
Func<Entity, object, object, object, bool> test)
{
return SplitEntity(entities, a => a.HasAll(t1, t2, t3));
}
/// <summary>
/// Splits the enumeration of entities into two separate enumerations, ones that
/// have the given component types and those which do not.
/// </summary>
/// <param name="entities">The entities to split into two lists.</param>
/// <param name="t1">The type of a required component.</param>
/// <param name="t2">The type of a required component.</param>
/// <param name="t3">The type of a required component.</param>
/// <param name="test">
/// An additional test function to determine if the entity is
/// included in the has list. If null, then entities with all the components will
/// be included.
/// </param>
/// <returns>A pair of enumerations, ones with the components and ones without.</returns>
public static SplitEntityEnumerations SplitEntity(
this IEnumerable<Entity> entities,
Type t1,
Type t2,
Type t3,
Func<Entity, object, object, object, bool> test
)
{
return SplitEntity(entities, a => a.HasAll(t1, t2, t3));
}
/// <summary>
/// Splits the enumeration of entities into two separate enumerations, ones that
/// have the given component types and those which do not.
/// </summary>
/// <param name="entities">The entities to split into two lists.</param>
/// <param name="t1">The type of a required component.</param>
/// <param name="t2">The type of a required component.</param>
/// <param name="t3">The type of a required component.</param>
/// <param name="t4">The type of a required component.</param>
/// <param name="test">
/// An additional test function to determine if the entity is
/// included in the has list. If null, then entities with all the components will
/// be included.
/// </param>
/// <returns>A pair of enumerations, ones with the components and ones without.</returns>
public static SplitEntityEnumerations SplitEntity(
this IEnumerable<Entity> entities,
Type t1,
Type t2,
Type t3,
Type t4,
Func<Entity, object, object, object, object, bool> test)
{
return SplitEntity(
entities,
a => a.HasAll(t1, t2, t3, t4)
&& test(
a,
a.Get<object>(t1),
a.Get<object>(t2),
a.Get<object>(t3),
a.Get<object>(t4)));
}
/// <summary>
/// Splits the enumeration of entities into two separate enumerations, ones that
/// have the given component types and those which do not.
/// </summary>
/// <param name="entities">The entities to split into two lists.</param>
/// <param name="t1">The type of a required component.</param>
/// <param name="t2">The type of a required component.</param>
/// <param name="t3">The type of a required component.</param>
/// <param name="t4">The type of a required component.</param>
/// <param name="test">
/// An additional test function to determine if the entity is
/// included in the has list. If null, then entities with all the components will
/// be included.
/// </param>
/// <returns>A pair of enumerations, ones with the components and ones without.</returns>
public static SplitEntityEnumerations SplitEntity(
this IEnumerable<Entity> entities,
Type t1,
Type t2,
Type t3,
Type t4,
Func<Entity, object, object, object, object, bool> test
)
{
return SplitEntity(
entities,
a =>
a.HasAll(t1, t2, t3, t4)
&& test(
a,
a.Get<object>(t1),
a.Get<object>(t2),
a.Get<object>(t3),
a.Get<object>(t4)
)
);
}
private static SplitEntityEnumerations SplitEntity(
IEnumerable<Entity> entities,
Func<Entity, bool> keySelector)
{
if (entities == null)
{
throw new ArgumentNullException(nameof(entities));
}
private static SplitEntityEnumerations SplitEntity(
IEnumerable<Entity> entities,
Func<Entity, bool> keySelector
)
{
if (entities == null)
{
throw new ArgumentNullException(nameof(entities));
}
IEnumerable<IGrouping<bool, Entity>> group = entities
.GroupBy(keySelector, a => a)
.ToList();
IEnumerable<IGrouping<bool, Entity>> group = entities.GroupBy(keySelector, a => a).ToList();
IEnumerable<Entity>? has = group
.Where(a => a.Key)
.SelectMany(a => a);
IEnumerable<Entity>? has = group.Where(a => a.Key).SelectMany(a => a);
IEnumerable<Entity>? hasNot = group
.Where(a => !a.Key)
.SelectMany(a => a);
IEnumerable<Entity>? hasNot = group.Where(a => !a.Key).SelectMany(a => a);
return new SplitEntityEnumerations(has, hasNot);
}
return new SplitEntityEnumerations(has, hasNot);
}
}

View file

@ -2,42 +2,40 @@ namespace MfGames.Gallium;
public static class WhereEntityExtensions
{
public static IEnumerable<Entity> WhereEntity<T1>(
this IEnumerable<Entity> entities,
Func<Entity, T1, bool> include)
{
return entities.Where(x => x.Has<T1>() && include(x, x.Get<T1>()));
}
public static IEnumerable<Entity> WhereEntity<T1>(
this IEnumerable<Entity> entities,
Func<Entity, T1, bool> include
)
{
return entities.Where(x => x.Has<T1>() && include(x, x.Get<T1>()));
}
public static IEnumerable<Entity> WhereEntity<T1, T2>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, bool> include)
{
return entities.Where(
x => x.HasAll<T1, T2>()
&& include(x, x.Get<T1>(), x.Get<T2>()));
}
public static IEnumerable<Entity> WhereEntity<T1, T2>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, bool> include
)
{
return entities.Where(x => x.HasAll<T1, T2>() && include(x, x.Get<T1>(), x.Get<T2>()));
}
public static IEnumerable<Entity> WhereEntity<T1, T2, T3>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, T3, bool> include)
{
return entities.Where(
x => x.HasAll<T1, T2, T3>()
&& include(x, x.Get<T1>(), x.Get<T2>(), x.Get<T3>()));
}
public static IEnumerable<Entity> WhereEntity<T1, T2, T3>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, T3, bool> include
)
{
return entities.Where(x =>
x.HasAll<T1, T2, T3>() && include(x, x.Get<T1>(), x.Get<T2>(), x.Get<T3>())
);
}
public static IEnumerable<Entity> WhereEntity<T1, T2, T3, T4>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, T3, T4, bool> include)
{
return entities.Where(
x => x.HasAll<T1, T2, T3, T4>()
&& include(
x,
x.Get<T1>(),
x.Get<T2>(),
x.Get<T3>(),
x.Get<T4>()));
}
public static IEnumerable<Entity> WhereEntity<T1, T2, T3, T4>(
this IEnumerable<Entity> entities,
Func<Entity, T1, T2, T3, T4, bool> include
)
{
return entities.Where(x =>
x.HasAll<T1, T2, T3, T4>()
&& include(x, x.Get<T1>(), x.Get<T2>(), x.Get<T3>(), x.Get<T4>())
);
}
}

View file

@ -2,27 +2,27 @@ namespace MfGames.Gallium;
public static class WhereEntityHasExtensions
{
public static IEnumerable<Entity> WhereEntityHas<T1>(
this IEnumerable<Entity> entities)
{
return entities.Where(x => x.Has<T1>());
}
public static IEnumerable<Entity> WhereEntityHas<T1>(this IEnumerable<Entity> entities)
{
return entities.Where(x => x.Has<T1>());
}
public static IEnumerable<Entity> WhereEntityHasAll
<T1, T2>(this IEnumerable<Entity> entities)
{
return entities.Where(x => x.HasAll<T1, T2>());
}
public static IEnumerable<Entity> WhereEntityHasAll<T1, T2>(this IEnumerable<Entity> entities)
{
return entities.Where(x => x.HasAll<T1, T2>());
}
public static IEnumerable<Entity> WhereEntityHasAll
<T1, T2, T3>(this IEnumerable<Entity> entities)
{
return entities.Where(x => x.HasAll<T1, T2, T3>());
}
public static IEnumerable<Entity> WhereEntityHasAll<T1, T2, T3>(
this IEnumerable<Entity> entities
)
{
return entities.Where(x => x.HasAll<T1, T2, T3>());
}
public static IEnumerable<Entity> WhereEntityHasAll
<T1, T2, T3, T4>(this IEnumerable<Entity> entities)
{
return entities.Where(x => x.HasAll<T1, T2, T3, T4>());
}
public static IEnumerable<Entity> WhereEntityHasAll<T1, T2, T3, T4>(
this IEnumerable<Entity> entities
)
{
return entities.Where(x => x.HasAll<T1, T2, T3, T4>());
}
}

View file

@ -2,27 +2,29 @@ namespace MfGames.Gallium;
public static class WhereEntityNotHasExtensions
{
public static IEnumerable<Entity> WhereEntityNotHas<T1>(
this IEnumerable<Entity> entities)
{
return entities.Where(x => !x.Has<T1>());
}
public static IEnumerable<Entity> WhereEntityNotHas<T1>(this IEnumerable<Entity> entities)
{
return entities.Where(x => !x.Has<T1>());
}
public static IEnumerable<Entity> WhereEntityNotHasAll
<T1, T2>(this IEnumerable<Entity> entities)
{
return entities.Where(x => !x.HasAll<T1, T2>());
}
public static IEnumerable<Entity> WhereEntityNotHasAll<T1, T2>(
this IEnumerable<Entity> entities
)
{
return entities.Where(x => !x.HasAll<T1, T2>());
}
public static IEnumerable<Entity> WhereEntityNotHasAll
<T1, T2, T3>(this IEnumerable<Entity> entities)
{
return entities.Where(x => !x.HasAll<T1, T2, T3>());
}
public static IEnumerable<Entity> WhereEntityNotHasAll<T1, T2, T3>(
this IEnumerable<Entity> entities
)
{
return entities.Where(x => !x.HasAll<T1, T2, T3>());
}
public static IEnumerable<Entity> WhereEntityNotHasAll
<T1, T2, T3, T4>(this IEnumerable<Entity> entities)
{
return entities.Where(x => !x.HasAll<T1, T2, T3, T4>());
}
public static IEnumerable<Entity> WhereEntityNotHasAll<T1, T2, T3, T4>(
this IEnumerable<Entity> entities
)
{
return entities.Where(x => !x.HasAll<T1, T2, T3, T4>());
}
}

View file

@ -7,25 +7,23 @@ namespace MfGames.IO.Extensions;
/// </summary>
public static class AssemblyExtensions
{
/// <summary>
/// Gets the directory for a given assembly.
/// </summary>
/// <param name="assembly">The assembly to get the directory.</param>
/// <returns>The directory or null if the assembly or location is null.</returns>
public static DirectoryInfo? GetDirectory(this Assembly? assembly)
{
return assembly.GetFile()?.Directory;
}
/// <summary>
/// Gets the directory for a given assembly.
/// </summary>
/// <param name="assembly">The assembly to get the directory.</param>
/// <returns>The directory or null if the assembly or location is null.</returns>
public static DirectoryInfo? GetDirectory(this Assembly? assembly)
{
return assembly.GetFile()?.Directory;
}
/// <summary>
/// Gets the file for a given assembly.
/// </summary>
/// <param name="assembly">The assembly to get the directory.</param>
/// <returns>The directory or null if the assembly or location is null.</returns>
public static FileInfo? GetFile(this Assembly? assembly)
{
return assembly?.Location == null
? null
: new FileInfo(assembly.Location);
}
/// <summary>
/// Gets the file for a given assembly.
/// </summary>
/// <param name="assembly">The assembly to get the directory.</param>
/// <returns>The directory or null if the assembly or location is null.</returns>
public static FileInfo? GetFile(this Assembly? assembly)
{
return assembly?.Location == null ? null : new FileInfo(assembly.Location);
}
}

View file

@ -9,41 +9,37 @@ namespace MfGames.IO.Extensions;
/// </summary>
public static class CryptoFileInfoExtensions
{
/// <summary>
/// Hashes the given input and returns the results.
/// </summary>
/// <param name="file">The FileInfo object or null.</param>
/// <param name="hash">The type of hash requested.</param>
/// <returns>Null if input is null, otherwise the hash value.</returns>
public static byte[]? ToHashBytes(
this FileInfo? file,
HashType hash = HashType.Sha512)
{
if (file == null)
{
return null;
}
/// <summary>
/// Hashes the given input and returns the results.
/// </summary>
/// <param name="file">The FileInfo object or null.</param>
/// <param name="hash">The type of hash requested.</param>
/// <returns>Null if input is null, otherwise the hash value.</returns>
public static byte[]? ToHashBytes(this FileInfo? file, HashType hash = HashType.Sha512)
{
if (file == null)
{
return null;
}
using FileStream stream = file.Open(
FileMode.Open,
FileAccess.Read,
FileShare.Read);
using FileStream stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
return hash.CreateHash().ComputeHash(stream);
}
return hash.CreateHash().ComputeHash(stream);
}
/// <summary>
/// Hashes the given input and returns the results as a string.
/// </summary>
/// <param name="file">The FileInfo object or null.</param>
/// <param name="hash">The type of hash requested.</param>
/// <param name="format">The format of the requested string.</param>
/// <returns>Null if input is null, otherwise the hash value.</returns>
public static string? ToHashString(
this FileInfo? file,
HashType hash = HashType.Sha512,
ByteStringFormat format = ByteStringFormat.LowercaseHex)
{
return file?.ToHashBytes(hash).ToByteString(format);
}
/// <summary>
/// Hashes the given input and returns the results as a string.
/// </summary>
/// <param name="file">The FileInfo object or null.</param>
/// <param name="hash">The type of hash requested.</param>
/// <param name="format">The format of the requested string.</param>
/// <returns>Null if input is null, otherwise the hash value.</returns>
public static string? ToHashString(
this FileInfo? file,
HashType hash = HashType.Sha512,
ByteStringFormat format = ByteStringFormat.LowercaseHex
)
{
return file?.ToHashBytes(hash).ToByteString(format);
}
}

View file

@ -5,166 +5,157 @@ namespace MfGames.IO.Extensions;
/// </summary>
public static class DirectoryInfoExtensions
{
/// <summary>
/// Makes sure a directory exists, creating it and any parents above it
/// as needed.
/// </summary>
/// <param name="directory"></param>
/// <returns>The directory passed in.</returns>
public static DirectoryInfo? CreateIfMissing(this DirectoryInfo? directory)
{
// Ignore blanks.
if (directory == null)
{
return null;
}
/// <summary>
/// Makes sure a directory exists, creating it and any parents above it
/// as needed.
/// </summary>
/// <param name="directory"></param>
/// <returns>The directory passed in.</returns>
public static DirectoryInfo? CreateIfMissing(this DirectoryInfo? directory)
{
// Ignore blanks.
if (directory == null)
{
return null;
}
// Check the parent first. We don't have to worry about null because
// we check that coming in.
directory.Parent.CreateIfMissing();
// Check the parent first. We don't have to worry about null because
// we check that coming in.
directory.Parent.CreateIfMissing();
// If the directory doesn't exist, create it.
if (!Directory.Exists(directory.FullName))
{
directory.Create();
}
// If the directory doesn't exist, create it.
if (!Directory.Exists(directory.FullName))
{
directory.Create();
}
return directory;
}
return directory;
}
/// <summary>
/// Searches for the Git root starting at the given directory.
/// </summary>
/// <param name="directory">The directory to start searching.</param>
/// <returns>The directory containing `.git`, otherwise null.</returns>
public static DirectoryInfo? FindGitRoot(this DirectoryInfo? directory)
{
return directory.FindSelfOrParent(IsGitRoot);
}
/// <summary>
/// Searches for the Git root starting at the given directory.
/// </summary>
/// <param name="directory">The directory to start searching.</param>
/// <returns>The directory containing `.git`, otherwise null.</returns>
public static DirectoryInfo? FindGitRoot(this DirectoryInfo? directory)
{
return directory.FindSelfOrParent(IsGitRoot);
}
/// <summary>
/// Searches for a parent that matches the given `match` method. This
/// will not compare the given directory to see if it matches.
/// </summary>
/// <param name="directory">The directory to start searching.</param>
/// <param name="match">The function that takes a directory to determine a match.</param>
/// <returns>A parent directory that matches, otherwise null.</returns>
public static DirectoryInfo? FindParent(
this DirectoryInfo? directory,
Func<DirectoryInfo, bool> match)
{
return directory == null
? null
: FindSelfOrParent(directory.Parent, match);
}
/// <summary>
/// Searches for a parent that matches the given `match` method. This
/// will not compare the given directory to see if it matches.
/// </summary>
/// <param name="directory">The directory to start searching.</param>
/// <param name="match">The function that takes a directory to determine a match.</param>
/// <returns>A parent directory that matches, otherwise null.</returns>
public static DirectoryInfo? FindParent(
this DirectoryInfo? directory,
Func<DirectoryInfo, bool> match
)
{
return directory == null ? null : FindSelfOrParent(directory.Parent, match);
}
/// <summary>
/// Searches the given directory and parents for the first one that
/// matches (moving up the directory tree). If this does not find
/// anything, then a null will be returned.
/// </summary>
/// <param name="directory">The directory to start searching.</param>
/// <param name="match">The function that takes a directory to determine a match.</param>
/// <returns>A parent directory that matches, otherwise null.</returns>
public static DirectoryInfo? FindSelfOrParent(
this DirectoryInfo? directory,
Func<DirectoryInfo, bool> match)
{
while (true)
{
// Validate our inputs.
if (match == null)
{
throw new ArgumentNullException(nameof(match));
}
/// <summary>
/// Searches the given directory and parents for the first one that
/// matches (moving up the directory tree). If this does not find
/// anything, then a null will be returned.
/// </summary>
/// <param name="directory">The directory to start searching.</param>
/// <param name="match">The function that takes a directory to determine a match.</param>
/// <returns>A parent directory that matches, otherwise null.</returns>
public static DirectoryInfo? FindSelfOrParent(
this DirectoryInfo? directory,
Func<DirectoryInfo, bool> match
)
{
while (true)
{
// Validate our inputs.
if (match == null)
{
throw new ArgumentNullException(nameof(match));
}
// If the directory is null, just return null. Same with
// non-existing directories. We don't use directory.Exists here
// because it seems to be cached and we get incorrect data.
if (directory == null || !Directory.Exists(directory.FullName))
{
return null;
}
// If the directory is null, just return null. Same with
// non-existing directories. We don't use directory.Exists here
// because it seems to be cached and we get incorrect data.
if (directory == null || !Directory.Exists(directory.FullName))
{
return null;
}
// Check this directory for a match, otherwise move up.
if (match(directory))
{
return directory;
}
// Check this directory for a match, otherwise move up.
if (match(directory))
{
return directory;
}
directory = directory.Parent;
}
}
directory = directory.Parent;
}
}
/// <summary>
/// Gets a DirectoryInfo by combining the path components with the
/// directory full path.
/// </summary>
/// <param name="directory">The directory to get the root path.</param>
/// <param name="paths">Additional child paths.</param>
/// <returns>A DirectoryInfo of the given path.</returns>
public static DirectoryInfo GetDirectory(
this DirectoryInfo directory,
params string[] paths)
{
if (directory == null)
{
throw new ArgumentNullException(nameof(directory));
}
/// <summary>
/// Gets a DirectoryInfo by combining the path components with the
/// directory full path.
/// </summary>
/// <param name="directory">The directory to get the root path.</param>
/// <param name="paths">Additional child paths.</param>
/// <returns>A DirectoryInfo of the given path.</returns>
public static DirectoryInfo GetDirectory(this DirectoryInfo directory, params string[] paths)
{
if (directory == null)
{
throw new ArgumentNullException(nameof(directory));
}
if (paths == null)
{
throw new ArgumentNullException(nameof(paths));
}
if (paths == null)
{
throw new ArgumentNullException(nameof(paths));
}
string[] parts = new[] { directory.FullName }
.Union(paths)
.ToArray();
string path = Path.Combine(parts);
var info = new DirectoryInfo(path);
string[] parts = new[] { directory.FullName }.Union(paths).ToArray();
string path = Path.Combine(parts);
var info = new DirectoryInfo(path);
return info;
}
return info;
}
/// <summary>
/// Gets a FileInfo by combining the path components with the directory
/// full path.
/// </summary>
/// <param name="directory">The directory to get the root path.</param>
/// <param name="paths">Additional child paths.</param>
/// <returns>A FileInfo of the given path.</returns>
public static FileInfo GetFile(
this DirectoryInfo directory,
params string[] paths)
{
if (directory == null)
{
throw new ArgumentNullException(nameof(directory));
}
/// <summary>
/// Gets a FileInfo by combining the path components with the directory
/// full path.
/// </summary>
/// <param name="directory">The directory to get the root path.</param>
/// <param name="paths">Additional child paths.</param>
/// <returns>A FileInfo of the given path.</returns>
public static FileInfo GetFile(this DirectoryInfo directory, params string[] paths)
{
if (directory == null)
{
throw new ArgumentNullException(nameof(directory));
}
if (paths == null)
{
throw new ArgumentNullException(nameof(paths));
}
if (paths == null)
{
throw new ArgumentNullException(nameof(paths));
}
string[] parts = new[] { directory.FullName }
.Union(paths)
.ToArray();
string path = Path.Combine(parts);
var info = new FileInfo(path);
string[] parts = new[] { directory.FullName }.Union(paths).ToArray();
string path = Path.Combine(parts);
var info = new FileInfo(path);
return info;
}
return info;
}
/// <summary>
/// Determines if the given directory contains a ".git" folder. If given
/// a null, this will always return false.
/// </summary>
/// <param name="directory">The directory to inspect.</param>
/// <returns>True if the directory contains ".git", otherwise false.</returns>
public static bool IsGitRoot(this DirectoryInfo? directory)
{
return directory != null
&& directory.GetDirectories(".git").Length > 0;
}
/// <summary>
/// Determines if the given directory contains a ".git" folder. If given
/// a null, this will always return false.
/// </summary>
/// <param name="directory">The directory to inspect.</param>
/// <returns>True if the directory contains ".git", otherwise false.</returns>
public static bool IsGitRoot(this DirectoryInfo? directory)
{
return directory != null && directory.GetDirectories(".git").Length > 0;
}
}

View file

@ -7,48 +7,45 @@ namespace MfGames.IO.Extensions;
/// </summary>
public static class FileInfoExtensions
{
/// <summary>
/// Reads all the text from a FileInfo object.
/// </summary>
/// <param name="file">The file to read from.</param>
/// <returns>The text contents.</returns>
public static string ReadAllText(this FileInfo file)
{
return File.ReadAllText(file.FullName);
}
/// <summary>
/// Reads all the text from a FileInfo object.
/// </summary>
/// <param name="file">The file to read from.</param>
/// <returns>The text contents.</returns>
public static string ReadAllText(this FileInfo file)
{
return File.ReadAllText(file.FullName);
}
/// <summary>
/// Reads all the text from a FileInfo object.
/// </summary>
/// <param name="file">The file to read from.</param>
/// <param name="encoding">The encoding to use.</param>
/// <returns>The text contents.</returns>
public static string ReadAllText(this FileInfo file, Encoding encoding)
{
return File.ReadAllText(file.FullName, encoding);
}
/// <summary>
/// Reads all the text from a FileInfo object.
/// </summary>
/// <param name="file">The file to read from.</param>
/// <param name="encoding">The encoding to use.</param>
/// <returns>The text contents.</returns>
public static string ReadAllText(this FileInfo file, Encoding encoding)
{
return File.ReadAllText(file.FullName, encoding);
}
/// <summary>
/// Writes out all the text to the given file.
/// </summary>
/// <param name="file">The file to write to.</param>
/// <param name="text">The text to write out.</param>
public static void WriteAllText(this FileInfo file, string text)
{
File.WriteAllText(file.FullName, text);
}
/// <summary>
/// Writes out all the text to the given file.
/// </summary>
/// <param name="file">The file to write to.</param>
/// <param name="text">The text to write out.</param>
public static void WriteAllText(this FileInfo file, string text)
{
File.WriteAllText(file.FullName, text);
}
/// <summary>
/// Writes out all the text to the given file.
/// </summary>
/// <param name="file">The file to write to.</param>
/// <param name="text">The text to write out.</param>
/// <param name="encoding">The encoding to use.</param>
public static void WriteAllText(
this FileInfo file,
string text,
Encoding encoding)
{
File.WriteAllText(file.FullName, text, encoding);
}
/// <summary>
/// Writes out all the text to the given file.
/// </summary>
/// <param name="file">The file to write to.</param>
/// <param name="text">The text to write out.</param>
/// <param name="encoding">The encoding to use.</param>
public static void WriteAllText(this FileInfo file, string text, Encoding encoding)
{
File.WriteAllText(file.FullName, text, encoding);
}
}

View file

@ -5,23 +5,23 @@ namespace MfGames.IO.Extensions;
/// </summary>
public static class TypeExtensions
{
/// <summary>
/// Gets the directory for a given type.
/// </summary>
/// <param name="type">The type assembly to get the directory.</param>
/// <returns>The directory or null if the assembly or location is null.</returns>
public static DirectoryInfo? GetDirectory(this Type? type)
{
return type?.Assembly.GetDirectory();
}
/// <summary>
/// Gets the directory for a given type.
/// </summary>
/// <param name="type">The type assembly to get the directory.</param>
/// <returns>The directory or null if the assembly or location is null.</returns>
public static DirectoryInfo? GetDirectory(this Type? type)
{
return type?.Assembly.GetDirectory();
}
/// <summary>
/// Gets the file for a given type's assembly.
/// </summary>
/// <param name="type">The type assembly to get the directory.</param>
/// <returns>The directory or null if the assembly or location is null.</returns>
public static FileInfo? GetFile(this Type? type)
{
return type?.Assembly.GetFile();
}
/// <summary>
/// Gets the file for a given type's assembly.
/// </summary>
/// <param name="type">The type assembly to get the directory.</param>
/// <returns>The directory or null if the assembly or location is null.</returns>
public static FileInfo? GetFile(this Type? type)
{
return type?.Assembly.GetFile();
}
}

View file

@ -5,39 +5,41 @@
/// </summary>
public class NestableReadLock : IDisposable
{
private readonly bool lockAcquired;
private readonly bool lockAcquired;
private readonly ReaderWriterLockSlim readerWriterLockSlim;
private readonly ReaderWriterLockSlim readerWriterLockSlim;
/// <summary>
/// Initializes a new instance of the <see cref="NestableReadLock" /> class.
/// </summary>
/// <param name="readerWriterLockSlim">The reader writer lock slim.</param>
public NestableReadLock(ReaderWriterLockSlim readerWriterLockSlim)
{
// Keep track of the lock since we'll need it to release the lock.
this.readerWriterLockSlim = readerWriterLockSlim;
/// <summary>
/// Initializes a new instance of the <see cref="NestableReadLock" /> class.
/// </summary>
/// <param name="readerWriterLockSlim">The reader writer lock slim.</param>
public NestableReadLock(ReaderWriterLockSlim readerWriterLockSlim)
{
// Keep track of the lock since we'll need it to release the lock.
this.readerWriterLockSlim = readerWriterLockSlim;
// If we already have a read or write lock, we don't do anything.
if (readerWriterLockSlim.IsReadLockHeld
|| readerWriterLockSlim.IsUpgradeableReadLockHeld
|| readerWriterLockSlim.IsWriteLockHeld)
{
this.lockAcquired = false;
}
else
{
readerWriterLockSlim.EnterReadLock();
this.lockAcquired = true;
}
}
// If we already have a read or write lock, we don't do anything.
if (
readerWriterLockSlim.IsReadLockHeld
|| readerWriterLockSlim.IsUpgradeableReadLockHeld
|| readerWriterLockSlim.IsWriteLockHeld
)
{
this.lockAcquired = false;
}
else
{
readerWriterLockSlim.EnterReadLock();
this.lockAcquired = true;
}
}
/// <inheritdoc />
public void Dispose()
{
if (this.lockAcquired)
{
this.readerWriterLockSlim.ExitReadLock();
}
}
/// <inheritdoc />
public void Dispose()
{
if (this.lockAcquired)
{
this.readerWriterLockSlim.ExitReadLock();
}
}
}

View file

@ -5,39 +5,38 @@
/// </summary>
public class NestableUpgradableReadLock : IDisposable
{
private readonly bool lockAcquired;
private readonly bool lockAcquired;
private readonly ReaderWriterLockSlim readerWriterLockSlim;
private readonly ReaderWriterLockSlim readerWriterLockSlim;
/// <summary>
/// Initializes a new instance of the <see cref="NestableUpgradableReadLock" />
/// class.
/// </summary>
/// <param name="readerWriterLockSlim">The reader writer lock slim.</param>
public NestableUpgradableReadLock(ReaderWriterLockSlim readerWriterLockSlim)
{
// Keep track of the lock since we'll need it to release the lock.
this.readerWriterLockSlim = readerWriterLockSlim;
/// <summary>
/// Initializes a new instance of the <see cref="NestableUpgradableReadLock" />
/// class.
/// </summary>
/// <param name="readerWriterLockSlim">The reader writer lock slim.</param>
public NestableUpgradableReadLock(ReaderWriterLockSlim readerWriterLockSlim)
{
// Keep track of the lock since we'll need it to release the lock.
this.readerWriterLockSlim = readerWriterLockSlim;
// If we already have a read or write lock, we don't do anything.
if (readerWriterLockSlim.IsUpgradeableReadLockHeld
|| readerWriterLockSlim.IsWriteLockHeld)
{
this.lockAcquired = false;
}
else
{
readerWriterLockSlim.EnterUpgradeableReadLock();
this.lockAcquired = true;
}
}
// If we already have a read or write lock, we don't do anything.
if (readerWriterLockSlim.IsUpgradeableReadLockHeld || readerWriterLockSlim.IsWriteLockHeld)
{
this.lockAcquired = false;
}
else
{
readerWriterLockSlim.EnterUpgradeableReadLock();
this.lockAcquired = true;
}
}
/// <inheritdoc />
public void Dispose()
{
if (this.lockAcquired)
{
this.readerWriterLockSlim.ExitUpgradeableReadLock();
}
}
/// <inheritdoc />
public void Dispose()
{
if (this.lockAcquired)
{
this.readerWriterLockSlim.ExitUpgradeableReadLock();
}
}
}

View file

@ -5,37 +5,37 @@
/// </summary>
public class NestableWriteLock : IDisposable
{
private readonly bool lockAcquired;
private readonly bool lockAcquired;
private readonly ReaderWriterLockSlim readerWriterLockSlim;
private readonly ReaderWriterLockSlim readerWriterLockSlim;
/// <summary>
/// Initializes a new instance of the <see cref="NestableWriteLock" /> class.
/// </summary>
/// <param name="readerWriterLockSlim">The reader writer lock slim.</param>
public NestableWriteLock(ReaderWriterLockSlim readerWriterLockSlim)
{
// Keep track of the lock since we'll need it to release the lock.
this.readerWriterLockSlim = readerWriterLockSlim;
/// <summary>
/// Initializes a new instance of the <see cref="NestableWriteLock" /> class.
/// </summary>
/// <param name="readerWriterLockSlim">The reader writer lock slim.</param>
public NestableWriteLock(ReaderWriterLockSlim readerWriterLockSlim)
{
// Keep track of the lock since we'll need it to release the lock.
this.readerWriterLockSlim = readerWriterLockSlim;
// If we already have a read or write lock, we don't do anything.
if (readerWriterLockSlim.IsWriteLockHeld)
{
this.lockAcquired = false;
}
else
{
readerWriterLockSlim.EnterWriteLock();
this.lockAcquired = true;
}
}
// If we already have a read or write lock, we don't do anything.
if (readerWriterLockSlim.IsWriteLockHeld)
{
this.lockAcquired = false;
}
else
{
readerWriterLockSlim.EnterWriteLock();
this.lockAcquired = true;
}
}
/// <inheritdoc />
public void Dispose()
{
if (this.lockAcquired)
{
this.readerWriterLockSlim.ExitWriteLock();
}
}
/// <inheritdoc />
public void Dispose()
{
if (this.lockAcquired)
{
this.readerWriterLockSlim.ExitWriteLock();
}
}
}

View file

@ -6,21 +6,21 @@
/// </summary>
public class ReadLock : IDisposable
{
private readonly ReaderWriterLockSlim readerWriterLockSlim;
private readonly ReaderWriterLockSlim readerWriterLockSlim;
/// <summary>
/// Initializes a new instance of the <see cref="ReadLock" /> class.
/// </summary>
/// <param name="readerWriterLockSlim">The reader writer lock slim.</param>
public ReadLock(ReaderWriterLockSlim readerWriterLockSlim)
{
this.readerWriterLockSlim = readerWriterLockSlim;
readerWriterLockSlim.EnterReadLock();
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadLock" /> class.
/// </summary>
/// <param name="readerWriterLockSlim">The reader writer lock slim.</param>
public ReadLock(ReaderWriterLockSlim readerWriterLockSlim)
{
this.readerWriterLockSlim = readerWriterLockSlim;
readerWriterLockSlim.EnterReadLock();
}
/// <inheritdoc />
public void Dispose()
{
this.readerWriterLockSlim.ExitReadLock();
}
/// <inheritdoc />
public void Dispose()
{
this.readerWriterLockSlim.ExitReadLock();
}
}

View file

@ -5,24 +5,24 @@ namespace MfGames.Locking;
/// </summary>
public class UpgradableLock : IDisposable
{
private readonly ReaderWriterLockSlim readerWriterLockSlim;
private readonly ReaderWriterLockSlim readerWriterLockSlim;
/// <summary>
/// Initializes a new instance of the <see cref="UpgradableLock" /> class.
/// </summary>
/// <param name="readerWriterLockSlim">The reader writer lock slim.</param>
public UpgradableLock(ReaderWriterLockSlim readerWriterLockSlim)
{
this.readerWriterLockSlim = readerWriterLockSlim;
readerWriterLockSlim.EnterUpgradeableReadLock();
}
/// <summary>
/// Initializes a new instance of the <see cref="UpgradableLock" /> class.
/// </summary>
/// <param name="readerWriterLockSlim">The reader writer lock slim.</param>
public UpgradableLock(ReaderWriterLockSlim readerWriterLockSlim)
{
this.readerWriterLockSlim = readerWriterLockSlim;
readerWriterLockSlim.EnterUpgradeableReadLock();
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or
/// resetting unmanaged resources.
/// </summary>
public void Dispose()
{
this.readerWriterLockSlim.ExitUpgradeableReadLock();
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or
/// resetting unmanaged resources.
/// </summary>
public void Dispose()
{
this.readerWriterLockSlim.ExitUpgradeableReadLock();
}
}

View file

@ -5,24 +5,24 @@ namespace MfGames.Locking;
/// </summary>
public class WriteLock : IDisposable
{
private readonly ReaderWriterLockSlim readerWriterLockSlim;
private readonly ReaderWriterLockSlim readerWriterLockSlim;
/// <summary>
/// Initializes a new instance of the <see cref="WriteLock" /> class.
/// </summary>
/// <param name="readerWriterLockSlim">The reader writer lock slim.</param>
public WriteLock(ReaderWriterLockSlim readerWriterLockSlim)
{
this.readerWriterLockSlim = readerWriterLockSlim;
readerWriterLockSlim.EnterWriteLock();
}
/// <summary>
/// Initializes a new instance of the <see cref="WriteLock" /> class.
/// </summary>
/// <param name="readerWriterLockSlim">The reader writer lock slim.</param>
public WriteLock(ReaderWriterLockSlim readerWriterLockSlim)
{
this.readerWriterLockSlim = readerWriterLockSlim;
readerWriterLockSlim.EnterWriteLock();
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or
/// resetting unmanaged resources.
/// </summary>
public void Dispose()
{
this.readerWriterLockSlim.ExitWriteLock();
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or
/// resetting unmanaged resources.
/// </summary>
public void Dispose()
{
this.readerWriterLockSlim.ExitWriteLock();
}
}

View file

@ -2,7 +2,6 @@ using Markdig;
using Markdig.Extensions.Tables;
using Markdig.Parsers.Inlines;
using Markdig.Renderers;
using MfGames.Markdown.Gemtext.Renderers;
using MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks;
@ -14,52 +13,52 @@ namespace MfGames.Markdown.Gemtext.Extensions;
/// <seealso cref="IMarkdownExtension" />
public class GemtextPipeTableExtension : IMarkdownExtension
{
/// <summary>
/// Initializes a new instance of the <see cref="GemtextPipeTableExtension" />
/// class.
/// </summary>
/// <param name="options">The options.</param>
public GemtextPipeTableExtension(GemtextPipeTableOptions? options = null)
{
this.Options = options ?? new GemtextPipeTableOptions();
}
/// <summary>
/// Initializes a new instance of the <see cref="GemtextPipeTableExtension" />
/// class.
/// </summary>
/// <param name="options">The options.</param>
public GemtextPipeTableExtension(GemtextPipeTableOptions? options = null)
{
this.Options = options ?? new GemtextPipeTableOptions();
}
/// <summary>
/// Gets the options.
/// </summary>
public GemtextPipeTableOptions Options { get; }
/// <summary>
/// Gets the options.
/// </summary>
public GemtextPipeTableOptions Options { get; }
/// <inheritdoc />
public void Setup(MarkdownPipelineBuilder pipeline)
{
pipeline.PreciseSourceLocation = true;
/// <inheritdoc />
public void Setup(MarkdownPipelineBuilder pipeline)
{
pipeline.PreciseSourceLocation = true;
if (!pipeline.BlockParsers.Contains<PipeTableBlockParser>())
{
pipeline.BlockParsers.Insert(0, new PipeTableBlockParser());
}
if (!pipeline.BlockParsers.Contains<PipeTableBlockParser>())
{
pipeline.BlockParsers.Insert(0, new PipeTableBlockParser());
}
LineBreakInlineParser? lineBreakParser =
pipeline.InlineParsers.FindExact<LineBreakInlineParser>();
LineBreakInlineParser? lineBreakParser =
pipeline.InlineParsers.FindExact<LineBreakInlineParser>();
if (!pipeline.InlineParsers.Contains<PipeTableParser>())
{
pipeline.InlineParsers.InsertBefore<EmphasisInlineParser>(
new PipeTableParser(lineBreakParser!, this.Options));
}
}
if (!pipeline.InlineParsers.Contains<PipeTableParser>())
{
pipeline.InlineParsers.InsertBefore<EmphasisInlineParser>(
new PipeTableParser(lineBreakParser!, this.Options)
);
}
}
/// <inheritdoc />
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is not GemtextRenderer gemtext)
{
return;
}
/// <inheritdoc />
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is not GemtextRenderer gemtext)
{
return;
}
gemtext.ObjectRenderers.Add(
new TableRenderer(
this.Options.OmitPreformatLines,
this.Options.ConfigureTableBuilder));
}
gemtext.ObjectRenderers.Add(
new TableRenderer(this.Options.OmitPreformatLines, this.Options.ConfigureTableBuilder)
);
}
}

View file

@ -1,19 +1,18 @@
using ConsoleTableExt;
using Markdig.Extensions.Tables;
namespace MfGames.Markdown.Gemtext.Extensions;
public class GemtextPipeTableOptions : PipeTableOptions
{
/// <summary>
/// Gets or sets the table builder to control formatting.
/// </summary>
public Action<ConsoleTableBuilder>? ConfigureTableBuilder { get; set; }
/// <summary>
/// Gets or sets the table builder to control formatting.
/// </summary>
public Action<ConsoleTableBuilder>? ConfigureTableBuilder { get; set; }
/// <summary>
/// Gets or sets a value whether the preformat (backticks) fence should
/// not be emitted.
/// </summary>
public bool OmitPreformatLines { get; set; }
/// <summary>
/// Gets or sets a value whether the preformat (backticks) fence should
/// not be emitted.
/// </summary>
public bool OmitPreformatLines { get; set; }
}

View file

@ -2,7 +2,6 @@ using Markdig;
using Markdig.Extensions.SmartyPants;
using Markdig.Parsers.Inlines;
using Markdig.Renderers;
using MfGames.Markdown.Gemtext.Renderers;
using MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines;
@ -13,42 +12,39 @@ namespace MfGames.Markdown.Gemtext.Extensions;
/// </summary>
public class GemtextSmartyPantsExtension : IMarkdownExtension
{
/// <summary>
/// Initializes a new instance of the <see cref="SmartyPantsExtension" /> class.
/// </summary>
/// <param name="options">The options.</param>
public GemtextSmartyPantsExtension(SmartyPantOptions? options)
{
this.Options = options ?? new SmartyPantOptions();
}
/// <summary>
/// Initializes a new instance of the <see cref="SmartyPantsExtension" /> class.
/// </summary>
/// <param name="options">The options.</param>
public GemtextSmartyPantsExtension(SmartyPantOptions? options)
{
this.Options = options ?? new SmartyPantOptions();
}
/// <summary>
/// Gets the options.
/// </summary>
public SmartyPantOptions Options { get; }
/// <summary>
/// Gets the options.
/// </summary>
public SmartyPantOptions Options { get; }
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.InlineParsers.Contains<SmartyPantsInlineParser>())
{
// Insert the parser after the code span parser
pipeline.InlineParsers.InsertAfter<CodeInlineParser>(
new SmartyPantsInlineParser());
}
}
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.InlineParsers.Contains<SmartyPantsInlineParser>())
{
// Insert the parser after the code span parser
pipeline.InlineParsers.InsertAfter<CodeInlineParser>(new SmartyPantsInlineParser());
}
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is not GemtextRenderer gemtextRenderer)
{
return;
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is not GemtextRenderer gemtextRenderer)
{
return;
}
if (!gemtextRenderer.ObjectRenderers
.Contains<GemtextSmartyPantRenderer>())
{
gemtextRenderer.ObjectRenderers.Add(
new GemtextSmartyPantRenderer(this.Options));
}
}
if (!gemtextRenderer.ObjectRenderers.Contains<GemtextSmartyPantRenderer>())
{
gemtextRenderer.ObjectRenderers.Add(new GemtextSmartyPantRenderer(this.Options));
}
}
}

View file

@ -1,6 +1,5 @@
using Markdig;
using Markdig.Renderers;
using MfGames.Markdown.Gemtext.Renderers;
namespace MfGames.Markdown.Gemtext.Extensions;
@ -12,17 +11,15 @@ namespace MfGames.Markdown.Gemtext.Extensions;
/// <seealso cref="IMarkdownExtension" />
public class HtmlAsCodeBlocks : IMarkdownExtension
{
/// <inheritdoc />
public void Setup(MarkdownPipelineBuilder pipeline)
{
}
/// <inheritdoc />
public void Setup(MarkdownPipelineBuilder pipeline) { }
/// <inheritdoc />
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is GemtextRenderer gemtext)
{
gemtext.HtmlBlockFormatting = HtmlBlockFormatting.CodeBlock;
}
}
/// <inheritdoc />
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is GemtextRenderer gemtext)
{
gemtext.HtmlBlockFormatting = HtmlBlockFormatting.CodeBlock;
}
}
}

View file

@ -1,6 +1,5 @@
using Markdig;
using Markdig.Renderers;
using MfGames.Markdown.Gemtext.Renderers;
using MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks;
@ -14,25 +13,22 @@ namespace MfGames.Markdown.Gemtext.Extensions;
/// <seealso cref="IMarkdownExtension" />
public class IncreaseHeaderDepthsAfterFirst : IMarkdownExtension
{
/// <inheritdoc />
public void Setup(MarkdownPipelineBuilder pipeline)
{
}
/// <inheritdoc />
public void Setup(MarkdownPipelineBuilder pipeline) { }
/// <inheritdoc />
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is not GemtextRenderer gemtext)
{
return;
}
/// <inheritdoc />
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is not GemtextRenderer gemtext)
{
return;
}
HeadingRenderer? heading =
gemtext.ObjectRenderers.Find<HeadingRenderer>();
HeadingRenderer? heading = gemtext.ObjectRenderers.Find<HeadingRenderer>();
if (heading != null)
{
heading.IncreaseHeaderDepthAfterFirst = true;
}
}
if (heading != null)
{
heading.IncreaseHeaderDepthAfterFirst = true;
}
}
}

View file

@ -1,6 +1,5 @@
using Markdig;
using Markdig.Renderers;
using MfGames.Markdown.Gemtext.Renderers;
namespace MfGames.Markdown.Gemtext.Extensions;
@ -11,53 +10,50 @@ namespace MfGames.Markdown.Gemtext.Extensions;
/// <seealso cref="IMarkdownExtension" />
public class SetBlockLinkHandling : IMarkdownExtension
{
public SetBlockLinkHandling(
BlockLinkHandling? blockLinkHandling = null,
EndLinkInlineFormatting? endLinkInlineFormatting = null,
int? nextFootnoteNumber = null)
{
this.BlockLinkHandling = blockLinkHandling;
this.EndLinkInlineFormatting = endLinkInlineFormatting;
this.NextFootnoteNumber = nextFootnoteNumber;
}
public SetBlockLinkHandling(
BlockLinkHandling? blockLinkHandling = null,
EndLinkInlineFormatting? endLinkInlineFormatting = null,
int? nextFootnoteNumber = null
)
{
this.BlockLinkHandling = blockLinkHandling;
this.EndLinkInlineFormatting = endLinkInlineFormatting;
this.NextFootnoteNumber = nextFootnoteNumber;
}
/// <summary>
/// Gets or sets how block links are handled. If this is null, then no
/// change is made to the current renderer.
/// </summary>
public BlockLinkHandling? BlockLinkHandling { get; set; }
/// <summary>
/// Gets or sets how block links are handled. If this is null, then no
/// change is made to the current renderer.
/// </summary>
public BlockLinkHandling? BlockLinkHandling { get; set; }
/// <summary>
/// Gets or sets how links are formatted if they are gathered to the
/// end of the paragraph or document. If this is null, then no change
/// will be made.
/// </summary>
public EndLinkInlineFormatting? EndLinkInlineFormatting { get; set; }
/// <summary>
/// Gets or sets how links are formatted if they are gathered to the
/// end of the paragraph or document. If this is null, then no change
/// will be made.
/// </summary>
public EndLinkInlineFormatting? EndLinkInlineFormatting { get; set; }
/// <summary>
/// Gets or sets the next footnote number. If this is null, then no
/// change will be made.
/// </summary>
public int? NextFootnoteNumber { get; set; }
/// <summary>
/// Gets or sets the next footnote number. If this is null, then no
/// change will be made.
/// </summary>
public int? NextFootnoteNumber { get; set; }
/// <inheritdoc />
public void Setup(MarkdownPipelineBuilder pipeline)
{
}
/// <inheritdoc />
public void Setup(MarkdownPipelineBuilder pipeline) { }
/// <inheritdoc />
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is not GemtextRenderer gemtext)
{
return;
}
/// <inheritdoc />
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is not GemtextRenderer gemtext)
{
return;
}
gemtext.BlockLinkHandling = this.BlockLinkHandling
?? gemtext.BlockLinkHandling;
gemtext.EndLinkInlineFormatting = this.EndLinkInlineFormatting
?? gemtext.EndLinkInlineFormatting;
gemtext.NextFootnoteNumber = this.NextFootnoteNumber
?? gemtext.NextFootnoteNumber;
}
gemtext.BlockLinkHandling = this.BlockLinkHandling ?? gemtext.BlockLinkHandling;
gemtext.EndLinkInlineFormatting =
this.EndLinkInlineFormatting ?? gemtext.EndLinkInlineFormatting;
gemtext.NextFootnoteNumber = this.NextFootnoteNumber ?? gemtext.NextFootnoteNumber;
}
}

View file

@ -1,6 +1,5 @@
using Markdig;
using Markdig.Renderers;
using MfGames.Markdown.Gemtext.Renderers;
namespace MfGames.Markdown.Gemtext.Extensions;
@ -12,38 +11,36 @@ namespace MfGames.Markdown.Gemtext.Extensions;
/// <seealso cref="IMarkdownExtension" />
public class SetInlineFormatting : IMarkdownExtension
{
/// <summary>
/// Gets or sets the override formatting for code lines
/// (backtick) spans.
/// </summary>
public InlineFormatting? Code { get; set; }
/// <summary>
/// Gets or sets the override formatting for code lines
/// (backtick) spans.
/// </summary>
public InlineFormatting? Code { get; set; }
/// <summary>
/// Sets or sets the override formatting for all inlines.
/// </summary>
public InlineFormatting? Default { get; set; }
/// <summary>
/// Sets or sets the override formatting for all inlines.
/// </summary>
public InlineFormatting? Default { get; set; }
/// <summary>
/// Gets or sets the override formatting for emphasis (italic
/// and bold) spans.
/// </summary>
public InlineFormatting? Emphasis { get; set; }
/// <summary>
/// Gets or sets the override formatting for emphasis (italic
/// and bold) spans.
/// </summary>
public InlineFormatting? Emphasis { get; set; }
/// <inheritdoc />
public void Setup(MarkdownPipelineBuilder pipeline)
{
}
/// <inheritdoc />
public void Setup(MarkdownPipelineBuilder pipeline) { }
/// <inheritdoc />
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is not GemtextRenderer gemtext)
{
return;
}
/// <inheritdoc />
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is not GemtextRenderer gemtext)
{
return;
}
gemtext.InlineFormatting = this.Default ?? gemtext.InlineFormatting;
gemtext.EmphasisFormatting = this.Emphasis;
gemtext.CodeFormatting = this.Code;
}
gemtext.InlineFormatting = this.Default ?? gemtext.InlineFormatting;
gemtext.EmphasisFormatting = this.Emphasis;
gemtext.CodeFormatting = this.Code;
}
}

View file

@ -1,7 +1,6 @@
using Markdig;
using Markdig.Parsers;
using Markdig.Syntax;
using MfGames.Markdown.Gemtext.Renderers;
namespace MfGames.Markdown.Gemtext;
@ -13,69 +12,69 @@ namespace MfGames.Markdown.Gemtext;
/// </summary>
public static class MarkdownGemtext
{
private static readonly MarkdownPipeline DefaultPipeline;
private static readonly MarkdownPipeline DefaultPipeline;
static MarkdownGemtext()
{
DefaultPipeline = new MarkdownPipelineBuilder()
.Build();
}
static MarkdownGemtext()
{
DefaultPipeline = new MarkdownPipelineBuilder().Build();
}
/// <summary>
/// Converts the given Markdown
/// </summary>
/// <param name="markdown">A Markdown text.</param>
/// <param name="pipeline">The pipeline used for the conversion.</param>
/// <param name="context">A parser context used for the parsing.</param>
/// <returns>The result of the conversion</returns>
public static string ToGemtext(
string markdown,
MarkdownPipeline? pipeline = null,
MarkdownParserContext? context = null)
{
if (markdown == null)
{
throw new ArgumentNullException(nameof(markdown));
}
/// <summary>
/// Converts the given Markdown
/// </summary>
/// <param name="markdown">A Markdown text.</param>
/// <param name="pipeline">The pipeline used for the conversion.</param>
/// <param name="context">A parser context used for the parsing.</param>
/// <returns>The result of the conversion</returns>
public static string ToGemtext(
string markdown,
MarkdownPipeline? pipeline = null,
MarkdownParserContext? context = null
)
{
if (markdown == null)
{
throw new ArgumentNullException(nameof(markdown));
}
pipeline ??= DefaultPipeline;
pipeline ??= DefaultPipeline;
MarkdownDocument document = MarkdownParser
.Parse(markdown, pipeline, context);
MarkdownDocument document = MarkdownParser.Parse(markdown, pipeline, context);
return ToGemtext(document, pipeline);
}
return ToGemtext(document, pipeline);
}
/// <summary>
/// Converts a Markdown document to HTML.
/// </summary>
/// <param name="document">A Markdown document.</param>
/// <param name="pipeline">The pipeline used for the conversion.</param>
/// <returns>The result of the conversion</returns>
/// <exception cref="ArgumentNullException">if markdown document variable is null</exception>
public static string ToGemtext(
this MarkdownDocument document,
MarkdownPipeline? pipeline = null)
{
// Make sure we have sane parameters.
if (document == null)
{
throw new ArgumentNullException(nameof(document));
}
/// <summary>
/// Converts a Markdown document to HTML.
/// </summary>
/// <param name="document">A Markdown document.</param>
/// <param name="pipeline">The pipeline used for the conversion.</param>
/// <returns>The result of the conversion</returns>
/// <exception cref="ArgumentNullException">if markdown document variable is null</exception>
public static string ToGemtext(
this MarkdownDocument document,
MarkdownPipeline? pipeline = null
)
{
// Make sure we have sane parameters.
if (document == null)
{
throw new ArgumentNullException(nameof(document));
}
pipeline ??= DefaultPipeline;
pipeline ??= DefaultPipeline;
// Set up the writer to contain the markdown and the Gemtext
// renderer.
var writer = new StringWriter();
GemtextRenderer renderer = new(writer);
// Set up the writer to contain the markdown and the Gemtext
// renderer.
var writer = new StringWriter();
GemtextRenderer renderer = new(writer);
pipeline.Setup(renderer);
pipeline.Setup(renderer);
// Render the Markdown into Gemtext and re turn the results.
renderer.Render(document);
renderer.Writer.Flush();
// Render the Markdown into Gemtext and re turn the results.
renderer.Render(document);
renderer.Writer.Flush();
return renderer.Writer.ToString() ?? string.Empty;
}
return renderer.Writer.ToString() ?? string.Empty;
}
}

View file

@ -5,29 +5,29 @@ namespace MfGames.Markdown.Gemtext.Renderers;
/// </summary>
public enum BlockLinkHandling
{
/// <summary>
/// Indicates that the paragraph should be broken apart and the link
/// included on its own line in the middle of the paragraph.
/// </summary>
InsertLine,
/// <summary>
/// Indicates that the paragraph should be broken apart and the link
/// included on its own line in the middle of the paragraph.
/// </summary>
InsertLine,
/// <summary>
/// Indicates that all the links in a paragraph should be gathered
/// and then emitted at the end of the paragraph. The text of the link
/// will be left in the paragraph.
/// </summary>
ParagraphEnd,
/// <summary>
/// Indicates that all the links in a paragraph should be gathered
/// and then emitted at the end of the paragraph. The text of the link
/// will be left in the paragraph.
/// </summary>
ParagraphEnd,
/// <summary>
/// Indicates that all the links in the document should be gathered
/// and then emitted at the end of the document. The text of the link
/// will be left in the paragraph.
/// </summary>
DocumentEnd,
/// <summary>
/// Indicates that all the links in the document should be gathered
/// and then emitted at the end of the document. The text of the link
/// will be left in the paragraph.
/// </summary>
DocumentEnd,
/// <summary>
/// Indicates that the links themselves should be removed and just the
/// text included in the paragraph.
/// </summary>
Remove,
/// <summary>
/// Indicates that the links themselves should be removed and just the
/// text included in the paragraph.
/// </summary>
Remove,
}

View file

@ -7,15 +7,15 @@ namespace MfGames.Markdown.Gemtext.Renderers;
/// </summary>
public enum EndLinkInlineFormatting
{
/// <summary>
/// Indicates that a footnote notation (`[1]`) will be insert into the
/// text and then the link will be displayed with the URL when gathered.
/// </summary>
Footnote,
/// <summary>
/// Indicates that a footnote notation (`[1]`) will be insert into the
/// text and then the link will be displayed with the URL when gathered.
/// </summary>
Footnote,
/// <summary>
/// Indicates that the text is put in as-is into the gathered link with
/// no footnote given in the block.
/// </summary>
Text,
/// <summary>
/// Indicates that the text is put in as-is into the gathered link with
/// no footnote given in the block.
/// </summary>
Text,
}

View file

@ -9,30 +9,30 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks;
/// <seealso cref="GemtextObjectRenderer{CodeBlock}" />
public class CodeBlockRenderer : GemtextObjectRenderer<CodeBlock>
{
protected override void Write(GemtextRenderer renderer, CodeBlock obj)
{
// We need to have two lines above this.
renderer.EnsureTwoLines();
protected override void Write(GemtextRenderer renderer, CodeBlock obj)
{
// We need to have two lines above this.
renderer.EnsureTwoLines();
// Code blocks are always fenced, but we allow for additional text
// at the end of them which is only in `FencedCodeBlock`.
if (obj is FencedCodeBlock fenced)
{
renderer.WriteLine("```" + fenced.Info);
}
else
{
renderer.WriteLine("```");
}
// Code blocks are always fenced, but we allow for additional text
// at the end of them which is only in `FencedCodeBlock`.
if (obj is FencedCodeBlock fenced)
{
renderer.WriteLine("```" + fenced.Info);
}
else
{
renderer.WriteLine("```");
}
renderer.WriteLeafRawLines(obj, true);
renderer.Write("```");
renderer.WriteLeafRawLines(obj, true);
renderer.Write("```");
// If we aren't at the end of the container, then add some spacing.
if (!renderer.IsLastInContainer)
{
renderer.WriteLine();
renderer.WriteLine();
}
}
// If we aren't at the end of the container, then add some spacing.
if (!renderer.IsLastInContainer)
{
renderer.WriteLine();
renderer.WriteLine();
}
}
}

View file

@ -8,15 +8,15 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks;
/// <seealso cref="GemtextObjectRenderer{CodeBlock}" />
public class CustomContainerRenderer : GemtextObjectRenderer<CustomContainer>
{
protected override void Write(GemtextRenderer renderer, CustomContainer obj)
{
renderer.EnsureTwoLines();
renderer.WriteChildren(obj);
protected override void Write(GemtextRenderer renderer, CustomContainer obj)
{
renderer.EnsureTwoLines();
renderer.WriteChildren(obj);
if (!renderer.IsLastInContainer)
{
renderer.WriteLine();
renderer.WriteLine();
}
}
if (!renderer.IsLastInContainer)
{
renderer.WriteLine();
renderer.WriteLine();
}
}
}

View file

@ -8,48 +8,46 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks;
/// <seealso cref="GemtextObjectRenderer{HeadingBlock}" />
public class HeadingRenderer : GemtextObjectRenderer<HeadingBlock>
{
private int currentHeading;
private int currentHeading;
/// <summary>
/// Gets or sets a value indicating whether the header depths are
/// increased after the first one.
/// </summary>
public bool IncreaseHeaderDepthAfterFirst { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the header depths are
/// increased after the first one.
/// </summary>
public bool IncreaseHeaderDepthAfterFirst { get; set; }
protected override void Write(
GemtextRenderer renderer,
HeadingBlock obj)
{
// Figure out the level we should be processing.
int level = obj.Level;
protected override void Write(GemtextRenderer renderer, HeadingBlock obj)
{
// Figure out the level we should be processing.
int level = obj.Level;
if (this.currentHeading++ > 0 && this.IncreaseHeaderDepthAfterFirst)
{
// Check the second header we see. If this header is H2 or
// higher, then we assume that the file has been already updated
// to handle the heading and we stop processing.
if (this.currentHeading == 2 && level != 1)
{
this.IncreaseHeaderDepthAfterFirst = false;
}
else
{
// We are bumping the heading levels up.
level++;
}
}
if (this.currentHeading++ > 0 && this.IncreaseHeaderDepthAfterFirst)
{
// Check the second header we see. If this header is H2 or
// higher, then we assume that the file has been already updated
// to handle the heading and we stop processing.
if (this.currentHeading == 2 && level != 1)
{
this.IncreaseHeaderDepthAfterFirst = false;
}
else
{
// We are bumping the heading levels up.
level++;
}
}
// Write out the prefix of the header.
string prefix = level switch
{
1 => "# ",
2 => "## ",
3 => "### ",
_ => "",
};
// Write out the prefix of the header.
string prefix = level switch
{
1 => "# ",
2 => "## ",
3 => "### ",
_ => "",
};
renderer.EnsureTwoLines();
renderer.Write(prefix);
renderer.WriteLeafInline(obj);
}
renderer.EnsureTwoLines();
renderer.Write(prefix);
renderer.WriteLeafInline(obj);
}
}

View file

@ -8,25 +8,25 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks;
/// <seealso cref="GemtextObjectRenderer{GemtextBlock}" />
public class HtmlBlockRenderer : GemtextObjectRenderer<HtmlBlock>
{
protected override void Write(GemtextRenderer renderer, HtmlBlock obj)
{
// If we are stripping out HTML blocks (default), then nothing to
// do with rendering.
if (renderer.HtmlBlockFormatting == HtmlBlockFormatting.Remove)
{
return;
}
protected override void Write(GemtextRenderer renderer, HtmlBlock obj)
{
// If we are stripping out HTML blocks (default), then nothing to
// do with rendering.
if (renderer.HtmlBlockFormatting == HtmlBlockFormatting.Remove)
{
return;
}
// Otherwise, we treat this as a fenced code block.
renderer.EnsureTwoLines();
renderer.WriteLine("```html");
renderer.WriteLeafRawLines(obj, true);
renderer.WriteLine("```");
// Otherwise, we treat this as a fenced code block.
renderer.EnsureTwoLines();
renderer.WriteLine("```html");
renderer.WriteLeafRawLines(obj, true);
renderer.WriteLine("```");
// If we aren't at the end of the container, then add some spacing.
if (!renderer.IsLastInContainer)
{
renderer.WriteLine();
}
}
// If we aren't at the end of the container, then add some spacing.
if (!renderer.IsLastInContainer)
{
renderer.WriteLine();
}
}
}

View file

@ -8,27 +8,25 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks;
/// <seealso cref="GemtextObjectRenderer{ListBlock}" />
public class ListRenderer : GemtextObjectRenderer<ListBlock>
{
protected override void Write(
GemtextRenderer renderer,
ListBlock listBlock)
{
// Lists need to be separated from the rest.
renderer.EnsureTwoLines();
protected override void Write(GemtextRenderer renderer, ListBlock listBlock)
{
// Lists need to be separated from the rest.
renderer.EnsureTwoLines();
// Go through each list item and write them out.
foreach (Block? item in listBlock)
{
// If the list only contains a link, then we just render the
// link instead.
var listItem = (ListItemBlock)item;
// Go through each list item and write them out.
foreach (Block? item in listBlock)
{
// If the list only contains a link, then we just render the
// link instead.
var listItem = (ListItemBlock)item;
if (!listItem.OnlyHasSingleLink())
{
renderer.EnsureLine();
renderer.Write("* ");
}
if (!listItem.OnlyHasSingleLink())
{
renderer.EnsureLine();
renderer.Write("* ");
}
renderer.WriteChildren(listItem);
}
}
renderer.WriteChildren(listItem);
}
}
}

View file

@ -1,5 +1,4 @@
using Markdig.Syntax;
using MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines;
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks;
@ -8,20 +7,17 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks;
/// A Gemtext renderer for a <see cref="MarkdownDocument" />.
/// </summary>
/// <seealso cref="GemtextObjectRenderer{ParagraphBlock}" />
public class MarkdownDocumentRenderer
: GemtextObjectRenderer<MarkdownDocument>
public class MarkdownDocumentRenderer : GemtextObjectRenderer<MarkdownDocument>
{
protected override void Write(
GemtextRenderer renderer,
MarkdownDocument obj)
{
// Simply write out the contents.
renderer.WriteChildren(obj);
protected override void Write(GemtextRenderer renderer, MarkdownDocument obj)
{
// Simply write out the contents.
renderer.WriteChildren(obj);
// If we get to the end of the document and we have gathered links,
// and we are in DocumentEnd mode, then write out the links. We
// don't test for the mode here because if there are links, we
// should write them out.
LinkInlineRenderer.WriteGatheredLinks(renderer);
}
// If we get to the end of the document and we have gathered links,
// and we are in DocumentEnd mode, then write out the links. We
// don't test for the mode here because if there are links, we
// should write them out.
LinkInlineRenderer.WriteGatheredLinks(renderer);
}
}

View file

@ -1,5 +1,4 @@
using Markdig.Syntax;
using MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines;
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks;
@ -10,34 +9,31 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks;
/// <seealso cref="GemtextObjectRenderer{ParagraphBlock}" />
public class ParagraphRenderer : GemtextObjectRenderer<ParagraphBlock>
{
protected override void Write(
GemtextRenderer renderer,
ParagraphBlock obj)
{
// If we aren't the first in the container, we need to break apart
// the lines to make it easier to read.
if (!renderer.IsFirstInContainer)
{
renderer.EnsureTwoLines();
}
protected override void Write(GemtextRenderer renderer, ParagraphBlock obj)
{
// If we aren't the first in the container, we need to break apart
// the lines to make it easier to read.
if (!renderer.IsFirstInContainer)
{
renderer.EnsureTwoLines();
}
// We need to save the state of the link rendering while handling
// this block.
if (obj.OnlyHasSingleLink())
{
renderer.WriteLeafInline(obj);
}
else
{
renderer.WhileLinkInsideBlock(
() => renderer.WriteLeafInline(obj));
}
// We need to save the state of the link rendering while handling
// this block.
if (obj.OnlyHasSingleLink())
{
renderer.WriteLeafInline(obj);
}
else
{
renderer.WhileLinkInsideBlock(() => renderer.WriteLeafInline(obj));
}
// If we get to the end of the paragraph and we have gathered links,
// and we are in ParagraphEnd mode, then write out the links.
if (renderer.BlockLinkHandling == BlockLinkHandling.ParagraphEnd)
{
LinkInlineRenderer.WriteGatheredLinks(renderer);
}
}
// If we get to the end of the paragraph and we have gathered links,
// and we are in ParagraphEnd mode, then write out the links.
if (renderer.BlockLinkHandling == BlockLinkHandling.ParagraphEnd)
{
LinkInlineRenderer.WriteGatheredLinks(renderer);
}
}
}

View file

@ -8,13 +8,13 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks;
/// <seealso cref="GemtextObjectRenderer{QuoteBlock}" />
public class QuoteBlockRenderer : GemtextObjectRenderer<QuoteBlock>
{
protected override void Write(GemtextRenderer renderer, QuoteBlock obj)
{
string quoteIndent = obj.QuoteChar + " ";
protected override void Write(GemtextRenderer renderer, QuoteBlock obj)
{
string quoteIndent = obj.QuoteChar + " ";
renderer.EnsureTwoLines();
renderer.PushIndent(quoteIndent);
renderer.WriteChildren(obj);
renderer.PopIndent();
}
renderer.EnsureTwoLines();
renderer.PushIndent(quoteIndent);
renderer.WriteChildren(obj);
renderer.PopIndent();
}
}

View file

@ -1,131 +1,126 @@
using ConsoleTableExt;
using Markdig.Extensions.Tables;
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks;
public class TableRenderer : GemtextObjectRenderer<Table>
{
private readonly Action<ConsoleTableBuilder>? configureTableBuilder;
private readonly Action<ConsoleTableBuilder>? configureTableBuilder;
private readonly bool omitPreformat;
private readonly bool omitPreformat;
public TableRenderer(
bool omitPreformat,
Action<ConsoleTableBuilder>? configureTableBuilder)
{
this.omitPreformat = omitPreformat;
this.configureTableBuilder = configureTableBuilder;
}
public TableRenderer(bool omitPreformat, Action<ConsoleTableBuilder>? configureTableBuilder)
{
this.omitPreformat = omitPreformat;
this.configureTableBuilder = configureTableBuilder;
}
protected override void Write(GemtextRenderer renderer, Table table)
{
// Since Gemtext doesn't have a table format per-se, we are going
// to use ConsoleTableEx to make a nicely-formatted table and emit
// the lines directly. That should produce the desired result.
protected override void Write(GemtextRenderer renderer, Table table)
{
// Since Gemtext doesn't have a table format per-se, we are going
// to use ConsoleTableEx to make a nicely-formatted table and emit
// the lines directly. That should produce the desired result.
// Gather up information about the data since that is where the
// builder starts with.
bool hasHeader = false;
List<object> header = new();
List<List<object>> data = new();
Dictionary<int, TextAligntment> align = new();
// Gather up information about the data since that is where the
// builder starts with.
bool hasHeader = false;
List<object> header = new();
List<List<object>> data = new();
Dictionary<int, TextAligntment> align = new();
foreach (TableRow row in table.OfType<TableRow>())
{
// If we haven't seen a header, then we include that.
if (!hasHeader && row.IsHeader)
{
header = GetCellValues(row);
SetAlignments(table, align, row);
continue;
}
foreach (TableRow row in table.OfType<TableRow>())
{
// If we haven't seen a header, then we include that.
if (!hasHeader && row.IsHeader)
{
header = GetCellValues(row);
SetAlignments(table, align, row);
continue;
}
// Otherwise, we treat it as a row and go through the columns.
List<object> cells = GetCellValues(row);
// Otherwise, we treat it as a row and go through the columns.
List<object> cells = GetCellValues(row);
data.Add(cells);
}
data.Add(cells);
}
// Set up the table.
ConsoleTableBuilder builder = ConsoleTableBuilder
.From(data)
.WithColumn(header.OfType<string>().ToArray())
.WithHeaderTextAlignment(align)
.WithTextAlignment(align);
// Set up the table.
ConsoleTableBuilder builder = ConsoleTableBuilder
.From(data)
.WithColumn(header.OfType<string>().ToArray())
.WithHeaderTextAlignment(align)
.WithTextAlignment(align);
this.configureTableBuilder?.Invoke(builder);
this.configureTableBuilder?.Invoke(builder);
// Format the final table.
string formatted = builder.Export().ToString().TrimEnd();
// Format the final table.
string formatted = builder.Export().ToString().TrimEnd();
// Write out the table including making sure two lines are above it.
renderer.EnsureTwoLines();
// Write out the table including making sure two lines are above it.
renderer.EnsureTwoLines();
if (!this.omitPreformat)
{
renderer.WriteLine("```");
}
if (!this.omitPreformat)
{
renderer.WriteLine("```");
}
renderer.WriteLine(formatted);
renderer.WriteLine(formatted);
if (!this.omitPreformat)
{
renderer.WriteLine("```");
renderer.WriteLine();
}
}
if (!this.omitPreformat)
{
renderer.WriteLine("```");
renderer.WriteLine();
}
}
private static List<object> GetCellValues(TableRow row)
{
List<object> cells = new();
private static List<object> GetCellValues(TableRow row)
{
List<object> cells = new();
foreach (TableCell cell in row.OfType<TableCell>())
{
// Write out to a text since we can't have a callback while
// rendering the table cells.
using var writer = new StringWriter();
var innerRenderer = new GemtextRenderer(writer);
foreach (TableCell cell in row.OfType<TableCell>())
{
// Write out to a text since we can't have a callback while
// rendering the table cells.
using var writer = new StringWriter();
var innerRenderer = new GemtextRenderer(writer);
innerRenderer.Render(cell);
cells.Add(writer.ToString());
}
innerRenderer.Render(cell);
cells.Add(writer.ToString());
}
return cells;
}
return cells;
}
private static void SetAlignments(
Table table,
Dictionary<int, TextAligntment> align,
TableRow row)
{
for (int i = 0; i < row.Count; i++)
{
// Copied from Markdig's version.
var cell = (TableCell)row[i];
int columnIndex = cell.ColumnIndex < 0
|| cell.ColumnIndex >=
table.ColumnDefinitions.Count
? i
: cell.ColumnIndex;
columnIndex =
columnIndex >= table.ColumnDefinitions.Count
? table.ColumnDefinitions.Count - 1
: columnIndex;
TableColumnAlign? alignment = table
.ColumnDefinitions[columnIndex]
.Alignment;
private static void SetAlignments(
Table table,
Dictionary<int, TextAligntment> align,
TableRow row
)
{
for (int i = 0; i < row.Count; i++)
{
// Copied from Markdig's version.
var cell = (TableCell)row[i];
int columnIndex =
cell.ColumnIndex < 0 || cell.ColumnIndex >= table.ColumnDefinitions.Count
? i
: cell.ColumnIndex;
columnIndex =
columnIndex >= table.ColumnDefinitions.Count
? table.ColumnDefinitions.Count - 1
: columnIndex;
TableColumnAlign? alignment = table.ColumnDefinitions[columnIndex].Alignment;
if (alignment.HasValue)
{
align[columnIndex] = alignment.Value switch
{
TableColumnAlign.Center => TextAligntment.Center,
TableColumnAlign.Left => TextAligntment.Left,
TableColumnAlign.Right => TextAligntment.Right,
_ => TextAligntment.Left,
};
}
}
}
if (alignment.HasValue)
{
align[columnIndex] = alignment.Value switch
{
TableColumnAlign.Center => TextAligntment.Center,
TableColumnAlign.Left => TextAligntment.Left,
TableColumnAlign.Right => TextAligntment.Right,
_ => TextAligntment.Left,
};
}
}
}
}

View file

@ -6,14 +6,11 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks;
/// A Gemtext renderer for a <see cref="ThematicBreakBlock" />.
/// </summary>
/// <seealso cref="GemtextObjectRenderer{ThematicBreakBlock}" />
public class ThematicBreakRenderer
: GemtextObjectRenderer<ThematicBreakBlock>
public class ThematicBreakRenderer : GemtextObjectRenderer<ThematicBreakBlock>
{
protected override void Write(
GemtextRenderer renderer,
ThematicBreakBlock obj)
{
renderer.EnsureTwoLines();
renderer.WriteLine("---");
}
protected override void Write(GemtextRenderer renderer, ThematicBreakBlock obj)
{
renderer.EnsureTwoLines();
renderer.WriteLine("---");
}
}

View file

@ -11,7 +11,5 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext;
/// <typeparam name="TObject">The type of the object.</typeparam>
/// <seealso cref="IMarkdownObjectRenderer" />
public abstract class GemtextObjectRenderer<TObject>
: MarkdownObjectRenderer<GemtextRenderer, TObject>
where TObject : MarkdownObject
{
}
: MarkdownObjectRenderer<GemtextRenderer, TObject>
where TObject : MarkdownObject { }

View file

@ -8,22 +8,22 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines;
/// <seealso cref="GemtextObjectRenderer{CodeInline}" />
public class CodeInlineRenderer : GemtextObjectRenderer<CodeInline>
{
protected override void Write(GemtextRenderer renderer, CodeInline obj)
{
const string Delimiter = "`";
InlineFormatting formatting = renderer.CodeFormattingResolved;
bool normalize = formatting == InlineFormatting.Normalize;
protected override void Write(GemtextRenderer renderer, CodeInline obj)
{
const string Delimiter = "`";
InlineFormatting formatting = renderer.CodeFormattingResolved;
bool normalize = formatting == InlineFormatting.Normalize;
if (normalize)
{
renderer.Write(Delimiter);
}
if (normalize)
{
renderer.Write(Delimiter);
}
renderer.Write(obj.Content);
renderer.Write(obj.Content);
if (normalize)
{
renderer.Write(Delimiter);
}
}
if (normalize)
{
renderer.Write(Delimiter);
}
}
}

View file

@ -6,14 +6,11 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines;
/// A Gemtext renderer for a <see cref="DelimiterInline" />.
/// </summary>
/// <seealso cref="GemtextObjectRenderer{DelimiterInline}" />
public class DelimiterInlineRenderer
: GemtextObjectRenderer<DelimiterInline>
public class DelimiterInlineRenderer : GemtextObjectRenderer<DelimiterInline>
{
protected override void Write(
GemtextRenderer renderer,
DelimiterInline obj)
{
renderer.Write(obj.ToLiteral());
renderer.WriteChildren(obj);
}
protected override void Write(GemtextRenderer renderer, DelimiterInline obj)
{
renderer.Write(obj.ToLiteral());
renderer.WriteChildren(obj);
}
}

View file

@ -8,24 +8,22 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines;
/// <seealso cref="GemtextObjectRenderer{EmphasisInline}" />
public class EmphasisInlineRenderer : GemtextObjectRenderer<EmphasisInline>
{
protected override void Write(
GemtextRenderer renderer,
EmphasisInline obj)
{
InlineFormatting formatting = renderer.EmphasisFormattingResolved;
bool normalize = formatting == InlineFormatting.Normalize;
string delimiter = new string('*', obj.DelimiterCount);
protected override void Write(GemtextRenderer renderer, EmphasisInline obj)
{
InlineFormatting formatting = renderer.EmphasisFormattingResolved;
bool normalize = formatting == InlineFormatting.Normalize;
string delimiter = new string('*', obj.DelimiterCount);
if (normalize)
{
renderer.Write(delimiter);
}
if (normalize)
{
renderer.Write(delimiter);
}
renderer.WriteChildren(obj);
renderer.WriteChildren(obj);
if (normalize)
{
renderer.Write(delimiter);
}
}
if (normalize)
{
renderer.Write(delimiter);
}
}
}

View file

@ -1,5 +1,4 @@
using System.Net;
using Markdig.Extensions.SmartyPants;
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines;
@ -8,33 +7,31 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines;
/// A Gemtext renderer for a <see cref="SmartyPant" />.
/// </summary>
/// <seealso cref="GemtextObjectRenderer{GemtextEntityInline}" />
public class GemtextSmartyPantRenderer
: GemtextObjectRenderer<SmartyPant>
public class GemtextSmartyPantRenderer : GemtextObjectRenderer<SmartyPant>
{
private static readonly SmartyPantOptions DefaultOptions = new();
private static readonly SmartyPantOptions DefaultOptions = new();
private readonly SmartyPantOptions options;
private readonly SmartyPantOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="HtmlSmartyPantRenderer" /> class.
/// </summary>
/// <param name="options">The options.</param>
/// <exception cref="ArgumentNullException"></exception>
public GemtextSmartyPantRenderer(SmartyPantOptions? options)
{
this.options = options
?? throw new ArgumentNullException(nameof(options));
}
/// <summary>
/// Initializes a new instance of the <see cref="HtmlSmartyPantRenderer" /> class.
/// </summary>
/// <param name="options">The options.</param>
/// <exception cref="ArgumentNullException"></exception>
public GemtextSmartyPantRenderer(SmartyPantOptions? options)
{
this.options = options ?? throw new ArgumentNullException(nameof(options));
}
protected override void Write(GemtextRenderer renderer, SmartyPant obj)
{
if (!this.options.Mapping.TryGetValue(obj.Type, out string? text))
{
DefaultOptions.Mapping.TryGetValue(obj.Type, out text);
}
protected override void Write(GemtextRenderer renderer, SmartyPant obj)
{
if (!this.options.Mapping.TryGetValue(obj.Type, out string? text))
{
DefaultOptions.Mapping.TryGetValue(obj.Type, out text);
}
string? unicode = WebUtility.HtmlDecode(text);
string? unicode = WebUtility.HtmlDecode(text);
renderer.Write(unicode);
}
renderer.Write(unicode);
}
}

View file

@ -6,13 +6,10 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines;
/// A Gemtext renderer for a <see cref="GemtextEntityInline" />.
/// </summary>
/// <seealso cref="GemtextObjectRenderer{GemtextEntityInline}" />
public class HtmlEntityInlineRenderer
: GemtextObjectRenderer<HtmlEntityInline>
public class HtmlEntityInlineRenderer : GemtextObjectRenderer<HtmlEntityInline>
{
protected override void Write(
GemtextRenderer renderer,
HtmlEntityInline obj)
{
renderer.Write(obj.Transcoded);
}
protected override void Write(GemtextRenderer renderer, HtmlEntityInline obj)
{
renderer.Write(obj.Transcoded);
}
}

View file

@ -6,26 +6,23 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines;
/// A Gemtext renderer for a <see cref="LineBreakInline" />.
/// </summary>
/// <seealso cref="GemtextObjectRenderer{LineBreakInline}" />
public class LineBreakInlineRenderer
: GemtextObjectRenderer<LineBreakInline>
public class LineBreakInlineRenderer : GemtextObjectRenderer<LineBreakInline>
{
/// <summary>
/// Gets or sets a value indicating whether to render this softline break as a
/// Gemtext hardline break tag (&lt;br /&gt;)
/// </summary>
public bool RenderAsHardlineBreak { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to render this softline break as a
/// Gemtext hardline break tag (&lt;br /&gt;)
/// </summary>
public bool RenderAsHardlineBreak { get; set; }
protected override void Write(
GemtextRenderer renderer,
LineBreakInline obj)
{
if (obj.IsHard || this.RenderAsHardlineBreak)
{
renderer.EnsureTwoLines();
}
else
{
renderer.Write(" ");
}
}
protected override void Write(GemtextRenderer renderer, LineBreakInline obj)
{
if (obj.IsHard || this.RenderAsHardlineBreak)
{
renderer.EnsureTwoLines();
}
else
{
renderer.Write(" ");
}
}
}

View file

@ -8,116 +8,110 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines;
/// <seealso cref="GemtextObjectRenderer{LinkInline}" />
public class LinkInlineRenderer : GemtextObjectRenderer<LinkInline>
{
/// <summary>
/// Writes out any gathered links in a block.
/// </summary>
/// <param name="renderer">The renderer being used.</param>
public static void WriteGatheredLinks(GemtextRenderer renderer)
{
// If we have no gathered links, then there is nothing to do.
if (renderer.GatheredLinks.Count <= 0)
{
return;
}
/// <summary>
/// Writes out any gathered links in a block.
/// </summary>
/// <param name="renderer">The renderer being used.</param>
public static void WriteGatheredLinks(GemtextRenderer renderer)
{
// If we have no gathered links, then there is nothing to do.
if (renderer.GatheredLinks.Count <= 0)
{
return;
}
// Put some space between the previous object and this one, then
// write out each link which is already formatted.
renderer.WriteLine();
// Put some space between the previous object and this one, then
// write out each link which is already formatted.
renderer.WriteLine();
foreach (string? link in renderer.GatheredLinks)
{
renderer.WriteLine();
renderer.Write(link);
}
foreach (string? link in renderer.GatheredLinks)
{
renderer.WriteLine();
renderer.Write(link);
}
// Clear out the list of links.
renderer.GatheredLinks.Clear();
}
// Clear out the list of links.
renderer.GatheredLinks.Clear();
}
protected override void Write(GemtextRenderer renderer, LinkInline link)
{
// Figure out the various states we have.
bool outside = !renderer.LinkInsideBlock;
bool insert = !outside
&& renderer.BlockLinkHandling ==
BlockLinkHandling.InsertLine;
bool gather = !outside
&& renderer.BlockLinkHandling switch
{
BlockLinkHandling.DocumentEnd => true,
BlockLinkHandling.ParagraphEnd => true,
_ => false,
};
bool hasText = link.FirstChild != null;
bool footnotes = renderer.EndLinkInlineFormatting
== EndLinkInlineFormatting.Footnote;
protected override void Write(GemtextRenderer renderer, LinkInline link)
{
// Figure out the various states we have.
bool outside = !renderer.LinkInsideBlock;
bool insert = !outside && renderer.BlockLinkHandling == BlockLinkHandling.InsertLine;
bool gather =
!outside
&& renderer.BlockLinkHandling switch
{
BlockLinkHandling.DocumentEnd => true,
BlockLinkHandling.ParagraphEnd => true,
_ => false,
};
bool hasText = link.FirstChild != null;
bool footnotes = renderer.EndLinkInlineFormatting == EndLinkInlineFormatting.Footnote;
// Bare links and ones where we insert into the paragraph have
// their own line.
string? url = link.GetDynamicUrl != null
? link.GetDynamicUrl() ?? link.Url
: link.Url;
// Bare links and ones where we insert into the paragraph have
// their own line.
string? url = link.GetDynamicUrl != null ? link.GetDynamicUrl() ?? link.Url : link.Url;
if (outside || insert)
{
// Make sure we are at the beginning of the line before
// rendering the link.
renderer.EnsureLine();
renderer.Write("=> ");
renderer.Write(url);
if (outside || insert)
{
// Make sure we are at the beginning of the line before
// rendering the link.
renderer.EnsureLine();
renderer.Write("=> ");
renderer.Write(url);
// If we have text, we need a space after the URL and before
// the text.
if (hasText)
{
renderer.Write(" ");
}
}
// If we have text, we need a space after the URL and before
// the text.
if (hasText)
{
renderer.Write(" ");
}
}
// Render the text for the link if we have it.
if (hasText)
{
renderer.WriteChildren(link);
}
// Render the text for the link if we have it.
if (hasText)
{
renderer.WriteChildren(link);
}
// If we are gathering, then write out a footnote.
if (gather)
{
int footnoteNumber = renderer.NextFootnoteNumber++;
string linkText = footnotes
? footnoteNumber + ": " + url
: GetLinkText(link);
// If we are gathering, then write out a footnote.
if (gather)
{
int footnoteNumber = renderer.NextFootnoteNumber++;
string linkText = footnotes ? footnoteNumber + ": " + url : GetLinkText(link);
if (footnotes)
{
renderer.Write($"[{footnoteNumber}]");
}
if (footnotes)
{
renderer.Write($"[{footnoteNumber}]");
}
renderer.GatheredLinks.Add("=> " + url + " " + linkText);
}
renderer.GatheredLinks.Add("=> " + url + " " + linkText);
}
// If we are inserting a line in the paragraph, we need a final
// newline so the text of the paragraph continues on the next
// line.
if (insert)
{
renderer.WriteLine();
}
}
// If we are inserting a line in the paragraph, we need a final
// newline so the text of the paragraph continues on the next
// line.
if (insert)
{
renderer.WriteLine();
}
}
private static string GetLinkText(LinkInline link)
{
// This little bit of nasty code basically spins up a new renderer
// to get the text of the link by itself. Then we return that
// directly so it can be rendered as a link.
StringWriter writer = new();
GemtextRenderer renderer = new(writer);
private static string GetLinkText(LinkInline link)
{
// This little bit of nasty code basically spins up a new renderer
// to get the text of the link by itself. Then we return that
// directly so it can be rendered as a link.
StringWriter writer = new();
GemtextRenderer renderer = new(writer);
renderer.WriteChildren(link);
writer.Close();
renderer.WriteChildren(link);
writer.Close();
string text = writer.ToString();
string text = writer.ToString();
return text;
}
return text;
}
}

View file

@ -8,31 +8,29 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines;
/// <seealso cref="GemtextObjectRenderer{LiteralInline}" />
public class LiteralInlineRenderer : GemtextObjectRenderer<LiteralInline>
{
protected override void Write(
GemtextRenderer renderer,
LiteralInline obj)
{
// If we are inside a paragraph and we are doing inline formatting,
// then we need to trim the text if we are before or after a link.
string content = obj.Content.ToString();
BlockLinkHandling handling = renderer.BlockLinkHandling;
bool isInsert = handling == BlockLinkHandling.InsertLine;
bool inBlock = renderer.LinkInsideBlock;
protected override void Write(GemtextRenderer renderer, LiteralInline obj)
{
// If we are inside a paragraph and we are doing inline formatting,
// then we need to trim the text if we are before or after a link.
string content = obj.Content.ToString();
BlockLinkHandling handling = renderer.BlockLinkHandling;
bool isInsert = handling == BlockLinkHandling.InsertLine;
bool inBlock = renderer.LinkInsideBlock;
if (inBlock && isInsert)
{
if (obj.PreviousSibling is LinkInline)
{
content = content.TrimStart();
}
if (inBlock && isInsert)
{
if (obj.PreviousSibling is LinkInline)
{
content = content.TrimStart();
}
if (obj.NextSibling is LinkInline)
{
content = content.TrimEnd();
}
}
if (obj.NextSibling is LinkInline)
{
content = content.TrimEnd();
}
}
// Write out the manipulated content.
renderer.Write(content);
}
// Write out the manipulated content.
renderer.Write(content);
}
}

View file

@ -8,27 +8,25 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext;
/// </summary>
public static class MarkdigExtensions
{
/// <summary>
/// Determines if the paragraph only contains a link.
/// </summary>
/// <param name="obj">The object to inspect.</param>
/// <returns>True if there is only a link in the paragraph.</returns>
public static bool OnlyHasSingleLink(this ParagraphBlock obj)
{
return obj.Inline != null
&& obj.Inline.Count() == 1
&& obj.Inline.FirstChild is LinkInline;
}
/// <summary>
/// Determines if the paragraph only contains a link.
/// </summary>
/// <param name="obj">The object to inspect.</param>
/// <returns>True if there is only a link in the paragraph.</returns>
public static bool OnlyHasSingleLink(this ParagraphBlock obj)
{
return obj.Inline != null && obj.Inline.Count() == 1 && obj.Inline.FirstChild is LinkInline;
}
/// <summary>
/// Determines if the list item only contains a link.
/// </summary>
/// <param name="obj">The object to inspect.</param>
/// <returns>True if there is only a link in the paragraph.</returns>
public static bool OnlyHasSingleLink(this ListItemBlock obj)
{
return obj.Count == 1
&& obj.LastChild is ParagraphBlock paragraphBlock
&& paragraphBlock.OnlyHasSingleLink();
}
/// <summary>
/// Determines if the list item only contains a link.
/// </summary>
/// <param name="obj">The object to inspect.</param>
/// <returns>True if there is only a link in the paragraph.</returns>
public static bool OnlyHasSingleLink(this ListItemBlock obj)
{
return obj.Count == 1
&& obj.LastChild is ParagraphBlock paragraphBlock
&& paragraphBlock.OnlyHasSingleLink();
}
}

View file

@ -1,7 +1,6 @@
using Markdig.Helpers;
using Markdig.Renderers;
using Markdig.Syntax;
using MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks;
using MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines;
@ -9,175 +8,172 @@ namespace MfGames.Markdown.Gemtext.Renderers;
public class GemtextRenderer : TextRendererBase<GemtextRenderer>
{
/// <inheritdoc />
public GemtextRenderer(TextWriter writer)
: base(writer)
{
// Set up our default values.
this.NextFootnoteNumber = 1;
this.GatheredLinks = new List<string>();
/// <inheritdoc />
public GemtextRenderer(TextWriter writer)
: base(writer)
{
// Set up our default values.
this.NextFootnoteNumber = 1;
this.GatheredLinks = new List<string>();
// Default block renderers.
this.ObjectRenderers.Add(new CustomContainerRenderer());
this.ObjectRenderers.Add(new CodeBlockRenderer());
this.ObjectRenderers.Add(new HeadingRenderer());
this.ObjectRenderers.Add(new HtmlBlockRenderer());
this.ObjectRenderers.Add(new ListRenderer());
this.ObjectRenderers.Add(new MarkdownDocumentRenderer());
this.ObjectRenderers.Add(new ParagraphRenderer());
this.ObjectRenderers.Add(new QuoteBlockRenderer());
this.ObjectRenderers.Add(new ThematicBreakRenderer());
// Default block renderers.
this.ObjectRenderers.Add(new CustomContainerRenderer());
this.ObjectRenderers.Add(new CodeBlockRenderer());
this.ObjectRenderers.Add(new HeadingRenderer());
this.ObjectRenderers.Add(new HtmlBlockRenderer());
this.ObjectRenderers.Add(new ListRenderer());
this.ObjectRenderers.Add(new MarkdownDocumentRenderer());
this.ObjectRenderers.Add(new ParagraphRenderer());
this.ObjectRenderers.Add(new QuoteBlockRenderer());
this.ObjectRenderers.Add(new ThematicBreakRenderer());
// Default inline renderers.
this.ObjectRenderers.Add(new CodeInlineRenderer());
this.ObjectRenderers.Add(new DelimiterInlineRenderer());
this.ObjectRenderers.Add(new EmphasisInlineRenderer());
this.ObjectRenderers.Add(new HtmlEntityInlineRenderer());
this.ObjectRenderers.Add(new LineBreakInlineRenderer());
this.ObjectRenderers.Add(new LinkInlineRenderer());
this.ObjectRenderers.Add(new LiteralInlineRenderer());
}
// Default inline renderers.
this.ObjectRenderers.Add(new CodeInlineRenderer());
this.ObjectRenderers.Add(new DelimiterInlineRenderer());
this.ObjectRenderers.Add(new EmphasisInlineRenderer());
this.ObjectRenderers.Add(new HtmlEntityInlineRenderer());
this.ObjectRenderers.Add(new LineBreakInlineRenderer());
this.ObjectRenderers.Add(new LinkInlineRenderer());
this.ObjectRenderers.Add(new LiteralInlineRenderer());
}
/// <summary>
/// Gets or sets how to handle links inside paragraphs and other blocks.
/// </summary>
public BlockLinkHandling BlockLinkHandling { get; set; }
/// <summary>
/// Gets or sets how to handle links inside paragraphs and other blocks.
/// </summary>
public BlockLinkHandling BlockLinkHandling { get; set; }
/// <summary>
/// Gets or sets the optional formatting for code inlines (backticks).
/// If this is unset, then `InlineFormatting` will be used.
/// </summary>
public InlineFormatting? CodeFormatting { get; set; }
/// <summary>
/// Gets or sets the optional formatting for code inlines (backticks).
/// If this is unset, then `InlineFormatting` will be used.
/// </summary>
public InlineFormatting? CodeFormatting { get; set; }
/// <summary>
/// Gets the actual formatting for code inlines (backticks) which
/// is either `CodeInlineFormatting` or `InlineFormatting` if that
/// is not set.
/// </summary>
public InlineFormatting CodeFormattingResolved =>
this.CodeFormatting ?? this.InlineFormatting;
/// <summary>
/// Gets the actual formatting for code inlines (backticks) which
/// is either `CodeInlineFormatting` or `InlineFormatting` if that
/// is not set.
/// </summary>
public InlineFormatting CodeFormattingResolved => this.CodeFormatting ?? this.InlineFormatting;
/// <summary>
/// Gets or sets the optional formatting for emphasis (which includes
/// italics and bolds). If this is unset, then `InlineFormatting`
/// will be used.
/// </summary>
public InlineFormatting? EmphasisFormatting { get; set; }
/// <summary>
/// Gets or sets the optional formatting for emphasis (which includes
/// italics and bolds). If this is unset, then `InlineFormatting`
/// will be used.
/// </summary>
public InlineFormatting? EmphasisFormatting { get; set; }
/// <summary>
/// Gets the actual formatting for emphasis which is either
/// `EmphasisFormatting` or `InlineFormatting` if that isn't set.
/// </summary>
public InlineFormatting EmphasisFormattingResolved =>
this.EmphasisFormatting ?? this.InlineFormatting;
/// <summary>
/// Gets the actual formatting for emphasis which is either
/// `EmphasisFormatting` or `InlineFormatting` if that isn't set.
/// </summary>
public InlineFormatting EmphasisFormattingResolved =>
this.EmphasisFormatting ?? this.InlineFormatting;
/// <summary>
/// Gets or sets the formatting for how links that are gathered at the
/// end of a paragraph or document are formatted inside the paragraph.
/// </summary>
public EndLinkInlineFormatting EndLinkInlineFormatting { get; set; }
/// <summary>
/// Gets or sets the formatting for how links that are gathered at the
/// end of a paragraph or document are formatted inside the paragraph.
/// </summary>
public EndLinkInlineFormatting EndLinkInlineFormatting { get; set; }
/// <summary>
/// Gets the current list of formatted links that have been gathered
/// up to this point for rendering.
/// </summary>
public List<string> GatheredLinks { get; }
/// <summary>
/// Gets the current list of formatted links that have been gathered
/// up to this point for rendering.
/// </summary>
public List<string> GatheredLinks { get; }
/// <summary>
/// Gets or sets the formatting rule for HTML blocks.
/// </summary>
public HtmlBlockFormatting HtmlBlockFormatting { get; set; }
/// <summary>
/// Gets or sets the formatting rule for HTML blocks.
/// </summary>
public HtmlBlockFormatting HtmlBlockFormatting { get; set; }
/// <summary>
/// Gets or sets the default formatting for all inlines.
/// </summary>
public InlineFormatting InlineFormatting { get; set; }
/// <summary>
/// Gets or sets the default formatting for all inlines.
/// </summary>
public InlineFormatting InlineFormatting { get; set; }
/// <summary>
/// An internal processing flag that determines if the rendered link
/// is inside a block or not to trigger extra handling.
/// </summary>
public bool LinkInsideBlock { get; set; }
/// <summary>
/// An internal processing flag that determines if the rendered link
/// is inside a block or not to trigger extra handling.
/// </summary>
public bool LinkInsideBlock { get; set; }
/// <summary>
/// Gets or sets the next footnote while rendering links.
/// </summary>
public int NextFootnoteNumber { get; set; }
/// <summary>
/// Gets or sets the next footnote while rendering links.
/// </summary>
public int NextFootnoteNumber { get; set; }
/// <summary>
/// Ensures there are two blank lines before an element.
/// </summary>
/// <returns></returns>
public GemtextRenderer EnsureTwoLines()
{
if (this.previousWasLine)
{
return this;
}
/// <summary>
/// Ensures there are two blank lines before an element.
/// </summary>
/// <returns></returns>
public GemtextRenderer EnsureTwoLines()
{
if (this.previousWasLine)
{
return this;
}
this.WriteLine();
this.WriteLine();
this.WriteLine();
this.WriteLine();
return this;
}
return this;
}
/// <summary>
/// A wrapper method to push the state of LinkInsideBlock while
/// performing an action.
/// </summary>
/// <param name="action">The action to perform.</param>
public void WhileLinkInsideBlock(Action action)
{
bool oldState = this.LinkInsideBlock;
this.LinkInsideBlock = true;
action();
this.LinkInsideBlock = oldState;
}
/// <summary>
/// A wrapper method to push the state of LinkInsideBlock while
/// performing an action.
/// </summary>
/// <param name="action">The action to perform.</param>
public void WhileLinkInsideBlock(Action action)
{
bool oldState = this.LinkInsideBlock;
this.LinkInsideBlock = true;
action();
this.LinkInsideBlock = oldState;
}
/// <summary>
/// Writes the lines of a <see cref="LeafBlock" />
/// </summary>
/// <param name="leafBlock">The leaf block.</param>
/// <param name="writeEndOfLines">if set to <c>true</c> write end of lines.</param>
/// <returns>This instance</returns>
public GemtextRenderer WriteLeafRawLines(
LeafBlock leafBlock,
bool writeEndOfLines)
{
// Make sure we have sane input.
if (leafBlock == null)
{
throw new ArgumentNullException(nameof(leafBlock));
}
/// <summary>
/// Writes the lines of a <see cref="LeafBlock" />
/// </summary>
/// <param name="leafBlock">The leaf block.</param>
/// <param name="writeEndOfLines">if set to <c>true</c> write end of lines.</param>
/// <returns>This instance</returns>
public GemtextRenderer WriteLeafRawLines(LeafBlock leafBlock, bool writeEndOfLines)
{
// Make sure we have sane input.
if (leafBlock == null)
{
throw new ArgumentNullException(nameof(leafBlock));
}
// If we have nothing to write, then don't do anything. Even though
// Markdig says this can't be null, `leafBlock.Lines` may be null
// according to the comments.
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (leafBlock.Lines.Lines == null)
{
return this;
}
// If we have nothing to write, then don't do anything. Even though
// Markdig says this can't be null, `leafBlock.Lines` may be null
// according to the comments.
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (leafBlock.Lines.Lines == null)
{
return this;
}
// Go through the block and write out each of the lines.
StringLineGroup lines = leafBlock.Lines;
StringLine[] slices = lines.Lines;
// Go through the block and write out each of the lines.
StringLineGroup lines = leafBlock.Lines;
StringLine[] slices = lines.Lines;
for (int i = 0; i < lines.Count; i++)
{
if (!writeEndOfLines && i > 0)
{
this.WriteLine();
}
for (int i = 0; i < lines.Count; i++)
{
if (!writeEndOfLines && i > 0)
{
this.WriteLine();
}
this.Write(ref slices[i].Slice);
this.Write(ref slices[i].Slice);
if (writeEndOfLines)
{
this.WriteLine();
}
}
if (writeEndOfLines)
{
this.WriteLine();
}
}
return this;
}
return this;
}
}

View file

@ -5,14 +5,14 @@ namespace MfGames.Markdown.Gemtext.Renderers;
/// </summary>
public enum HtmlBlockFormatting
{
/// <summary>
/// Indicates that HTML code blocks should just be removed.
/// </summary>
Remove,
/// <summary>
/// Indicates that HTML code blocks should just be removed.
/// </summary>
Remove,
/// <summary>
/// Indicates that HTML code blocks should be treated as blocks with
/// "html" as the type.
/// </summary>
CodeBlock,
/// <summary>
/// Indicates that HTML code blocks should be treated as blocks with
/// "html" as the type.
/// </summary>
CodeBlock,
}

View file

@ -6,15 +6,15 @@ namespace MfGames.Markdown.Gemtext.Renderers;
/// </summary>
public enum InlineFormatting
{
/// <summary>
/// Indicates that the inline should be remove and only the text
/// rendered.
/// </summary>
Remove,
/// <summary>
/// Indicates that the inline should be remove and only the text
/// rendered.
/// </summary>
Remove,
/// <summary>
/// Indicates that the inline should be left in place in a normalized
/// form (such as converting `_italics_` into `*italics*`).
/// </summary>
Normalize,
/// <summary>
/// Indicates that the inline should be left in place in a normalized
/// form (such as converting `_italics_` into `*italics*`).
/// </summary>
Normalize,
}

View file

@ -4,8 +4,8 @@ namespace MfGames.Markdown.Extensions;
public class WikiLink : LinkInline
{
public WikiLink()
{
this.IsClosed = false;
}
public WikiLink()
{
this.IsClosed = false;
}
}

View file

@ -9,39 +9,34 @@ namespace MfGames.Markdown.Extensions;
/// </summary>
public class WikiLinkExtension : IMarkdownExtension
{
public WikiLinkExtension()
: this(null)
{
}
public WikiLinkExtension()
: this(null) { }
public WikiLinkExtension(WikiLinkOptions? options)
{
this.Options = options ?? new WikiLinkOptions();
}
public WikiLinkExtension(WikiLinkOptions? options)
{
this.Options = options ?? new WikiLinkOptions();
}
public WikiLinkOptions Options { get; set; }
public WikiLinkOptions Options { get; set; }
/// <inheritdoc />
public void Setup(MarkdownPipelineBuilder pipeline)
{
WikiLinkInlineParser? parser = pipeline.InlineParsers
.FindExact<WikiLinkInlineParser>();
/// <inheritdoc />
public void Setup(MarkdownPipelineBuilder pipeline)
{
WikiLinkInlineParser? parser = pipeline.InlineParsers.FindExact<WikiLinkInlineParser>();
if (parser != null)
{
return;
}
if (parser != null)
{
return;
}
parser = new WikiLinkInlineParser(this.Options);
pipeline.InlineParsers.InsertBefore<LinkInlineParser>(parser);
}
parser = new WikiLinkInlineParser(this.Options);
pipeline.InlineParsers.InsertBefore<LinkInlineParser>(parser);
}
/// <inheritdoc />
public void Setup(
MarkdownPipeline pipeline,
IMarkdownRenderer renderer)
{
// No setup needed here because we're using LinkInline which does the
// bulk of the work.
}
/// <inheritdoc />
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
// No setup needed here because we're using LinkInline which does the
// bulk of the work.
}
}

View file

@ -6,105 +6,98 @@ namespace MfGames.Markdown.Extensions;
public class WikiLinkInlineParser : InlineParser
{
private readonly WikiLinkOptions options;
private readonly WikiLinkOptions options;
public WikiLinkInlineParser(WikiLinkOptions options)
{
this.options = options;
this.OpeningCharacters = new[] { '[' };
}
public WikiLinkInlineParser(WikiLinkOptions options)
{
this.options = options;
this.OpeningCharacters = new[] { '[' };
}
/// <inheritdoc />
public override bool Match(
InlineProcessor processor,
ref StringSlice slice)
{
// We are looking for the `[[` opening for the tag and that the first
// one isn't escaped.
if (IsNotDelimiter(slice, '['))
{
return false;
}
/// <inheritdoc />
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
// We are looking for the `[[` opening for the tag and that the first
// one isn't escaped.
if (IsNotDelimiter(slice, '['))
{
return false;
}
// We need to loop over the entire link, including the `[[` and `]]`
// while keeping track since we'll swallow additional characters beyond
// the link.
int linkStart = slice.Start;
int linkEnd = slice.Start;
// We need to loop over the entire link, including the `[[` and `]]`
// while keeping track since we'll swallow additional characters beyond
// the link.
int linkStart = slice.Start;
int linkEnd = slice.Start;
slice.Start += 2;
slice.Start += 2;
// Our content starts after the double '[['.
int contentStart = slice.Start;
// Our content starts after the double '[['.
int contentStart = slice.Start;
// We need to find the end of the link (the `]]`).
while (IsNotDelimiter(slice, ']'))
{
slice.NextChar();
linkEnd = slice.Start;
}
// We need to find the end of the link (the `]]`).
while (IsNotDelimiter(slice, ']'))
{
slice.NextChar();
linkEnd = slice.Start;
}
// Pull out the components before we adjust for the ']]' for the end.
int contentEnd = linkEnd;
// Pull out the components before we adjust for the ']]' for the end.
int contentEnd = linkEnd;
// Finish skipping over the `]]`.
slice.NextChar();
slice.NextChar();
// Finish skipping over the `]]`.
slice.NextChar();
slice.NextChar();
// Format the label and the URL.
string content = slice.Text.Substring(
contentStart,
contentEnd - contentStart);
string[] contentParts = content.Split('|', 2);
string label = contentParts.Last();
string url = this.options.GetUrl(contentParts.First());
// Format the label and the URL.
string content = slice.Text.Substring(contentStart, contentEnd - contentStart);
string[] contentParts = content.Split('|', 2);
string label = contentParts.Last();
string url = this.options.GetUrl(contentParts.First());
// Add in any trailing components. This merges the `'s` from
// `[[Dale]]'s` into the label.
while (this.options.IsTrailingLink(slice.CurrentChar))
{
label += slice.CurrentChar;
slice.NextChar();
linkEnd++;
}
// Add in any trailing components. This merges the `'s` from
// `[[Dale]]'s` into the label.
while (this.options.IsTrailingLink(slice.CurrentChar))
{
label += slice.CurrentChar;
slice.NextChar();
linkEnd++;
}
// Create the link that we're replacing.
WikiLink link = new()
{
Span =
{
Start = processor.GetSourcePosition(
linkStart,
out int line,
out int column),
},
Line = line,
Column = column,
Url = url,
IsClosed = true,
};
// Create the link that we're replacing.
WikiLink link =
new()
{
Span =
{
Start = processor.GetSourcePosition(linkStart, out int line, out int column),
},
Line = line,
Column = column,
Url = url,
IsClosed = true,
};
link.AppendChild(
new LiteralInline()
{
Line = line,
Column = column,
Content = new StringSlice(label),
IsClosed = true,
});
link.AppendChild(
new LiteralInline()
{
Line = line,
Column = column,
Content = new StringSlice(label),
IsClosed = true,
}
);
// Replace the inline and then indicate we have a match.
processor.Inline = link;
// Replace the inline and then indicate we have a match.
processor.Inline = link;
return true;
}
return true;
}
private static bool IsNotDelimiter(
StringSlice slice,
char delimiter)
{
return slice.CurrentChar != delimiter
|| slice.PeekChar() != delimiter
|| slice.PeekCharExtra(-1) == '\\';
}
private static bool IsNotDelimiter(StringSlice slice, char delimiter)
{
return slice.CurrentChar != delimiter
|| slice.PeekChar() != delimiter
|| slice.PeekCharExtra(-1) == '\\';
}
}

View file

@ -4,26 +4,26 @@ namespace MfGames.Markdown.Extensions;
public class WikiLinkOptions
{
public WikiLinkOptions()
{
this.GetUrl = a => a;
this.IsTrailingLink = a => a.IsAlpha() || a == '\'';
}
public WikiLinkOptions()
{
this.GetUrl = a => a;
this.IsTrailingLink = a => a.IsAlpha() || a == '\'';
}
/// <summary>
/// The callback to determine the link from the given wiki link. This does
/// not include trailing additions or use the label (e.g., `(ab|cd)` would
/// get `ab` as the parameters of this function.
/// </summary>
public Func<string, string> GetUrl { get; set; }
/// <summary>
/// The callback to determine the link from the given wiki link. This does
/// not include trailing additions or use the label (e.g., `(ab|cd)` would
/// get `ab` as the parameters of this function.
/// </summary>
public Func<string, string> GetUrl { get; set; }
/// <summary>
/// <para>
/// A callback to determine if the text after the link should be merged
/// with the link label. This allows links such as [[Dale]]'s to be turned
/// into "Dale's" but pointing to "Dale" as a page.
/// </para>
/// <para>The default is to include any character or the apostrophe.</para>
/// </summary>
public Func<char, bool> IsTrailingLink { get; set; }
/// <summary>
/// <para>
/// A callback to determine if the text after the link should be merged
/// with the link label. This allows links such as [[Dale]]'s to be turned
/// into "Dale's" but pointing to "Dale" as a page.
/// </para>
/// <para>The default is to include any character or the apostrophe.</para>
/// </summary>
public Func<char, bool> IsTrailingLink { get; set; }
}

View file

@ -5,9 +5,9 @@ in [MarkDig](https://github.com/xoofx/markdig), an extensible library for conver
The library includes the following:
- An extension for converting wiki links, such as `[[MfGames]]` into a link based on the title. This is for both HTML
and Gemtext.
- A output library for using MarkDig to generate Gemtext for Gemini pods.
- An extension for converting wiki links, such as `[[MfGames]]` into a link based on the title. This is for both HTML
and Gemtext.
- A output library for using MarkDig to generate Gemtext for Gemini pods.
The documentation is rather light at the moment, but the tests can show various ways of using the libraries.
@ -37,5 +37,5 @@ repository.
The two libraries are:
- MfGames.Markdown
- MfGames.Markdown.Gemtext
- MfGames.Markdown
- MfGames.Markdown.Gemtext

View file

@ -4,15 +4,12 @@ namespace MfGames.Nitride.Calendar;
public class CreateCalendarValidator : AbstractValidator<CreateCalender>
{
public CreateCalendarValidator()
{
this.RuleFor(x => x.Path)
.NotNull();
public CreateCalendarValidator()
{
this.RuleFor(x => x.Path).NotNull();
this.RuleFor(x => x.GetEventSummary)
.NotNull();
this.RuleFor(x => x.GetEventSummary).NotNull();
this.RuleFor(x => x.GetEventUrl)
.NotNull();
}
this.RuleFor(x => x.GetEventUrl).NotNull();
}
}

View file

@ -1,16 +1,12 @@
using FluentValidation;
using Ical.Net.CalendarComponents;
using Ical.Net.DataTypes;
using Ical.Net.Serialization;
using MfGames.Gallium;
using MfGames.Nitride.Contents;
using MfGames.Nitride.Generators;
using MfGames.Nitride.Temporal;
using NodaTime;
using Zio;
namespace MfGames.Nitride.Calendar;
@ -23,91 +19,88 @@ namespace MfGames.Nitride.Calendar;
[WithProperties]
public partial class CreateCalender : OperationBase
{
private readonly TimeService clock;
private readonly TimeService clock;
private readonly IValidator<CreateCalender> validator;
private readonly IValidator<CreateCalender> validator;
public CreateCalender(
IValidator<CreateCalender> validator,
TimeService clock)
{
this.validator = validator;
this.clock = clock;
}
public CreateCalender(IValidator<CreateCalender> validator, TimeService clock)
{
this.validator = validator;
this.clock = clock;
}
/// <summary>
/// Gets or sets a callback to get the summary of the event representing
/// the entity.
/// </summary>
public Func<Entity, string>? GetEventSummary { get; set; }
/// <summary>
/// Gets or sets a callback to get the summary of the event representing
/// the entity.
/// </summary>
public Func<Entity, string>? GetEventSummary { get; set; }
/// <summary>
/// Gets or sets a callback to get the optional URL of an event for
/// the entity.
/// </summary>
public Func<Entity, Uri?>? GetEventUrl { get; set; }
/// <summary>
/// Gets or sets a callback to get the optional URL of an event for
/// the entity.
/// </summary>
public Func<Entity, Uri?>? GetEventUrl { get; set; }
/// <summary>
/// Gets or sets the file system path for the resulting calendar.
/// </summary>
public UPath? Path { get; set; }
/// <summary>
/// Gets or sets the file system path for the resulting calendar.
/// </summary>
public UPath? Path { get; set; }
/// <inheritdoc />
public override IEnumerable<Entity> Run(
IEnumerable<Entity> input,
CancellationToken cancellationToken = default)
{
this.validator.ValidateAndThrow(this);
/// <inheritdoc />
public override IEnumerable<Entity> Run(
IEnumerable<Entity> input,
CancellationToken cancellationToken = default
)
{
this.validator.ValidateAndThrow(this);
SplitEntityEnumerations split = input.SplitEntity<Instant>();
IEnumerable<Entity> datedAndCalendars =
this.CreateCalendarEntity(split.HasAll);
SplitEntityEnumerations split = input.SplitEntity<Instant>();
IEnumerable<Entity> datedAndCalendars = this.CreateCalendarEntity(split.HasAll);
return datedAndCalendars.Union(split.NotHasAll);
}
return datedAndCalendars.Union(split.NotHasAll);
}
private IEnumerable<Entity> CreateCalendarEntity(
IEnumerable<Entity> entities)
{
// Create the calendar in the same time zone as the rest of the system.
var calendar = new Ical.Net.Calendar();
private IEnumerable<Entity> CreateCalendarEntity(IEnumerable<Entity> entities)
{
// Create the calendar in the same time zone as the rest of the system.
var calendar = new Ical.Net.Calendar();
calendar.TimeZones.Add(new VTimeZone(this.clock.DateTimeZone.Id));
calendar.TimeZones.Add(new VTimeZone(this.clock.DateTimeZone.Id));
// Go through the events and add all of them.
var input = entities.ToList();
IEnumerable<CalendarEvent> events =
input.Select(this.CreateCalendarEvent);
// Go through the events and add all of them.
var input = entities.ToList();
IEnumerable<CalendarEvent> events = input.Select(this.CreateCalendarEvent);
calendar.Events.AddRange(events);
calendar.Events.AddRange(events);
// Create the iCalendar file.
var serializer = new CalendarSerializer();
string serializedCalendar = serializer.SerializeToString(calendar);
// Create the iCalendar file.
var serializer = new CalendarSerializer();
string serializedCalendar = serializer.SerializeToString(calendar);
// Create the calendar entity and populate everything.
Entity calendarEntity = new Entity().Set(IsCalendar.Instance)
.Set(this.Path!.Value)
.SetTextContent(serializedCalendar);
// Create the calendar entity and populate everything.
Entity calendarEntity = new Entity()
.Set(IsCalendar.Instance)
.Set(this.Path!.Value)
.SetTextContent(serializedCalendar);
// Return the results along with the new calendar.
return input.Union(new[] { calendarEntity });
}
// Return the results along with the new calendar.
return input.Union(new[] { calendarEntity });
}
private CalendarEvent CreateCalendarEvent(Entity entity)
{
Instant instant = entity.Get<Instant>();
var when = this.clock.ToDateTime(instant);
string summary = this.GetEventSummary!(entity);
Uri? url = this.GetEventUrl?.Invoke(entity);
private CalendarEvent CreateCalendarEvent(Entity entity)
{
Instant instant = entity.Get<Instant>();
var when = this.clock.ToDateTime(instant);
string summary = this.GetEventSummary!(entity);
Uri? url = this.GetEventUrl?.Invoke(entity);
var calendarEvent = new CalendarEvent
{
Summary = summary,
Start = new CalDateTime(when),
Url = url,
};
var calendarEvent = new CalendarEvent
{
Summary = summary,
Start = new CalDateTime(when),
Url = url,
};
return calendarEvent;
}
return calendarEvent;
}
}

View file

@ -6,6 +6,4 @@ namespace MfGames.Nitride.Calendar;
/// A marker component for identifying an entity that represents a calendar.
/// </summary>
[SingletonComponent]
public partial class IsCalendar
{
}
public partial class IsCalendar { }

View file

@ -1,26 +1,23 @@
using Autofac;
using MfGames.Nitride.Temporal.Setup;
namespace MfGames.Nitride.Calendar;
public static class NitrideCalendarBuilderExtensions
{
private static bool loaded;
private static bool loaded;
public static NitrideBuilder UseCalendar(this NitrideBuilder builder)
{
// If we've already loaded, then we have a problem.
if (loaded)
{
throw new InvalidOperationException(
"Cannot use UseCalendar() more than once.");
}
public static NitrideBuilder UseCalendar(this NitrideBuilder builder)
{
// If we've already loaded, then we have a problem.
if (loaded)
{
throw new InvalidOperationException("Cannot use UseCalendar() more than once.");
}
loaded = true;
loaded = true;
// Get the configuration so we can set the various options.
return builder
.ConfigureContainer(x => x.RegisterModule<NitrideCalendarModule>());
}
// Get the configuration so we can set the various options.
return builder.ConfigureContainer(x => x.RegisterModule<NitrideCalendarModule>());
}
}

View file

@ -1,16 +1,15 @@
using Autofac;
using MfGames.Nitride.Temporal.Setup;
namespace MfGames.Nitride.Calendar;
public class NitrideCalendarModule : Module
{
/// <inheritdoc />
protected override void Load(ContainerBuilder builder)
{
builder.RegisterModule<NitrideTemporalModule>();
builder.RegisterOperators(this);
builder.RegisterValidators(this);
}
/// <inheritdoc />
protected override void Load(ContainerBuilder builder)
{
builder.RegisterModule<NitrideTemporalModule>();
builder.RegisterOperators(this);
builder.RegisterValidators(this);
}
}

View file

@ -1,13 +1,9 @@
using System.Runtime.CompilerServices;
using CliWrap;
using CliWrap.Buffered;
using FluentValidation;
using MfGames.Gallium;
using MfGames.Nitride.Generators;
using Serilog;
// ReSharper disable ClassNeverInstantiated.Global
@ -20,102 +16,97 @@ namespace MfGames.Nitride.Exec;
[WithProperties]
public partial class ExecOperation : AsyncOperationBase
{
private readonly ILogger logger;
private readonly ILogger logger;
private readonly IValidator<ExecOperation> validator;
private readonly IValidator<ExecOperation> validator;
public ExecOperation(
ILogger logger,
IValidator<ExecOperation> validator)
{
this.logger = logger.ForContext<ExecOperation>();
this.validator = validator;
}
public ExecOperation(ILogger logger, IValidator<ExecOperation> validator)
{
this.logger = logger.ForContext<ExecOperation>();
this.validator = validator;
}
/// <summary>
/// Gets or sets the command associated with this operation.
/// </summary>
public Func<Command>? CreateCommand { get; set; }
/// <summary>
/// Gets or sets the command associated with this operation.
/// </summary>
public Func<Command>? CreateCommand { get; set; }
/// <summary>
/// Gets or sets a callback to process the buffered output.
/// </summary>
/// <remarks>
/// This is mutually exclusive with OnResult.
/// </remarks>
public Func<BufferedCommandResult, IEnumerable<Entity>>? OnBufferedResult
{
get;
set;
}
/// <summary>
/// Gets or sets a callback to process the buffered output.
/// </summary>
/// <remarks>
/// This is mutually exclusive with OnResult.
/// </remarks>
public Func<BufferedCommandResult, IEnumerable<Entity>>? OnBufferedResult { get; set; }
/// <summary>
/// Gets or sets a callback to process the output.
/// </summary>
/// <remarks>
/// This is mutually exclusive with OnBufferedResult.
/// </remarks>
public Func<CommandResult, IEnumerable<Entity>>? OnResult { get; set; }
/// <summary>
/// Gets or sets a callback to process the output.
/// </summary>
/// <remarks>
/// This is mutually exclusive with OnBufferedResult.
/// </remarks>
public Func<CommandResult, IEnumerable<Entity>>? OnResult { get; set; }
/// <inheritdoc />
public override async IAsyncEnumerable<Entity> RunAsync(
IAsyncEnumerable<Entity> input,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
// Make sure everything is validated.
await this.validator.ValidateAndThrowAsync(this, cancellationToken);
/// <inheritdoc />
public override async IAsyncEnumerable<Entity> RunAsync(
IAsyncEnumerable<Entity> input,
[EnumeratorCancellation] CancellationToken cancellationToken = default
)
{
// Make sure everything is validated.
await this.validator.ValidateAndThrowAsync(this, cancellationToken);
// Drain the inputs.
await foreach (Entity item in input.WithCancellation(cancellationToken))
{
yield return item;
}
// Drain the inputs.
await foreach (Entity item in input.WithCancellation(cancellationToken))
{
yield return item;
}
// Create the command from the input, then execute it as a buffered
// output if we have a buffered result callback, otherwise as a command
// result.
Command command = this.CreateCommand!();
// Create the command from the input, then execute it as a buffered
// output if we have a buffered result callback, otherwise as a command
// result.
Command command = this.CreateCommand!();
if (this.OnBufferedResult != null)
{
BufferedCommandResult result = await command
.ExecuteBufferedAsync(cancellationToken);
if (this.OnBufferedResult != null)
{
BufferedCommandResult result = await command.ExecuteBufferedAsync(cancellationToken);
this.logger.Debug(
"Execute buffered: {Command} {Arguments} = {ExitCode}",
command.TargetFilePath,
command.Arguments,
result.ExitCode);
this.logger.Debug(
"Execute buffered: {Command} {Arguments} = {ExitCode}",
command.TargetFilePath,
command.Arguments,
result.ExitCode
);
IEnumerable<Entity> list = this.OnBufferedResult(result);
IEnumerable<Entity> list = this.OnBufferedResult(result);
foreach (Entity item in list)
{
yield return item;
}
}
else
{
CommandResult result = await command
.ExecuteAsync(cancellationToken);
foreach (Entity item in list)
{
yield return item;
}
}
else
{
CommandResult result = await command.ExecuteAsync(cancellationToken);
this.logger.Debug(
"Execute: {Command} {Arguments} = {ExitCode}",
command.TargetFilePath,
command.Arguments,
result.ExitCode);
this.logger.Debug(
"Execute: {Command} {Arguments} = {ExitCode}",
command.TargetFilePath,
command.Arguments,
result.ExitCode
);
IEnumerable<Entity>? list = this.OnResult?.Invoke(result);
IEnumerable<Entity>? list = this.OnResult?.Invoke(result);
if (list == null)
{
yield break;
}
if (list == null)
{
yield break;
}
foreach (Entity item in list)
{
yield return item;
}
}
}
foreach (Entity item in list)
{
yield return item;
}
}
}
}

View file

@ -2,21 +2,19 @@ namespace MfGames.Nitride.Exec.Setup;
public static class NitrideExecBuilderExtensions
{
private static bool loaded;
private static bool loaded;
public static NitrideBuilder UseExec(this NitrideBuilder builder)
{
// If we've already loaded, then we have a problem.
if (loaded)
{
throw new InvalidOperationException(
"Cannot use UseExec() more than once.");
}
public static NitrideBuilder UseExec(this NitrideBuilder builder)
{
// If we've already loaded, then we have a problem.
if (loaded)
{
throw new InvalidOperationException("Cannot use UseExec() more than once.");
}
loaded = true;
loaded = true;
// Get the configuration so we can set the various options.
return builder
.UseModule<NitrideExecModule>();
}
// Get the configuration so we can set the various options.
return builder.UseModule<NitrideExecModule>();
}
}

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