diff --git a/src/AutoMapper/AutoMapper.csproj b/src/AutoMapper/AutoMapper.csproj new file mode 100644 index 0000000..3653d84 --- /dev/null +++ b/src/AutoMapper/AutoMapper.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.0 + hydrogen.AutoMapper + hydrogen.AutoMapper + + + + + + + + + + + diff --git a/src/AutoMapper/AutoMapperConfigAttribute.cs b/src/AutoMapper/AutoMapperConfigAttribute.cs new file mode 100644 index 0000000..aacca65 --- /dev/null +++ b/src/AutoMapper/AutoMapperConfigAttribute.cs @@ -0,0 +1,17 @@ +using System; + +namespace hydrogen.AutoMapper +{ + /// + /// Specifies that a type has AutoMapper configuration + /// + /// + /// Any type that is decorated by this attribute, should have a public, static method called "ConfigureAutoMapper" + /// that doesn't take any arguments, and should return void. This method will be called during AutoMapper configuration, + /// by AutoMapperConfigurator, prior to validation of mappings. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] + public class AutoMapperConfigAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/src/AutoMapper/AutoMapperConfigurationExtensions.cs b/src/AutoMapper/AutoMapperConfigurationExtensions.cs new file mode 100644 index 0000000..c9eaeef --- /dev/null +++ b/src/AutoMapper/AutoMapperConfigurationExtensions.cs @@ -0,0 +1,40 @@ +using System; +using System.Linq.Expressions; +using AutoMapper; + +namespace hydrogen.AutoMapper +{ + public static class AutoMapperConfigurationExtensions + { + public static IMappingExpression IgnoreAll(this IMappingExpression expression) + { + expression.ForAllMembers(opt => opt.Ignore()); + return expression; + } + + public static IMappingExpression IgnoreUnmappedProperties(this IMappingExpression expression) + { + var typeMap = Mapper.Configuration.FindTypeMapFor(); + if (typeMap != null) + { + foreach (var unmappedPropertyName in typeMap.GetUnmappedPropertyNames()) + { + expression.ForMember(unmappedPropertyName, opt => opt.Ignore()); + } + } + + return expression; + } + + public static IMappingExpression Ignore(this IMappingExpression expression, Expression> destinationMember) + { + return expression.ForMember(destinationMember, opt => opt.Ignore()); + } + + public static IMappingExpression IgnoreSource(this IMappingExpression expression, Expression> sourceMember) + { + return expression.ForSourceMember(sourceMember, opt => opt.Ignore()); + } + + } +} \ No newline at end of file diff --git a/src/AutoMapper/AutoMapperConfigurator.cs b/src/AutoMapper/AutoMapperConfigurator.cs new file mode 100644 index 0000000..8d22040 --- /dev/null +++ b/src/AutoMapper/AutoMapperConfigurator.cs @@ -0,0 +1,41 @@ +using System; +using System.Linq; +using System.Reflection; +using hydrogen.General.Collections; + +namespace hydrogen.AutoMapper +{ + public static class AutoMapperConfigurator + { + public static void Scan(Assembly assembly) + { + assembly.GetTypes() + .Where(t => t.GetCustomAttributes(typeof(AutoMapperConfigAttribute)).Any()) + .OrderBy(GetDistanceFromObject) + .ForEach(type => + { + var method = type.GetMethod("ConfigureAutoMapper", new Type[0]); + if (method == null || !method.IsStatic || method.ReturnType != typeof(void) || method.GetParameters().Any()) + throw new InvalidOperationException( + "Type " + type.FullName + " is decorated with [AutoMapperConfigAttribute] but does not contain a public static method with the signature of 'void ConfigureAutoMapper()'"); + + method.Invoke(null, null); + }); + } + + private static int GetDistanceFromObject(Type t) + { + var result = 0; + while (!(typeof(object) == t)) + { + if (t == null) + return -1; + + result++; + t = t.BaseType; + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/General/Collections/CollectionExtensions.cs b/src/General/Collections/CollectionExtensions.cs new file mode 100644 index 0000000..77bc53a --- /dev/null +++ b/src/General/Collections/CollectionExtensions.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; + +namespace hydrogen.General.Collections +{ + public static class CollectionExtensions + { + public static void AddAll(this ICollection target, IEnumerable items) + { + foreach (var item in items) + { + target.Add(item); + } + } + + public static void AddAll(this ISet target, IEnumerable items) + { + foreach (var item in items) + { + target.Add(item); + } + } + + public static void AddIfNotContains(this ICollection target, T item) + { + if (!target.Contains(item)) + target.Add(item); + } + + public static void SetItemExistance(this ICollection target, T item, bool existance) + { + if (existance && !target.Contains(item)) + target.Add(item); + else + target.Remove(item); + } + } +} \ No newline at end of file diff --git a/src/General/Collections/LinkedDictionary.cs b/src/General/Collections/LinkedDictionary.cs new file mode 100644 index 0000000..d56849e --- /dev/null +++ b/src/General/Collections/LinkedDictionary.cs @@ -0,0 +1,615 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace hydrogen.General.Collections +{ + /// + /// Linked-list backend IDictionary implementation. + /// Code copied and changed from REVISION 679 of following SVN repository: + /// https://slog.dk/svn/home/jensen/source/linked_dictionary/trunk/ + /// + /// + /// + public abstract class AbstractLinkedDictionary : IDictionary + { + #region Fields + + protected readonly IDictionary Backend; + protected volatile uint Updates; + protected LinkedKeyValuePair First; + protected LinkedKeyValuePair Last; + + #endregion + + #region Initialization + + protected AbstractLinkedDictionary(IDictionary backend) + { + Backend = backend; + } + + #endregion + + #region Abstracts + + protected abstract bool RemoveItem(TKey key); + protected abstract void AddItem(TKey key, TValue value); + protected abstract void SetItem(TKey key, TValue value); + protected abstract LinkedKeyValuePair GetItem(TKey key); + + #endregion + + #region Explicit directional iteration + + public IEnumerator> GetEnumeratorForward() + { + return new LinkedDictionaryEnumerator(this, true); + } + + public IEnumerator> GetEnumeratorBackward() + { + return new LinkedDictionaryEnumerator(this, false); + } + + public ICollection ValuesForward => new DictionaryValues(this, true); + + public ICollection ValuesBackward => new DictionaryValues(this, false); + + public ICollection KeysForward => new DictionaryKeys(this, true); + + public ICollection KeysBackward => new DictionaryKeys(this, false); + + #endregion + + #region Implementation of IEnumerable + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + #region Implementation of IEnumerable> + + public virtual IEnumerator> GetEnumerator() + { + return GetEnumeratorForward(); + } + + #endregion + + #region Implementation of ICollection> + + public void Add(KeyValuePair item) + { + Add(item.Key, item.Value); + } + + public void Clear() + { + Updates++; + Backend.Clear(); + First = null; + Last = null; + } + + public bool Contains(KeyValuePair item) + { + return Backend.ContainsKey(item.Key) && Backend[item.Key].Value.Equals(item.Value); + } + + public void CopyTo(KeyValuePair[] array, int index) + { + foreach (KeyValuePair e in this) + array.SetValue(e, index++); + } + + public bool Remove(KeyValuePair item) + { + if (Contains(item)) + return Remove(item.Key); + + return false; + } + + public int Count => Backend.Count; + + public bool IsReadOnly => Backend.IsReadOnly; + + #endregion + + #region Implementation of IDictionary + + public bool ContainsKey(TKey key) + { + return Backend.ContainsKey(key); + } + + public void Add(TKey key, TValue value) + { + Updates++; + AddItem(key, value); + } + + public bool Remove(TKey key) + { + Updates++; + return RemoveItem(key); + } + + public bool TryGetValue(TKey key, out TValue value) + { + var item = GetItem(key); + if (item != null) + { + value = item.Value; + return true; + } + + value = default(TValue); + return false; + } + + public TValue this[TKey key] + { + get + { + return GetItem(key).Value; + } + set + { + Updates++; + SetItem(key, value); + } + } + + public virtual ICollection Keys => KeysForward; + + public virtual ICollection Values => ValuesForward; + + #endregion + + #region Inner types + + public class LinkedKeyValuePair + { + public LinkedKeyValuePair Previous { get; set; } + public KeyValuePair KeyValuePair { get; } + public LinkedKeyValuePair Next { get; set; } + + public TKey Key => KeyValuePair.Key; + + public TValue Value => KeyValuePair.Value; + + public LinkedKeyValuePair(TKey key, TValue value, LinkedKeyValuePair prev, LinkedKeyValuePair next) + { + KeyValuePair = new KeyValuePair(key, value); + Previous = prev; + Next = next; + } + } + + private class LinkedDictionaryEnumerator : IEnumerator> + { + private readonly AbstractLinkedDictionary _parent; + private readonly bool _forward; + private readonly uint _updates; + private LinkedKeyValuePair _current; + + public LinkedDictionaryEnumerator(AbstractLinkedDictionary parent, bool forward) + { + _parent = parent; + _forward = forward; + _current = null; + _updates = parent.Updates; + } + + #region IEnumerator Members + + public void Reset() + { + _current = null; + } + + public KeyValuePair Current => new KeyValuePair(_current.Key, _current.Value); + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + if (_parent.Updates != _updates) + throw new InvalidOperationException("Collection was modified after the enumerator was created"); + if (_current == null) + _current = _forward ? _parent.First : _parent.Last; + else + _current = _forward ? _current.Next : _current.Previous; + return _current != null; + } + + public void Dispose() + { + } + + #endregion + + #region Key and Value direct access members + + public TKey Key + { + get + { + if (_current == null) + throw new IndexOutOfRangeException(); + + return _current.Key; + } + } + public TValue Value + { + get + { + if (_current == null) + throw new IndexOutOfRangeException(); + + return _current.Value; + } + } + + #endregion + } + + private struct DictionaryKeys : ICollection + { + private readonly AbstractLinkedDictionary _parent; + private readonly bool _forward; + + public DictionaryKeys(AbstractLinkedDictionary parent, bool forward) + { + _parent = parent; + _forward = forward; + } + + #region Implementation of IEnumerable + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + #region Implementation of IEnumerable + + public IEnumerator GetEnumerator() + { + return new KeysEnumerator(new LinkedDictionaryEnumerator(_parent, _forward)); + } + + #endregion + + #region Implementation of ICollection + + public void Add(TKey item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(TKey item) + { + return _parent.Backend.ContainsKey(item); + } + + public void CopyTo(TKey[] array, int index) + { + foreach (object o in this) + array.SetValue(o, index++); + } + + public bool Remove(TKey item) + { + throw new NotSupportedException(); + } + + public int Count => _parent.Backend.Count; + + public bool IsReadOnly => true; + + #endregion + + #region Inner types + + private struct KeysEnumerator : IEnumerator + { + private readonly LinkedDictionaryEnumerator _enumerator; + + public KeysEnumerator(LinkedDictionaryEnumerator enumerator) + { + _enumerator = enumerator; + } + + #region IEnumerator Members + + public void Reset() + { + _enumerator.Reset(); + } + + public TKey Current => _enumerator.Key; + + public bool MoveNext() + { + return _enumerator.MoveNext(); + } + + public void Dispose() + { + } + + object IEnumerator.Current => Current; + + #endregion + } + + #endregion + } + + public struct DictionaryValues : ICollection + { + private readonly AbstractLinkedDictionary _parent; + private readonly bool _forward; + + public DictionaryValues(AbstractLinkedDictionary parent, bool forward) + { + _parent = parent; + _forward = forward; + } + + #region Implementation of IEnumerable + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + #region Implementation of IEnumerable + + public IEnumerator GetEnumerator() + { + return new ValuesEnumerator(new LinkedDictionaryEnumerator(_parent, _forward)); + } + + #endregion + + #region Implementation of ICollection + + public void Add(TValue item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(TValue item) + { + return _parent.Backend.Values.Any(l => l.Value.Equals(item)); + } + + public void CopyTo(TValue[] array, int index) + { + foreach (object o in this) + array.SetValue(o, index++); + } + + public bool Remove(TValue item) + { + throw new NotSupportedException(); + } + + public int Count => _parent.Count; + + public bool IsReadOnly => true; + + #endregion + + #region Inner types + + struct ValuesEnumerator : IEnumerator + { + private readonly LinkedDictionaryEnumerator _enumerator; + + public ValuesEnumerator(LinkedDictionaryEnumerator enumerator) + { + _enumerator = enumerator; + } + + #region IEnumerator Members + + public void Reset() + { + _enumerator.Reset(); + } + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + return _enumerator.MoveNext(); + } + + public TValue Current => _enumerator.Value; + + public void Dispose() + { + } + + #endregion + } + + #endregion + } + + #endregion + + #region Debug Helpers + +#if DEBUG + // ReSharper disable UnusedMember.Local + static object[] Array(ICollection c) { var a = new object[c.Count]; c.CopyTo(a, 0); return a; } + // ReSharper restore UnusedMember.Local +#endif + + #endregion + } + + /// + /// Provides an IDictionary which is iterated in the inverse order of update + /// + public class UpdateLinkedDictionary : AbstractLinkedDictionary + { + public UpdateLinkedDictionary() + : this(new Dictionary()) + { + } + + public UpdateLinkedDictionary(IDictionary backend) + : base(backend) + { + } + + protected override void AddItem(TKey key, TValue value) + { + if (Backend.ContainsKey(key)) + throw new ArgumentException($"Key \"{key}\" already present in dictionary"); + + var l = new LinkedKeyValuePair(key, value, Last, null); + if (Last != null) + Last.Next = l; + + Last = l; + if (First == null) + First = l; + + Backend.Add(key, l); + } + + protected override LinkedKeyValuePair GetItem(TKey key) + { + LinkedKeyValuePair result; + return Backend.TryGetValue(key, out result) ? result : null; + } + + protected override bool RemoveItem(TKey key) + { + LinkedKeyValuePair l; + if (Backend.TryGetValue(key, out l)) + { + if (l != null) + { + LinkedKeyValuePair pre = l.Previous; + LinkedKeyValuePair nxt = l.Next; + + if (pre != null) + pre.Next = nxt; + else + First = nxt; + if (nxt != null) + nxt.Previous = pre; + else + Last = pre; + + } + + return Backend.Remove(key); + } + + return false; + } + + protected override void SetItem(TKey key, TValue value) + { + LinkedKeyValuePair l = GetItem(key); + if (l != null) + RemoveItem(key); + + AddItem(key, value); + } + } + + public class LruDictionary : UpdateLinkedDictionary + { + public LruDictionary() + : this(new Dictionary()) + { + } + + public LruDictionary(IDictionary backend) + : base(backend) + { + } + + protected override LinkedKeyValuePair GetItem(TKey key) + { + LinkedKeyValuePair l; + if (!Backend.TryGetValue(key, out l)) + return null; + + if (l == null) + return null; + + LinkedKeyValuePair nxt = l.Next; + if (nxt != null) // last => no-change + { + Updates++; // looking is updating + // note, atleast 2 items in chain now, since l != last + LinkedKeyValuePair pre = l.Previous; + if (pre == null) + First = nxt; + else + pre.Next = l.Next; + + nxt.Previous = pre; // nxt != null since l != last + Last.Next = l; + l.Next = null; + l.Previous = Last; + Last = l; + } + + return l; + } + } + + public class MruDictionary : LruDictionary + { + public MruDictionary() + : this(new Dictionary()) + { + } + + public MruDictionary(IDictionary backend) + : base(backend) + { + } + + public override IEnumerator> GetEnumerator() + { + return GetEnumeratorForward(); + } + + public override ICollection Keys => KeysBackward; + + public override ICollection Values => ValuesBackward; + } +} \ No newline at end of file diff --git a/src/General/Collections/PagedList.cs b/src/General/Collections/PagedList.cs new file mode 100644 index 0000000..d69d18f --- /dev/null +++ b/src/General/Collections/PagedList.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace hydrogen.General.Collections +{ + public class PagedList : IEnumerable + { + private PaginationStats _paginationStats; + + public PaginationStats Stats => _paginationStats; + + public int PageSize => _paginationStats.PageSize; + + public int PageNumber => _paginationStats.PageNumber; + + public int TotalNumberOfPages => _paginationStats.TotalNumberOfPages; + + public int TotalNumberOfItems => _paginationStats.TotalNumberOfItems; + + public int FirstItemIndex => _paginationStats.FirstItemIndex; + + public int LastItemIndex => _paginationStats.FirstItemIndex + (PageItems?.Count ?? 0) - 1; + + public List PageItems { get; set; } + + #region Initialization + + [Obsolete("Use static builder methods instead")] + public PagedList(int totalNumberOfItems, int pageSize, int pageNumber) + { + _paginationStats = PaginationStats.FromPageNumber(totalNumberOfItems, pageSize, pageNumber); + } + + [Obsolete("Use static builder methods instead")] + public PagedList(List allItems) + { + PageItems = allItems; + _paginationStats = PaginationStats.SinglePage(allItems.Count); + } + + private PagedList() + { + } + + public static PagedList BuildFromCompleteList(List allItems) + { + var result = new PagedList + { + PageItems = allItems, + _paginationStats = PaginationStats.SinglePage(allItems.Count) + }; + + return result; + } + + public static PagedList BuildUsingPageNumber(int totalNumberOfItems, int pageSize, int pageNumber) + { + var result = new PagedList + { + _paginationStats = PaginationStats.FromPageNumber(totalNumberOfItems, pageSize, pageNumber) + }; + + return result; + } + + public static PagedList BuildUsingStartIndex(int totalNumberOfItems, int pageSize, int startIndex) + { + var result = new PagedList + { + _paginationStats = PaginationStats.FromStartIndex(totalNumberOfItems, pageSize, startIndex) + }; + + return result; + } + + public static PagedList BuildUsingPagedList(PagedList source) + { + var result = new PagedList + { + _paginationStats = PaginationStats.FromPaginationStats(source._paginationStats) + }; + + return result; + } + + public static PagedList BuildUsingPagedList(PagedList source, Func mapping) + { + var result = BuildUsingPagedList(source); + result.PageItems = source.PageItems.Select(mapping).ToList(); + + return result; + } + + #endregion + + #region Implementation of IEnumerable + + IEnumerator IEnumerable.GetEnumerator() + { + if (PageItems != null) + return PageItems.GetEnumerator(); + + throw new InvalidOperationException("List does not have a value."); + } + + #endregion + + #region Implementation of IEnumerable + + public IEnumerator GetEnumerator() + { + if (PageItems != null) + return PageItems.GetEnumerator(); + + throw new InvalidOperationException("List does not have a value."); + } + + #endregion + + #region Public methods + + public void FillFrom(IQueryable query) + { + PageItems = query.Skip(FirstItemIndex - 1).Take(PageSize).ToList(); + } + + public void FillFrom(IEnumerable items) + { + PageItems = items.Skip(FirstItemIndex - 1).Take(PageSize).ToList(); + } + + public void FillFromNoSkip(IEnumerable items) + { + PageItems = items.Take(PageSize).ToList(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/General/Collections/PaginationStats.cs b/src/General/Collections/PaginationStats.cs new file mode 100644 index 0000000..bfd34be --- /dev/null +++ b/src/General/Collections/PaginationStats.cs @@ -0,0 +1,84 @@ +using System; + +namespace hydrogen.General.Collections +{ + public class PaginationStats + { + private int _pageSize; + private int _pageNumber; + private int _totalNumberOfItems; + private int _totalNumberOfPages; + private int _firstItemIndex; + + public int PageSize => _pageSize; + + public int PageNumber => _pageNumber; + + public int TotalNumberOfPages => _totalNumberOfPages; + + public int TotalNumberOfItems => _totalNumberOfItems; + + public int FirstItemIndex => _firstItemIndex; + + #region Initialization + + private PaginationStats() + { + } + + public static PaginationStats SinglePage(int count) + { + var result = new PaginationStats(); + + result._totalNumberOfItems = result._pageSize = count; + result._pageNumber = result._totalNumberOfPages = result._firstItemIndex = 1; + + return result; + } + + public static PaginationStats FromPageNumber(int totalNumberOfItems, int pageSize, int pageNumber) + { + var result = new PaginationStats + { + _totalNumberOfItems = totalNumberOfItems, + _pageSize = pageSize, + _totalNumberOfPages = (totalNumberOfItems + pageSize - 1)/pageSize + }; + + result._pageNumber = Math.Max(Math.Min(pageNumber, result._totalNumberOfPages), 1); + result._firstItemIndex = (result._pageNumber - 1) * result._pageSize + 1; + + return result; + } + + public static PaginationStats FromStartIndex(int totalNumberOfItems, int pageSize, int startIndex) + { + var result = new PaginationStats + { + _totalNumberOfItems = totalNumberOfItems, + _pageSize = pageSize, + _totalNumberOfPages = (totalNumberOfItems + pageSize - 1)/pageSize, + _firstItemIndex = Math.Max(Math.Min(startIndex, totalNumberOfItems), 1), + _pageNumber = (startIndex + pageSize - 2)/pageSize + 1 + }; + + return result; + } + + public static PaginationStats FromPaginationStats(PaginationStats source) + { + var result = new PaginationStats + { + _totalNumberOfItems = source._totalNumberOfItems, + _pageSize = source._pageSize, + _totalNumberOfPages = source._totalNumberOfPages, + _firstItemIndex = source._firstItemIndex, + _pageNumber = source._pageNumber + }; + + return result; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/General/Collections/ReadOnlyDictionary.cs b/src/General/Collections/ReadOnlyDictionary.cs new file mode 100644 index 0000000..00e6529 --- /dev/null +++ b/src/General/Collections/ReadOnlyDictionary.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; + +namespace hydrogen.General.Collections +{ + public class ReadOnlyDictionary : IDictionary + { + private readonly IDictionary _dict; + + public ReadOnlyDictionary(IDictionary backingDict) + { + _dict = backingDict; + } + + public void Add(TKey key, TValue value) + { + throw new InvalidOperationException(); + } + + public bool ContainsKey(TKey key) + { + return _dict.ContainsKey(key); + } + + public ICollection Keys => _dict.Keys; + + public bool Remove(TKey key) + { + throw new InvalidOperationException(); + } + + public bool TryGetValue(TKey key, out TValue value) + { + return _dict.TryGetValue(key, out value); + } + + public ICollection Values => _dict.Values; + + public TValue this[TKey key] + { + get { return _dict[key]; } + set { throw new InvalidOperationException(); } + } + + public void Add(KeyValuePair item) + { + throw new InvalidOperationException(); + } + + public void Clear() + { + throw new InvalidOperationException(); + } + + public bool Contains(KeyValuePair item) + { + return _dict.Contains(item); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + _dict.CopyTo(array, arrayIndex); + } + + public int Count => _dict.Count; + + public bool IsReadOnly => true; + + public bool Remove(KeyValuePair item) + { + throw new InvalidOperationException(); + } + + public IEnumerator> GetEnumerator() + { + return _dict.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return ((System.Collections.IEnumerable)_dict).GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/General/Collections/StackExtensions.cs b/src/General/Collections/StackExtensions.cs new file mode 100644 index 0000000..b96dfc7 --- /dev/null +++ b/src/General/Collections/StackExtensions.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace hydrogen.General.Collections +{ + public static class StackExtensions + { + public static void PushAll(this Stack stack, IEnumerable elements) + { + if (elements == null) + return; + + foreach (var element in elements) + { + stack.Push(element); + } + } + } +} \ No newline at end of file diff --git a/src/General/General.csproj b/src/General/General.csproj index 55b7ed9..a2b4a2b 100644 --- a/src/General/General.csproj +++ b/src/General/General.csproj @@ -8,7 +8,7 @@ clay-one Hydrogen.General hydrogen libraries for .NET Standard is a set of utilities and extensions that are used frequently in different projects. - Copyright appson.tech 2017 + Copyright clay-one 2017 https://github.com/clay-one/hydrogen.git hydrogen dotnet clay-one @@ -18,4 +18,42 @@ + + + + + + + + True + True + TimeSpanLocalizationResources.resx + + + True + True + NumericStringUtilsResources.resx + + + True + True + UtilResources.resx + + + + + + ResXFileCodeGenerator + TimeSpanLocalizationResources.Designer.cs + + + ResXFileCodeGenerator + NumericStringUtilsResources.Designer.cs + + + ResXFileCodeGenerator + UtilResources.Designer.cs + + + diff --git a/src/General/Geo/GoogleMapsUtil.cs b/src/General/Geo/GoogleMapsUtil.cs new file mode 100644 index 0000000..fcdebbd --- /dev/null +++ b/src/General/Geo/GoogleMapsUtil.cs @@ -0,0 +1,38 @@ +using System; + +namespace hydrogen.General.Geo +{ + public static class GoogleMapsUtil + { + public const int DefaultWorldPx = 256; + + public static int GetBoundsZoomLevel(LatLngBounds bounds, int widthPx, int heightPx) + { + var latFraction = (LatRad(bounds.NorthLat) - LatRad(bounds.SouthLat)) / Math.PI; + + var lngDiff = bounds.EastLng - bounds.WestLng; + var lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360; + + var latZoom = GetFractionZoomLevel(heightPx, DefaultWorldPx, latFraction); + var lngZoom = GetFractionZoomLevel(widthPx, DefaultWorldPx, lngFraction); + + return Math.Min(latZoom, Math.Min(lngZoom, 21)); + } + + public static int GetFractionZoomLevel(int mapPx, int worldPx, double fraction) + { + return (int)Math.Floor(Math.Log((double)mapPx / worldPx / fraction) / Math.Log(2)); + } + + #region Private helpers + + private static double LatRad(double lat) + { + var sin = Math.Sin(lat*Math.PI/180); + var radX2 = Math.Log((1 + sin)/(1 - sin))/2; + return Math.Max(Math.Min(radX2, Math.PI), -Math.PI)/2; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/General/Geo/LatLng.cs b/src/General/Geo/LatLng.cs new file mode 100644 index 0000000..85143dc --- /dev/null +++ b/src/General/Geo/LatLng.cs @@ -0,0 +1,21 @@ + +namespace hydrogen.General.Geo +{ + public class LatLng + { + public static readonly LatLng Zero = new LatLng {Lat = 0, Lng = 0}; + + public double Lat { get; set; } + public double Lng { get; set; } + + public string ToGoogleApi() + { + return $"new google.maps.LatLng({Lat}, {Lng})"; + } + + public string ToWkt() + { + return $"POINT({Lng} {Lat})"; + } + } +} \ No newline at end of file diff --git a/src/General/Geo/LatLngBounds.cs b/src/General/Geo/LatLngBounds.cs new file mode 100644 index 0000000..ccef9bd --- /dev/null +++ b/src/General/Geo/LatLngBounds.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; + +namespace hydrogen.General.Geo +{ + public class LatLngBounds + { + public LatLng NorthEast { get; set; } + public LatLng SouthWest { get; set; } + + public double NorthLat + { + get { return NorthEast.Lat; } + set { NorthEast.Lat = value; } + } + + public double SouthLat + { + get { return SouthWest.Lat; } + set { SouthWest.Lat = value; } + } + + public double EastLng + { + get { return NorthEast.Lng; } + set { NorthEast.Lng = value; } + } + + public double WestLng + { + get { return SouthWest.Lng; } + set { SouthWest.Lng = value; } + } + + public LatLngBounds() + { + NorthEast = new LatLng(); + SouthWest = new LatLng(); + } + + public void ExtentToContain(LatLng point) + { + if (point.Lat > NorthLat) + NorthLat = point.Lat; + + if (point.Lat < SouthLat) + SouthLat = point.Lat; + + if (point.Lng > EastLng) + EastLng = point.Lng; + + if (point.Lng < WestLng) + WestLng = point.Lng; + } + + public LatLng GetCenter() + { + return new LatLng + { + Lat = (NorthLat + SouthLat)/2, + Lng = (EastLng + WestLng)/2 + }; + } + + public LatLngBounds GetSmallestContainingRect() + { + var center = GetCenter(); + var latSpan = Math.Abs(NorthLat - SouthLat); + var lngSpan = Math.Abs(EastLng - WestLng); + var largerSpan = Math.Max(latSpan, lngSpan); + + return new LatLngBounds + { + NorthEast = new LatLng { Lat = center.Lat + largerSpan/2, Lng = center.Lng + largerSpan/2 }, + SouthWest = new LatLng { Lat = center.Lat - largerSpan/2, Lng = center.Lng - largerSpan/2 } + }; + } + + public LatLngBounds GetLargestContainedRect() + { + var center = GetCenter(); + var latSpan = Math.Abs(NorthLat - SouthLat); + var lngSpan = Math.Abs(EastLng - WestLng); + var smallerSpan = Math.Min(latSpan, lngSpan); + + return new LatLngBounds + { + NorthEast = new LatLng { Lat = center.Lat + smallerSpan/2, Lng = center.Lng + smallerSpan/2 }, + SouthWest = new LatLng { Lat = center.Lat - smallerSpan/2, Lng = center.Lng - smallerSpan/2 } + }; + } + + public string ToGoogleApi() + { + return $"new google.maps.LatLngBounds({SouthWest.ToGoogleApi()}, {NorthEast.ToGoogleApi()})"; + } + + public static LatLngBounds BoundsOf(LatLng point) + { + return new LatLngBounds {NorthEast = point, SouthWest = point}; + } + + public static LatLngBounds BoundsOf(IEnumerable points) + { + LatLngBounds result = null; + + foreach (var point in points) + { + if (point == null) + continue; + + if (result == null) + result = BoundsOf(point); + else + result.ExtentToContain(point); + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/General/Geo/LatLngPath.cs b/src/General/Geo/LatLngPath.cs new file mode 100644 index 0000000..ddb0f01 --- /dev/null +++ b/src/General/Geo/LatLngPath.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Linq; + +namespace hydrogen.General.Geo +{ + public class LatLngPath + { + public LatLngPath() + { + Points = new List(); + } + + public List Points { get; set; } + public string Title { get; set; } + + public LatLngPath SetTitle(string title) + { + Title = title; + return this; + } + + public string ToGoogleApi() + { + return "[" + string.Join(", ", Points.Select(ll => ll.ToGoogleApi())) + "]"; + } + } +} \ No newline at end of file diff --git a/src/General/Localization/DateTimeLocalizationExtensions.cs b/src/General/Localization/DateTimeLocalizationExtensions.cs new file mode 100644 index 0000000..ef1c18d --- /dev/null +++ b/src/General/Localization/DateTimeLocalizationExtensions.cs @@ -0,0 +1,27 @@ +using System; + +namespace hydrogen.General.Localization +{ + public static class DateTimeLocalizationExtensions + { + public static string ToLocalizedDateString(this DateTime dateTime) + { + return DateTimeLocalizationUtils.ToLocalizedDateString(dateTime); + } + + public static string ToLocalizedDateString(this DateTime? dateTime) + { + return DateTimeLocalizationUtils.ToLocalizedDateString(dateTime); + } + + public static string ToLocalizedTimeString(this DateTime dateTime) + { + return DateTimeLocalizationUtils.ToLocalizedTimeString(dateTime); + } + + public static string ToLocalizedTimeString(this DateTime? dateTime) + { + return DateTimeLocalizationUtils.ToLocalizedTimeString(dateTime); + } + } +} \ No newline at end of file diff --git a/src/General/Localization/DateTimeLocalizationUtils.cs b/src/General/Localization/DateTimeLocalizationUtils.cs new file mode 100644 index 0000000..caf6595 --- /dev/null +++ b/src/General/Localization/DateTimeLocalizationUtils.cs @@ -0,0 +1,87 @@ +using System; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace hydrogen.General.Localization +{ + public class DateTimeLocalizationUtils + { + private static readonly PersianCalendar PersianCalendar = new PersianCalendar(); + private static readonly Regex ShamsiDateRegex = new Regex(@"^(1[34][0-9][0-9])/(0?[1-9]|10|11|12)/(0?[1-9]|[12][0-9]|30|31)$"); + + #region Public methods + + public static string ToLocalizedDateString(DateTime dateTime) + { + // TODO: Make the conversion locale-sensitive + return PersianCalendar.GetYear(dateTime).ToString("0000") + "/" + + PersianCalendar.GetMonth(dateTime).ToString("00") + "/" + + PersianCalendar.GetDayOfMonth(dateTime).ToString("00"); + } + + public static string ToLocalizedTimeString(DateTime dateTime) + { + return dateTime.Hour.ToString("00") + ":" + dateTime.Minute.ToString("00"); + } + + public static string ToLocalizedDateString(DateTime? dateTime) + { + return dateTime?.ToLocalizedDateString(); + } + + public static string ToLocalizedTimeString(DateTime? dateTime) + { + return dateTime?.ToLocalizedTimeString(); + } + + public static DateTime? FromLocalizedDateString(string input) + { + var shamsiMatch = ShamsiDateRegex.Match(input); + DateTime? result; + + if (shamsiMatch.Success) + result = TryParseShamsiDate(shamsiMatch.Groups[1].Value, shamsiMatch.Groups[2].Value, shamsiMatch.Groups[3].Value); + else + result = TryParseGregorianDate(input); + + return result; + + } + + #endregion + + #region Private helper methods + + private static DateTime? TryParseGregorianDate(string stringValue) + { + DateTime result; + if (!DateTime.TryParse(stringValue, out result)) + return null; + + return result; + } + + private static DateTime? TryParseShamsiDate(string yearString, string monthString, string dayString) + { + int year; + int month; + int day; + + if (!int.TryParse(yearString, out year)) return null; + if (!int.TryParse(monthString, out month)) return null; + if (!int.TryParse(dayString, out day)) return null; + + var calendar = new PersianCalendar(); + try + { + return calendar.ToDateTime(year, month, day, 0, 0, 0, 0); + } + catch (ArgumentOutOfRangeException) + { + return null; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/General/Localization/EnumExtensions.cs b/src/General/Localization/EnumExtensions.cs new file mode 100644 index 0000000..d54b5ff --- /dev/null +++ b/src/General/Localization/EnumExtensions.cs @@ -0,0 +1,23 @@ +using System.Resources; + +namespace hydrogen.General.Localization +{ + public static class EnumExtensions + { + public static string Label(this TEnum enumObject, ResourceManager resourceManager = null) + where TEnum : struct + { + return resourceManager == null + ? enumObject.ToString() + : resourceManager.GetString(typeof (TEnum).Name + "_" + enumObject) ?? + resourceManager.GetString(typeof (TEnum).Name) ?? + enumObject.ToString(); + } + + public static string Label(this TEnum? enumObject, ResourceManager resourceManager = null) + where TEnum : struct + { + return enumObject?.Label(resourceManager); + } + } +} \ No newline at end of file diff --git a/src/General/Localization/EnumLocalizationUtils.cs b/src/General/Localization/EnumLocalizationUtils.cs new file mode 100644 index 0000000..bae92bc --- /dev/null +++ b/src/General/Localization/EnumLocalizationUtils.cs @@ -0,0 +1,50 @@ +using System; +using System.Resources; +using hydrogen.General.Text; +using hydrogen.General.Utils; + +namespace hydrogen.General.Localization +{ + public static class EnumLocalizationUtils + { + public static string ToLocalizedString(string fullName, ResourceManager resourceManager = null) + { + return resourceManager?.GetString(fullName); + } + + public static string ToLocalizedString(string enumTypeName, string enumItemName, + ResourceManager resourceManager = null) + { + if (enumItemName.IsNullOrWhitespace()) + return null; + + if (enumTypeName.IsNullOrWhitespace()) + return ToLocalizedString(enumItemName, resourceManager); + + return + ToLocalizedString(enumTypeName + "_" + enumItemName, resourceManager) ?? + ToLocalizedString(enumItemName, resourceManager) ?? + enumItemName; + } + + public static string ToLocalizedString(Type enumType, object enumObject, ResourceManager resourceManager = null) + { + return ToLocalizedString( + enumType.IfNotNull(t => t.Name), + enumObject.IfNotNull(o => o.ToString()), + resourceManager); + } + + public static string ToLocalizedString(this TEnum enumObject, ResourceManager resourceManager = null) + where TEnum : struct + { + return ToLocalizedString(typeof(TEnum), enumObject, resourceManager); + } + + public static string ToLocalizedString(this TEnum? enumObject, ResourceManager resourceManager = null) + where TEnum : struct + { + return enumObject?.ToLocalizedString(resourceManager); + } + } +} \ No newline at end of file diff --git a/src/General/Localization/TimeSpanLocalizationExtensions.cs b/src/General/Localization/TimeSpanLocalizationExtensions.cs new file mode 100644 index 0000000..0862af6 --- /dev/null +++ b/src/General/Localization/TimeSpanLocalizationExtensions.cs @@ -0,0 +1,27 @@ +using System; + +namespace hydrogen.General.Localization +{ + public static class TimeSpanLocalizationExtensions + { + public static string ToLocalizedDurationString(this TimeSpan timeSpan, int maxNumberOfParts = 2, string zeroDurationString = null) + { + return TimeSpanLocalizationUtils.BuildDurationText(timeSpan, maxNumberOfParts, zeroDurationString); + } + + public static string ToLocalizedRelativeString(this TimeSpan timeSpan, int maxNumberOfParts = 2, string zeroDurationString = null) + { + return TimeSpanLocalizationUtils.BuildRelativeText(timeSpan, maxNumberOfParts, zeroDurationString); + } + + public static string ToLocalizedDurationString(this TimeSpan? timeSpan, int maxNumberOfParts = 2, string zeroDurationString = null) + { + return TimeSpanLocalizationUtils.BuildDurationText(timeSpan, maxNumberOfParts, zeroDurationString); + } + + public static string ToLocalizedRelativeString(this TimeSpan? timeSpan, int maxNumberOfParts = 2, string zeroDurationString = null) + { + return TimeSpanLocalizationUtils.BuildRelativeText(timeSpan, maxNumberOfParts, zeroDurationString); + } + } +} \ No newline at end of file diff --git a/src/General/Localization/TimeSpanLocalizationResources.Designer.cs b/src/General/Localization/TimeSpanLocalizationResources.Designer.cs new file mode 100644 index 0000000..f0e14e4 --- /dev/null +++ b/src/General/Localization/TimeSpanLocalizationResources.Designer.cs @@ -0,0 +1,243 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace hydrogen.General.Localization { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class TimeSpanLocalizationResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal TimeSpanLocalizationResources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("hydrogen.General.Localization.TimeSpanLocalizationResources", typeof(TimeSpanLocalizationResources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to {0} پیش. + /// + internal static string Ago { + get { + return ResourceManager.GetString("Ago", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} روز. + /// + internal static string Days { + get { + return ResourceManager.GetString("Days", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} ساعت. + /// + internal static string Hours { + get { + return ResourceManager.GetString("Hours", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to و . + /// + internal static string Joiner { + get { + return ResourceManager.GetString("Joiner", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} بعد. + /// + internal static string Later { + get { + return ResourceManager.GetString("Later", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} دقیقه. + /// + internal static string Minutes { + get { + return ResourceManager.GetString("Minutes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} ماه. + /// + internal static string Months { + get { + return ResourceManager.GetString("Months", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to یک روز. + /// + internal static string OneDay { + get { + return ResourceManager.GetString("OneDay", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to یک ساعت. + /// + internal static string OneHour { + get { + return ResourceManager.GetString("OneHour", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to یک دقیقه. + /// + internal static string OneMinute { + get { + return ResourceManager.GetString("OneMinute", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to یک ماه. + /// + internal static string OneMonth { + get { + return ResourceManager.GetString("OneMonth", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to یک ثانیه. + /// + internal static string OneSecod { + get { + return ResourceManager.GetString("OneSecod", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to یک هفته. + /// + internal static string OneWeek { + get { + return ResourceManager.GetString("OneWeek", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to یک سال. + /// + internal static string OneYear { + get { + return ResourceManager.GetString("OneYear", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to همان لحظه. + /// + internal static string SameMoment { + get { + return ResourceManager.GetString("SameMoment", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} ثانیه. + /// + internal static string Seconds { + get { + return ResourceManager.GetString("Seconds", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to نامشخص. + /// + internal static string Unknown { + get { + return ResourceManager.GetString("Unknown", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} هفته. + /// + internal static string Weeks { + get { + return ResourceManager.GetString("Weeks", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} سال. + /// + internal static string Years { + get { + return ResourceManager.GetString("Years", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to صفر. + /// + internal static string ZeroDuration { + get { + return ResourceManager.GetString("ZeroDuration", resourceCulture); + } + } + } +} diff --git a/src/General/Localization/TimeSpanLocalizationResources.resx b/src/General/Localization/TimeSpanLocalizationResources.resx new file mode 100644 index 0000000..c882506 --- /dev/null +++ b/src/General/Localization/TimeSpanLocalizationResources.resx @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + {0} پیش + + + {0} روز + + + {0} ساعت + + + و + + + {0} بعد + + + {0} دقیقه + + + {0} ماه + + + یک روز + + + یک ساعت + + + یک دقیقه + + + یک ماه + + + یک ثانیه + + + یک هفته + + + یک سال + + + همان لحظه + + + {0} ثانیه + + + نامشخص + + + {0} هفته + + + {0} سال + + + صفر + + \ No newline at end of file diff --git a/src/General/Localization/TimeSpanLocalizationUtils.cs b/src/General/Localization/TimeSpanLocalizationUtils.cs new file mode 100644 index 0000000..681248b --- /dev/null +++ b/src/General/Localization/TimeSpanLocalizationUtils.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; + +namespace hydrogen.General.Localization +{ + public static class TimeSpanLocalizationUtils + { + public static string BuildRelativeText(TimeSpan? timeSpan, int maxNumberOfParts, string zeroDurationString = null) + { + if (!timeSpan.HasValue) + return TimeSpanLocalizationResources.Unknown; + + return BuildRelativeText(timeSpan.Value, maxNumberOfParts, zeroDurationString); + } + + public static string BuildDurationText(TimeSpan? timeSpan, int maxNumberOfParts, string zeroDurationString = null) + { + if (!timeSpan.HasValue) + return TimeSpanLocalizationResources.Unknown; + + return BuildDurationText(timeSpan.Value, maxNumberOfParts, zeroDurationString); + } + + public static string BuildRelativeText(TimeSpan timeSpan, int maxNumberOfParts = 2, string zeroDurationString = null) + { + if (Math.Abs(timeSpan.TotalSeconds) < 1) + return zeroDurationString ?? TimeSpanLocalizationResources.SameMoment; + + string result = BuildDurationText(timeSpan, maxNumberOfParts); + return string.Format(timeSpan.Ticks < 0 ? TimeSpanLocalizationResources.Ago : TimeSpanLocalizationResources.Later, result); + } + + public static string BuildDurationText(TimeSpan timeSpan, int maxNumberOfParts = 2, string zeroDurationString = null) + { + if (maxNumberOfParts > 7 || maxNumberOfParts < 1) + throw new ArgumentException("maxNumberOfParts should be between 1 and 7"); + + if (timeSpan.Ticks < 0) + timeSpan = timeSpan.Duration(); + + var days = timeSpan.Days; + int years = days/365; + days %= 365; + int months = days/30; + days %= 30; + int weeks = days/7; + days %= 7; + + var resultParts = BuildTextArray(years, months, weeks, days, timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds, maxNumberOfParts); + if (resultParts == null || resultParts.Count < 1) + return zeroDurationString ?? TimeSpanLocalizationResources.ZeroDuration; + + return string.Join(TimeSpanLocalizationResources.Joiner, resultParts); + } + + private static IList BuildTextArray(int years, int months, int weeks, int days, int hours, int minutes, int seconds, int maxNumberOfParts) + { + var result = new List(maxNumberOfParts); + var remainingNumberOfParts = maxNumberOfParts; + + if (years != 0) + { + result.Add(years == 1 ? TimeSpanLocalizationResources.OneYear : string.Format(TimeSpanLocalizationResources.Years, years)); + remainingNumberOfParts--; + } + + if (months != 0 && remainingNumberOfParts > 0) + { + result.Add(months == 1 ? TimeSpanLocalizationResources.OneMonth : string.Format(TimeSpanLocalizationResources.Months, months)); + remainingNumberOfParts--; + } + + if (weeks != 0 && remainingNumberOfParts > 0) + { + result.Add(weeks == 1 ? TimeSpanLocalizationResources.OneWeek : string.Format(TimeSpanLocalizationResources.Weeks, weeks)); + remainingNumberOfParts--; + } + + if (days != 0 && remainingNumberOfParts > 0) + { + result.Add(days == 1 ? TimeSpanLocalizationResources.OneDay : string.Format(TimeSpanLocalizationResources.Days, days)); + remainingNumberOfParts--; + } + + if (hours != 0 && remainingNumberOfParts > 0) + { + result.Add(hours == 1 ? TimeSpanLocalizationResources.OneHour : string.Format(TimeSpanLocalizationResources.Hours, hours)); + remainingNumberOfParts--; + } + + if (minutes != 0 && remainingNumberOfParts > 0) + { + result.Add(minutes == 1 ? TimeSpanLocalizationResources.OneMinute : string.Format(TimeSpanLocalizationResources.Minutes, minutes)); + remainingNumberOfParts--; + } + + if (seconds != 0 && remainingNumberOfParts > 0) + { + result.Add(seconds == 1 ? TimeSpanLocalizationResources.OneSecod : string.Format(TimeSpanLocalizationResources.Seconds, seconds)); + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/General/Model/IChangeHistoryEntity.cs b/src/General/Model/IChangeHistoryEntity.cs new file mode 100644 index 0000000..619104e --- /dev/null +++ b/src/General/Model/IChangeHistoryEntity.cs @@ -0,0 +1,6 @@ +namespace hydrogen.General.Model +{ + public interface IChangeHistoryEntity + { + } +} \ No newline at end of file diff --git a/src/General/Model/ICreationTime.cs b/src/General/Model/ICreationTime.cs new file mode 100644 index 0000000..da7a226 --- /dev/null +++ b/src/General/Model/ICreationTime.cs @@ -0,0 +1,9 @@ +using System; + +namespace hydrogen.General.Model +{ + public interface ICreationTime + { + DateTime CreationTime { get; set; } + } +} \ No newline at end of file diff --git a/src/General/Model/IDecimalIdEntity.cs b/src/General/Model/IDecimalIdEntity.cs new file mode 100644 index 0000000..c0b13b1 --- /dev/null +++ b/src/General/Model/IDecimalIdEntity.cs @@ -0,0 +1,12 @@ +namespace hydrogen.General.Model +{ + public interface IDecimalIdEntity + { + decimal Id { get; } + } + + public interface IDecimalIDEntity + { + decimal Id { get; } + } +} \ No newline at end of file diff --git a/src/General/Model/IEntityContent.cs b/src/General/Model/IEntityContent.cs new file mode 100644 index 0000000..496117f --- /dev/null +++ b/src/General/Model/IEntityContent.cs @@ -0,0 +1,6 @@ +namespace hydrogen.General.Model +{ + public interface IEntityContent + { + } +} \ No newline at end of file diff --git a/src/General/Model/IEntityContentContainer.cs b/src/General/Model/IEntityContentContainer.cs new file mode 100644 index 0000000..cb51044 --- /dev/null +++ b/src/General/Model/IEntityContentContainer.cs @@ -0,0 +1,8 @@ +namespace hydrogen.General.Model +{ + public interface IEntityContentContainer where TContent : class, IEntityContent + { + string ContentString { get; set; } + TContent Content { get; set; } + } +} \ No newline at end of file diff --git a/src/General/Model/IIndexedEntity.cs b/src/General/Model/IIndexedEntity.cs new file mode 100644 index 0000000..a8ceb1e --- /dev/null +++ b/src/General/Model/IIndexedEntity.cs @@ -0,0 +1,9 @@ +using System; + +namespace hydrogen.General.Model +{ + public interface IIndexedEntity + { + DateTime? IndexedTime { get; set; } + } +} \ No newline at end of file diff --git a/src/General/Model/ILastModificationTime.cs b/src/General/Model/ILastModificationTime.cs new file mode 100644 index 0000000..2f276a8 --- /dev/null +++ b/src/General/Model/ILastModificationTime.cs @@ -0,0 +1,9 @@ +using System; + +namespace hydrogen.General.Model +{ + public interface ILastModificationTime + { + DateTime LastModificationTime { get; set; } + } +} \ No newline at end of file diff --git a/src/General/Model/ISimpleIDEntity.cs b/src/General/Model/ISimpleIDEntity.cs new file mode 100644 index 0000000..2eebf18 --- /dev/null +++ b/src/General/Model/ISimpleIDEntity.cs @@ -0,0 +1,12 @@ +namespace hydrogen.General.Model +{ + public interface ISimpleIDEntity + { + long ID { get; } + } + + public interface ISimpleIdEntity + { + long Id { get; } + } +} \ No newline at end of file diff --git a/src/General/Model/IStateTime.cs b/src/General/Model/IStateTime.cs new file mode 100644 index 0000000..dc83bd2 --- /dev/null +++ b/src/General/Model/IStateTime.cs @@ -0,0 +1,9 @@ +using System; + +namespace hydrogen.General.Model +{ + public interface IStateTime + { + DateTime StateTime { get; set; } + } +} \ No newline at end of file diff --git a/src/General/Model/PagedListOutput.cs b/src/General/Model/PagedListOutput.cs new file mode 100644 index 0000000..e900b82 --- /dev/null +++ b/src/General/Model/PagedListOutput.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace hydrogen.General.Model +{ + public class PagedListOutput + { + public List PageItems { get; set; } + public int PageNumber { get; set; } + public int TotalNumberOfPages { get; set; } + public int TotalNumberOfItems { get; set; } + } +} \ No newline at end of file diff --git a/src/General/Security/CryptoRandomNumberUtil.cs b/src/General/Security/CryptoRandomNumberUtil.cs new file mode 100644 index 0000000..4aa2b65 --- /dev/null +++ b/src/General/Security/CryptoRandomNumberUtil.cs @@ -0,0 +1,65 @@ +using System; +using System.Security.Cryptography; +using System.Text; + +namespace hydrogen.General.Security +{ + public class CryptoRandomNumberUtil + { + private static readonly RandomNumberGenerator RandomNumberGenerator = new RNGCryptoServiceProvider(); + + public static int GetInt32() + { + byte[] resultBytes = GetBytes(4); + return BitConverter.ToInt32(resultBytes, 0); + } + + public static int GetInt32(int min, int max) + { + if (max < min) + throw new ArgumentException("Invalid range"); + + return (Math.Abs(GetInt32()%(max - min + 1))) + min; + } + + public static byte[] GetBytes(int length, bool nonZeroOnly = false) + { + var randomBytes = new byte[length]; + if (nonZeroOnly) + RandomNumberGenerator.GetNonZeroBytes(randomBytes); + else + RandomNumberGenerator.GetBytes(randomBytes); + + return randomBytes; + } + + public static string GetNumericString(int length) + { + var randomBytes = GetBytes(length); + var result = new StringBuilder(); + + foreach (byte b in randomBytes) + result.Append(b%10); + + return result.ToString(); + } + + public static string GetAlphaNumericString(int length) + { + var randomBytes = GetBytes(length); + var result = new StringBuilder(); + + foreach (byte b in randomBytes) + { + int i = b%36; + + if (i < 10) + result.Append(i); + else + result.Append((char)('a' + (i - 10))); + } + + return result.ToString(); + } + } +} \ No newline at end of file diff --git a/src/General/Streams/ManifestResourceUtil.cs b/src/General/Streams/ManifestResourceUtil.cs new file mode 100644 index 0000000..341ce2f --- /dev/null +++ b/src/General/Streams/ManifestResourceUtil.cs @@ -0,0 +1,23 @@ +using System.IO; +using System.Reflection; + +namespace hydrogen.General.Streams +{ + public static class ManifestResourceUtil + { + public static byte[] GetManifestResourceBytes(Assembly assembly, string manifestResourceName) + { + using (var resourceStream = assembly.GetManifestResourceStream(manifestResourceName)) + { + if (resourceStream == null) + return null; + + using (var memStream = new MemoryStream()) + { + resourceStream.CopyTo(memStream); + return memStream.ToArray(); + } + } + } + } +} \ No newline at end of file diff --git a/src/General/Text/Base32.cs b/src/General/Text/Base32.cs new file mode 100644 index 0000000..498da9d --- /dev/null +++ b/src/General/Text/Base32.cs @@ -0,0 +1,129 @@ +using System; + +namespace hydrogen.General.Text +{ + public class Base32 + { + public static byte[] ToBytes(string input) + { + if (string.IsNullOrEmpty(input)) + { + throw new ArgumentNullException(nameof(input)); + } + + input = input.TrimEnd('='); //remove padding characters + int byteCount = input.Length * 5 / 8; //this must be TRUNCATED + byte[] returnArray = new byte[byteCount]; + + byte curByte = 0, bitsRemaining = 8; + int arrayIndex = 0; + + foreach (char c in input) + { + int cValue = CharToValue(c); + + int mask; + if (bitsRemaining > 5) + { + mask = cValue << (bitsRemaining - 5); + curByte = (byte)(curByte | mask); + bitsRemaining -= 5; + } + else + { + mask = cValue >> (5 - bitsRemaining); + curByte = (byte)(curByte | mask); + returnArray[arrayIndex++] = curByte; + curByte = (byte)(cValue << (3 + bitsRemaining)); + bitsRemaining += 3; + } + } + + //if we didn't end with a full byte + if (arrayIndex != byteCount) + { + returnArray[arrayIndex] = curByte; + } + + return returnArray; + } + + public static string ToString(byte[] input) + { + if (input == null || input.Length == 0) + { + throw new ArgumentNullException(nameof(input)); + } + + int charCount = (int)Math.Ceiling(input.Length / 5d) * 8; + char[] returnArray = new char[charCount]; + + byte nextChar = 0, bitsRemaining = 5; + int arrayIndex = 0; + + foreach (byte b in input) + { + nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining))); + returnArray[arrayIndex++] = ValueToChar(nextChar); + + if (bitsRemaining < 4) + { + nextChar = (byte)((b >> (3 - bitsRemaining)) & 31); + returnArray[arrayIndex++] = ValueToChar(nextChar); + bitsRemaining += 5; + } + + bitsRemaining -= 3; + nextChar = (byte)((b << bitsRemaining) & 31); + } + + //if we didn't end with a full char + if (arrayIndex != charCount) + { + returnArray[arrayIndex++] = ValueToChar(nextChar); + while (arrayIndex != charCount) returnArray[arrayIndex++] = '='; //padding + } + + return new string(returnArray); + } + + private static int CharToValue(char c) + { + int value = c; + + //65-90 == uppercase letters + if (value < 91 && value > 64) + { + return value - 65; + } + //50-55 == numbers 2-7 + if (value < 56 && value > 49) + { + return value - 24; + } + //97-122 == lowercase letters + if (value < 123 && value > 96) + { + return value - 97; + } + + throw new ArgumentException("Character is not a Base32 character.", nameof(c)); + } + + private static char ValueToChar(byte b) + { + if (b < 26) + { + return (char)(b + 65); + } + + if (b < 32) + { + return (char)(b + 24); + } + + throw new ArgumentException("Byte is not a value Base32 value.", nameof(b)); + } + + } +} \ No newline at end of file diff --git a/src/General/Text/Base64Url.cs b/src/General/Text/Base64Url.cs new file mode 100644 index 0000000..8ee1243 --- /dev/null +++ b/src/General/Text/Base64Url.cs @@ -0,0 +1,47 @@ +using System; +using System.Text; + +namespace hydrogen.General.Text +{ + /// + /// Modified Base64 for URL applications ('base64url' encoding) + /// + /// See http://tools.ietf.org/html/rfc4648 + /// For more information see http://en.wikipedia.org/wiki/Base64 + /// + public class Base64Url + { + /// + /// Modified Base64 for URL applications ('base64url' encoding) + /// + /// See http://tools.ietf.org/html/rfc4648 + /// For more information see http://en.wikipedia.org/wiki/Base64 + /// + /// + /// Input byte array converted to a base64ForUrl encoded string + public static string ToBase64ForUrlString(byte[] input) + { + StringBuilder result = new StringBuilder(Convert.ToBase64String(input).TrimEnd('=')); + result.Replace('+', '-'); + result.Replace('/', '_'); + return result.ToString(); + } + /// + /// Modified Base64 for URL applications ('base64url' encoding) + /// + /// See http://tools.ietf.org/html/rfc4648 + /// For more information see http://en.wikipedia.org/wiki/Base64 + /// + /// + /// Input base64ForUrl encoded string as the original byte array + public static byte[] FromBase64ForUrlString(string base64ForUrlInput) + { + int padChars = (base64ForUrlInput.Length % 4) == 0 ? 0 : (4 - (base64ForUrlInput.Length % 4)); + StringBuilder result = new StringBuilder(base64ForUrlInput, base64ForUrlInput.Length + padChars); + result.Append(String.Empty.PadRight(padChars, '=')); + result.Replace('-', '+'); + result.Replace('_', '/'); + return Convert.FromBase64String(result.ToString()); + } + } +} diff --git a/src/General/Text/DigitLocalizationUtils.cs b/src/General/Text/DigitLocalizationUtils.cs new file mode 100644 index 0000000..77b7db3 --- /dev/null +++ b/src/General/Text/DigitLocalizationUtils.cs @@ -0,0 +1,37 @@ +using System.Text; + +namespace hydrogen.General.Text +{ + public static class DigitLocalizationUtils + { + public static string ToEnglish(string input) + { + var result = new StringBuilder(input); + for (int i = 0; i < result.Length; i++) + { + if (result[i] >= 0x0660 && result[i] <= 0x669) + result[i] = (char) (result[i] + '0' - 0x0660); + + if (result[i] >= 0x06F0 && result[i] <= 0x6F9) + result[i] = (char) (result[i] + '0' - 0x06F0); + } + + return result.ToString(); + } + + public static string ToPersian(string input) + { + var result = new StringBuilder(input); + for (int i = 0; i < result.Length; i++) + { + if (result[i] >= '0' && result[i] <= '9') + result[i] = (char) (result[i] + 0x06F0 - '0'); + + if (result[i] >= 0x0660 && result[i] <= 0x669) + result[i] = (char) (result[i] + 0x06F0 - 0x0660); + } + + return result.ToString(); + } + } +} \ No newline at end of file diff --git a/src/General/Text/EmailUtils.cs b/src/General/Text/EmailUtils.cs new file mode 100644 index 0000000..57d42a4 --- /dev/null +++ b/src/General/Text/EmailUtils.cs @@ -0,0 +1,22 @@ +using System.Text.RegularExpressions; + +namespace hydrogen.General.Text +{ + public static class EmailUtils + { + private const string EmailRegexString = @"[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?"; + + private static readonly Regex EmailRegex = new Regex(EmailRegexString); + private static readonly Regex WholeEmailRegex = new Regex(RegexUtils.CreateWholeInputRegex(EmailRegexString)); + + public static bool IsValidEmail(string email, bool allowPartial) + { + return (allowPartial ? EmailRegex : WholeEmailRegex).IsMatch(email); + } + + public static bool IsValidEmail(string email) + { + return IsValidEmail(email, false); + } + } +} \ No newline at end of file diff --git a/src/General/Text/NumericStringUtils.cs b/src/General/Text/NumericStringUtils.cs new file mode 100644 index 0000000..e5ce93c --- /dev/null +++ b/src/General/Text/NumericStringUtils.cs @@ -0,0 +1,152 @@ +using System; +using System.Globalization; + +namespace hydrogen.General.Text +{ + public static class NumericStringUtils + { + private const string CardinalTextualNumberResourcePrefix = "CardinalTextual"; + private const string OrdinalTextualNumberResourcePrefix = "OrdinalTextual"; + + public static string ShortNumericString(decimal? input) + { + if (!input.HasValue) + return null; + + if (input == 0) + return NumericStringUtilsResources.CardinalTextual0; + + decimal number = input.Value; + string format = NumericStringUtilsResources.NumericOnes; + + if (number >= 1000) + { + number /= 1000; + format = NumericStringUtilsResources.NumericThousands; + } + + if (number >= 1000) + { + number /= 1000; + format = NumericStringUtilsResources.NumericMillions; + } + + if (number >= 1000) + { + number /= 1000; + format = NumericStringUtilsResources.NumericBillions; + } + + number = Math.Round(number, Math.Max(3 - CountIntegralDigits(number), 0)); + return string.Format(format, number); + } + + public static string FullyTextualNumber(decimal? input) + { + if (!input.HasValue) + return null; + + bool negative = input.Value < 0; + input = Math.Abs(input.Value); + + // Fractions are not supported for now. + long integerPart = Convert.ToInt32(input); + + // Only implementing numbers under 1000 for now + if (input > 1000) + return ShortNumericString(input); + + string result = FullyTextualNumber3Digits(integerPart, false); + return negative ? string.Format(NumericStringUtilsResources.TextualNegativeFormat, result) : result; + } + + public static string FullyTextualOrdinalNumber(long? input) + { + if (!input.HasValue) + return null; + + bool negative = input.Value < 0; + input = Math.Abs(input.Value); + + // Only implementing numbers under 1000 for now + string result; + + if (input.Value == 1) + result = NumericStringUtilsResources.First; + else if (input > 1000) + result = string.Format(NumericStringUtilsResources.GenericOrdinalFormat, ShortNumericString(input)); + else + result = FullyTextualNumber3Digits(input.Value, true); + + return negative ? string.Format(NumericStringUtilsResources.TextualNegativeFormat, result) : result; + } + + public static String BytesToString(long byteCount) + { + // Copied from the following SO question: + // http://stackoverflow.com/questions/281640/how-do-i-get-a-human-readable-file-size-in-bytes-abbreviation-using-net + + string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; //Longs run out around EB + if (byteCount == 0) + return "0 " + suf[0]; + + long bytes = Math.Abs(byteCount); + int place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024))); + double num = Math.Round(bytes / Math.Pow(1024, place), 1); + return (Math.Sign(byteCount) * num).ToString(CultureInfo.InvariantCulture) + " " + suf[place]; + } + + #region Private helper methods + + private static int CountIntegralDigits(decimal input) + { + int result = 1; + while (input >= 10) + { + result++; + input /= 10; + } + + return result; + } + + private static string FullyTextualNumber2Digits(long input, bool ordinal) + { + if (input <= 20) + return NumericStringUtilsResources.ResourceManager.GetString(GetTextualNumberResourcePrefix(ordinal) + input); + + long ones = input%10; + long tens = input - ones; + + if (ones == 0) + return NumericStringUtilsResources.ResourceManager.GetString(GetTextualNumberResourcePrefix(ordinal) + tens); + + return NumericStringUtilsResources.ResourceManager.GetString(GetTextualNumberResourcePrefix(false) + tens) + + NumericStringUtilsResources.TextualJointer + + NumericStringUtilsResources.ResourceManager.GetString(GetTextualNumberResourcePrefix(ordinal) + ones); + } + + private static string FullyTextualNumber3Digits(long input, bool ordinal) + { + if (input < 100) + return FullyTextualNumber2Digits(input, ordinal); + + long ones = input%100; + long hundreds = input - ones; + + if (ones == 0) + return NumericStringUtilsResources.ResourceManager.GetString(GetTextualNumberResourcePrefix(ordinal) + hundreds); + + return NumericStringUtilsResources.ResourceManager.GetString(GetTextualNumberResourcePrefix(false) + hundreds) + + NumericStringUtilsResources.TextualJointer + + FullyTextualNumber2Digits(ones, ordinal); + } + + private static string GetTextualNumberResourcePrefix(bool ordinal) + { + return ordinal ? OrdinalTextualNumberResourcePrefix : CardinalTextualNumberResourcePrefix; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/General/Text/NumericStringUtilsResources.Designer.cs b/src/General/Text/NumericStringUtilsResources.Designer.cs new file mode 100644 index 0000000..7a0acca --- /dev/null +++ b/src/General/Text/NumericStringUtilsResources.Designer.cs @@ -0,0 +1,819 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace hydrogen.General.Text { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class NumericStringUtilsResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal NumericStringUtilsResources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("hydrogen.General.Text.NumericStringUtilsResources", typeof(NumericStringUtilsResources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to صفر. + /// + internal static string CardinalTextual0 { + get { + return ResourceManager.GetString("CardinalTextual0", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to یک. + /// + internal static string CardinalTextual1 { + get { + return ResourceManager.GetString("CardinalTextual1", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ده. + /// + internal static string CardinalTextual10 { + get { + return ResourceManager.GetString("CardinalTextual10", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to صد. + /// + internal static string CardinalTextual100 { + get { + return ResourceManager.GetString("CardinalTextual100", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to هزار. + /// + internal static string CardinalTextual1000 { + get { + return ResourceManager.GetString("CardinalTextual1000", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to یازده. + /// + internal static string CardinalTextual11 { + get { + return ResourceManager.GetString("CardinalTextual11", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to دوازده. + /// + internal static string CardinalTextual12 { + get { + return ResourceManager.GetString("CardinalTextual12", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to سیزده. + /// + internal static string CardinalTextual13 { + get { + return ResourceManager.GetString("CardinalTextual13", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to چهارده. + /// + internal static string CardinalTextual14 { + get { + return ResourceManager.GetString("CardinalTextual14", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to پانزده. + /// + internal static string CardinalTextual15 { + get { + return ResourceManager.GetString("CardinalTextual15", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to شانزده. + /// + internal static string CardinalTextual16 { + get { + return ResourceManager.GetString("CardinalTextual16", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to هفده. + /// + internal static string CardinalTextual17 { + get { + return ResourceManager.GetString("CardinalTextual17", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to هجده. + /// + internal static string CardinalTextual18 { + get { + return ResourceManager.GetString("CardinalTextual18", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to نوزده. + /// + internal static string CardinalTextual19 { + get { + return ResourceManager.GetString("CardinalTextual19", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to دو. + /// + internal static string CardinalTextual2 { + get { + return ResourceManager.GetString("CardinalTextual2", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to بیست. + /// + internal static string CardinalTextual20 { + get { + return ResourceManager.GetString("CardinalTextual20", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to دویست. + /// + internal static string CardinalTextual200 { + get { + return ResourceManager.GetString("CardinalTextual200", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to سه. + /// + internal static string CardinalTextual3 { + get { + return ResourceManager.GetString("CardinalTextual3", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to سی. + /// + internal static string CardinalTextual30 { + get { + return ResourceManager.GetString("CardinalTextual30", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to سیصد. + /// + internal static string CardinalTextual300 { + get { + return ResourceManager.GetString("CardinalTextual300", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to چهار. + /// + internal static string CardinalTextual4 { + get { + return ResourceManager.GetString("CardinalTextual4", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to چهل. + /// + internal static string CardinalTextual40 { + get { + return ResourceManager.GetString("CardinalTextual40", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to چهارصد. + /// + internal static string CardinalTextual400 { + get { + return ResourceManager.GetString("CardinalTextual400", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to پنج. + /// + internal static string CardinalTextual5 { + get { + return ResourceManager.GetString("CardinalTextual5", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to پنجاه. + /// + internal static string CardinalTextual50 { + get { + return ResourceManager.GetString("CardinalTextual50", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to پانصد. + /// + internal static string CardinalTextual500 { + get { + return ResourceManager.GetString("CardinalTextual500", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to شش. + /// + internal static string CardinalTextual6 { + get { + return ResourceManager.GetString("CardinalTextual6", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to شصت. + /// + internal static string CardinalTextual60 { + get { + return ResourceManager.GetString("CardinalTextual60", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ششصد. + /// + internal static string CardinalTextual600 { + get { + return ResourceManager.GetString("CardinalTextual600", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to هفت. + /// + internal static string CardinalTextual7 { + get { + return ResourceManager.GetString("CardinalTextual7", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to هفتاد. + /// + internal static string CardinalTextual70 { + get { + return ResourceManager.GetString("CardinalTextual70", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to هفتصد. + /// + internal static string CardinalTextual700 { + get { + return ResourceManager.GetString("CardinalTextual700", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to هشت. + /// + internal static string CardinalTextual8 { + get { + return ResourceManager.GetString("CardinalTextual8", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to هشتاد. + /// + internal static string CardinalTextual80 { + get { + return ResourceManager.GetString("CardinalTextual80", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to هشتصد. + /// + internal static string CardinalTextual800 { + get { + return ResourceManager.GetString("CardinalTextual800", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to نه. + /// + internal static string CardinalTextual9 { + get { + return ResourceManager.GetString("CardinalTextual9", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to نود. + /// + internal static string CardinalTextual90 { + get { + return ResourceManager.GetString("CardinalTextual90", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to نهصد. + /// + internal static string CardinalTextual900 { + get { + return ResourceManager.GetString("CardinalTextual900", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to اول. + /// + internal static string First { + get { + return ResourceManager.GetString("First", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} ام. + /// + internal static string GenericOrdinalFormat { + get { + return ResourceManager.GetString("GenericOrdinalFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0:#.##} میلیارد. + /// + internal static string NumericBillions { + get { + return ResourceManager.GetString("NumericBillions", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0:#.##} میلیون. + /// + internal static string NumericMillions { + get { + return ResourceManager.GetString("NumericMillions", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0:#.##}. + /// + internal static string NumericOnes { + get { + return ResourceManager.GetString("NumericOnes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0:#.##} هزار. + /// + internal static string NumericThousands { + get { + return ResourceManager.GetString("NumericThousands", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to صفرم. + /// + internal static string OrdinalTextual0 { + get { + return ResourceManager.GetString("OrdinalTextual0", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to یکم. + /// + internal static string OrdinalTextual1 { + get { + return ResourceManager.GetString("OrdinalTextual1", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to دهم. + /// + internal static string OrdinalTextual10 { + get { + return ResourceManager.GetString("OrdinalTextual10", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to صدم. + /// + internal static string OrdinalTextual100 { + get { + return ResourceManager.GetString("OrdinalTextual100", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to هزارم. + /// + internal static string OrdinalTextual1000 { + get { + return ResourceManager.GetString("OrdinalTextual1000", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to یازدهم. + /// + internal static string OrdinalTextual11 { + get { + return ResourceManager.GetString("OrdinalTextual11", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to دوازدهم. + /// + internal static string OrdinalTextual12 { + get { + return ResourceManager.GetString("OrdinalTextual12", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to سیزدهم. + /// + internal static string OrdinalTextual13 { + get { + return ResourceManager.GetString("OrdinalTextual13", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to چهاردهم. + /// + internal static string OrdinalTextual14 { + get { + return ResourceManager.GetString("OrdinalTextual14", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to پانزدهم. + /// + internal static string OrdinalTextual15 { + get { + return ResourceManager.GetString("OrdinalTextual15", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to شانزدهم. + /// + internal static string OrdinalTextual16 { + get { + return ResourceManager.GetString("OrdinalTextual16", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to هفدهم. + /// + internal static string OrdinalTextual17 { + get { + return ResourceManager.GetString("OrdinalTextual17", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to هجدهم. + /// + internal static string OrdinalTextual18 { + get { + return ResourceManager.GetString("OrdinalTextual18", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to نوزدهم. + /// + internal static string OrdinalTextual19 { + get { + return ResourceManager.GetString("OrdinalTextual19", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to دوم. + /// + internal static string OrdinalTextual2 { + get { + return ResourceManager.GetString("OrdinalTextual2", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to بیستم. + /// + internal static string OrdinalTextual20 { + get { + return ResourceManager.GetString("OrdinalTextual20", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to دویستم. + /// + internal static string OrdinalTextual200 { + get { + return ResourceManager.GetString("OrdinalTextual200", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to سوم. + /// + internal static string OrdinalTextual3 { + get { + return ResourceManager.GetString("OrdinalTextual3", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to سی ام. + /// + internal static string OrdinalTextual30 { + get { + return ResourceManager.GetString("OrdinalTextual30", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to سیصدم. + /// + internal static string OrdinalTextual300 { + get { + return ResourceManager.GetString("OrdinalTextual300", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to چهارم. + /// + internal static string OrdinalTextual4 { + get { + return ResourceManager.GetString("OrdinalTextual4", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to چهلم. + /// + internal static string OrdinalTextual40 { + get { + return ResourceManager.GetString("OrdinalTextual40", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to چهارصدم. + /// + internal static string OrdinalTextual400 { + get { + return ResourceManager.GetString("OrdinalTextual400", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to پنجم. + /// + internal static string OrdinalTextual5 { + get { + return ResourceManager.GetString("OrdinalTextual5", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to پنجاهم. + /// + internal static string OrdinalTextual50 { + get { + return ResourceManager.GetString("OrdinalTextual50", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to پانصدم. + /// + internal static string OrdinalTextual500 { + get { + return ResourceManager.GetString("OrdinalTextual500", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ششم. + /// + internal static string OrdinalTextual6 { + get { + return ResourceManager.GetString("OrdinalTextual6", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to شصتم. + /// + internal static string OrdinalTextual60 { + get { + return ResourceManager.GetString("OrdinalTextual60", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ششصدم. + /// + internal static string OrdinalTextual600 { + get { + return ResourceManager.GetString("OrdinalTextual600", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to هفتم. + /// + internal static string OrdinalTextual7 { + get { + return ResourceManager.GetString("OrdinalTextual7", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to هفتادم. + /// + internal static string OrdinalTextual70 { + get { + return ResourceManager.GetString("OrdinalTextual70", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to هفتصدم. + /// + internal static string OrdinalTextual700 { + get { + return ResourceManager.GetString("OrdinalTextual700", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to هشتم. + /// + internal static string OrdinalTextual8 { + get { + return ResourceManager.GetString("OrdinalTextual8", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to هشتادم. + /// + internal static string OrdinalTextual80 { + get { + return ResourceManager.GetString("OrdinalTextual80", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to هشتصدم. + /// + internal static string OrdinalTextual800 { + get { + return ResourceManager.GetString("OrdinalTextual800", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to نهم. + /// + internal static string OrdinalTextual9 { + get { + return ResourceManager.GetString("OrdinalTextual9", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to نودم. + /// + internal static string OrdinalTextual90 { + get { + return ResourceManager.GetString("OrdinalTextual90", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to نهصدم. + /// + internal static string OrdinalTextual900 { + get { + return ResourceManager.GetString("OrdinalTextual900", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to و . + /// + internal static string TextualJointer { + get { + return ResourceManager.GetString("TextualJointer", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to منهای {0}. + /// + internal static string TextualNegativeFormat { + get { + return ResourceManager.GetString("TextualNegativeFormat", resourceCulture); + } + } + } +} diff --git a/src/General/Text/NumericStringUtilsResources.resx b/src/General/Text/NumericStringUtilsResources.resx new file mode 100644 index 0000000..fd95c60 --- /dev/null +++ b/src/General/Text/NumericStringUtilsResources.resx @@ -0,0 +1,372 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + صفر + + + یک + + + ده + + + صد + + + هزار + + + یازده + + + دوازده + + + سیزده + + + چهارده + + + پانزده + + + شانزده + + + هفده + + + هجده + + + نوزده + + + دو + + + بیست + + + دویست + + + سه + + + سی + + + سیصد + + + چهار + + + چهل + + + چهارصد + + + پنج + + + پنجاه + + + پانصد + + + شش + + + شصت + + + ششصد + + + هفت + + + هفتاد + + + هفتصد + + + هشت + + + هشتاد + + + هشتصد + + + نه + + + نود + + + نهصد + + + اول + + + {0} ام + + + {0:#.##} میلیارد + + + {0:#.##} میلیون + + + {0:#.##} + + + {0:#.##} هزار + + + صفرم + + + یکم + + + دهم + + + صدم + + + هزارم + + + یازدهم + + + دوازدهم + + + سیزدهم + + + چهاردهم + + + پانزدهم + + + شانزدهم + + + هفدهم + + + هجدهم + + + نوزدهم + + + دوم + + + بیستم + + + دویستم + + + سوم + + + سی ام + + + سیصدم + + + چهارم + + + چهلم + + + چهارصدم + + + پنجم + + + پنجاهم + + + پانصدم + + + ششم + + + شصتم + + + ششصدم + + + هفتم + + + هفتادم + + + هفتصدم + + + هشتم + + + هشتادم + + + هشتصدم + + + نهم + + + نودم + + + نهصدم + + + و + + + منهای {0} + + \ No newline at end of file diff --git a/src/General/Text/PersianCharacterNormalizer.cs b/src/General/Text/PersianCharacterNormalizer.cs new file mode 100644 index 0000000..024763d --- /dev/null +++ b/src/General/Text/PersianCharacterNormalizer.cs @@ -0,0 +1,53 @@ +using System; + +namespace hydrogen.General.Text +{ + /// + /// Source code copied from PersianNormalizer class within Lucene.Net + /// + public class PersianCharacterNormalizer + { + public const char Yeh = 'ي'; + public const char FarsiYeh = 'ی'; + public const char YehBarree = 'ے'; + public const char Keheh = 'ک'; + public const char Kaf = 'ك'; + public const char HamzaAbove = 'ٔ'; + public const char HehYeh = 'ۀ'; + public const char HehGoal = 'ہ'; + public const char Heh = 'ه'; + + public static int Normalize(char[] s, int len) + { + for (int pos = 0; pos < len; ++pos) + { + switch (s[pos]) + { + case 'ۀ': + case 'ہ': + s[pos] = 'ه'; + break; + case 'ی': + case 'ے': + s[pos] = 'ي'; + break; + case 'ٔ': + len = Delete(s, pos, len); + --pos; + break; + case 'ک': + s[pos] = 'ك'; + break; + } + } + return len; + } + + private static int Delete(char[] s, int pos, int len) + { + if (pos < len) + Array.Copy(s, pos + 1, s, pos, len - pos - 1); + return len - 1; + } + } +} \ No newline at end of file diff --git a/src/General/Text/RegexCache.cs b/src/General/Text/RegexCache.cs new file mode 100644 index 0000000..aabd990 --- /dev/null +++ b/src/General/Text/RegexCache.cs @@ -0,0 +1,74 @@ +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; +using hydrogen.General.Collections; + +namespace hydrogen.General.Text +{ + public class RegexCache + { + private readonly LruCache _cache; + + public RegexCache(int size) + { + _cache = new LruCache(size); + } + + public Regex GetPatternForRegex(string regex) + { + Regex pattern = _cache.Get(regex); + if (pattern == null) + { + pattern = new Regex(regex); + _cache.Put(regex, pattern); + } + return pattern; + } + + // This method is used for testing. + public bool ContainsRegex(string regex) + { + return _cache.ContainsKey(regex); + } + + private class LruCache + { + // LinkedHashMap offers a straightforward implementation of LRU cache. + private readonly LruDictionary _map; + private readonly int _size; + + public LruCache(int size) + { + _size = size; + _map = new LruDictionary(); + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public TV Get(TK key) + { + TV result; + if (!_map.TryGetValue(key, out result)) + return default(TV); + + return result; + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public void Put(TK key, TV value) + { + _map[key] = value; + if (_map.Count > _size) + { + var first = _map.Keys.First(); + _map.Remove(first); + } + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public bool ContainsKey(TK key) + { + return _map.ContainsKey(key); + } + } + } +} \ No newline at end of file diff --git a/src/General/Text/RegexExtensions.cs b/src/General/Text/RegexExtensions.cs new file mode 100644 index 0000000..d107b04 --- /dev/null +++ b/src/General/Text/RegexExtensions.cs @@ -0,0 +1,27 @@ +using System.Text.RegularExpressions; + +namespace hydrogen.General.Text +{ + public static class RegexExtensions + { + public static bool IsMatchWhole(this Regex regex, string input) + { + return regex.Match(input).SuccessWholeInput(input); + } + + public static bool IsMatchStart(this Regex regex, string input) + { + return regex.Match(input).SuccessInputStart(); + } + + public static bool SuccessWholeInput(this Match match, string input) + { + return match.Success && match.Index == 0 && match.Length == input.Length; + } + + public static bool SuccessInputStart(this Match match) + { + return match.Success && match.Index == 0; + } + } +} diff --git a/src/General/Text/RegexUtils.cs b/src/General/Text/RegexUtils.cs new file mode 100644 index 0000000..cabf45f --- /dev/null +++ b/src/General/Text/RegexUtils.cs @@ -0,0 +1,10 @@ +namespace hydrogen.General.Text +{ + public static class RegexUtils + { + public static string CreateWholeInputRegex(string regex) + { + return "\\A(?:" + regex + ")\\z"; + } + } +} \ No newline at end of file diff --git a/src/General/Text/SmsMessageUtils.cs b/src/General/Text/SmsMessageUtils.cs new file mode 100644 index 0000000..50e6317 --- /dev/null +++ b/src/General/Text/SmsMessageUtils.cs @@ -0,0 +1,19 @@ +namespace hydrogen.General.Text +{ + public static class SmsMessageUtils + { + public const int MaxUnicodeFirstSegmentLength = 70; + public const int MaxUnicodeSegmentLength = 67; + + public static int CalculateNumberOfSegments(string messageText) + { + if (messageText == null) + return 0; + + if (messageText.Length <= MaxUnicodeFirstSegmentLength) + return 1; + + return (messageText.Length + MaxUnicodeSegmentLength - 1)/MaxUnicodeSegmentLength; + } + } +} \ No newline at end of file diff --git a/src/General/Text/StringBuilderExtensions.cs b/src/General/Text/StringBuilderExtensions.cs new file mode 100644 index 0000000..f8d3b11 --- /dev/null +++ b/src/General/Text/StringBuilderExtensions.cs @@ -0,0 +1,23 @@ +using System.Text; + +namespace hydrogen.General.Text +{ + public static class StringBuilderExtensions + { + public static StringBuilder AppendSeparator(this StringBuilder builder, string separator) + { + if (builder.Length > 0) + return builder.Append(separator); + + return builder; + } + + public static StringBuilder AppendIf(this StringBuilder builder, bool condition, string value) + { + if (condition) + builder.Append(value); + + return builder; + } + } +} \ No newline at end of file diff --git a/src/General/Text/StringUtils.cs b/src/General/Text/StringUtils.cs new file mode 100644 index 0000000..bab4728 --- /dev/null +++ b/src/General/Text/StringUtils.cs @@ -0,0 +1,20 @@ +using System.Linq; + +namespace hydrogen.General.Text +{ + public static class StringUtils + { + public static string JoinNonEmpty(string separator, params string[] strings) + { + return string.Join(separator, strings.Where(s => !string.IsNullOrWhiteSpace(s)).ToArray()); + } + + public static string PrependIfNotEmpty(string prefix, string content) + { + if (string.IsNullOrWhiteSpace(content)) + return string.Empty; + + return prefix + content; + } + } +} \ No newline at end of file diff --git a/src/General/Utils/CsvUtils.cs b/src/General/Utils/CsvUtils.cs new file mode 100644 index 0000000..cd69b30 --- /dev/null +++ b/src/General/Utils/CsvUtils.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace hydrogen.General.Utils +{ + public static class CsvUtils + { + public static string ToCsvString(IEnumerable enumerable, Func toString = null) + { + if (enumerable == null) + return string.Empty; + + toString = toString ?? (t => t.ToString()); + return string.Join(",", enumerable.Select(toString)); + } + + public static IEnumerable ParseInt32Enumerable(string csv) + { + if (string.IsNullOrWhiteSpace(csv)) + return Enumerable.Empty(); + + return csv.Split(',').Where(v => !string.IsNullOrWhiteSpace(v)).Select(int.Parse); + } + + public static IEnumerable ParseInt64Enumerable(string csv) + { + if (string.IsNullOrWhiteSpace(csv)) + return Enumerable.Empty(); + + return csv.Split(',').Where(v => !string.IsNullOrWhiteSpace(v)).Select(long.Parse); + } + + public static IEnumerable ParseEnumArray(string csv) + { + if (string.IsNullOrWhiteSpace(csv)) + return Enumerable.Empty(); + + return csv.Split(',').Where(p => !string.IsNullOrWhiteSpace(p)).Select(p => (T) Enum.ToObject(typeof (T), int.Parse(p))); + } + } +} \ No newline at end of file diff --git a/src/General/Utils/DisposableExtensions.cs b/src/General/Utils/DisposableExtensions.cs new file mode 100644 index 0000000..1d91ec2 --- /dev/null +++ b/src/General/Utils/DisposableExtensions.cs @@ -0,0 +1,23 @@ +using System; + +namespace hydrogen.General.Utils +{ + public static class DisposableExtensions + { + public static void Use(this TDisposable disposable, Action a) where TDisposable : IDisposable + { + using (disposable) + { + a(disposable); + } + } + + public static TResult Use(this TDisposable disposable, Func f) where TDisposable : IDisposable + { + using (disposable) + { + return f(disposable); + } + } + } +} \ No newline at end of file diff --git a/src/General/Utils/GuidUtils.cs b/src/General/Utils/GuidUtils.cs new file mode 100644 index 0000000..f6b6a75 --- /dev/null +++ b/src/General/Utils/GuidUtils.cs @@ -0,0 +1,23 @@ +using System; + +namespace hydrogen.General.Utils +{ + public static class GuidUtils + { + public static string ToUrlFriendly(this Guid guid) + { + string enc = Convert.ToBase64String(guid.ToByteArray()); + enc = enc.Replace("/", "_"); + enc = enc.Replace("+", "-"); + return enc.Substring(0, 22); + } + + public static Guid ParseUrlFriendlyGuid(string urlFriendlyGuid) + { + urlFriendlyGuid = urlFriendlyGuid.Replace("_", "/"); + urlFriendlyGuid = urlFriendlyGuid.Replace("-", "+"); + byte[] buffer = Convert.FromBase64String(urlFriendlyGuid + "=="); + return new Guid(buffer); + } + } +} \ No newline at end of file diff --git a/src/General/Utils/ObjectUtils.cs b/src/General/Utils/ObjectUtils.cs new file mode 100644 index 0000000..5caab45 --- /dev/null +++ b/src/General/Utils/ObjectUtils.cs @@ -0,0 +1,12 @@ +namespace hydrogen.General.Utils +{ + public static class ObjectUtils + { + public static void Swap(ref T t1, ref T t2) + { + var temp = t1; + t1 = t2; + t2 = temp; + } + } +} \ No newline at end of file diff --git a/src/General/Utils/RandomUtil.cs b/src/General/Utils/RandomUtil.cs new file mode 100644 index 0000000..fee9b62 --- /dev/null +++ b/src/General/Utils/RandomUtil.cs @@ -0,0 +1,24 @@ +using System; +using System.Threading; + +namespace hydrogen.General.Utils +{ + /// + /// Provides thread-based random number generators, and guarantees unique seeds for their initialization. + /// Code copied from Jon Skeet's website, from the following URL: + /// http://csharpindepth.com/Articles/Chapter12/Random.aspx + /// + public static class RandomProvider + { + private static int _seed = Environment.TickCount; + + private static readonly ThreadLocal RandomWrapper = new ThreadLocal(() => + new Random(Interlocked.Increment(ref _seed)) + ); + + public static Random GetThreadRandom() + { + return RandomWrapper.Value; + } + } +} \ No newline at end of file diff --git a/src/General/Utils/UtilResources.Designer.cs b/src/General/Utils/UtilResources.Designer.cs new file mode 100644 index 0000000..eae0236 --- /dev/null +++ b/src/General/Utils/UtilResources.Designer.cs @@ -0,0 +1,126 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace hydrogen.General.Utils { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class UtilResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal UtilResources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("hydrogen.General.Utils.UtilResources", typeof(UtilResources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to محیط تولید. + /// + internal static string ApplicationEnvironmentType_Development { + get { + return ResourceManager.GetString("ApplicationEnvironmentType_Development", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to محیط بهره برداری. + /// + internal static string ApplicationEnvironmentType_Production { + get { + return ResourceManager.GetString("ApplicationEnvironmentType_Production", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to محیط آماده سازی. + /// + internal static string ApplicationEnvironmentType_Staging { + get { + return ResourceManager.GetString("ApplicationEnvironmentType_Staging", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to محیط تست. + /// + internal static string ApplicationEnvironmentType_Test { + get { + return ResourceManager.GetString("ApplicationEnvironmentType_Test", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to خیر. + /// + internal static string Boolean_False { + get { + return ResourceManager.GetString("Boolean_False", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to بله. + /// + internal static string Boolean_True { + get { + return ResourceManager.GetString("Boolean_True", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ----. + /// + internal static string SelectItem_NotSelected { + get { + return ResourceManager.GetString("SelectItem_NotSelected", resourceCulture); + } + } + } +} diff --git a/src/General/Utils/UtilResources.resx b/src/General/Utils/UtilResources.resx new file mode 100644 index 0000000..7519649 --- /dev/null +++ b/src/General/Utils/UtilResources.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + محیط تولید + + + محیط بهره برداری + + + محیط آماده سازی + + + محیط تست + + + خیر + + + بله + + + ---- + + \ No newline at end of file diff --git a/src/hydrogen.sln b/src/hydrogen.sln index 455cf15..f8319d2 100644 --- a/src/hydrogen.sln +++ b/src/hydrogen.sln @@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27703.2035 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "General", "General\General.csproj", "{2436344A-7627-4F64-81FA-E9B5AB6FB844}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "General", "General\General.csproj", "{2436344A-7627-4F64-81FA-E9B5AB6FB844}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoMapper", "AutoMapper\AutoMapper.csproj", "{4280757D-3D3D-4DBD-9F9C-7F89F1027452}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +17,10 @@ Global {2436344A-7627-4F64-81FA-E9B5AB6FB844}.Debug|Any CPU.Build.0 = Debug|Any CPU {2436344A-7627-4F64-81FA-E9B5AB6FB844}.Release|Any CPU.ActiveCfg = Release|Any CPU {2436344A-7627-4F64-81FA-E9B5AB6FB844}.Release|Any CPU.Build.0 = Release|Any CPU + {4280757D-3D3D-4DBD-9F9C-7F89F1027452}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4280757D-3D3D-4DBD-9F9C-7F89F1027452}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4280757D-3D3D-4DBD-9F9C-7F89F1027452}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4280757D-3D3D-4DBD-9F9C-7F89F1027452}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE