rm.Extensions 3.1.1

This package has a SemVer 2.0.0 package version: 3.1.1+a4f8f1731fb71b68ef0f2bda9a5386c1b21ade39.
dotnet add package rm.Extensions --version 3.1.1
                    
NuGet\Install-Package rm.Extensions -Version 3.1.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="rm.Extensions" Version="3.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="rm.Extensions" Version="3.1.1" />
                    
Directory.Packages.props
<PackageReference Include="rm.Extensions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add rm.Extensions --version 3.1.1
                    
#r "nuget: rm.Extensions, 3.1.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package rm.Extensions@3.1.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=rm.Extensions&version=3.1.1
                    
Install as a Cake Addin
#tool nuget:?package=rm.Extensions&version=3.1.1
                    
Install as a Cake Tool

csharp-extensions

A collection of utility C# extension methods.

main: Build Status dev: Build Status

nuget:
Install-Package rm.Extensions

NuGet version (rm.Extensions)

string extensions:
var s = "";
if (s.IsNullOrEmpty()) { /**/ }
if (s.IsNullOrWhiteSpace()) { /**/ }
// some string that could be null/empty/whitespace
string s = null; // or "value"
string text = "default";
if (!s.IsNullOrWhiteSpace()) text = s.Trim();
// fluent code by avoiding comparison
string text = s.OrEmpty().Trim(); // "" when s is null/empty/whitespace
string text = s.Or("default").Trim(); // "default" when s is null/empty/whitespace
// or using null-conditional and null-coalesce operators
string text = s.NullIfEmpty()?.Trim() ?? ""
string text = s.NullIfWhiteSpace()?.Trim() ?? "default"
// html-en/decode, url-en/decode
string s = "";
string htmlencoded = s.HtmlEncode();
string htmldecoded = s.HtmlDecode();
string urlencoded = s.UrlEncode();
string urldecoded = s.UrlDecode();
// "".Format() instead of string.Format()
"{0} is a {1}".Format("this", "test");
// parameter index is optional
"{} is a {}".Format("this", "test");
"{} is a {1}".Format("this", "test"); // mixing is ok
// parameter meta is allowed
"The name is {0}. {first} {last}.".Format(lastName, firstName, lastName); // adding arg meta is ok
"The name is {last}. {first} {last}.".Format(lastName, firstName); // bit intelligent about repeating arg meta
// bool try-parse string with default value
bool b = "".ToBool(defaultValue: true);
// b: true
// munge a password, up to two chars
string[] munged = "pass".Munge().ToArray();
// munged: { "pa$$", "pa55", "p@ss", "p@$$", "p@55" }
string[] munged = "ai".Munge().ToArray();
// munged: { "a1", "a!", "@i", "@1", "@!" }
string[] munged = "pw".Munge().ToArray();
// munged: { "puu", "p2u" }
// unmunge a password
string[] unmunged = "h@x0r".Unmunge().ToArray();
// unmunged: { "haxor" }
string[] unmunged = "puu".Unmunge().ToArray();
string[] unmunged = "p2u".Unmunge().ToArray();
// unmunged: { "pw" }
// scrabble characters of word (like the game)
var word = "on";
var scrabbled = word.Scrabble();
// scrabbled: { "o", "on", "n", "no" }
// parse a string in UTC format as DateTime
DateTime date = "2013-04-01T03:42:14-04:00".ParseAsUtc();
// date: 4/1/2013 7:42:14 AM, Kind: Utc
// convert a string to title case
string result = "war and peace".ToTitleCase();
// result: "War And Peace"
// split a csv string
string[] result = "a,b;c|d".SplitCsv().ToArray();
// result: [ "a", "b", "c", "d" ]
// substring from start
string result = "this is a test".SubstringFromStart(4);
// result: "this"
// substring till end
string result = "this is a test".SubstringTillEnd(4);
// result: "test"
// substring by specifying start index and end index
string result = "this".SubstringByIndex(1, 3);
// result: "hi"
ThrowIf extensions:
public void SomeMethod(object obj1, object obj2) 
{
	// throws ArgumentNullException if object is null
	obj1.ThrowIfArgumentNull("obj1");
	obj2.ThrowIfArgumentNull("obj2");
	// OR 
	new[] { obj1, obj2 }.ThrowIfAnyArgumentNull();
	
	// ...
	
	object obj = DoSomething();
	// throws NullReferenceException if object is null
	obj.ThrowIfNull("obj");
	// OR 
	new[] { obj1, obj2 }.ThrowIfAnyNull();
}
public void SomeMethod(string s1, string s2) 
{
	// throws ArgumentNullException or EmptyException if string is null or empty
	s1.ThrowIfNullOrEmptyArgument("s1"); // or s1.ThrowIfNullOrWhiteSpaceArgument("s1")
	s2.ThrowIfNullOrEmptyArgument("s2");
	// OR 
	new[] { s1, s2 }.ThrowIfNullOrEmptyArgument();
	
	// ...
	
	string s = DoSomething();
	// throws NullReferenceException or EmptyException if string is null or empty.
	s.ThrowIfNullOrEmpty("s"); // or s1.ThrowIfNullOrWhiteSpace("s")
}
DateTime extensions:
// gives date in UTC format string
string dateUtc = date.ToUtcFormatString();
// dateUtc: "1994-11-05T13:15:30.000Z"
// gives min date that can be inserted in sql database without exception (SqlDateTime.MinValue)
DateTime date = new DateTime().ToSqlDateTimeMinUtc();
// date: 1/1/1753 12:00:00 AM
// date read from db or parsed from string has its Kind as Unspecified.
// specifying its kind as UTC is needed if date is expected to be UTC.
// ToUniversalTime() assumes that the kind is local while converting it and is undesirable.
DateTime date = DateTime.Parse("4/1/2014 12:00:00 AM").AsUtcKind();
// date: 4/1/2014 12:00:00 AM, Kind: Utc
IEnumerable extensions:
// creates chunks of given collection of specified size
IEnumerable<IEnumerable<int>> chunks = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }.Chunk(3);
// chunks: { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 }, { 10 } }
// if a collection is null or empty
var collection = new[] { 1, 2 };
if (collection.IsNullOrEmpty()) { /**/ }
// split a collection into n parts
var collection = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
IEnumerable<IEnumerable<int>> splits = collection.Split(3);
// splits: { { 1, 4, 7, 10 }, { 2, 5, 8 }, { 3, 6, 9 } }
// if a collection is sorted
var collection1 = new[] { 1, 2, 3, 4 };
bool isSorted1 = collection1.IsSorted();
var collection2 = new[] { 7, 5, 3 };
bool isSorted2 = collection2.IsSorted();
// isSorted1, isSorted2: true
// Double(), DoubleOrDefault() as Single(), SingleOrDefault()
IEnumerable<int> doubleitems = new[] { 1, 2 }.Double();
// doubleitems: { 1, 2 }
IEnumerable<int> doubleitems = new[] { 1, 2, 3 }.Double(x => x > 1);
// doubleitems: { 2, 3 }
IEnumerable<int> doubleordefaultitems = new int[0].DoubleOrDefault();
// doubleordefaultitems: null
IEnumerable<int> doubleordefaultitems = new[] { 1, 2, 3 }.DoubleOrDefault(x => x > 1);
// doubleordefaultitems: { 2, 3 }

// throws InvalidOperationException
new[] { 1 }.Double();
new[] { 1 }.Double(x => x > 0);
new[] { 1 }.DoubleOrDefault();
new[] { 1 }.DoubleOrDefault(x => x > 0);
// shuffle collection in O(n) time (Fisher-Yates shuffle, revised by Knuth)
var shuffled = new[] { 0, 1, 2, 3 }.Shuffle();
// shuffled: { 2, 3, 1, 0 }
// slice a collection as Python (http://docs.python.org/2/tutorial/introduction.html#strings)
var a = new[] { 0, 1, 2, 3, 4 }
var slice = a.Slice(step: 2);
// slice: { 0, 2, 4 }

a.Slice(start, end);		// items start through end-1
a.Slice(start);				// items start through the rest of the array
a.Slice(0, end);			// items from the beginning through end-1
a.Slice();					// a copy of the whole array
a.Slice(start, end, step);	// start through not past end, by step
a.Slice(-1);				// last item in the array
a.Slice(-2);				// last two items in the array
a.Slice(-3, -2);			// third last item in the array
a.Slice(0, -2);				// everything except the last two items
a.Slice(step: -1);			// copy with array reversed

// help
a.Slice(end: 2)			// 1st 2
a.Slice(2)				// except 1st 2
a.Slice(-2)				// last 2
a.Slice(0, -2)			// except last 2
a.Slice(1, 1 + 1)		// 2nd char
a.Slice(-2, -2 + 1)		// 2nd last char
// scrabble a list of words (like the game)
var words = { "this", "test" };
var scrabbled = words.Scrabble();
// scrabbled: { "this", "thistest", "test", "testthis" }
// check an enumerable's count efficiently
if (enumerable.Count() == 2) { ... } // inefficient for large enumerable
if (enumerable.HasCount(2)) { ... }
if (enumerable.HasCountOfAtLeast(2)) { ... } // count >= 2
// get permutations or combinations for particular r
var result = new[] { 1, 2 }.Permutation(2);
// result: { { 1, 2 }, { 2, 1 } }
var result = new[] { 1, 2 }.Combination(2);
// result: { { 1, 2 } }
// if a collection is empty instead of !collection.Any()
var collection = new[] { 1, 2 };
if (collection.IsEmpty()) { /**/ }
// get top n or bottom n efficiently (using min/max-heap)
IEnumerable<int> top_n = { 2, 3, 1, 4, 5 }.Top(3);
IEnumerable<int> bottom_n = { 2, 3, 1, 4, 5 }.Bottom(3);
// top_n: { 3, 5, 4 }
// bottom_n: { 3, 1, 2 }
// get top n or bottom n from IEnumerable
IEnumerable<Person> top_n = persons.Top(3);
IEnumerable<Person> bottom_n = persons.Bottom(3);
// get top n or bottom n by using a key selector or/and comparer
IEnumerable<Person> oldest_3 = persons.Top(3, x => x.Age);
IEnumerable<Person> youngest_3 = persons.Bottom(3, x => x.Age);
IEnumerable<Person> oldest_3 = persons.Top(3, personByAgeComparer);
IEnumerable<Person> youngest_3 = persons.Bottom(3, personByAgeComparer);
IEnumerable<Person> oldest_3 = persons.Top(3, x => x.Age, ageComparer);
IEnumerable<Person> youngest_3 = persons.Bottom(3, x => x.Age, ageComparer);
// source.Except(second, comparer) linqified instead of a full-blown class for comparer
source.ExceptBy(second, x => x.Member);
// same for source.Distinct(comparer)
source.DistinctBy(x => x.Member);
// same for source.OrderBy(keySelector, comparer)
source.OrderBy(x => x.Property,
	(p1, p2) => p1.CompareTo(p2) // where p1, p2 are of same type as x.Property
);
// source.OrEmpty() or source.EmptyIfDefault() to avoid a null check
foreach (var item in source.OrEmpty()) { /**/ }
foreach (var item in source.EmptyIfDefault()) { /**/ }
// instead of
if (source != null) { foreach (var item in source) { /**/ } }
// TrySingle() to get single without exception
if (source.TrySingle(out singleT)) { ... }
// OneOrDefault() to get the only one element or default
//	input		firstOrDefault	singleOrDefault		oneOrDefault
//	{ 1, 2 }	1				throws				default
//	{ 1 }		1				1					1
//	{ }			default			default				default
var oneT = source.OneOrDefault();
// calls IEnumerable.Contains()
bool result = value.In(source);
bool result = value.In(source, comparer);
IList extensions:
// RemoveLast() to remove last item(s) in list
list.RemoveLast();
list.RemoveLast(2);
Enum extensions:
enum Color { Red = 1, Green, Blue };
Color color = "Red".Parse<Color>();
// OR
Color color;
"Red".TryParse<Color>(out color);
// color: Color.Red
enum Color
{
	[Description("Red color")] Red = 1, 
	Green, 
	[Description("Blue color")] Blue
}
string redDesc = Color.Red.GetDescription();
string greenDesc = Color.Green.GetDescription();
// redDesc: "Red color"
// greenDesc: "Green"
enum Color { Red = 1, Green, Blue };
Color color = "Red".GetEnumValue<Color>();
// color: Color.Red

