79627963

Date: 2025-05-19 01:07:15
Score: 0.5
Natty:
Report link
    // Look Ma! Recursion everywhere!
    public sealed partial class VersionMgmt
    {
        #region Properties
        private char _separator = '.';
        // NOTE: If you want to increase this ( i.e. >5 ), the AsInt accessor needs modification!
        private const byte MAX_DEPTH = 5; 
        #endregion

        #region Constructors
        /// <summary>Instantiate the class via the <seealso cref="Parse(string)"/> static function.</summary>
        private VersionMgmt() { }

        public VersionMgmt( params uint[] values )
        {
            if (values is not null && values.Length > 0)
            {
                // NOTE: C# Ranges are exclusive of the last value specified so, "..6" = FIVE items!
                if (values.Length >= MAX_DEPTH) values = values[ ..(MAX_DEPTH + 1)]; 
                this.Value = values[ 0 ];
                if ( values.Length > 1)
                    this.Add( new VersionMgmt( values[ 1.. ] ) );
            }
        }
        #endregion

        #region Accessors
        public VersionMgmt this[ int index ]
        {
            get
            {
                if ( index < 0 ) index = Length - 1;
                if ( index >= Length ) throw new ArgumentOutOfRangeException( $"The supplied index, {index} exceeds the size of this Version value ({Length})." );

                return index == 0 ? this : HasChild ? this.Child[ index - 1 ] : null;
            }
        }

        public uint Value { get; private set; } = 1;

        private bool HasChild => this.Child is not null;

        private bool IsRoot => this.Parent is null;

        private VersionMgmt Root => IsRoot ? this : this.Parent.Root;

        private VersionMgmt Parent { get; set; } = null;

        public int Length => HasChild ? Child.Length + 1 : 1;

        private VersionMgmt Child { get; set; } = null;

        public char Separator
        {
            get => this._separator;
            set { if ( ".:/-".Contains( value ) ) { this._separator = value; } }
        }

        public string Suffix { get; set; } = string.Empty;

        /// <summary>Facilitates basic serialization by encoding the version as a 64-bit unsigned integer.</summary>
        /// <remarks>
        /// When using this function, the maximum value that can be stored for each segment is:<br/>
        /// Segment 1-3:  0-1023 ( 0x003ff / 10-bits ea)<br/>
        /// Segment 4-5: 0-131070 ( 0x1ffff / 17-bits ea)<br/>
        /// Max version value: 1023.1023.1023.131070.131070 (64 bits)
        /// </remarks>
        public ulong AsInt
        {
            get => Length switch
            {
                < 3 => (this.Value & 0x1ffff) | (HasChild ? (Child.Value << 17) : 0), // range 0 - 131,070
                _ => (this.Value & 0x003ff) | Child.Value << 10, // range 0 - 1023
            };
            set
            {
                this.Value = (uint)(Length < 3 ? (value & 0x03ff) : (value & 0x1ffff));
                if ( HasChild ) this.Child.AsInt = Length < 3 ? value >> 10 : value >> 17;
            }
        }
        #endregion

        #region Operators
        public static implicit operator Version( VersionMgmt source ) => source is null ? new Version() : new Version( source.ToString( '.', 4 ) );
        public static implicit operator VersionMgmt( Version source ) => source is null ? new( 1,0,0,0 ) : Parse( source.ToString() );
        public static implicit operator VersionMgmt( ulong source ) => new() { AsInt = source };
        public static implicit operator ulong( VersionMgmt source ) => source is null ? 0 : source.AsInt;
        public static implicit operator uint[]( VersionMgmt source ) => source is null ? [] : source.ToUIntArray();
        public static implicit operator VersionMgmt( uint[] source ) => source is null || source.Length == 0 ? new( 1, 0, 0, 0 ) : new VersionMgmt( source );
        #endregion

        #region Methods
        /// <summary>Faclitates incrementing the version number.</summary>
        /// <param name="value">The amount by which to increment it.</param>
        /// <param name="depth">The 0-based index of the version element to increment.</param>
        /// <returns>The new value of the element incremented.</returns>
        /// <remarks>If the supplied <paramref name="depth"/> is greater than the length of the version,
        /// the last element will be incremented. If it's a negative number, the depth counts from the end backwards 
        /// (i.e. -1 = last value, -2 = second last, etc.)
        /// </remarks>
        public VersionMgmt Increment( uint value = 1, int depth = -1 )
        {
            if ( depth < 0 ) depth = Math.Max(0,Length + depth);

            if ( (depth > 0) && HasChild )
                this.Child.Increment( value, --depth );
            else
                this.Value += value;

            return this;
        }

        /// <summary>Adds a supplied child node to the version.</summary>
        /// <param name="child">The child node to add.</param>
        /// <returns>The index of the newly added child.</returns>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="ArgumentNullException"></exception>
        /// <remarks>
        /// A version may only comprise up to MAX_DEPTH nodes. Attempting to add beyond this limit 
        /// generates an <seealso cref="InvalidOperationException"/>.<br/>New nodes <i>are always appended to the end</i> 
        /// of the version sequence, regardless of which node actually calls this method.</remarks>
        private int Add( VersionMgmt child )
        {
            if ( Root.Length >= MAX_DEPTH ) throw new InvalidOperationException( "This version is full." );
            ArgumentNullException.ThrowIfNull( child );

            if ( this.Child is null )
            {
                child.Parent = this;
                this.Child = child;
            }
            else 
                this.Child.Add( child );

            return Length;
        }

        private int Add( string value ) => this.Add( Parse( value ) );

        /// <summary>Returns the Version as an array of individual <see cref="VersionMgmt"/> objects.</summary>
        /// <remarks><b>NOTE</b>: If the passed <paramref name="depth"/> value is zero, the returned result will be a zero-length array!</remarks>
        public VersionMgmt[] ToArray( byte depth = MAX_DEPTH )
        {
            if (depth == 0) return [];

            VersionMgmt[] result = new VersionMgmt[ Math.Min( Length, depth ) ];
            for (int i = 0; i < result.Length; i++)
                result[ i ] = this[ i ];
            return result;
        }

        /// <summary>Returns the value of the version as an array of <see cref="uint"/> values.</summary>
        public uint[] ToUIntArray( byte depth = MAX_DEPTH )
        {
            if (depth == 0) return [];

            var segments = ToArray( depth );
            var result = new uint[ segments.Length ];
            for (int i = 0; i < segments.Length; i++ ) result[i] = segments[ i ].Value;
            return result;
        }

        /// <summary>The full version value with its natural separators and appended suffix.</summary>
        public override string ToString() => this.ToString( -1 );

        /// <summary>The full version value with its natural separators and optional appended suffix.</summary>
        public string ToString( bool suppressSuffix ) => this.ToString( -1, suppressSuffix );

        /// <summary>Facilitates creating a subset string of the full version value, limited to a specified depth.</summary>
        /// <param name="maxDepth">The <i>maximum</i> number of elements to include in the result. If this exceeds the number of elements, only they will be returned.</param>
        /// <returns>A string containing the number of version elements managed up to the depth specified, using the natural separation characters of the stored version.</returns>
        /// <remarks>If the specified depth is negative, or exceeds the length of the managed version value, the entire value will be returned.</remarks>
        public string ToString( int maxDepth, bool suppressSuffix = false ) => this.ToString( this._separator, maxDepth ) + (suppressSuffix ? "" : Suffix );

        /// <summary>Facilitates returning the version string with a designated separator, to a specified depth.</summary>
        /// <param name="divider">What character to use as a separator. <b>This overrides the stored/natural separator values!</b></param>
        /// <param name="maxDepth">The maximum number of version elements to include.</param>
        internal string ToString( char divider, int maxDepth = -1 )
        {
            if (maxDepth  < 0) maxDepth = Math.Max(0,Length + maxDepth);

            return $"{Value}" + ((maxDepth > 0) && HasChild ? $"{divider}" + this.Child.ToString( divider, maxDepth - 1 ) : "");
        }

        /// <summary>Given a string, searches for a valid version number, and parses it into a <seealso cref="VersionMgmt"/> object.</summary>
        /// <remarks>
        /// To prevent abuse, the parser only reads the first <b>six</b> (6) version elements it finds in the supplied string. If no
        /// valid version values can be found in the supplied string, an <seealso cref="ArgumentException"/> will be thrown. If 
        /// </remarks>
        public static VersionMgmt Parse( string source, uint increment = 0, int depth = -1 )
        {
            VersionMgmt result;
            source ??= string.Empty;
            string suffix = VersionMgmtSuffixCapture_Rx().Match( source ).Value.Trim();
            if ( !string.IsNullOrWhiteSpace( source = VersionMgmtCleaner_Rx().Replace( source, "" ) ) )
            {
                Match m = VersionMgmtValidation_Rx().Match( source );
                if ( m.Success && m.Groups[ "ver" ].Success )
                {
                    m = VersionMgmtParser2_Rx().Match( m.Groups[ "ver" ].Value );
                    if ( m.Success && m.Groups[ "value" ].Success )
                    {
                        result = new()
                        {
                            Value = uint.Parse( m.Groups[ "value" ].Value ),
                            Suffix = suffix,
                        };
                        if ( m.Groups[ "div" ].Success )
                        {
                            result.Separator = m.Groups[ "div" ].Value[ 0 ];
                            if ( m.Groups[ "remainder" ].Success ) result.Add( m.Groups[ "remainder" ].Value );
                        }
                        if ( increment > 0 ) result.Increment( increment, depth );
                        return result;
                    }
                }
                else
                {
                    if (VersionMgmtParser3_Rx().IsMatch( source ))
                    {
                        result = new() { Value = uint.Parse( source ) };
                        if (increment > 0) result.Increment( increment,depth );
                        return result;
                    }
                }
            }
            throw new ArgumentException( "No valid versions were found within the supplied string!" );
        }

        /// <summary>Tries to parse the supplied string.</summary>
        /// <param name="source">The string to attempt to parse.</param>
        /// <param name="result">If successful, a new <see cref="VersionMgmt"/> object derived from the supplied <paramref name="source"/>.</param>
        /// <param name="increment">(Optional): If provided, will increment the generated version according to the <see cref="VersionMgmt.Increment(uint, int)"/> rules.</param>
        /// <param name="depth">(Optional): See: <seealso cref="VersionMgmt.Increment(uint, int)"/>.</param>
        /// <returns><b>TRUE</b> if parsing of the supplied string was successful.</returns>
        public static bool TryParse( string source, out VersionMgmt result, uint increment = 0, int depth = -1 )
        {
            result = null;
            try
            {
                result = Parse( source, increment, depth );
                return true;
            }
            catch (OverflowException) { }
            catch (ArgumentException) { }
            return false;
        }

        /// <summary>Tests a supplied string and reports if a valid version value was detected within it.</summary>
        /// <returns><b>TRUE</b> if the supplied string contains a parseable version value.</returns>
        public static bool ContainsVersion( string source )
        {
            if ( string.IsNullOrWhiteSpace( source ) ) return false;
            //return Regex.IsMatch( source, @"(?<ver>(?:[\d]+[.:/-]){1,5}[\d]+)", RegexOptions.None );
            return VersionMgmtValidation_Rx().IsMatch( source );
        }

        [GeneratedRegex( @"(?<ver>(?:[\d]+[.:/-]){1,5}[\d]+)", RegexOptions.None )]
        public static partial Regex VersionMgmtValidation_Rx();

        [GeneratedRegex( @"^(?<value>[\d]+)(?<div>[.:/-])?(?<remainder>.+)?$", RegexOptions.None )]
        private static partial Regex VersionMgmtParser2_Rx();

        [GeneratedRegex( @"^[\d]+$", RegexOptions.None )] private static partial Regex VersionMgmtParser3_Rx();

        [GeneratedRegex( @"(?:[^\d]+$|[^\d./:-])" , RegexOptions.None )] private static partial Regex VersionMgmtCleaner_Rx();

        [GeneratedRegex( @" ?[^\S]+$" )] private static partial Regex VersionMgmtSuffixCapture_Rx();
        #endregion
    }

This is a simple, self-contained Version management class for C#.

It handles variable-length versions from just 1, up-to 5 numeric segments (plus an unmanaged alphanumeric suffix), supports incrementing, optional customized suffixes, and integrated parsing/translation to/from ulong, System.Version, string, and uint[]

Reasons:
  • RegEx Blacklisted phrase (1.5): fix ?
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (0.5):
Posted by: NetXpert