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
[*] ["*.cs"]
charset=utf-8 indent_size = 4
csharp_new_line_before_members_in_object_initializers=false indent_style = "space"
csharp_preferred_modifier_order=public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion tab_width = 4
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
[*.md] ["*.md"]
max_line_length=off max_line_length = "off"
trim_trailing_whitespace=false
[*.{appxmanifest,build,config,csproj,dbml,discomap,dtd,jsproj,lsproj,njsproj,nuspec,proj,props,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}] ["*.{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_size = 2
indent_style=space indent_style = "space"
tab_width=2 tab_width = 2
[*.{diff,patch}] ["package.json"]
end_of_line=unset indent_size = 2
indent_size=unset indent_style = "space"
insert_final_newline=unset tab_width = 2
trim_trailing_whitespace=unset
[package.json] ["{LICENSES/**,LICENSE}"]
indent_size=2 charset = "unset"
indent_style=space end_of_line = "unset"
tab_width=2 indent_size = "unset"
indent_style = "unset"
[{LICENSES/**,LICENSE}] insert_final_newline = "unset"
charset=unset trim_trailing_whitespace = "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/ tests/artifacts/
# nixago: ignore-linked-files # nixago: ignore-linked-files
/.prettierrc.json
/lefthook.yml /lefthook.yml
/.conform.yaml /.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 Examples of behavior that contributes to a positive environment for our
community include: community include:
- Demonstrating empathy and kindness toward other people - Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences - Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback - Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes, - Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall - Focusing on what is best not just for us as individuals, but for the overall
community community
Examples of unacceptable behavior include: Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of - The use of sexualized language or imagery, and sexual attention or advances of
any kind any kind
- Trolling, insulting or derogatory comments, and personal or political attacks - Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment - Public or private harassment
- Publishing others' private information, such as a physical or email address, - Publishing others' private information, such as a physical or email address,
without their explicit permission without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a - Other conduct which could reasonably be considered inappropriate in a
professional setting professional setting
## Enforcement Responsibilities ## 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/ForOtherTypes/@EntryValue">UseVarWhenEvident</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@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: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> <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/=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/=VsBulb/@EntryIndexedValue">DO_NOTHING</s:String>
<s:String x:Key="/Default/Environment/PerformanceGuide/SwitchBehaviour/=XAML_0020Designer/@EntryIndexedValue">LIVE_MONITOR</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_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_002ECSharpFileLayoutPatternsUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@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 ## Libraries
- [Gallium](./gallium/) - [Gallium](./gallium/)
- [Nitride](./nitride/) - [Nitride](./nitride/)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,46 +2,38 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MfGames.Gallium; using MfGames.Gallium;
using MfGames.Nitride.Pipelines; using MfGames.Nitride.Pipelines;
using Serilog; using Serilog;
using Zio; using Zio;
namespace NitridePipelines.Pipelines; namespace NitridePipelines.Pipelines;
public class DelayPipeline1 : PipelineBase public class DelayPipeline1 : PipelineBase
{ {
private readonly ILogger logger; private readonly ILogger logger;
public DelayPipeline1( public DelayPipeline1(ILogger logger, InputPipeline1 input1)
ILogger logger, {
InputPipeline1 input1) this.logger = logger.ForContext<DelayPipeline1>();
{ this.AddDependency(input1);
this.logger = logger.ForContext<DelayPipeline1>(); }
this.AddDependency(input1);
}
/// <inheritdoc /> /// <inheritdoc />
public override IAsyncEnumerable<Entity> RunAsync( public override IAsyncEnumerable<Entity> RunAsync(
IEnumerable<Entity> entities, IEnumerable<Entity> entities,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default
{ )
entities = entities {
.Select( entities = entities.Select(entity =>
entity => {
{ Task.Delay(1000, cancellationToken).Wait(cancellationToken);
Task.Delay(1000, cancellationToken).Wait(cancellationToken);
this.logger.Information( this.logger.Information("Delayed {Value}", entity.Get<UPath>());
"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.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MfGames.Gallium; using MfGames.Gallium;
using MfGames.Nitride.IO; using MfGames.Nitride.IO;
using MfGames.Nitride.IO.Contents; using MfGames.Nitride.IO.Contents;
using Serilog; using Serilog;
using Zio; using Zio;
namespace NitridePipelines.Pipelines; namespace NitridePipelines.Pipelines;
public class InputPipeline1 : FileSystemWatchablePipelineBase public class InputPipeline1 : FileSystemWatchablePipelineBase
{ {
private readonly ReadFiles readFiles; private readonly ReadFiles readFiles;
public InputPipeline1( public InputPipeline1(ILogger logger, IFileSystem fileSystem, ReadFiles readFiles)
ILogger logger, : base(logger, fileSystem)
IFileSystem fileSystem, {
ReadFiles readFiles) this.readFiles = readFiles.WithPattern("/input/input1/*.txt");
: base(logger, fileSystem) }
{
this.readFiles = readFiles
.WithPattern("/input/input1/*.txt");
}
/// <inheritdoc /> /// <inheritdoc />
protected override UPath WatchPath => "/input/input1"; protected override UPath WatchPath => "/input/input1";
/// <inheritdoc /> /// <inheritdoc />
public override IAsyncEnumerable<Entity> RunAsync( public override IAsyncEnumerable<Entity> RunAsync(
IEnumerable<Entity> _, IEnumerable<Entity> _,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default
{ )
IEnumerable<Entity> entities = this.readFiles {
.Run() IEnumerable<Entity> entities = this.readFiles.Run()
.Select( .Select(entity =>
entity => {
{ Task.Delay(1000, cancellationToken).Wait(cancellationToken);
Task.Delay(1000, cancellationToken).Wait(cancellationToken);
this.Logger.Information( this.Logger.Information("Read {Value}", entity.Get<UPath>());
"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.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MfGames.Gallium; using MfGames.Gallium;
using MfGames.Nitride.IO; using MfGames.Nitride.IO;
using MfGames.Nitride.IO.Contents; using MfGames.Nitride.IO.Contents;
using Serilog; using Serilog;
using Zio; using Zio;
namespace NitridePipelines.Pipelines; namespace NitridePipelines.Pipelines;
public class InputPipeline2 : FileSystemWatchablePipelineBase public class InputPipeline2 : FileSystemWatchablePipelineBase
{ {
private readonly ILogger logger; private readonly ILogger logger;
private readonly ReadFiles readFiles; private readonly ReadFiles readFiles;
public InputPipeline2( public InputPipeline2(ILogger logger, IFileSystem fileSystem, ReadFiles readFiles)
ILogger logger, : base(logger, fileSystem)
IFileSystem fileSystem, {
ReadFiles readFiles) this.logger = logger.ForContext<InputPipeline2>();
: base(logger, fileSystem) this.readFiles = readFiles.WithPattern("/input/input2/*.txt");
{ }
this.logger = logger.ForContext<InputPipeline2>();
this.readFiles = readFiles
.WithPattern("/input/input2/*.txt");
}
/// <inheritdoc /> /// <inheritdoc />
protected override UPath WatchPath => "/input/input2"; protected override UPath WatchPath => "/input/input2";
/// <inheritdoc /> /// <inheritdoc />
public override IAsyncEnumerable<Entity> RunAsync( public override IAsyncEnumerable<Entity> RunAsync(
IEnumerable<Entity> _, IEnumerable<Entity> _,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default
{ )
IEnumerable<Entity> entities = this.readFiles {
.Run() IEnumerable<Entity> entities = this.readFiles.Run()
.Select( .Select(entity =>
entity => {
{ Task.Delay(1000, cancellationToken).Wait(cancellationToken);
Task.Delay(1000, cancellationToken).Wait(cancellationToken);
this.logger.Information( this.logger.Information("Read {Value}", entity.Get<UPath>());
"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.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MfGames.Gallium; using MfGames.Gallium;
using MfGames.Nitride.Pipelines; using MfGames.Nitride.Pipelines;
using Serilog; using Serilog;
using Zio; using Zio;
namespace NitridePipelines.Pipelines; namespace NitridePipelines.Pipelines;
public class OutputPipeline1 : PipelineBase public class OutputPipeline1 : PipelineBase
{ {
private readonly ILogger logger; private readonly ILogger logger;
public OutputPipeline1( public OutputPipeline1(ILogger logger, DelayPipeline1 delay1, InputPipeline2 input2)
ILogger logger, {
DelayPipeline1 delay1, this.logger = logger.ForContext<OutputPipeline1>();
InputPipeline2 input2) this.AddDependency(delay1, input2);
{ }
this.logger = logger.ForContext<OutputPipeline1>();
this.AddDependency(delay1, input2);
}
/// <inheritdoc /> /// <inheritdoc />
public override IAsyncEnumerable<Entity> RunAsync( public override IAsyncEnumerable<Entity> RunAsync(
IEnumerable<Entity> entities, IEnumerable<Entity> entities,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default
{ )
entities = entities {
.Select( entities = entities.Select(entity =>
entity => {
{ Task.Delay(1000, cancellationToken).Wait(cancellationToken);
Task.Delay(1000, cancellationToken).Wait(cancellationToken);
this.logger.Information( this.logger.Information("Pretended to write {Value}", entity.Get<UPath>());
"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.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MfGames.Gallium; using MfGames.Gallium;
using MfGames.Nitride.Pipelines; using MfGames.Nitride.Pipelines;
using Serilog; using Serilog;
using Zio; using Zio;
namespace NitridePipelines.Pipelines; namespace NitridePipelines.Pipelines;
public class OutputPipeline2 : PipelineBase public class OutputPipeline2 : PipelineBase
{ {
private readonly ILogger logger; private readonly ILogger logger;
public OutputPipeline2( public OutputPipeline2(ILogger logger, InputPipeline2 input2)
ILogger logger, {
InputPipeline2 input2) this.logger = logger.ForContext<OutputPipeline2>();
{ this.AddDependency(input2);
this.logger = logger.ForContext<OutputPipeline2>(); }
this.AddDependency(input2);
}
/// <inheritdoc /> /// <inheritdoc />
public override IAsyncEnumerable<Entity> RunAsync( public override IAsyncEnumerable<Entity> RunAsync(
IEnumerable<Entity> entities, IEnumerable<Entity> entities,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default
{ )
entities = entities {
.Select( entities = entities.Select(entity =>
entity => {
{ Task.Delay(1000, cancellationToken).Wait(cancellationToken);
Task.Delay(1000, cancellationToken).Wait(cancellationToken);
this.logger.Information( this.logger.Information("Pretended to write {Value}", entity.Get<UPath>());
"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;
using System.CommandLine.Invocation; using System.CommandLine.Invocation;
using System.Threading.Tasks; using System.Threading.Tasks;
using MfGames.ToolBuilder; using MfGames.ToolBuilder;
using MfGames.ToolBuilder.Config; using MfGames.ToolBuilder.Config;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SampleTool.Commands; namespace SampleTool.Commands;
public class ConfigCommand : Command, ICommandHandler, ITopCommand 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 /> /// <inheritdoc />
public ConfigCommand( public ConfigCommand(ILoggerFactory loggerFactory, ConfigToolService service)
ILoggerFactory loggerFactory, : base("config", "Sets and displays the configuration settings")
ConfigToolService service) {
: base( this.logger = loggerFactory.CreateLogger<ConfigCommand>();
"config", this.service = service;
"Sets and displays the configuration settings") this.Handler = this;
{
this.logger = loggerFactory.CreateLogger<ConfigCommand>();
this.service = service;
this.Handler = this;
this.setOption = new Option<string>( this.setOption = new Option<string>("--set", "Sets the text value in the setting");
"--set",
"Sets the text value in the setting");
this.AddOption(this.setOption); this.AddOption(this.setOption);
} }
/// <inheritdoc /> /// <inheritdoc />
public int Invoke(InvocationContext context) public int Invoke(InvocationContext context)
{ {
return this.InvokeAsync(context).Result; return this.InvokeAsync(context).Result;
} }
/// <inheritdoc /> /// <inheritdoc />
public Task<int> InvokeAsync(InvocationContext context) public Task<int> InvokeAsync(InvocationContext context)
{ {
// Get the settings and report that it was being created. // Get the settings and report that it was being created.
ConfigCommandSettings settings = ConfigCommandSettings settings =
this.service.ReadDefaultConfigFile<ConfigCommandSettings>(); this.service.ReadDefaultConfigFile<ConfigCommandSettings>();
if (settings == null) if (settings == null)
{ {
this.logger.LogInformation("Creating configuration file"); this.logger.LogInformation("Creating configuration file");
settings = new ConfigCommandSettings(); settings = new ConfigCommandSettings();
} }
// If we have a set command, then provide it. // If we have a set command, then provide it.
string value = context.ParseResult string value = context.ParseResult.GetValueForOption(this.setOption);
.GetValueForOption(this.setOption);
if (value != null) if (value != null)
{ {
settings.Value = value; settings.Value = value;
} }
// Increment the counter. // Increment the counter.
settings.TimesRead++; settings.TimesRead++;
// Report the values. // Report the values.
Console.WriteLine( Console.WriteLine(JsonConvert.SerializeObject(settings, Formatting.Indented));
JsonConvert.SerializeObject(settings, Formatting.Indented));
// Write out the settings. // Write out the settings.
this.service.WriteDefaultConfigFile(settings); this.service.WriteDefaultConfigFile(settings);
return Task.FromResult(0); return Task.FromResult(0);
} }
private class ConfigCommandSettings private class ConfigCommandSettings
{ {
public int TimesRead { get; set; } 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;
using System.CommandLine.Invocation; using System.CommandLine.Invocation;
using System.Threading.Tasks; using System.Threading.Tasks;
using MfGames.ToolBuilder; using MfGames.ToolBuilder;
namespace SampleTool.Commands; namespace SampleTool.Commands;
public class CrashCommand : Command, ICommandHandler, ITopCommand public class CrashCommand : Command, ICommandHandler, ITopCommand
{ {
private readonly Option<bool> messyOption; private readonly Option<bool> messyOption;
/// <inheritdoc /> /// <inheritdoc />
public CrashCommand() public CrashCommand()
: base("crash", "Crash the application with an exception") : base("crash", "Crash the application with an exception")
{ {
this.Handler = this; this.Handler = this;
this.messyOption = new Option<bool>("--messy"); this.messyOption = new Option<bool>("--messy");
this.AddOption(this.messyOption); this.AddOption(this.messyOption);
} }
/// <inheritdoc /> /// <inheritdoc />
public int Invoke(InvocationContext context) public int Invoke(InvocationContext context)
{ {
return this.InvokeAsync(context).Result; return this.InvokeAsync(context).Result;
} }
/// <inheritdoc /> /// <inheritdoc />
public Task<int> InvokeAsync(InvocationContext context) public Task<int> InvokeAsync(InvocationContext context)
{ {
bool messy = context.ParseResult.GetValueForOption(this.messyOption); bool messy = context.ParseResult.GetValueForOption(this.messyOption);
if (messy) if (messy)
{ {
throw new Exception( throw new Exception("This command crashed messily as requested.");
"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;
using System.CommandLine.Invocation; using System.CommandLine.Invocation;
using System.Threading.Tasks; using System.Threading.Tasks;
using MfGames.ToolBuilder; using MfGames.ToolBuilder;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
namespace SampleTool.Commands; namespace SampleTool.Commands;
public class LogCommand : Command, ICommandHandler, ITopCommand 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 /> /// <inheritdoc />
public LogCommand( public LogCommand(ILoggerFactory loggerFactory, ILogger serilogLogger)
ILoggerFactory loggerFactory, : base("log", "Shows various logging messages using Serilog and Microsoft")
ILogger serilogLogger) {
: base( this.serilogLogger = serilogLogger;
"log", this.serilogContextLogger = serilogLogger.ForContext<LogCommand>();
"Shows various logging messages using Serilog and Microsoft") this.extensionLogger = loggerFactory.CreateLogger<LogCommand>();
{ this.Handler = this;
this.serilogLogger = serilogLogger; }
this.serilogContextLogger = serilogLogger.ForContext<LogCommand>();
this.extensionLogger = loggerFactory.CreateLogger<LogCommand>();
this.Handler = this;
}
/// <inheritdoc /> /// <inheritdoc />
public int Invoke(InvocationContext context) public int Invoke(InvocationContext context)
{ {
return this.InvokeAsync(context).Result; return this.InvokeAsync(context).Result;
} }
/// <inheritdoc /> /// <inheritdoc />
public Task<int> InvokeAsync(InvocationContext context) public Task<int> InvokeAsync(InvocationContext context)
{ {
// Show the serilog logging. // Show the serilog logging.
this.serilogLogger.Verbose("Serilog Verbose()"); this.serilogLogger.Verbose("Serilog Verbose()");
this.serilogLogger.Debug("Serilog Debug()"); this.serilogLogger.Debug("Serilog Debug()");
this.serilogLogger.Information("Serilog Information()"); this.serilogLogger.Information("Serilog Information()");
this.serilogLogger.Warning("Serilog Warning()"); this.serilogLogger.Warning("Serilog Warning()");
this.serilogLogger.Error("Serilog Error()"); this.serilogLogger.Error("Serilog Error()");
this.serilogLogger.Fatal("Serilog Fatal()"); this.serilogLogger.Fatal("Serilog Fatal()");
// Show serilog with context. // Show serilog with context.
this.serilogContextLogger.Information( this.serilogContextLogger.Information("Serilog Information() with context");
"Serilog Information() with context");
// Show the extension logging. // Show the extension logging.
this.extensionLogger.LogTrace( this.extensionLogger.LogTrace("System.Extension.Logging LogTrace");
"System.Extension.Logging LogTrace"); this.extensionLogger.LogDebug("System.Extension.Logging LogDebug");
this.extensionLogger.LogDebug( this.extensionLogger.LogInformation("System.Extension.Logging LogInformation");
"System.Extension.Logging LogDebug"); this.extensionLogger.LogWarning("System.Extension.Logging LogWarning");
this.extensionLogger.LogInformation( this.extensionLogger.LogError("System.Extension.Logging LogError");
"System.Extension.Logging LogInformation"); this.extensionLogger.LogCritical("System.Extension.Logging LogCritical");
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. // Show Serilog working through logging extensions.
var hash = new var hash = new
{ {
Number = 1, Number = 1,
String = "String", String = "String",
Inner = new { Nested = true } Inner = new { Nested = true }
}; };
this.serilogLogger.Information( this.serilogLogger.Information(
"Serilog Contextual parameters {Name} and {Quotes:l}", "Serilog Contextual parameters {Name} and {Quotes:l}",
"extension logger", "extension logger",
"without quotes"); "without quotes"
this.serilogLogger.Information( );
"Serilog Contextual nested object {@Nested}", this.serilogLogger.Information("Serilog Contextual nested object {@Nested}", hash);
hash);
this.extensionLogger.LogInformation( this.extensionLogger.LogInformation(
"System.Extension.Logging parameters {Name} and {Quotes:l}", "System.Extension.Logging parameters {Name} and {Quotes:l}",
"extension logger", "extension logger",
"without quotes"); "without quotes"
this.extensionLogger.LogInformation( );
"System.Extension.Logging nested object {@Nested}", this.extensionLogger.LogInformation(
hash); "System.Extension.Logging nested object {@Nested}",
hash
);
// We're good. // We're good.
return Task.FromResult(0); return Task.FromResult(0);
} }
} }

View file

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

View file

@ -3,7 +3,6 @@ using System.CommandLine;
using System.CommandLine.Invocation; using System.CommandLine.Invocation;
using System.Data; using System.Data;
using System.Threading.Tasks; using System.Threading.Tasks;
using MfGames.ToolBuilder; using MfGames.ToolBuilder;
using MfGames.ToolBuilder.Tables; using MfGames.ToolBuilder.Tables;
@ -11,49 +10,46 @@ namespace SampleTool.Commands;
public class TableCommand : Command, ICommandHandler, ITopCommand public class TableCommand : Command, ICommandHandler, ITopCommand
{ {
private readonly DataTable table; private readonly DataTable table;
private readonly TableToolService tableService; private readonly TableToolService tableService;
/// <inheritdoc /> /// <inheritdoc />
public TableCommand(TableToolService.Factory tableService) public TableCommand(TableToolService.Factory tableService)
: base("table", "Display a Markdown table") : base("table", "Display a Markdown table")
{ {
// Create the table structure. // Create the table structure.
this.table = new DataTable(); this.table = new DataTable();
this.table.Columns.Add("DefaultString", typeof(string)); this.table.Columns.Add("DefaultString", typeof(string));
this.table.Columns.Add("DefaultInt32", typeof(int)); this.table.Columns.Add("DefaultInt32", typeof(int));
this.table.Columns.Add("HiddenString", typeof(string)); this.table.Columns.Add("HiddenString", typeof(string));
// Create the table service for formatting and displaying results. // Create the table service for formatting and displaying results.
this.tableService = tableService( this.tableService = tableService(
this.table, this.table,
new List<string> new List<string> { "DefaultString", "DefaultInt32", }
{ )
"DefaultString", .Attach(this);
"DefaultInt32",
})
.Attach(this);
// This class handles the command. // This class handles the command.
this.Handler = this; this.Handler = this;
} }
/// <inheritdoc /> /// <inheritdoc />
public int Invoke(InvocationContext context) public int Invoke(InvocationContext context)
{ {
return this.InvokeAsync(context).Result; return this.InvokeAsync(context).Result;
} }
/// <inheritdoc /> /// <inheritdoc />
public Task<int> InvokeAsync(InvocationContext context) public Task<int> InvokeAsync(InvocationContext context)
{ {
this.table.Rows.Add("Row 1", 1, "Hidden 1"); this.table.Rows.Add("Row 1", 1, "Hidden 1");
this.table.Rows.Add("Row 2", 10, "Hidden 2"); this.table.Rows.Add("Row 2", 10, "Hidden 2");
this.table.Rows.Add("Row 3", 100, "Hidden 3"); 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 System.Threading.Tasks;
using Autofac; using Autofac;
using MfGames.ToolBuilder; using MfGames.ToolBuilder;
using MfGames.ToolBuilder.Config; using MfGames.ToolBuilder.Config;
using MfGames.ToolBuilder.Tables; using MfGames.ToolBuilder.Tables;
@ -10,19 +8,19 @@ namespace SampleTool;
public static class Program public static class Program
{ {
public static async Task<int> Main(string[] args) public static async Task<int> Main(string[] args)
{ {
return await ToolBoxBuilder return await ToolBoxBuilder
.Create(args) .Create(args)
.UseUserConfiguration("mfgames-toolbuilder-sample") .UseUserConfiguration("mfgames-toolbuilder-sample")
.ConfigureContainer(ConfigureContainer) .ConfigureContainer(ConfigureContainer)
.Build() .Build()
.RunAsync(); .RunAsync();
} }
private static void ConfigureContainer(ContainerBuilder builder) private static void ConfigureContainer(ContainerBuilder builder)
{ {
builder.RegisterModule<SampleToolModule>(); builder.RegisterModule<SampleToolModule>();
builder.RegisterModule<ToolBuilderTablesModule>(); builder.RegisterModule<ToolBuilderTablesModule>();
} }
} }

View file

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

View file

@ -1,136 +1,12 @@
{ {
"nodes": { "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": { "flake-utils": {
"locked": { "locked": {
"lastModified": 1659877975, "lastModified": 1653893745,
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", "rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -141,11 +17,11 @@
}, },
"flake-utils_2": { "flake-utils_2": {
"locked": { "locked": {
"lastModified": 1667395993, "lastModified": 1653893745,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", "rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -154,97 +30,296 @@
"type": "github" "type": "github"
} }
}, },
"haumea": { "flake-utils_3": {
"inputs": {
"nixpkgs": [
"std",
"dmerge",
"nixlib"
]
},
"locked": { "locked": {
"lastModified": 1685133229, "lastModified": 1653893745,
"narHash": "sha256-FePm/Gi9PBSNwiDFq3N+DWdfxFq0UKsVVTJS3cQPn94=", "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "nix-community", "owner": "numtide",
"repo": "haumea", "repo": "flake-utils",
"rev": "34dd58385092a23018748b50f9b23de6266dffc2", "rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nix-community", "owner": "numtide",
"ref": "v0.2.2", "repo": "flake-utils",
"repo": "haumea",
"type": "github" "type": "github"
} }
}, },
"incl": { "flake-utils_4": {
"inputs": {
"nixlib": [
"std",
"dmerge",
"nixlib"
]
},
"locked": { "locked": {
"lastModified": 1669263024, "lastModified": 1653893745,
"narHash": "sha256-E/+23NKtxAqYG/0ydYgxlgarKnxmDbg6rCMWnOBqn9Q=", "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "divnix", "owner": "numtide",
"repo": "incl", "repo": "flake-utils",
"rev": "ce7bebaee048e4cd7ebdb4cee7885e00c4e2abca", "rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "divnix", "owner": "numtide",
"repo": "incl", "repo": "flake-utils",
"type": "github" "type": "github"
} }
}, },
"n2c": { "flake-utils_5": {
"inputs": {
"flake-utils": [
"std",
"flake-utils"
],
"nixpkgs": [
"std",
"nixpkgs"
]
},
"locked": { "locked": {
"lastModified": 1685771919, "lastModified": 1653893745,
"narHash": "sha256-3lVKWrhNXjHJB6QkZ2SJaOs4X/mmYXtY6ovPVpDMOHc=", "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "nlewo", "owner": "numtide",
"repo": "nix2container", "repo": "flake-utils",
"rev": "95e2220911874064b5d809f8d35f7835184c4ddf", "rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nlewo", "owner": "numtide",
"repo": "nix2container", "repo": "flake-utils",
"type": "github" "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": { "nixago": {
"inputs": { "inputs": {
"flake-utils": [ "flake-utils": "flake-utils",
"std", "nixago-exts": "nixago-exts",
"flake-utils"
],
"nixago-exts": [
"std",
"blank"
],
"nixpkgs": [ "nixpkgs": [
"std", "mfgames-project-setup",
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1683210100, "lastModified": 1687381756,
"narHash": "sha256-bhGDOlkWtlhVECpoOog4fWiFJmLCpVEg09a40aTjCbw=", "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", "owner": "nix-community",
"repo": "nixago", "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" "type": "github"
}, },
"original": { "original": {
@ -253,284 +328,88 @@
"type": "github" "type": "github"
} }
}, },
"nixlib": { "nixago_4": {
"inputs": {
"flake-utils": "flake-utils_7",
"nixago-exts": "nixago-exts_4",
"nixpkgs": [
"mfgames-project-setup",
"nixago-exts",
"nixpkgs"
]
},
"locked": { "locked": {
"lastModified": 1681001314, "lastModified": 1676070010,
"narHash": "sha256-5sDnCLdrKZqxLPK4KA8+f4A3YKO/u6ElpMILvX0g72c=", "narHash": "sha256-iYzJIWptE1EUD8VINAg66AAMUajizg8JUYN3oBmb8no=",
"owner": "nix-community", "owner": "nix-community",
"repo": "nixpkgs.lib", "repo": "nixago",
"rev": "367c0e1086a4eb4502b24d872cea2c7acdd557f4", "rev": "d480ba6c0c16e2c5c0bd2122852d6a0c9ad1ed0e",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nix-community", "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" "type": "github"
} }
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1693565476, "lastModified": 1706098335,
"narHash": "sha256-ya00zHt7YbPo3ve/wNZ/6nts61xt7wK/APa6aZAfey0=", "narHash": "sha256-r3dWjT8P9/Ah5m5ul4WqIWD8muj5F+/gbCdjiNVBKmU=",
"owner": "NixOS", "rev": "a77ab169a83a4175169d78684ddd2e54486ac651",
"repo": "nixpkgs", "revCount": 554858,
"rev": "aa8aa7e2ea35ce655297e8322dc82bf77a31d04b", "type": "tarball",
"type": "github" "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2311.554858%2Brev-a77ab169a83a4175169d78684ddd2e54486ac651/018d46f0-798f-71dc-a8c5-4689c46f7d12/source.tar.gz"
}, },
"original": { "original": {
"id": "nixpkgs", "type": "tarball",
"ref": "nixos-unstable", "url": "https://flakehub.com/f/NixOS/nixpkgs/%2A.tar.gz"
"type": "indirect"
} }
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1677063315, "lastModified": 1709569716,
"narHash": "sha256-qiB4ajTeAOVnVSAwCNEEkoybrAlA+cpeiBxLobHndE8=", "narHash": "sha256-iOR44RU4jQ+YPGrn+uQeYAp7Xo7Z/+gT+wXJoGxxLTY=",
"owner": "nixos", "rev": "617579a787259b9a6419492eaac670a5f7663917",
"repo": "nixpkgs", "revCount": 556422,
"rev": "988cc958c57ce4350ec248d2d53087777f9e1949", "type": "tarball",
"type": "github" "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2311.556422%2Brev-617579a787259b9a6419492eaac670a5f7663917/018e0df2-b0f7-7a27-af1a-04150ef0f2c7/source.tar.gz"
}, },
"original": { "original": {
"owner": "nixos", "type": "tarball",
"ref": "nixos-unstable", "url": "https://flakehub.com/f/NixOS/nixpkgs/%2A.tar.gz"
"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"
} }
}, },
"root": { "root": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs", "mfgames-project-setup": "mfgames-project-setup",
"std": "std" "nixpkgs": "nixpkgs_2"
}
},
"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"
} }
} }
}, },

View file

@ -1,26 +1,52 @@
{ {
description = "A variety of .NET core libraries used for development";
inputs = { inputs = {
std.url = "github:divnix/std/v0.23.2"; nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/*.tar.gz";
std.inputs.nixpkgs.follows = "nixpkgs"; mfgames-project-setup.url = "git+https://src.mfgames.com/nixos-contrib/mfgames-project-setup-flake.git";
nixpkgs.url = "nixpkgs/nixos-unstable";
}; };
outputs = inputs @ { outputs = inputs @ { self, nixpkgs, mfgames-project-setup, ... }:
self, let
std, # Helpers for producing system-specific outputs
... supportedSystems = [ "x86_64-linux" ];
}: forEachSupportedSystem = f: nixpkgs.lib.genAttrs supportedSystems (system: f {
std.growOn { inherit system;
inherit inputs; pkgs = import nixpkgs { inherit system; };
systems = ["x86_64-linux"]; });
cellsFrom = ./nix; in
cellBlocks = with std.blockTypes; [ rec
(devshells "shells") {
(nixago "configs") # Set up the developer shell.
]; devShells = forEachSupportedSystem ({ system, pkgs }:
} { let
devShells = std.harvest self ["common" "shells"]; 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 public enum ByteStringFormat
{ {
/// <summary> /// <summary>
/// Indicates that the format should be lowercase hex characters. /// Indicates that the format should be lowercase hex characters.
/// </summary> /// </summary>
LowercaseHex, LowercaseHex,
/// <summary> /// <summary>
/// Indicates that the format should be uppercase hex characters. /// Indicates that the format should be uppercase hex characters.
/// </summary> /// </summary>
UppercaseHex, UppercaseHex,
/// <summary> /// <summary>
/// Indicates that the format should be Base64. /// Indicates that the format should be Base64.
/// </summary> /// </summary>
Base64, Base64,
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,6 @@
using Markdig; using Markdig;
using Markdig.Parsers; using Markdig.Parsers;
using Markdig.Syntax; using Markdig.Syntax;
using MfGames.Markdown.Gemtext.Renderers; using MfGames.Markdown.Gemtext.Renderers;
namespace MfGames.Markdown.Gemtext; namespace MfGames.Markdown.Gemtext;
@ -13,69 +12,69 @@ namespace MfGames.Markdown.Gemtext;
/// </summary> /// </summary>
public static class MarkdownGemtext public static class MarkdownGemtext
{ {
private static readonly MarkdownPipeline DefaultPipeline; private static readonly MarkdownPipeline DefaultPipeline;
static MarkdownGemtext() static MarkdownGemtext()
{ {
DefaultPipeline = new MarkdownPipelineBuilder() DefaultPipeline = new MarkdownPipelineBuilder().Build();
.Build(); }
}
/// <summary> /// <summary>
/// Converts the given Markdown /// Converts the given Markdown
/// </summary> /// </summary>
/// <param name="markdown">A Markdown text.</param> /// <param name="markdown">A Markdown text.</param>
/// <param name="pipeline">The pipeline used for the conversion.</param> /// <param name="pipeline">The pipeline used for the conversion.</param>
/// <param name="context">A parser context used for the parsing.</param> /// <param name="context">A parser context used for the parsing.</param>
/// <returns>The result of the conversion</returns> /// <returns>The result of the conversion</returns>
public static string ToGemtext( public static string ToGemtext(
string markdown, string markdown,
MarkdownPipeline? pipeline = null, MarkdownPipeline? pipeline = null,
MarkdownParserContext? context = null) MarkdownParserContext? context = null
{ )
if (markdown == null) {
{ if (markdown == null)
throw new ArgumentNullException(nameof(markdown)); {
} throw new ArgumentNullException(nameof(markdown));
}
pipeline ??= DefaultPipeline; pipeline ??= DefaultPipeline;
MarkdownDocument document = MarkdownParser MarkdownDocument document = MarkdownParser.Parse(markdown, pipeline, context);
.Parse(markdown, pipeline, context);
return ToGemtext(document, pipeline); return ToGemtext(document, pipeline);
} }
/// <summary> /// <summary>
/// Converts a Markdown document to HTML. /// Converts a Markdown document to HTML.
/// </summary> /// </summary>
/// <param name="document">A Markdown document.</param> /// <param name="document">A Markdown document.</param>
/// <param name="pipeline">The pipeline used for the conversion.</param> /// <param name="pipeline">The pipeline used for the conversion.</param>
/// <returns>The result of the conversion</returns> /// <returns>The result of the conversion</returns>
/// <exception cref="ArgumentNullException">if markdown document variable is null</exception> /// <exception cref="ArgumentNullException">if markdown document variable is null</exception>
public static string ToGemtext( public static string ToGemtext(
this MarkdownDocument document, this MarkdownDocument document,
MarkdownPipeline? pipeline = null) MarkdownPipeline? pipeline = null
{ )
// Make sure we have sane parameters. {
if (document == null) // Make sure we have sane parameters.
{ if (document == null)
throw new ArgumentNullException(nameof(document)); {
} throw new ArgumentNullException(nameof(document));
}
pipeline ??= DefaultPipeline; pipeline ??= DefaultPipeline;
// Set up the writer to contain the markdown and the Gemtext // Set up the writer to contain the markdown and the Gemtext
// renderer. // renderer.
var writer = new StringWriter(); var writer = new StringWriter();
GemtextRenderer renderer = new(writer); GemtextRenderer renderer = new(writer);
pipeline.Setup(renderer); pipeline.Setup(renderer);
// Render the Markdown into Gemtext and re turn the results. // Render the Markdown into Gemtext and re turn the results.
renderer.Render(document); renderer.Render(document);
renderer.Writer.Flush(); 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> /// </summary>
public enum BlockLinkHandling public enum BlockLinkHandling
{ {
/// <summary> /// <summary>
/// Indicates that the paragraph should be broken apart and the link /// Indicates that the paragraph should be broken apart and the link
/// included on its own line in the middle of the paragraph. /// included on its own line in the middle of the paragraph.
/// </summary> /// </summary>
InsertLine, InsertLine,
/// <summary> /// <summary>
/// Indicates that all the links in a paragraph should be gathered /// 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 /// and then emitted at the end of the paragraph. The text of the link
/// will be left in the paragraph. /// will be left in the paragraph.
/// </summary> /// </summary>
ParagraphEnd, ParagraphEnd,
/// <summary> /// <summary>
/// Indicates that all the links in the document should be gathered /// 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 /// and then emitted at the end of the document. The text of the link
/// will be left in the paragraph. /// will be left in the paragraph.
/// </summary> /// </summary>
DocumentEnd, DocumentEnd,
/// <summary> /// <summary>
/// Indicates that the links themselves should be removed and just the /// Indicates that the links themselves should be removed and just the
/// text included in the paragraph. /// text included in the paragraph.
/// </summary> /// </summary>
Remove, Remove,
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,14 +6,11 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Blocks;
/// A Gemtext renderer for a <see cref="ThematicBreakBlock" />. /// A Gemtext renderer for a <see cref="ThematicBreakBlock" />.
/// </summary> /// </summary>
/// <seealso cref="GemtextObjectRenderer{ThematicBreakBlock}" /> /// <seealso cref="GemtextObjectRenderer{ThematicBreakBlock}" />
public class ThematicBreakRenderer public class ThematicBreakRenderer : GemtextObjectRenderer<ThematicBreakBlock>
: GemtextObjectRenderer<ThematicBreakBlock>
{ {
protected override void Write( protected override void Write(GemtextRenderer renderer, ThematicBreakBlock obj)
GemtextRenderer renderer, {
ThematicBreakBlock obj) renderer.EnsureTwoLines();
{ renderer.WriteLine("---");
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> /// <typeparam name="TObject">The type of the object.</typeparam>
/// <seealso cref="IMarkdownObjectRenderer" /> /// <seealso cref="IMarkdownObjectRenderer" />
public abstract class GemtextObjectRenderer<TObject> public abstract class GemtextObjectRenderer<TObject>
: MarkdownObjectRenderer<GemtextRenderer, TObject> : MarkdownObjectRenderer<GemtextRenderer, TObject>
where TObject : MarkdownObject where TObject : MarkdownObject { }
{
}

View file

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

View file

@ -6,14 +6,11 @@ namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines;
/// A Gemtext renderer for a <see cref="DelimiterInline" />. /// A Gemtext renderer for a <see cref="DelimiterInline" />.
/// </summary> /// </summary>
/// <seealso cref="GemtextObjectRenderer{DelimiterInline}" /> /// <seealso cref="GemtextObjectRenderer{DelimiterInline}" />
public class DelimiterInlineRenderer public class DelimiterInlineRenderer : GemtextObjectRenderer<DelimiterInline>
: GemtextObjectRenderer<DelimiterInline>
{ {
protected override void Write( protected override void Write(GemtextRenderer renderer, DelimiterInline obj)
GemtextRenderer renderer, {
DelimiterInline obj) renderer.Write(obj.ToLiteral());
{ renderer.WriteChildren(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}" /> /// <seealso cref="GemtextObjectRenderer{EmphasisInline}" />
public class EmphasisInlineRenderer : GemtextObjectRenderer<EmphasisInline> public class EmphasisInlineRenderer : GemtextObjectRenderer<EmphasisInline>
{ {
protected override void Write( protected override void Write(GemtextRenderer renderer, EmphasisInline obj)
GemtextRenderer renderer, {
EmphasisInline obj) InlineFormatting formatting = renderer.EmphasisFormattingResolved;
{ bool normalize = formatting == InlineFormatting.Normalize;
InlineFormatting formatting = renderer.EmphasisFormattingResolved; string delimiter = new string('*', obj.DelimiterCount);
bool normalize = formatting == InlineFormatting.Normalize;
string delimiter = new string('*', obj.DelimiterCount);
if (normalize) if (normalize)
{ {
renderer.Write(delimiter); renderer.Write(delimiter);
} }
renderer.WriteChildren(obj); renderer.WriteChildren(obj);
if (normalize) if (normalize)
{ {
renderer.Write(delimiter); renderer.Write(delimiter);
} }
} }
} }

View file

@ -1,5 +1,4 @@
using System.Net; using System.Net;
using Markdig.Extensions.SmartyPants; using Markdig.Extensions.SmartyPants;
namespace MfGames.Markdown.Gemtext.Renderers.Gemtext.Inlines; 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" />. /// A Gemtext renderer for a <see cref="SmartyPant" />.
/// </summary> /// </summary>
/// <seealso cref="GemtextObjectRenderer{GemtextEntityInline}" /> /// <seealso cref="GemtextObjectRenderer{GemtextEntityInline}" />
public class GemtextSmartyPantRenderer public class GemtextSmartyPantRenderer : GemtextObjectRenderer<SmartyPant>
: GemtextObjectRenderer<SmartyPant>
{ {
private static readonly SmartyPantOptions DefaultOptions = new(); private static readonly SmartyPantOptions DefaultOptions = new();
private readonly SmartyPantOptions options; private readonly SmartyPantOptions options;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HtmlSmartyPantRenderer" /> class. /// Initializes a new instance of the <see cref="HtmlSmartyPantRenderer" /> class.
/// </summary> /// </summary>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentNullException"></exception>
public GemtextSmartyPantRenderer(SmartyPantOptions? options) public GemtextSmartyPantRenderer(SmartyPantOptions? options)
{ {
this.options = options this.options = options ?? throw new ArgumentNullException(nameof(options));
?? throw new ArgumentNullException(nameof(options)); }
}
protected override void Write(GemtextRenderer renderer, SmartyPant obj) protected override void Write(GemtextRenderer renderer, SmartyPant obj)
{ {
if (!this.options.Mapping.TryGetValue(obj.Type, out string? text)) if (!this.options.Mapping.TryGetValue(obj.Type, out string? text))
{ {
DefaultOptions.Mapping.TryGetValue(obj.Type, out 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" />. /// A Gemtext renderer for a <see cref="GemtextEntityInline" />.
/// </summary> /// </summary>
/// <seealso cref="GemtextObjectRenderer{GemtextEntityInline}" /> /// <seealso cref="GemtextObjectRenderer{GemtextEntityInline}" />
public class HtmlEntityInlineRenderer public class HtmlEntityInlineRenderer : GemtextObjectRenderer<HtmlEntityInline>
: GemtextObjectRenderer<HtmlEntityInline>
{ {
protected override void Write( protected override void Write(GemtextRenderer renderer, HtmlEntityInline obj)
GemtextRenderer renderer, {
HtmlEntityInline obj) renderer.Write(obj.Transcoded);
{ }
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" />. /// A Gemtext renderer for a <see cref="LineBreakInline" />.
/// </summary> /// </summary>
/// <seealso cref="GemtextObjectRenderer{LineBreakInline}" /> /// <seealso cref="GemtextObjectRenderer{LineBreakInline}" />
public class LineBreakInlineRenderer public class LineBreakInlineRenderer : GemtextObjectRenderer<LineBreakInline>
: GemtextObjectRenderer<LineBreakInline>
{ {
/// <summary> /// <summary>
/// Gets or sets a value indicating whether to render this softline break as a /// Gets or sets a value indicating whether to render this softline break as a
/// Gemtext hardline break tag (&lt;br /&gt;) /// Gemtext hardline break tag (&lt;br /&gt;)
/// </summary> /// </summary>
public bool RenderAsHardlineBreak { get; set; } public bool RenderAsHardlineBreak { get; set; }
protected override void Write( protected override void Write(GemtextRenderer renderer, LineBreakInline obj)
GemtextRenderer renderer, {
LineBreakInline obj) if (obj.IsHard || this.RenderAsHardlineBreak)
{ {
if (obj.IsHard || this.RenderAsHardlineBreak) renderer.EnsureTwoLines();
{ }
renderer.EnsureTwoLines(); else
} {
else renderer.Write(" ");
{ }
renderer.Write(" "); }
}
}
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -9,39 +9,34 @@ namespace MfGames.Markdown.Extensions;
/// </summary> /// </summary>
public class WikiLinkExtension : IMarkdownExtension public class WikiLinkExtension : IMarkdownExtension
{ {
public WikiLinkExtension() public WikiLinkExtension()
: this(null) : this(null) { }
{
}
public WikiLinkExtension(WikiLinkOptions? options) public WikiLinkExtension(WikiLinkOptions? options)
{ {
this.Options = options ?? new WikiLinkOptions(); this.Options = options ?? new WikiLinkOptions();
} }
public WikiLinkOptions Options { get; set; } public WikiLinkOptions Options { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public void Setup(MarkdownPipelineBuilder pipeline) public void Setup(MarkdownPipelineBuilder pipeline)
{ {
WikiLinkInlineParser? parser = pipeline.InlineParsers WikiLinkInlineParser? parser = pipeline.InlineParsers.FindExact<WikiLinkInlineParser>();
.FindExact<WikiLinkInlineParser>();
if (parser != null) if (parser != null)
{ {
return; return;
} }
parser = new WikiLinkInlineParser(this.Options); parser = new WikiLinkInlineParser(this.Options);
pipeline.InlineParsers.InsertBefore<LinkInlineParser>(parser); pipeline.InlineParsers.InsertBefore<LinkInlineParser>(parser);
} }
/// <inheritdoc /> /// <inheritdoc />
public void Setup( public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
MarkdownPipeline pipeline, {
IMarkdownRenderer renderer) // No setup needed here because we're using LinkInline which does the
{ // bulk of the work.
// 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 public class WikiLinkInlineParser : InlineParser
{ {
private readonly WikiLinkOptions options; private readonly WikiLinkOptions options;
public WikiLinkInlineParser(WikiLinkOptions options) public WikiLinkInlineParser(WikiLinkOptions options)
{ {
this.options = options; this.options = options;
this.OpeningCharacters = new[] { '[' }; this.OpeningCharacters = new[] { '[' };
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool Match( public override bool Match(InlineProcessor processor, ref StringSlice slice)
InlineProcessor processor, {
ref StringSlice slice) // We are looking for the `[[` opening for the tag and that the first
{ // one isn't escaped.
// We are looking for the `[[` opening for the tag and that the first if (IsNotDelimiter(slice, '['))
// one isn't escaped. {
if (IsNotDelimiter(slice, '[')) return false;
{ }
return false;
}
// We need to loop over the entire link, including the `[[` and `]]` // We need to loop over the entire link, including the `[[` and `]]`
// while keeping track since we'll swallow additional characters beyond // while keeping track since we'll swallow additional characters beyond
// the link. // the link.
int linkStart = slice.Start; int linkStart = slice.Start;
int linkEnd = slice.Start; int linkEnd = slice.Start;
slice.Start += 2; slice.Start += 2;
// Our content starts after the double '[['. // Our content starts after the double '[['.
int contentStart = slice.Start; int contentStart = slice.Start;
// We need to find the end of the link (the `]]`). // We need to find the end of the link (the `]]`).
while (IsNotDelimiter(slice, ']')) while (IsNotDelimiter(slice, ']'))
{ {
slice.NextChar(); slice.NextChar();
linkEnd = slice.Start; linkEnd = slice.Start;
} }
// Pull out the components before we adjust for the ']]' for the end. // Pull out the components before we adjust for the ']]' for the end.
int contentEnd = linkEnd; int contentEnd = linkEnd;
// Finish skipping over the `]]`. // Finish skipping over the `]]`.
slice.NextChar(); slice.NextChar();
slice.NextChar(); slice.NextChar();
// Format the label and the URL. // Format the label and the URL.
string content = slice.Text.Substring( string content = slice.Text.Substring(contentStart, contentEnd - contentStart);
contentStart, string[] contentParts = content.Split('|', 2);
contentEnd - contentStart); string label = contentParts.Last();
string[] contentParts = content.Split('|', 2); string url = this.options.GetUrl(contentParts.First());
string label = contentParts.Last();
string url = this.options.GetUrl(contentParts.First());
// Add in any trailing components. This merges the `'s` from // Add in any trailing components. This merges the `'s` from
// `[[Dale]]'s` into the label. // `[[Dale]]'s` into the label.
while (this.options.IsTrailingLink(slice.CurrentChar)) while (this.options.IsTrailingLink(slice.CurrentChar))
{ {
label += slice.CurrentChar; label += slice.CurrentChar;
slice.NextChar(); slice.NextChar();
linkEnd++; linkEnd++;
} }
// Create the link that we're replacing. // Create the link that we're replacing.
WikiLink link = new() WikiLink link =
{ new()
Span = {
{ Span =
Start = processor.GetSourcePosition( {
linkStart, Start = processor.GetSourcePosition(linkStart, out int line, out int column),
out int line, },
out int column), Line = line,
}, Column = column,
Line = line, Url = url,
Column = column, IsClosed = true,
Url = url, };
IsClosed = true,
};
link.AppendChild( link.AppendChild(
new LiteralInline() new LiteralInline()
{ {
Line = line, Line = line,
Column = column, Column = column,
Content = new StringSlice(label), Content = new StringSlice(label),
IsClosed = true, IsClosed = true,
}); }
);
// Replace the inline and then indicate we have a match. // Replace the inline and then indicate we have a match.
processor.Inline = link; processor.Inline = link;
return true; return true;
} }
private static bool IsNotDelimiter( private static bool IsNotDelimiter(StringSlice slice, char delimiter)
StringSlice slice, {
char delimiter) return slice.CurrentChar != delimiter
{ || slice.PeekChar() != delimiter
return slice.CurrentChar != delimiter || slice.PeekCharExtra(-1) == '\\';
|| slice.PeekChar() != delimiter }
|| slice.PeekCharExtra(-1) == '\\';
}
} }

View file

@ -4,26 +4,26 @@ namespace MfGames.Markdown.Extensions;
public class WikiLinkOptions public class WikiLinkOptions
{ {
public WikiLinkOptions() public WikiLinkOptions()
{ {
this.GetUrl = a => a; this.GetUrl = a => a;
this.IsTrailingLink = a => a.IsAlpha() || a == '\''; this.IsTrailingLink = a => a.IsAlpha() || a == '\'';
} }
/// <summary> /// <summary>
/// The callback to determine the link from the given wiki link. This does /// 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 /// not include trailing additions or use the label (e.g., `(ab|cd)` would
/// get `ab` as the parameters of this function. /// get `ab` as the parameters of this function.
/// </summary> /// </summary>
public Func<string, string> GetUrl { get; set; } public Func<string, string> GetUrl { get; set; }
/// <summary> /// <summary>
/// <para> /// <para>
/// A callback to determine if the text after the link should be merged /// 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 /// with the link label. This allows links such as [[Dale]]'s to be turned
/// into "Dale's" but pointing to "Dale" as a page. /// into "Dale's" but pointing to "Dale" as a page.
/// </para> /// </para>
/// <para>The default is to include any character or the apostrophe.</para> /// <para>The default is to include any character or the apostrophe.</para>
/// </summary> /// </summary>
public Func<char, bool> IsTrailingLink { get; set; } 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: 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 - An extension for converting wiki links, such as `[[MfGames]]` into a link based on the title. This is for both HTML
and Gemtext. and Gemtext.
- A output library for using MarkDig to generate Gemtext for Gemini pods. - 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. 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: The two libraries are:
- MfGames.Markdown - MfGames.Markdown
- MfGames.Markdown.Gemtext - MfGames.Markdown.Gemtext

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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