// enumValue.GetEnumName() is fastest of all
// fastest, dictionary lookup after 1st call
if (Color.Red.GetEnumName() == "Red") { /**/ }
// slightly slow, dictionary lookup after 1st call
if ("Red".GetEnumValue<Color>() == Color.Red) { /**/ }
// slow, due to reflection
if ("Red".Parse<Color>() == Color.Red) { /**/ }
// slowest, due to reflection
if (Color.Red.ToString() == "Red") { /**/ }
enum Color
{
	[Description("Red color")] Red = 1, 
	Green, 
	[Description("Blue color")] Blue
}
IDictionary<string, string> colorsMap = EnumExtension.GetEnumNameToDescriptionMap<Color>();
// build a select list
IEnumerable<ListItem> selectOptions = colorsMap
	.Select(x => new ListItem() { text: x.Value, value: x.Key });
//  <select>
//    <option value="Red">Red color</option>
//    <option value="Green">Green</option>
//    <option value="Blue">Blue color</option>
//  </select>
enum Color
{
	[Description("Red color")] Red = 1, 
	Green, 
	[Description("Blue color")] Blue
}
string redName = "Red color".GetEnumNameFromDescription<Color>()
// redName: "Red"
bool hasValue = 0.IsDefined<Color>();
// hasValue: false
bool hasValue = 1.IsDefined<Color>();
// hasValue: true
enum Color
{
	[Description("Red color")] Red = 1, 
	Green, 
	[Description("Blue color")] Blue
}
// compile error: cannot have int as enum values or hyphen sign in enum values
enum Grade { Toddler, Pre-K, Kindergarten, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, College }
// work-around: use Description attribute
enum Grade 
{
	Toddler = 1, 
	[Description("Pre-K")] PreK, 
	Kindergarten, 
	[Description("1")] One, 
	[Description("2")] Two, 
	[Description("3")] Three, 
	[Description("4")] Four, 
	[Description("5")] Five, 
	[Description("6")] Six, 
	[Description("7")] Seven, 
	[Description("8")] Eight, 
	[Description("9")] Nine, 
	[Description("10")] Ten, 
	[Description("11")] Eleven, 
	[Description("12")] Twelve, 
	College
}

