(feature): init

This commit is contained in:
Maksym Sadovnychyy 2024-09-03 18:38:52 +02:00
commit 8ac380c7d4
17 changed files with 1644 additions and 0 deletions

63
.gitattributes vendored Normal file
View File

@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

262
.gitignore vendored Normal file
View File

@ -0,0 +1,262 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
project.fragment.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
#*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
.directory
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc

197
README.md Normal file
View File

@ -0,0 +1,197 @@
# MaksIT.Core
MaksIT.Core is a collection of helper methods and extensions for .NET projects, designed to simplify common tasks and improve code readability. The library includes extensions for `Guid`, `string`, `Object`, and a base class for creating enumeration types.
## Table of Contents
- [Installation](#installation)
- [Usage](#usage)
- [Enumeration](#enumeration)
- [Guid Extensions](#guid-extensions)
- [Object Extensions](#object-extensions)
- [String Extensions](#string-extensions)
- [Available Methods](#available-methods)
- [Enumeration Methods](#enumeration-methods)
- [Guid Methods](#guid-methods)
- [Object Methods](#object-methods)
- [String Methods](#string-methods)
- [Contributing](#contributing)
- [License](#license)
## Installation
To install MaksIT.Core, add the package to your project via NuGet:
```sh
dotnet add package MaksIT.Core
```
Or manually add it to your `.csproj` file:
```xml
<PackageReference Include="MaksIT.Core" Version="1.0.0" />
```
## Usage
### Enumeration
The `Enumeration` base class provides a way to create strongly-typed enums in C#. This is useful for scenarios where you need more functionality than the default `enum` type.
**Example:**
```csharp
public class Status : Enumeration
{
public static readonly Status Active = new Status(1, "Active");
public static readonly Status Inactive = new Status(2, "Inactive");
private Status(int id, string name) : base(id, name) { }
}
// Usage
var activeStatus = Status.FromValue<Status>(1);
Console.WriteLine(activeStatus.Name); // Output: Active
```
### Guid Extensions
The `GuidExtensions` class contains extensions for working with `Guid` types.
**Example:**
```csharp
Guid guid = Guid.NewGuid();
Guid? nullableGuid = guid.ToNullable();
```
### Object Extensions
The `ObjectExtensions` class provides extensions for working with objects.
**Example:**
```csharp
var person = new { Name = "John", Age = 30 };
string json = person.ToJson();
Console.WriteLine(json); // Output: {"name":"John","age":30}
```
### String Extensions
The `StringExtensions` class provides a variety of useful string manipulation methods.
**Example:**
```csharp
string text = "Hello World";
bool isLike = text.Like("Hello*"); // SQL-like matching
Console.WriteLine(isLike); // Output: True
```
## Available Methods
### Enumeration Methods
- **`GetAll<T>()`**: Retrieves all static fields of a given type `T` that derive from `Enumeration`.
- **`Equals(object? obj)`**: Determines whether the specified object is equal to the current object.
- **`GetHashCode()`**: Returns the hash code for the current object.
- **`AbsoluteDifference(Enumeration firstValue, Enumeration secondValue)`**: Computes the absolute difference between two enumeration values.
- **`FromValue<T>(int value)`**: Retrieves an instance of type `T` from its integer value.
- **`FromDisplayName<T>(string displayName)`**: Retrieves an instance of type `T` from its display name.
- **`CompareTo(object? other)`**: Compares the current instance with another object of the same type.
### Guid Methods
- **`ToNullable(this Guid id)`**: Converts a `Guid` to a nullable `Guid?`. Returns `null` if the `Guid` is `Guid.Empty`.
### Object Methods
- **`ToJson<T>(this T? obj)`**: Converts an object to a JSON string using default serialization options.
- **`ToJson<T>(this T? obj, List<JsonConverter>? converters)`**: Converts an object to a JSON string using custom converters.
### String Methods
- **`Like(this string? text, string? wildcardedText)`**: Determines if a string matches a given wildcard pattern (SQL LIKE).
- **`Left(this string s, int count)`**: Returns the left substring of the specified length.
- **`Right(this string s, int count)`**: Returns the right substring of the specified length.
- **`Mid(this string s, int index, int count)`**: Returns a substring starting from the specified index with the specified length.
- **`ToInteger(this string s)`**: Converts a string to an integer, returning zero if conversion fails.
- **`IsInteger(this string s)`**: Determines whether the string represents an integer.
- **`Prepend(this StringBuilder sb, string content)`**: Prepends content to the beginning of a `StringBuilder`.
- **`ToEnum<T>(this string input)`**: Converts a string to an enum value of type `T`.
- **`ToNullableEnum<T>(this string input)`**: Converts a string to a nullable enum value of type `T`.
- **`ToNull(this string s)`**: Returns `null` if the string is empty or whitespace.
- **`NullIfEmptyString(this string s)`**: Returns `null` if the string is empty or whitespace, otherwise returns the original string.
- **`ToLong(this string s)`**: Converts a string to a long, returning a hash code if conversion fails.
- **`ToNullableLong(this string s)`**: Converts a string to a nullable long, returning `null` if conversion fails.
- **`ToInt(this string s)`**: Converts a string to an int, returning a hash code if conversion fails.
- **`ToNullableInt(this string s)`**: Converts a string to a nullable int, returning `null` if conversion fails.
- **`ToUint(this string s)`**: Converts a string to a uint, returning a hash code if conversion fails.
- **`ToNullableUint(this string s)`**: Converts a string to a nullable uint, returning `null` if conversion fails.
- **`ToDecimal(this string s)`**: Converts a string to a decimal, returning a hash code if conversion fails.
- **`ToNullableDecimal(this string s)`**: Converts a string to a nullable decimal, returning `null` if conversion fails.
- **`ToDouble(this string s)`**: Converts a string to a double, returning a hash code if conversion fails.
- **`ToNullableDouble(this string s)`**: Converts a string to a nullable double, returning `null` if conversion fails.
- **`ToDate(this string s, string[] formats)`**: Converts a string to a `DateTime` object using a specified format.
- **`ToDate(this string s)`**: Converts a string to a `DateTime` object using the default format.
- **`ToNullableDate(this string s)`**: Converts a string to a nullable `DateTime` object using the default format.
- **`ToNullableDate(this string s, string[] formats)`**: Converts a string to a nullable `DateTime` object using specified formats.
- **`ToDateTime(this string s, string[] formats)`**: Converts a string to a `DateTime` object using specified formats.
- **`ToDateTime(this string s)`**: Converts a string to a `DateTime` object using the default formats.
- **`ToNullableDateTime(this string s)`**: Converts a string to a nullable `DateTime` object using the default formats.
- **`ToNullableDateTime(this string s, string[] formats)`**: Converts a string to a nullable `DateTime` object using specified formats.
- **`ToBool(this string s)`**: Converts a string to a boolean.
- **`ToNullableBool(this string s)`**: Converts a string to a nullable boolean.
- **`ToGuid(this string text)`**: Converts a string to a `Guid`.
- **`ToNullableGuid(this string s)`**: Converts a string to a nullable `Guid`.
- **`StringSplit(this string s, char c)`**: Splits a string by a specified character and trims each resulting element.
- **`ToTitle(this string s)`**: Converts the first character of the string to uppercase.
- **`ExtractUrls(this string s)`**: Extracts all URLs from a string.
- **`Format(this string s, params object[] args)`**: Formats a string using specified arguments.
- **`Excerpt(this string s, int length = 60)`**: Truncates a string to a specified length, adding ellipses if necessary.
- **`ToObject<T>(this string s)`**: Deserializes a JSON string into an object of type `T`.
- **`ToObject<T>(this string s, List<JsonConverter> converters)`**: Deserializes a JSON string into an object of type `T` using custom converters.
- **`IsValidEmail(this string? s)`**: Validates whether the string is a valid email format.
- **`HtmlToPlainText(this string htmlCode)`**: Converts HTML content to plain text.
- **`ToCamelCase(this string input)`**: Converts a string to camel case.
## Contribution
Contributions to this project are welcome! Please fork the repository and submit a pull request with your changes. If you encounter any issues or have feature requests, feel free to open an issue on GitHub.
## License
This project is licensed under the MIT License. See the full license text below.
---
### MIT License
```
MIT License
Copyright (c) 2024 Maksym Sadovnychyy (MAKS-IT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
## Contact
For any questions or inquiries, please reach out via GitHub or [email](mailto:maksym.sadovnychyy@gmail.com).

View File

@ -0,0 +1,165 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace MaksIT.Core.Abstractions.Tests {
public class TestEnumeration : Enumeration {
public static readonly TestEnumeration First = new TestEnumeration(1, "First");
public static readonly TestEnumeration Second = new TestEnumeration(2, "Second");
public static readonly TestEnumeration Third = new TestEnumeration(3, "Third");
public TestEnumeration(int id, string name) : base(id, name) { }
}
public class EnumerationTests {
[Fact]
public void GetAll_ShouldReturnAllEnumerations() {
// Act
var allValues = Enumeration.GetAll<TestEnumeration>().ToList();
// Assert
Assert.NotNull(allValues);
Assert.Equal(3, allValues.Count);
Assert.Contains(TestEnumeration.First, allValues);
Assert.Contains(TestEnumeration.Second, allValues);
Assert.Contains(TestEnumeration.Third, allValues);
}
[Theory]
[InlineData(1, "First")]
[InlineData(2, "Second")]
[InlineData(3, "Third")]
public void FromValue_ShouldReturnEnumerationByValue(int id, string expectedName) {
// Act
var result = Enumeration.FromValue<TestEnumeration>(id);
// Assert
Assert.NotNull(result);
Assert.Equal(expectedName, result.Name);
}
[Theory]
[InlineData("First", 1)]
[InlineData("Second", 2)]
[InlineData("Third", 3)]
public void FromDisplayName_ShouldReturnEnumerationByName(string displayName, int expectedId) {
// Act
var result = Enumeration.FromDisplayName<TestEnumeration>(displayName);
// Assert
Assert.NotNull(result);
Assert.Equal(expectedId, result.Id);
}
[Fact]
public void AbsoluteDifference_ShouldReturnCorrectDifference() {
// Act
var difference = Enumeration.AbsoluteDifference(TestEnumeration.First, TestEnumeration.Third);
// Assert
Assert.Equal(2, difference);
}
[Fact]
public void Equals_SameReference_ShouldReturnTrue() {
// Act
var result = TestEnumeration.First.Equals(TestEnumeration.First);
// Assert
Assert.True(result);
}
[Fact]
public void Equals_DifferentReferencesSameValues_ShouldReturnTrue() {
// Arrange
var firstCopy = Enumeration.FromValue<TestEnumeration>(1);
// Act
var result = TestEnumeration.First.Equals(firstCopy);
// Assert
Assert.True(result);
}
[Fact]
public void Equals_DifferentValues_ShouldReturnFalse() {
// Act
var result = TestEnumeration.First.Equals(TestEnumeration.Second);
// Assert
Assert.False(result);
}
[Fact]
public void CompareTo_ShouldReturnZeroForEqualValues() {
// Arrange
var firstCopy = Enumeration.FromValue<TestEnumeration>(1);
// Act
var result = TestEnumeration.First.CompareTo(firstCopy);
// Assert
Assert.Equal(0, result);
}
[Fact]
public void CompareTo_ShouldReturnPositiveForGreaterValue() {
// Act
var result = TestEnumeration.Second.CompareTo(TestEnumeration.First);
// Assert
Assert.True(result > 0);
}
[Fact]
public void CompareTo_ShouldReturnNegativeForLesserValue() {
// Act
var result = TestEnumeration.First.CompareTo(TestEnumeration.Second);
// Assert
Assert.True(result < 0);
}
[Fact]
public void CompareTo_InvalidComparison_ShouldThrowArgumentException() {
// Arrange
var nonEnumerationObject = new object();
// Act & Assert
Assert.Throws<ArgumentException>(() => TestEnumeration.First.CompareTo(nonEnumerationObject));
}
[Fact]
public void GetHashCode_ShouldReturnIdHashCode() {
// Act
var hashCode = TestEnumeration.First.GetHashCode();
// Assert
Assert.Equal(TestEnumeration.First.Id.GetHashCode(), hashCode);
}
[Fact]
public void ToString_ShouldReturnName() {
// Act
var result = TestEnumeration.First.ToString();
// Assert
Assert.Equal("First", result);
}
[Fact]
public void Parse_InvalidValue_ShouldThrowInvalidOperationException() {
// Act & Assert
Assert.Throws<InvalidOperationException>(() => Enumeration.FromValue<TestEnumeration>(999));
}
[Fact]
public void Parse_InvalidDisplayName_ShouldThrowInvalidOperationException() {
// Act & Assert
Assert.Throws<InvalidOperationException>(() => Enumeration.FromDisplayName<TestEnumeration>("NonExistent"));
}
}
}

View File

@ -0,0 +1,72 @@
using System;
using Xunit;
namespace MaksIT.Core.Extensions.Tests {
public class GuidExtensionsTests {
[Fact]
public void ToNullable_WithEmptyGuid_ShouldReturnNull() {
// Arrange
var emptyGuid = Guid.Empty;
// Act
var result = emptyGuid.ToNullable();
// Assert
Assert.Null(result);
}
[Fact]
public void ToNullable_WithNonEmptyGuid_ShouldReturnSameGuid() {
// Arrange
var nonEmptyGuid = Guid.NewGuid();
// Act
var result = nonEmptyGuid.ToNullable();
// Assert
Assert.NotNull(result);
Assert.Equal(nonEmptyGuid, result);
}
[Fact]
public void ToNullable_WithDefaultGuid_ShouldReturnNull() {
// Arrange
var defaultGuid = default(Guid);
// Act
var result = defaultGuid.ToNullable();
// Assert
Assert.Null(result);
}
[Fact]
public void ToNullable_WithSameGuidTwice_ShouldReturnSameGuidEachTime() {
// Arrange
var guid = Guid.NewGuid();
// Act
var result1 = guid.ToNullable();
var result2 = guid.ToNullable();
// Assert
Assert.Equal(result1, result2);
}
[Fact]
public void ToNullable_WithMultipleNewGuids_ShouldReturnUniqueNonEmptyResults() {
// Arrange
var guid1 = Guid.NewGuid();
var guid2 = Guid.NewGuid();
// Act
var result1 = guid1.ToNullable();
var result2 = guid2.ToNullable();
// Assert
Assert.NotEqual(result1, result2);
Assert.NotNull(result1);
Assert.NotNull(result2);
}
}
}

View File

@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using MaksIT.Core.Extensions;
using Xunit;
namespace MaksIT.Core.Tests.Extensions {
public class ObjectExtensionsTests {
private class TestObject {
public required string Name { get; set; }
public int Age { get; set; }
public string? Address { get; set; }
}
private class CustomDateTimeConverter : JsonConverter<DateTime> {
private readonly string _format;
public CustomDateTimeConverter(string format) {
_format = format;
}
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
var dateString = reader.GetString();
if (dateString is null) {
throw new JsonException("Expected a date string but got null.");
}
return DateTime.ParseExact(dateString, _format, null);
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) {
writer.WriteStringValue(value.ToString(_format));
}
}
[Fact]
public void ToJson_WithNullObject_ShouldReturnEmptyJson() {
// Arrange
TestObject? obj = null;
// Act
var result = obj.ToJson();
// Assert
Assert.Equal("{}", result);
}
[Fact]
public void ToJson_WithSimpleObject_ShouldReturnCorrectJson() {
// Arrange
var obj = new TestObject {
Name = "John Doe",
Age = 30
};
// Act
var result = obj.ToJson();
// Assert
Assert.Equal("{\"name\":\"John Doe\",\"age\":30}", result);
}
[Fact]
public void ToJson_WithObjectAndNullValues_ShouldIgnoreNullProperties() {
// Arrange
var obj = new TestObject {
Name = "John Doe",
Age = 30,
Address = null
};
// Act
var result = obj.ToJson();
// Assert
Assert.Equal("{\"name\":\"John Doe\",\"age\":30}", result);
}
[Fact]
public void ToJson_WithCustomJsonConverter_ShouldApplyConverter() {
// Arrange
var obj = new DateTime(2023, 08, 30);
var converters = new List<JsonConverter> { new CustomDateTimeConverter("yyyy-MM-dd") };
// Act
var result = obj.ToJson(converters);
// Assert
Assert.Equal("\"2023-08-30\"", result);
}
[Fact]
public void ToJson_WithComplexObjectAndConverters_ShouldSerializeCorrectly() {
// Arrange
var obj = new {
Name = "Jane Doe",
BirthDate = new DateTime(1990, 12, 25)
};
var converters = new List<JsonConverter> { new CustomDateTimeConverter("yyyy/MM/dd") };
// Act
var result = obj.ToJson(converters);
// Assert
Assert.Equal("{\"name\":\"Jane Doe\",\"birthDate\":\"1990/12/25\"}", result);
}
[Fact]
public void ToJson_WithEmptyObject_ShouldReturnEmptyJsonObject() {
// Arrange
var obj = new { };
// Act
var result = obj.ToJson();
// Assert
Assert.Equal("{}", result);
}
}
}

View File

@ -0,0 +1,244 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using MaksIT.Core.Extensions;
using Xunit;
namespace MaksIT.Core.Tests.Extensions {
public class StringExtensionsTests {
[Theory]
[InlineData("Hello World", "H*", true)] // Match starts with 'H'
[InlineData("Hello World", "h*", true)] // Case insensitive match
[InlineData("Hello World", "*World", true)] // Match ends with 'World'
[InlineData("Hello World", "Hello?World", true)] // '?' should match exactly one character (space in this case)
[InlineData("Hello World", "*W?rld", true)] // '?' matches 'o' in 'World'
[InlineData("Hello World", "Goodbye*", false)] // No match for 'Goodbye*'
public void Like_ShouldReturnExpectedResults(string input, string pattern, bool expected) {
// Act
bool result = input.Like(pattern);
// Assert
Assert.Equal(expected, result);
}
[Theory]
[InlineData("abcdef", 3, "abc")]
[InlineData("abcdef", 0, "")]
[InlineData("abcdef", 10, "abcdef")]
public void Left_ShouldReturnLeftSubstring(string input, int count, string expected) {
// Act
string result = input.Left(count);
// Assert
Assert.Equal(expected, result);
}
[Theory]
[InlineData("abcdef", 3, "def")]
[InlineData("abcdef", 0, "")]
[InlineData("abcdef", 10, "abcdef")]
public void Right_ShouldReturnRightSubstring(string input, int count, string expected) {
// Act
string result = input.Right(count);
// Assert
Assert.Equal(expected, result);
}
[Theory]
[InlineData("abcdef", 1, 3, "bcd")]
[InlineData("abcdef", 0, 2, "ab")]
[InlineData("abcdef", 4, 10, "ef")]
[InlineData("abcdef", 6, 2, "")]
public void Mid_ShouldReturnSubstring(string input, int index, int count, string expected) {
// Act
string result = input.Mid(index, count);
// Assert
Assert.Equal(expected, result);
}
[Theory]
[InlineData("123", 123)]
[InlineData("abc", 0)]
[InlineData(null, 0)]
[InlineData("", 0)]
public void ToInteger_ShouldConvertToInteger(string input, int expected) {
// Act
int result = input.ToInteger();
// Assert
Assert.Equal(expected, result);
}
[Theory]
[InlineData("123", true)]
[InlineData("-123", true)]
[InlineData("abc", false)]
[InlineData("123abc", false)]
public void IsInteger_ShouldReturnIfStringIsInteger(string input, bool expected) {
// Act
bool result = input.IsInteger();
// Assert
Assert.Equal(expected, result);
}
[Fact]
public void Prepend_ShouldPrependStringToStringBuilder() {
// Arrange
var sb = new StringBuilder("World");
string content = "Hello ";
// Act
sb.Prepend(content);
// Assert
Assert.Equal("Hello World", sb.ToString());
}
[Theory]
[InlineData("1", DayOfWeek.Monday)]
[InlineData("Tuesday", DayOfWeek.Tuesday)]
[InlineData("5", DayOfWeek.Friday)]
public void ToEnum_ShouldConvertStringToEnum(string input, DayOfWeek expected) {
// Act
DayOfWeek result = input.ToEnum<DayOfWeek>();
// Assert
Assert.Equal(expected, result);
}
[Fact]
public void ToEnum_InvalidValue_ShouldThrowNotSupportedException() {
// Arrange
string input = "NotAnEnumValue";
// Act & Assert
Assert.Throws<NotSupportedException>(() => input.ToEnum<DayOfWeek>());
}
[Theory]
[InlineData(" ", null)]
[InlineData("", null)]
[InlineData("valid", "valid")]
public void ToNull_ShouldReturnNullForWhitespaceOrEmptyString(string input, string expected) {
// Act
var result = input.ToNull();
// Assert
Assert.Equal(expected, result);
}
[Theory]
[InlineData("123", 123L)]
public void ToLong_ShouldConvertToLong_WhenValidLong(string input, long? expected) {
// Act
var result = input.ToLong();
// Assert
Assert.Equal(expected, result);
}
[Theory]
[InlineData("abc", null)]
[InlineData("", null)]
public void ToLong_ShouldReturnNull_WhenInvalidLong(string input, long? expected) {
// Act
var result = input.ToLong();
// Assert
Assert.Equal(expected, result);
}
[Theory]
[InlineData("2021-08-30", new[] { "yyyy-MM-dd" }, "2021-08-30T00:00:00Z")]
[InlineData("30/08/2021", new[] { "dd/MM/yyyy" }, "2021-08-30T00:00:00Z")]
public void ToDate_ShouldConvertToDate(string input, string[] formats, string expected) {
// Act
var result = input.ToDate(formats);
// Assert
Assert.Equal(DateTime.Parse(expected, null, DateTimeStyles.RoundtripKind), result);
}
[Theory]
[InlineData("2021-08-30T00:00:00Z", "2021-08-30T00:00:00Z")]
[InlineData("Now", "Now")]
public void ToDateTime_ShouldConvertToDateTime(string input, string expected) {
// Act
var result = input.ToDateTime();
// Assert
if (expected == "Now") {
Assert.Equal(DateTime.Now.ToString("dd/MM/yyyy"), result.ToString("dd/MM/yyyy"));
}
else {
Assert.Equal(DateTime.Parse(expected, null, DateTimeStyles.RoundtripKind), result);
}
}
[Theory]
[InlineData("ok", true)]
[InlineData("yes", true)]
[InlineData("true", true)]
[InlineData("1", true)]
[InlineData("no", false)]
[InlineData("false", false)]
[InlineData("0", false)]
[InlineData("invalid", false)]
public void ToBool_ShouldConvertToBool(string input, bool expected) {
// Act
var result = input.ToBool();
// Assert
Assert.Equal(expected, result);
}
[Theory]
[InlineData("e02fd0e4-00fd-090A-ca30-0d00a0038ba0", "e02fd0e4-00fd-090a-ca30-0d00a0038ba0")]
[InlineData("invalid-guid", null)]
public void ToGuid_ShouldConvertStringToGuid(string input, string expected) {
// Act
if (expected == null) {
var result = input.ToGuid();
// Assert that it returns a valid Guid (from MD5 hash, not an exception)
Assert.IsType<Guid>(result);
Assert.NotEqual(Guid.Empty, result); // Check that it does not return Guid.Empty
}
else {
var result = input.ToGuid();
// Assert
Assert.Equal(Guid.Parse(expected), result);
}
}
[Theory]
[InlineData("<p>Hello World</p>", "Hello World")]
[InlineData("<script>alert('test');</script><p>Hello</p>", "Hello")]
public void HtmlToPlainText_ShouldConvertHtmlToPlainText(string input, string expected) {
// Act
var result = input.HtmlToPlainText();
// Assert
Assert.Equal(expected, result);
}
[Theory]
[InlineData("hello world", "helloWorld")]
[InlineData("Hello World", "helloWorld")]
[InlineData("HELLO_WORLD", "helloWorld")]
[InlineData("HELLO-WORLD", "helloWorld")]
public void ToCamelCase_ShouldConvertToCamelCase(string input, string expected) {
// Act
var result = input.ToCamelCase();
// Assert
Assert.Equal(expected, result);
}
}
}

View File

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MaksIT.Core\MaksIT.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
</Project>

31
src/MaksIT.Core.sln Normal file
View File

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MaksIT.Core", "MaksIT.Core\MaksIT.Core.csproj", "{4AE39520-D4F7-4C5F-ACE9-9E79AEAF3228}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MaksIT.Core.Tests", "MaksIT.Core.Tests\MaksIT.Core.Tests.csproj", "{B67A43DA-AFFC-4510-8D51-08F1FF84CC5B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4AE39520-D4F7-4C5F-ACE9-9E79AEAF3228}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4AE39520-D4F7-4C5F-ACE9-9E79AEAF3228}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4AE39520-D4F7-4C5F-ACE9-9E79AEAF3228}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4AE39520-D4F7-4C5F-ACE9-9E79AEAF3228}.Release|Any CPU.Build.0 = Release|Any CPU
{B67A43DA-AFFC-4510-8D51-08F1FF84CC5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B67A43DA-AFFC-4510-8D51-08F1FF84CC5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B67A43DA-AFFC-4510-8D51-08F1FF84CC5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B67A43DA-AFFC-4510-8D51-08F1FF84CC5B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9BCC72D1-8BE8-4924-AF73-C8E86E16EC59}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace MaksIT.Core.Abstractions {
public abstract class Enumeration : IComparable {
public string Name { get; }
public int Id { get; }
protected Enumeration(int id, string name) => (Id, Name) = (id, name);
public override string ToString() => Name;
public static IEnumerable<T> GetAll<T>() where T : Enumeration =>
typeof(T).GetFields(BindingFlags.Public |
BindingFlags.Static |
BindingFlags.DeclaredOnly)
.Select(f => f.GetValue(null))
.Cast<T>();
public override bool Equals(object? obj) =>
obj is Enumeration otherValue &&
GetType() == obj.GetType() &&
Id == otherValue.Id;
public override int GetHashCode() => Id.GetHashCode();
public static int AbsoluteDifference(Enumeration firstValue, Enumeration secondValue) =>
Math.Abs(firstValue.Id - secondValue.Id);
public static T FromValue<T>(int value) where T : Enumeration =>
Parse<T, int>(value, nameof(value), item => item.Id == value);
public static T FromDisplayName<T>(string displayName) where T : Enumeration =>
Parse<T, string>(displayName, nameof(displayName), item => item.Name == displayName);
private static T Parse<T, TK>(TK value, string description, Func<T, bool> predicate) where T : Enumeration =>
GetAll<T>().FirstOrDefault(predicate) ??
throw new InvalidOperationException($"'{value}' is not a valid {description} in {typeof(T)}");
public int CompareTo(object? other) {
if (other is Enumeration otherEnumeration)
return Id.CompareTo(otherEnumeration.Id);
else
throw new ArgumentException($"Object is not of type {nameof(Enumeration)}");
}
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MaksIT.Core.Extensions {
public static class GuidExtensions {
public static Guid? ToNullable(this Guid id) {
// Return null if the Guid is the default value (Guid.Empty)
return id == default ? null : id;
}
}
}

View File

@ -0,0 +1,36 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MaksIT.Core.Extensions;
public static class ObjectExtensions {
/// <summary>
/// Converts object to json string
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <returns></returns>
public static string ToJson<T>(this T? obj) => obj.ToJson(null);
/// <summary>
/// Converts object to json string
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <param name="converters"></param>
/// <returns></returns>
public static string ToJson<T>(this T? obj, List<JsonConverter>? converters) {
if (obj == null)
return "{}";
var options = new JsonSerializerOptions {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
converters?.ForEach(x => options.Converters.Add(x));
return JsonSerializer.Serialize(obj, options);
}
}

View File

@ -0,0 +1,232 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
namespace MaksIT.Core.Extensions {
public static partial class StringExtensions {
/// <summary>
/// SQL Like implementation using wildcard patterns.
/// </summary>
public static bool Like(this string? text, string? wildcardedText) {
if (text is null || wildcardedText is null) return false;
return Regex.IsMatch(text, wildcardedText.WildcardToRegular(), RegexOptions.IgnoreCase | RegexOptions.Multiline);
}
/// <summary>
/// Converts a wildcarded string to a regular expression.
/// </summary>
private static string WildcardToRegular(this string value) =>
$"^{Regex.Escape(value).Replace("\\?", ".").Replace("\\*", ".*")}$";
/// <summary>
/// Returns the left substring of the specified length.
/// </summary>
public static string Left(this string s, int count) =>
s.Substring(0, Math.Min(count, s.Length));
/// <summary>
/// Returns the right substring of the specified length.
/// </summary>
public static string Right(this string s, int count) =>
s.Substring(Math.Max(0, s.Length - count));
/// <summary>
/// Returns a substring starting from the specified index with the specified length.
/// </summary>
public static string Mid(this string s, int index, int count) =>
s.Substring(index, Math.Min(count, s.Length - index));
/// <summary>
/// Converts the string to an integer, returning zero if conversion fails.
/// </summary>
public static int ToInteger(this string s) =>
int.TryParse(s, out var integerValue) ? integerValue : 0;
/// <summary>
/// Determines whether the string represents an integer.
/// </summary>
public static bool IsInteger(this string s) => Regex.IsMatch(s, @"^-?\d+$");
public static StringBuilder Prepend(this StringBuilder sb, string content) => sb.Insert(0, content);
public static T ToEnum<T>(this string input) where T : struct {
if (string.IsNullOrWhiteSpace(input))
throw new ArgumentException("Input cannot be null or empty.", nameof(input));
if (Enum.TryParse(input, true, out T result))
return result;
var enumType = typeof(T);
foreach (T enumItem in Enum.GetValues(enumType)) {
var att = enumType.GetMember(enumItem.ToString() ?? string.Empty)[0]
.GetCustomAttributes(typeof(DisplayAttribute), false)
.SingleOrDefault() as DisplayAttribute;
var displayName = att?.GetName();
if (input.Equals(displayName, StringComparison.InvariantCultureIgnoreCase))
return enumItem;
}
throw new NotSupportedException($"Cannot parse the value '{input}' for {enumType}");
}
public static T? ToNullableEnum<T>(this string input) where T : struct =>
!string.IsNullOrWhiteSpace(input) ? input.ToEnum<T>() : null;
public static string? ToNull(this string s) => string.IsNullOrWhiteSpace(s) ? null : s;
public static string? NullIfEmptyString(this string s) => s.ToNull();
public static long? ToLong(this string s) =>
long.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out var result) ? result : (long?)null;
public static long? ToNullableLong(this string s) => string.IsNullOrWhiteSpace(s) ? (long?)null : s.ToLong();
public static int? ToInt(this string s) =>
int.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out var result) ? result : (int?)null;
public static int? ToNullableInt(this string s) => string.IsNullOrWhiteSpace(s) ? (int?)null : s.ToInt();
public static uint? ToUint(this string s) =>
uint.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out var result) ? result : (uint?)null;
public static uint? ToNullableUint(this string s) => string.IsNullOrWhiteSpace(s) ? (uint?)null : s.ToUint();
public static decimal? ToDecimal(this string s) =>
decimal.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out var result) ? result : (decimal?)null;
public static decimal? ToNullableDecimal(this string s) => string.IsNullOrWhiteSpace(s) ? (decimal?)null : s.ToDecimal();
public static double? ToDouble(this string s) =>
double.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out var result) ? result : (double?)null;
public static double? ToNullableDouble(this string s) => string.IsNullOrWhiteSpace(s) ? (double?)null : s.ToDouble();
#region DateTime
public static DateTime ToDate(this string s, string[] formats) =>
DateTime.TryParseExact(s, formats, CultureInfo.InvariantCulture, DateTimeStyles.None, out var datetime)
? DateTime.SpecifyKind(datetime, DateTimeKind.Utc)
: throw new FormatException($"The date [{s}] is not in the required format: [{formats[0]}]");
public static DateTime ToDate(this string s) => s.ToDate(new[] { "dd/MM/yyyy" });
public static DateTime? ToNullableDate(this string s) => string.IsNullOrEmpty(s) ? (DateTime?)null : s.ToDate();
public static DateTime? ToNullableDate(this string s, string[] formats) => string.IsNullOrEmpty(s) ? (DateTime?)null : s.ToDate(formats);
public static DateTime ToDateTime(this string s, string[] formats) {
if (s.Equals("Now", StringComparison.OrdinalIgnoreCase)) return DateTime.Now;
if (s.Equals("UtcNow", StringComparison.OrdinalIgnoreCase)) return DateTime.UtcNow;
if (s.Equals("Today", StringComparison.OrdinalIgnoreCase)) return DateTime.Today;
return DateTime.TryParseExact(s, formats, CultureInfo.InvariantCulture, DateTimeStyles.None, out var result)
? DateTime.SpecifyKind(result, DateTimeKind.Utc)
: throw new FormatException($"Unable to parse exact date from value [{s}] with formats [{string.Join(", ", formats)}]");
}
public static DateTime ToDateTime(this string s) => s.ToDateTime(new[] { "dd/MM/yyyy", "dd/MM/yyyy HH:mm:ss", "dd/MM/yyyy HH:mm", "yyyy-MM-dd'T'HH:mm:ss'Z'" });
public static DateTime? ToNullableDateTime(this string s) => string.IsNullOrWhiteSpace(s) ? (DateTime?)null : s.ToDateTime();
public static DateTime? ToNullableDateTime(this string s, string[] formats) => string.IsNullOrWhiteSpace(s) ? (DateTime?)null : s.ToDateTime(formats);
#endregion
public static bool ToBool(this string s) =>
new[] { "ok", "yes", "y", "true", "1" }.Contains(s, StringComparer.InvariantCultureIgnoreCase);
public static bool? ToNullableBool(this string s) => string.IsNullOrWhiteSpace(s) ? (bool?)null : s.ToBool();
public static Guid ToGuid(this string text) =>
Guid.TryParse(text, out var value) ? value : new Guid(MD5.Create().ComputeHash(Encoding.Default.GetBytes(text.ToUpper())));
public static Guid? ToNullableGuid(this string s) => string.IsNullOrWhiteSpace(s) ? (Guid?)null : s.ToGuid();
public static string[] StringSplit(this string s, char c) =>
s.Split(c).Select(x => x.Trim()).ToArray();
public static string ToTitle(this string s) => string.IsNullOrWhiteSpace(s) ? s : char.ToUpper(s[0]) + s[1..];
[GeneratedRegex(@"(http|ftp|https):\\/\\/([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:\\/~+#-]*[\\w@?^=%&\\/~+#-])", RegexOptions.Compiled)]
private static partial Regex UrlsRegex();
public static IEnumerable<Uri> ExtractUrls(this string s) =>
UrlsRegex().Matches(s).Cast<Match>()
.Select(match => match.Value)
.Where(url => Uri.TryCreate(url, UriKind.Absolute, out _))
.Select(url => new Uri(url))
.Distinct();
public static string Format(this string s, params object[] args) => string.Format(s, args);
public static string Excerpt(this string s, int length = 60) =>
string.IsNullOrWhiteSpace(s) ? s : s.Length <= length ? s : $"{s.Substring(0, length - 3)}...";
public static T? ToObject<T>(this string s) => ToObjectCore<T>(s, null);
public static T? ToObject<T>(this string s, List<JsonConverter> converters) => ToObjectCore<T>(s, converters);
private static T? ToObjectCore<T>(string s, List<JsonConverter>? converters) {
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
converters?.ForEach(x => options.Converters.Add(x));
return JsonSerializer.Deserialize<T>(s, options);
}
public static bool IsValidEmail(this string? s) {
if (s is null) return false;
const string pattern = @"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$";
var regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture, TimeSpan.FromSeconds(2));
return regex.IsMatch(s);
}
public static string HtmlToPlainText(this string htmlCode) {
if (string.IsNullOrEmpty(htmlCode))
return htmlCode;
var sb = new StringBuilder(htmlCode);
// Remove new lines, tabs, and multiple spaces
sb.Replace("\n", " ").Replace("\t", " ");
sb = new StringBuilder(Regex.Replace(sb.ToString(), "\\s+", " "));
// Remove <head> and <script> sections
sb = new StringBuilder(Regex.Replace(sb.ToString(), "<head.*?</head>", "", RegexOptions.IgnoreCase | RegexOptions.Singleline));
sb = new StringBuilder(Regex.Replace(sb.ToString(), "<script.*?</script>", "", RegexOptions.IgnoreCase | RegexOptions.Singleline));
// Replace HTML entities
string[] oldWords = { "&nbsp;", "&amp;", "&quot;", "&lt;", "&gt;", "&reg;", "&copy;", "&bull;", "&trade;", "&#39;" };
string[] newWords = { " ", "&", "\"", "<", ">", "®", "©", "•", "™", "'" };
for (int i = 0; i < oldWords.Length; i++) sb.Replace(oldWords[i], newWords[i]);
// Handle line breaks
sb.Replace("<br>", "\n").Replace("<br ", "\n<br ").Replace("<p ", "\n<p ");
// Remove all HTML tags
var plainText = Regex.Replace(sb.ToString(), "<[^>]*>", "").Trim();
return plainText;
}
public static string ToCamelCase(this string input) {
if (string.IsNullOrEmpty(input)) return input;
var words = input.Split(new[] { ' ', '-', '_' }, StringSplitOptions.RemoveEmptyEntries);
for (var i = 0; i < words.Length; i++) {
words[i] = i == 0 ? words[i].ToLower() : char.ToUpper(words[i][0]) + words[i][1..].ToLower();
}
return string.Join("", words);
}
}
}

View File

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>MaksIT.$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
<!-- NuGet package metadata -->
<PackageId>MaksIT.Core</PackageId>
<Version>1.0.0</Version>
<Authors>Maksym Sadovnychyy</Authors>
<Company>MAKS-IT</Company>
<Product>MaksIT.Core</Product>
<Description>MaksIT.Core is a collection of helper methods and extensions for .NET projects, designed to simplify common tasks and improve code readability. The library includes extensions for `Guid`, `string`, `Object`, and a base class for creating enumeration types.</Description>
<PackageTags>dotnet;enumeration;string;guid;object;parsers;extensions</PackageTags>
<RepositoryUrl>https://github.com/MAKS-IT-COM/maksit-core</RepositoryUrl>
<License>MIT</License>
<RequireLicenseAcceptance>false</RequireLicenseAcceptance>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="../../README.md" Pack="true" PackagePath="" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,7 @@
@echo off
REM Change directory to the location of the script
cd /d %~dp0
REM Invoke the PowerShell script (Release-NuGetPackage.ps1) in the same directory
powershell -ExecutionPolicy Bypass -File "%~dp0Release-NuGetPackage.ps1"

View File

@ -0,0 +1,46 @@
# Retrieve the API key from the environment variable
$apiKey = $env:NUGET_MAKS_IT
if (-not $apiKey) {
Write-Host "Error: API key not found in environment variable NUGET_MAKS_IT."
exit 1
}
# NuGet source
$nugetSource = "https://api.nuget.org/v3/index.json"
# Define paths
$solutionDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$projectDir = "$solutionDir\MaksIT.Core"
$outputDir = "$projectDir\bin\Release"
# Clean previous builds
Write-Host "Cleaning previous builds..."
dotnet clean $projectDir -c Release
# Build the project
Write-Host "Building the project..."
dotnet build $projectDir -c Release
# Pack the NuGet package
Write-Host "Packing the project..."
dotnet pack $projectDir -c Release --no-build
# Look for the .nupkg file
$packageFile = Get-ChildItem -Path $outputDir -Filter "*.nupkg" -Recurse | Sort-Object LastWriteTime -Descending | Select-Object -First 1
if ($packageFile) {
Write-Host "Package created successfully: $($packageFile.FullName)"
# Push the package to NuGet
Write-Host "Pushing the package to NuGet..."
dotnet nuget push $packageFile.FullName -k $apiKey -s $nugetSource --skip-duplicate
if ($LASTEXITCODE -eq 0) {
Write-Host "Package pushed successfully."
} else {
Write-Host "Failed to push the package."
}
} else {
Write-Host "Package creation failed. No .nupkg file found."
exit 1
}

View File

@ -0,0 +1,49 @@
#!/bin/sh
# Retrieve the API key from the environment variable
apiKey=$NUGET_MAKS_IT
if [ -z "$apiKey" ]; then
echo "Error: API key not found in environment variable NUGET_MAKS_IT."
exit 1
fi
# NuGet source
nugetSource="https://api.nuget.org/v3/index.json"
# Define paths
scriptDir=$(dirname "$0")
solutionDir=$(realpath "$scriptDir")
projectDir="$solutionDir/MaksIT.Core"
outputDir="$projectDir/bin/Release"
# Clean previous builds
echo "Cleaning previous builds..."
dotnet clean "$projectDir" -c Release
# Build the project
echo "Building the project..."
dotnet build "$projectDir" -c Release
# Pack the NuGet package
echo "Packing the project..."
dotnet pack "$projectDir" -c Release --no-build
# Look for the .nupkg file
packageFile=$(find "$outputDir" -name "*.nupkg" -print0 | xargs -0 ls -t | head -n 1)
if [ -n "$packageFile" ]; then
echo "Package created successfully: $packageFile"
# Push the package to NuGet
echo "Pushing the package to NuGet..."
dotnet nuget push "$packageFile" -k "$apiKey" -s "$nugetSource" --skip-duplicate
if [ $? -eq 0 ]; then
echo "Package pushed successfully."
else
echo "Failed to push the package."
fi
else
echo "Package creation failed. No .nupkg file found."
exit 1
fi