Rougamo.Fody
1.3.2
See the version list below for details.
dotnet add package Rougamo.Fody --version 1.3.2
NuGet\Install-Package Rougamo.Fody -Version 1.3.2
<PackageReference Include="Rougamo.Fody" Version="1.3.2" />
paket add Rougamo.Fody --version 1.3.2
#r "nuget: Rougamo.Fody, 1.3.2"
// Install Rougamo.Fody as a Cake Addin #addin nuget:?package=Rougamo.Fody&version=1.3.2 // Install Rougamo.Fody as a Cake Tool #tool nuget:?package=Rougamo.Fody&version=1.3.2
Rougamo - 肉夹馍
Translated by google
中文 | English
Rougamo, a Chinese snack, perhaps somewhat similar to a hamburger. Wrapping important logic code to provide AOP is like wrapping delicious stuffing in buns.
Quick start
// 1. Install-Package Rougamo.Fody
// 2. Define the class to inherit MoAttribute, and define the code that needs to be woven
public class LoggingAttribute : MoAttribute
{
public override void OnEntry(MethodContext context)
{
Log.Info("Before the method execution");
}
public override void OnException(MethodContext context)
{
Log.Error("When an exception occurs when the method is executed", context.Exception);
}
public override void OnSuccess(MethodContext context)
{
Log.Info("After the method executes successfully");
}
public override void OnExit(MethodContext context)
{
Log.Info("After the method is executed, whether it succeeds or fails");
}
}
// 3. Apply Attribute
public class Service
{
[Logging]
public static int Sync(Model model)
{
// ...
}
[Logging]
public async Task<Data> Async(int id)
{
// ...
}
}
Batch apply based on method accessibility
In the quick start, we introduced how to weave the code into the specified method, but in actual use, it may be very cumbersome and intrusive to add
this attribute to each method or many methods in a huge project, so MoAttribute
is designed to be applied to methods, classes, assemblies, and modules,
while setting accessibility attributes for added flexibility.
// 1. While inheriting MoAttribute, override the Flags attribute, and default to InstancePublic (public instance method) when not overridden.
public class LoggingAttribute : MoAttribute
{
// Changing to all public methods is valid, whether it is an instance method or a static method.
public override AccessFlags Flags => AccessFlags.Public;
// Method override omitted.
}
// 2. Apply
// 2.1. apply to class
[Logging]
public class Service
{
// Logging whill be apply.
public static void M1() { }
// Logging whill be apply.
public void M2() { }
// protected access level does not apply Logging code weaving.
protected void M3() { }
}
// 2.2. Applied to an assembly, all public methods of the assembly will be statically woven into the application.
[assembly: Logging]
Weaving by implementing an empty interface(IRougamo<>)
If marking each method via Attribute feels too cumbersome or intrusive, and batch weaving via method accessibility is too general and not custom enough, weaving via an empty interface will provide intrusiveness and convenient way.
// 1. Define the code that needs to be woven.(Because LoggingMo is used as a generic parameter of the IRougamo interface, you can simply implement the IMo interface here, and of course you can inherit MoAttribute like LoggingAttribute in Quick Start)
public class LoggingMo : IMo
{
public override AccessFlags Flags => AccessFlags.All;
public override void OnEntry(MethodContext context)
{
// Information including input parameters, class instances, method descriptions, etc. can be obtained from the context object.
Log.Info("Before the method execution");
}
public override void OnException(MethodContext context)
{
Log.Error("When an exception occurs when the method is executed", context.Exception);
}
public override void OnExit(MethodContext context)
{
Log.Info("After the method is executed, whether it succeeds or fails");
}
public override void OnSuccess(MethodContext context)
{
Log.Info("After the method executes successfully");
}
}
// 2. Define an empty interface. The ILoggingRougamo interface defined in this step can be skipped, and IRougamo<LoggingMo> can also be implemented directly in the next step.
public interface ILoggingRougamo : IRougamo<LoggingMo>
{
}
// 3. Apply an empty interface. If you are used to defining a parent interface/parent class for the same type/domain class when programming, you only need the parent interface/parent class to implement the interface.
public interface IRepository<TModel, TId> : ILoggingRougamo
{
// ...
}
Exception handling and modifying return values(v1.1.0)
In the OnException
method, you can call the HandledException
method of MethodContext
to indicate that the exception has been handled and set the return value.
In the OnEntry
and OnSuccess
methods, you can modify the actual method by calling the ReplaceReturnValue
method of MethodContext
The return value of
ReturnValue
, ExceptionHandled
and other attributes should not be used to modify the return value and handle exceptions directly. HandledException
and
ReplaceReturnValue
contain some other logic, which may be updated in the future. Also note that Iterator/AsyncIterator
does not have this functionality.
public class TestAttribute : MoAttribute
{
public override void OnException(MethodContext context)
{
// Handle exceptions and set the return value to newReturnValue. If the method has no return value (void), simply pass in null
context.HandledException(this, newReturnValue);
}
public override void OnSuccess(MethodContext context)
{
// Modify method return value
context.ReplaceReturnValue(this, newReturnValue);
}
}
rewrite method arguments(v1.3.0)
In OnEntry
, you can modify the arguments of the method by modifying the elements of MethodContext.Arguments
. In order to be compatible with the situation where MethodContext.Arguments
may be used to store some temporary values in the old version (although the possibility is very small),
so you also need to set MethodContext.RewriteArguments
to true
to confirm the rewrite arguments.
public class DefaultValueAttribute : MoAttribute
{
public override void OnEntry(MethodContext context)
{
context.RewriteArguments = true;
var parameters = context.Method.GetParameters();
for (var i = 0; i < parameters.Length; i++)
{
if (parameters[i].ParameterType == typeof(string) && context.Arguments[i] == null)
{
context.Arguments[i] = string.Empty;
}
}
}
}
public class Test
{
// When the value is null, an empty string will be returned
[DefaultValue]
public string EmptyIfNull(string value) => value;
}
Unified processing logic for async/non-async methods(v1.2.0)
Before version 1.2.0, for methods with similar method signatures Task<int> M()
, when using async syntax, the type of the return value obtained through MethodContext.ReturnValue
is int
, while in The type of the return value obtained when the async syntax is not used is Task<int>
. This is not a bug, but the initial setting. Just as when we use the sync
syntax, the return value of the code is int
, but without the When using the async syntax, the return value of the code is like Task<int>
. But when you first came into contact
with the project, it may be difficult to notice this setting and in the process of using it, you also hope that there is no method using async syntax to execute OnSuccess/OnExit
after the Task is executed, so 1.2.0 introduced ExMoAttribute , Use the processing logic of the previous async syntax method for the
Task/ValueTask` return value method with and
without async syntax.
Note that ExMoAttribute
and MoAttribute
have the following differences:
ExMoAttribute
overridable method namedExOnEntry/ExOnException/ExOnSuccess/ExOnExit
- The return value of
ExMoAttribute
is obtained throughMethodContext.ExReturnValue
, and the value obtained throughMethodContext.ReturnValue
will be the value ofTask/ValueTask
- Whether the return value of
ExMoAttribute
is replaced/set, it is obtained throughMethodContext.ExReturnValueReplaced
, and the value obtained throughMethodContext.ReturnValueReplaced
is generally true (because it is replaced with the Task returned by ContinueWith) - The return value type of
ExMoAttribute
is obtained throughMethodContext.ExReturnType
. What is the difference betweenReturnType/RealReturnType/ExReturnType
can be seen in the description of the respective attribute document or the type document description ofExMoAttribute
[Fact]
public async Task Test()
{
Assert.Equal(1, Sync());
Assert.Equal(-1, SyncFailed1());
Assert.Throws<InvalidOperationException>(() => SyncFailed3());
Assert.Equal(1, await NonAsync());
Assert.Equal(-1, await NonAsyncFailed1());
Assert.Equal(-1, await NonAsyncFailed2());
await Assert.ThrowsAsync<InvalidOperationException>(() => NonAsyncFailed3());
await Assert.ThrowsAsync<InvalidOperationException>(() => NonAsyncFailed4());
Assert.Equal(1, await Async());
Assert.Equal(-1, await AsyncFailed1());
Assert.Equal(-1, await AsyncFailed2());
await Assert.ThrowsAsync<InvalidOperationException>(() => AsyncFailed3());
await Assert.ThrowsAsync<InvalidOperationException>(() => AsyncFailed4());
}
[FixedInt]
static int Sync()
{
return int.MaxValue;
}
[FixedInt]
static int SyncFailed1()
{
throw new NotImplementedException();
}
[FixedInt]
static int SyncFailed3()
{
throw new InvalidOperationException();
}
[FixedInt]
static Task<int> NonAsync()
{
return Task.FromResult(int.MinValue);
}
[FixedInt]
static Task<int> NonAsyncFailed1()
{
throw new NotImplementedException();
}
[FixedInt]
static Task<int> NonAsyncFailed2()
{
return Task.Run(int () => throw new NotImplementedException());
}
[FixedInt]
static Task<int> NonAsyncFailed3()
{
throw new InvalidOperationException();
}
[FixedInt]
static Task<int> NonAsyncFailed4()
{
return Task.Run(int () => throw new InvalidOperationException());
}
[FixedInt]
static async Task<int> Async()
{
await Task.Yield();
return int.MaxValue / 2;
}
[FixedInt]
static async Task<int> AsyncFailed1()
{
throw new NotImplementedException();
}
[FixedInt]
static async Task<int> AsyncFailed2()
{
await Task.Yield();
throw new NotImplementedException();
}
[FixedInt]
static async Task<int> AsyncFailed3()
{
throw new InvalidOperationException();
}
[FixedInt]
static async Task<int> AsyncFailed4()
{
await Task.Yield();
throw new InvalidOperationException();
}
class FixedIntAttribute : ExMoAttribute
{
protected override void ExOnException(MethodContext context)
{
if (context.Exception is NotImplementedException)
{
context.HandledException(this, -1);
}
}
protected override void ExOnSuccess(MethodContext context)
{
context.ReplaceReturnValue(this, 1);
}
}
Ignore weaving(IgnoreMoAttribute)
In the quick start, we introduced how to apply in batches. Since the rules of batch references only limit the accessibility of methods, there may be some methods that
meet the rules and do not want to apply weaving. At this time, you can use IgnoreMoAttribute
to specify method/ class, then that method/class (all methods) will
ignore weaving. If IgnoreMoAttribute
is applied to an assembly or module, all weaving will be ignored for that assembly/module. Additionally, it is possible to
specify ignored weaving types via MoTypes when applying the IgnoreMoAttribute
.
// The current assembly ignores all weaving
[assembly: IgnoreMo]
// The current assembly ignores weaving of TheMoAttribute
[assembly: IgnoreMo(MoTypes = new[] { typeof(TheMoAttribute))]
// The current class ignores all weaving
[IgnoreMo]
class Class1
{
// ...
}
// The current class ignores weaving of TheMoAttribute
[IgnoreMo(MoTypes = new[] { typeof(TheMoAttribute))]
class Class2
{
// ...
}
Attribute proxy weaving(MoProxyAttribute)
If you have used some third-party components to mark some methods with Attribute, and now you want to perform aop operations on these marked methods, but do not want to
manually add the Attribute mark of rougamo one by one, you can step by proxy. Complete aop weaving. Another example is that your project now has a lot of obsolete methods
marked with ObsoleteAttribute
. You want to output the call stack log when the expired method is called, to check which entries are using these expired methods. You can
also do this in this way.
public class ObsoleteProxyMoAttribute : MoAttribute
{
public override void OnEntry(MethodContext context)
{
Log.Warning("expired method was called." + Environment.StackTrace);
}
}
[module: MoProxy(typeof(ObsoleteAttribute), typeof(ObsoleteProxyMoAttribute))]
public class Cls
{
[Obsolete]
private int GetId()
{
// This method weaves the application into the code
return 123;
}
}
Weave Mutex
Single type mutex(IRougamo<,>)
Since we have two weaving methods, Attribute tag and interface implementation, it may be applied at the same time, and if the content of the two weaving is the same, there will be repeated weaving. In order to avoid this as much as possible In this case, when the interface is defined, mutually exclusive types can be defined, that is, only one can take effect at the same time, and which one takes effect is determined according to Priority (#Priority).
public class Mo1Attribute : MoAttribute
{
// ...
}
public class Mo2Attribute : MoAttribute
{
// ...
}
public class Mo3Attribute : MoAttribute
{
// ...
}
public class Test : IRougamo<Mo1Attribute, Mo2Attribute>
{
[Mo2]
public void M1()
{
// Mo2Attribute is applied to the method, the priority is higher than the Mo1Attribute implemented by the interface, and the Mo2Attribute will be applied
}
[Mo3]
public void M2()
{
// Mo1Attribute and Mo3Attribute are not mutually exclusive, both will be applied
}
}
Multitype Mutual Exclusion(IRepulsionsRougamo<,>)
IRougamo<,>
can only be mutually exclusive with one type, IRepulsionsRougamo<,>
can be mutually exclusive with multiple types.
public class Mo1Attribute : MoAttribute
{
}
public class Mo2Attribute : MoAttribute
{
}
public class Mo3Attribute : MoAttribute
{
}
public class Mo4Attribute : MoAttribute
{
}
public class Mo5Attribute : MoAttribute
{
}
public class TestRepulsion : MoRepulsion
{
public override Type[] Repulsions => new[] { typeof(Mo2Attribute), typeof(Mo3Attribute) };
}
[assembly: Mo2]
[assembly: Mo5]
public class Class2 : IRepulsionsRougamo<Mo1Attribute, TestRepulsion>
{
[Mo3]
public void M1()
{
// Mo1 is mutually exclusive with Mo2 and Mo3, but since Mo3 has a higher priority than Mo1, when Mo1 does not take effect, all mutually exclusive types will take effect.
// So eventually Mo2Attribute, Mo3Attribute, Mo5Attribute will be applied.
Console.WriteLine("m1");
}
[Mo4]
public void M2()
{
// Mo1 is mutually exclusive with Mo2 and Mo3, but since Mo1 has a higher priority than Mo2, Mo2 will not take effect
// Eventually Mo1Attribute, Mo4Attribute, Mo5Attribute will be applied
Console.WriteLine("m2");
}
}
<font color=red>Through the above example, you may notice that this multi-type mutual exclusion is not mutual exclusion between multiple types, but the mutual exclusion of
the first generic type and the type defined by the second generic type, and the second generic type is mutually exclusive. They are not mutually exclusive. Just like the
above example, when Mo1Attribute
does not take effect, the mutually exclusive Mo2Attribute
and Mo3Attribute
will take effect. It needs to be understood here that the
reason for defining mutual exclusion is the possible repeated application of Attribute and empty interface implementation, not to exclude all weaving repetitions. At the
same time, it is not recommended to use multiple mutual exclusion definitions, which is prone to logical confusion. It is recommended to carefully consider a set of
unified rules before application weaving, rather than random definitions, and then try to use multiple mutual exclusions to solve problems.</font>
Priority
IgnoreMoAttribute
- Method
MoAttribute
- Method
MoProxyAttribute
- Type
MoAttribute
- Type
MoProxyAttribute
- Type
IRougamo<>
,IRougamo<,>
,IRepulsionsRougamo<,>
- Assembly & Module
MoAttribute
Switch
Rougamo is developed by individuals. Due to limited capabilities, the research on IL is not so thorough, and with the development of .NET, some new types, new semantics and even new IL instructions will continue to appear. Therefore, there may exist Some bugs, and the bugs at the IL level may not be able to quickly locate the problem and fix it, so here is a switch to avoid code weaving without removing the Rougamo reference. Therefore, it is recommended that you use Rougamo for code weaving When the woven code does not affect the business, such as logs and APM. If you want to use static weaving components that are stable and can be quickly supported when you encounter problems, it is recommended to use PostSharp
Rougamo is developed on the basis of fody. After referencing Rougamo, the first compilation will generate a FodyWeavers.xml
file. The default content is as follows
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Rougamo />
</Weavers>
When you want to disable Rougamo, you need to add the attribute enabled
to the Rougamo
node of the configuration file and set the value to false
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Rougamo enabled="false" />
</Weavers>
Record yield return IEnumerable/IAsyncEnumerable return value
We know that using yield return
syntax sugar + IEnumerable
return value method, after calling the method, the code of the method is not actually executed, the actual
execution of the code is when you access the elements in the IEnumerable
object When, for example, you go to foreach this object or call ToList/ToArray
, and the
returned elements are not stored in an array/linked list (the specific principle is not explained here), so by default there is no The method directly obtains the
collection of all elements returned by yield return IEnumerable
.
But there may be some scenarios with strict code monitoring that need to record all return values, so in the implementation, I created an array to save all the returned elements,
but since this array is created additionally, it will take up additional memory space, and at the same time It is not clear how big the set of elements returned by this
IEnumerable
is, so in order to avoid excessive memory consumption, the return value of yield return IEnumerable
will not be recorded by default. The Rougamo
node of
FodyWeavers.xmladds attribute configuration
enumerable-returns="true"`.
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Rougamo enumerable-returns="true" />
</Weavers>
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Fody (>= 6.2.5)
- System.Threading.Tasks.Extensions (>= 4.5.4)
NuGet packages (42)
Showing the top 5 NuGet packages that depend on Rougamo.Fody:
Package | Downloads |
---|---|
HandeSoft.Core
Package Description |
|
HandeSoft.Web.Core
Package Description |
|
BotSharp.Abstraction
Package Description |
|
Ray.Infrastructure
This client library is a infrastructure that including extensions and helpers etc. |
|
HZY.Framework.Core
HZY Framework 核心 1、ScheduledAttribute 定时任务特性标记 2、IServerMetricMonitoringService 服务器指标监控 CPU、内存、硬盘、运行时长 3、HZY.Framework.DynamicApiController 动态Api控制器 4、HZY.Framework.DependencyInjection 依赖注入 |
GitHub repositories (5)
Showing the top 5 popular GitHub repositories that depend on Rougamo.Fody:
Repository | Stars |
---|---|
RayWangQvQ/BiliBiliToolPro
B 站(bilibili)自动任务工具,支持docker、青龙、k8s等多种部署方式。敏感肌也能用。
|
|
dotnetcore/FreeSql
🦄 .NET aot orm, C# orm, VB.NET orm, Mysql orm, Postgresql orm, SqlServer orm, Oracle orm, Sqlite orm, Firebird orm, 达梦 orm, 人大金仓 orm, 神通 orm, 翰高 orm, 南大通用 orm, 虚谷 orm, 国产 orm, Clickhouse orm, DuckDB orm, TDengine orm, QuestDB orm, MsAccess orm.
|
|
SciSharp/BotSharp
The AI Agent Framework in .NET
|
|
2881099/FreeSql.AdminLTE
这是一个 .NETCore MVC 中间件,基于 AdminLTE 前端框架动态产生 FreeSql 实体的增删查改界面。
|
|
ThingsGateway/ThingsGateway
ThingsGateway is a cross platform high-performance edge acquisition gateway based on Net8, providing underlying PLC communication libraries, communication debugging software, and more.
|
Version | Downloads | Last updated | |
---|---|---|---|
4.0.4 | 1,935 | 9/29/2024 | |
4.0.4-preview-1727349912 | 91 | 9/26/2024 | |
4.0.3 | 1,217 | 9/16/2024 | |
4.0.3-preview-1726120802 | 95 | 9/12/2024 | |
4.0.3-preview-1725957423 | 103 | 9/10/2024 | |
4.0.2 | 502 | 9/9/2024 | |
4.0.2-preview-1725956948 | 94 | 9/10/2024 | |
4.0.2-preview-1725875652 | 94 | 9/9/2024 | |
4.0.2-preview-1725466232 | 95 | 9/4/2024 | |
4.0.1 | 2,616 | 9/2/2024 | |
4.0.1-preview-1725141430 | 87 | 8/31/2024 | |
4.0.0 | 5,940 | 8/10/2024 | |
4.0.0-priview-1723306347 | 108 | 8/10/2024 | |
4.0.0-priview-1722831925 | 85 | 8/5/2024 | |
3.1.0 | 1,499 | 7/16/2024 | |
3.0.2 | 436 | 7/8/2024 | |
3.0.2-priview-1720363148 | 102 | 7/7/2024 | |
3.0.2-priview-1720251661 | 101 | 7/6/2024 | |
3.0.1 | 185 | 7/4/2024 | |
3.0.1-priview-1720089186 | 106 | 7/4/2024 | |
3.0.1-priview-1720085112 | 89 | 7/4/2024 | |
3.0.0 | 3,330 | 5/4/2024 | |
3.0.0-priview-1714754497 | 77 | 5/3/2024 | |
3.0.0-priview-1714407561 | 131 | 4/29/2024 | |
2.3.1 | 7,316 | 4/23/2024 | |
2.3.1-priview-1713854631 | 113 | 4/23/2024 | |
2.3.1-priview-1713791514 | 102 | 4/22/2024 | |
2.3.0 | 3,765 | 3/10/2024 | |
2.3.0-priview-1709894403 | 112 | 3/8/2024 | |
2.2.0 | 2,925 | 1/20/2024 | |
2.2.0-priview-1705656978 | 109 | 1/19/2024 | |
2.2.0-priview-1705571301 | 103 | 1/18/2024 | |
2.2.0-priview-1705566213 | 108 | 1/18/2024 | |
2.2.0-priview-1702899195 | 182 | 12/18/2023 | |
2.1.1 | 4,604 | 12/14/2023 | |
2.1.1-priview-1702545048 | 142 | 12/14/2023 | |
2.1.1-priview-1702542781 | 140 | 12/14/2023 | |
2.0.1 | 1,245 | 11/16/2023 | |
2.0.0 | 2,979 | 10/8/2023 | |
2.0.0-priview-1696783135 | 135 | 10/8/2023 | |
2.0.0-priview-1696592398 | 130 | 10/6/2023 | |
2.0.0-priview-1695658688 | 156 | 9/25/2023 | |
2.0.0-priview-1695465141 | 148 | 9/23/2023 | |
2.0.0-priview-1680984436 | 221 | 4/8/2023 | |
2.0.0-priview-1680981587 | 181 | 4/8/2023 | |
1.4.1 | 11,343 | 3/12/2023 | |
1.4.1-priview-1678603084 | 188 | 3/12/2023 | |
1.4.1-priview-1678557697 | 189 | 3/11/2023 | |
1.4.1-priview-1678557463 | 187 | 3/11/2023 | |
1.4.0 | 2,641 | 3/1/2023 | |
1.4.0-beta | 358 | 2/27/2023 | |
1.4.0-alpha | 233 | 2/25/2023 | |
1.3.4 | 56,057 | 2/17/2023 | |
1.3.3 | 976 | 1/17/2023 | |
1.3.2 | 18,224 | 12/20/2022 | |
1.3.1 | 341 | 12/20/2022 | |
1.3.1-beta | 178 | 12/14/2022 | |
1.3.0 | 1,280 | 12/8/2022 | |
1.2.3 | 347 | 1/17/2023 | |
1.2.2 | 320 | 12/20/2022 | |
1.2.2-beta | 168 | 12/14/2022 | |
1.2.1 | 695 | 11/29/2022 | |
1.2.1-beta | 163 | 11/29/2022 | |
1.2.0 | 2,061 | 9/14/2022 | |
1.2.0-beta | 183 | 9/12/2022 | |
1.2.0-alpha2 | 173 | 9/12/2022 | |
1.2.0-alpha1 | 180 | 8/31/2022 | |
1.2.0-alpha | 173 | 8/30/2022 | |
1.1.4 | 373 | 11/29/2022 | |
1.1.4-alpha | 189 | 12/25/2022 | |
1.1.3 | 532 | 9/11/2022 | |
1.1.2 | 1,647 | 8/22/2022 | |
1.1.2-beta | 180 | 8/22/2022 | |
1.1.1 | 2,781 | 8/8/2022 | |
1.1.1-beta | 190 | 8/1/2022 | |
1.1.0 | 630 | 7/28/2022 | |
1.1.0-beta | 205 | 7/15/2022 | |
1.1.0-alpha4 | 188 | 6/24/2022 | |
1.1.0-alpha3 | 174 | 6/24/2022 | |
1.1.0-alpha2 | 173 | 6/23/2022 | |
1.1.0-alpha1 | 176 | 6/22/2022 | |
1.1.0-alpha | 190 | 5/22/2022 | |
1.0.3 | 710 | 5/6/2022 | |
1.0.3-beta | 193 | 4/26/2022 | |
1.0.2 | 694 | 12/23/2021 | |
1.0.1 | 7,733 | 11/23/2021 | |
1.0.1-beta | 4,919 | 11/23/2021 |
Fix #30 again.