// to sort gradesUnsorted, use GetEnumValueFromDescription<T>() and GetDescription<T>() methods
string[] gradesUnsorted = new[] { "Pre-K", "1", "College", "2", "Toddler" };
Grade[] grades = gradesUnsorted
	.Select(x => x.GetEnumValueFromDescription<Grade>()).ToArray();
Array.Sort(grades);
string[] gradesSorted = grades.Select(x => x.GetDescription());
// gradesSorted: { "Toddler", "Pre-K", "1", "2", "College" } 
NameValueCollection extensions:
// get query string for name-value collection
NameValueCollection nvc = new NameValueCollection { {"k1,", "v1"}, {"k2", "v2"} };
string query = nvc.ToQueryString(); // OR nvc.ToQueryString(prefixQuestionMark: false);
// query: "?k1%2C=v1&k2=v2" // OR "k1%2C=v1&k2=v2"
TimeSpan extensions:
// round timespan as ms, s, m, h, d, wk, mth, y.
string round = TimeSpan.FromDays(10).Round();
// round: "1wk"
// n Days, Hours, Minutes, Seconds, Milliseconds, etc.
TimeSpan ts = 10.Days();
Uri extensions:
// calculate uri's checksum (sha1, md5)
var uri = new Uri(@"https://www.google.com/images/srpr/logo11w.png") // url
	// OR new Uri(@"D:\temp\images\logo.png"); // local file
	// OR new Uri(new Uri(@"D:\temp\"), @".\images\logo.png"); // dir and relative path
string sha1 = uri.Checksum(Hasher.sha1);
string md5 = uri.Checksum(Hasher.md5);
// sha1: 349841408d1aa1f5a8892686fbdf54777afc0b2c
// md5: 57e396baedfe1a034590339082b9abce
Helper methods:
// swap two values or references
Helper.Swap(ref a, ref b);
decimal extensions:
// truncate decimal to specified digits
12.349m.TruncateTo(2); // 12.34m
int extensions:
// round int as k, m, g
1000.Round(); // "1k"
1000000.Round(); // "1m"
1500.Round(); // "1k"
1500.Round(1); // "1.5k"
Graph extensions:
// if graph is cyclic (used for deadlock detection)
bool isCyclic = graph.IsCyclic();
StringBuilder extensions:
// instead of buffer.AppendLine(string.Format(format, args))
buffer.AppendLine(format, args);
// reverse StringBuilder in-place
buffer.Reverse();
Dictionary extensions:
// for key in dictionary, get value if exists or default / specified value
var value = dictionary.GetValueOrDefault(key);
var value = dictionary.GetValueOrDefault(key, other);
// get dictionary as readonly
var dictionaryReadonly = dictionary.AsReadOnly();
Wrapped extensions:
// wrap (box) any type to avoid using pass by ref parameters
var intw = new Wrapped<int>(1); // or 1.Wrap();
// intw.Value = 1
BitSet:
BitSet bitset = new BitSet(10); // 0 to 10 inclusive
bitset.Add(5); // add 5
bitset.Add(6);
bitset.Remove(5); // remove 5
bitset.Remove(3);
bitset.Toggle(3); // toggle 3
bool has2 = bitset.Has(2); // if has 2
bitset.Clear(); // remove all
foreach(int item in bitset) { /**/ }
Circular Queue:
CircularQueue<int> cq = new CircularQueue<int>(capacity: 2);
cq.Enqueue(1);
cq.Enqueue(2);
cq.Enqueue(3);
cq.Enqueue(4);
int head;
head = cq.Dequeue(); // returns 3
head = cq.Dequeue(); // returns 4
Circular Stack:
CircularStack<int> cq = new CircularStack<int>(capacity: 2);
cq.Push(1);
cq.Push(2);
cq.Push(3);
cq.Push(4);
int top;
top = cq.Pop(); // returns 4
top = cq.Pop(); // returns 3
Deque:
Deque<int> dq = new Deque<int>();
Node<int> node = dq.Enqueue(1);
dq.Enqueue(2);
dq.Delete(node); // delete in O(1) time
int i = dq.Dequeue(); // returns 2
LRU cache:
LruCache<int, int> cache = new LruCache<int, int>(5);
cache.Insert(key: 2, value: 2);
cache.Get(2); // returns value 2
var count = cache.Count(); // return 1
cache.IsEmpty(); // returns false
cache.Remove(2); // removes 2 from cache
cache.IsFull(); // returns false
cache.Capacity(); // returns 5
cache.Clear(); // clears cache
Guid:
// guid.ToByteArray() is sensitive to endianness, but
// guid.ToByteArrayMatchingStringRepresentation() is not and matches guid.ToString()
// see https://stackoverflow.com/questions/9195551/why-does-guid-tobytearray-order-the-bytes-the-way-it-does
var bytes = Guid.Parse(someGuid).ToByteArrayMatchingStringRepresentation();
// similar roundtrip method
var guid = bytes.ToGuidMatchingStringRepresentation(); // same as someGuid
// other than base16 (hex) formats
var guidBase64 = guid.ToBase64String();
var guidBase64Url = guid.ToBase64UrlString();
var guidBase32 = guid.ToBase32String();
Base64, Base64Url:
var base64 = s.ToUtf8Bytes().Base64Encode();
var s = base64.Base64Decode().ToUtf8String();

var base64Url = s.ToUtf8Bytes().Base64UrlEncode();
var s = base64Url.Base64UrlDecode().ToUtf8String();
Random:
var gaussian = rng.NextGaussian(mu: mu, sigma: sigma);
var s = rng.NextString(length: 10, charset: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_");
var doubleN = rng.NextDouble(minValue: 5.0d, maxValue: 10.0d);
var decimalN = rng.NextDecimal(minValue: 5.0m, maxValue: 10.0m);
Base16 (Hex):

note: net5.0+ has Convert.ToHexString(bytes), Convert.FromHexString(hex) which are faster than below.

var base16 = s.ToUtf8Bytes().Base16Encode();
var s = base16.Base16Decode().ToUtf8String();
// or
var hex = s.ToUtf8Bytes().ToHexString();
var s = hex.FromHexString().ToUtf8String();
char extensions:
// all/most System.Char static utility functions
var c = 'a'.ToUpper(); // returns 'A'
var isLower = c.IsLower(); // returns false
var isLetter = c.IsLetter(); // returns true
Base32:
// Douglas Crockford's Base32 impl
var base32 = s.ToUtf8Bytes().Base32Encode();
var s = base32.Base32Decode().ToUtf8String();
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  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.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.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 is compatible. 
.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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.
  • .NETStandard 2.1

    • No dependencies.
  • net6.0

    • No dependencies.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on rm.Extensions:

Package Downloads
rm.DelegatingHandlers

Provides DelegatingHandlers.

rm.Masking

Provides masking methods.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.1.1 3,329 11/19/2023
3.1.0 211 10/15/2023
3.1.0-alpha3 64 10/14/2023
3.1.0-alpha2 84 9/24/2023
3.1.0-alpha1 102 3/12/2023
3.1.0-alpha0 92 2/12/2023
3.0.2 176 9/24/2023
3.0.1 4,153 2/12/2023
3.0.0-alpha0 113 2/2/2023
2.10.1-alpha 102 1/29/2023
2.10.0-alpha1 103 1/3/2023
2.9.2-alpha 113 1/29/2023
2.9.1-alpha1 115 8/14/2022
2.9.0-alpha0 143 1/2/2022
2.8.2 14,938 1/29/2023
2.8.2-alpha 107 1/29/2023
2.8.1 13,660 2/14/2022
2.8.1-alpha0 121 2/14/2022
2.8.0 1,496 1/2/2022
2.8.0-alpha0 145 1/2/2022
2.7.1 9,028 11/28/2021
2.7.1-alpha0 1,288 11/28/2021
2.7.0 184 11/14/2021
2.7.0-alpha0 163 11/14/2021
2.6.0 196 11/14/2021
2.6.0-alpha0 276 11/14/2021
2.5.4 2,012 11/2/2021
2.5.4-alpha0 160 11/2/2021
2.5.3-alpha0 183 11/2/2021
2.5.2 219 11/1/2021
2.5.2-alpha0 217 11/1/2021
2.5.1 244 10/31/2021
2.5.0 232 10/31/2021
2.5.0-alpha1 223 10/31/2021
2.5.0-alpha0 141 10/22/2021
2.4.0 234 10/30/2021
2.4.0-alpha2 230 10/30/2021
2.4.0-alpha1 220 10/2/2021
2.4.0-alpha0 147 9/23/2021
2.3.0 288 10/22/2021
2.3.0-alpha1 165 10/22/2021
2.3.0-alpha0 162 9/23/2021
2.2.0 224 9/12/2021
2.2.0-alpha1 204 9/9/2021
2.2.0-alpha0 194 9/9/2021
2.1.0 198 9/6/2021
2.1.0-alpha3 146 9/6/2021
2.1.0-alpha2 151 9/6/2021
2.1.0-alpha1 212 9/5/2021
2.1.0-alpha0 150 9/5/2021
2.0.0 179 9/6/2021
2.0.0-alpha2 154 8/24/2021
2.0.0-alpha1 155 8/23/2021
2.0.0-alpha 361 11/28/2020
1.5.1-alpha 259 11/3/2020
1.5.0 540 9/5/2021
1.5.0-alpha 553 9/13/2020
1.4.0 1,150 8/24/2020
1.4.0-alpha 561 8/23/2020
1.3.6-alpha 622 5/20/2019
1.3.5-alpha 682 1/8/2019
1.3.4-alpha 711 12/23/2018
1.3.1-alpha 689 12/22/2018
1.3.0-alpha 778 12/3/2018
1.2.2 966 12/3/2018
1.2.2-alpha 947 7/29/2018
1.2.1-alpha 1,021 3/6/2018
1.1.2-alpha 1,241 3/4/2018
1.1.1-alpha 1,008 2/17/2018
1.1.0-alpha 1,203 2/17/2018
1.0.0-alpha 1,676 2/11/2018

tag: v3.1.1