Normalize incomplete version strings to X.Y.Z format. Complete versions (3+ numeric parts) are left unchanged. Incomplete versions are padded with zeros. Pre-release versions (e.g., 4.0.0-ALPHA) are handled correctly. Supports semantic versioning where "3.1"
(version: str)
| 750 | |
| 751 | @staticmethod |
| 752 | def _normalize_version(version: str) -> str: |
| 753 | """ |
| 754 | Normalize incomplete version strings to X.Y.Z format. |
| 755 | Complete versions (3+ numeric parts) are left unchanged. |
| 756 | Incomplete versions are padded with zeros. |
| 757 | Pre-release versions (e.g., 4.0.0-ALPHA) are handled correctly. |
| 758 | |
| 759 | Supports semantic versioning where "3.1" matches "3.1.0". |
| 760 | But keeps distinct versions separate: 3.6.0, 3.6.1, 3.6.2 are NOT normalized to the same value. |
| 761 | |
| 762 | Examples: |
| 763 | - "3.1" -> "3.1.0" (2 parts, pad to 3) |
| 764 | - "3" -> "3.0.0" (1 part, pad to 3) |
| 765 | - "3.1.0" -> "3.1.0" (3 parts, unchanged) |
| 766 | - "3.6.1" -> "3.6.1" (3 parts, unchanged) |
| 767 | - "3.6.2" -> "3.6.2" (3 parts, unchanged - NOT collapsed!) |
| 768 | - "4.0.0-ALPHA" -> "4.0.0-ALPHA" (pre-release, unchanged) |
| 769 | - "4.0-ALPHA" -> "4.0.0-ALPHA" (incomplete pre-release, pad to 3 numeric parts) |
| 770 | - "4.0.0-ALPHA.0" -> "4.0.0-ALPHA" (remove spurious .0 from pre-release) |
| 771 | - "3.1.0.0" -> "3.1.0.0" (4 parts, unchanged) |
| 772 | """ |
| 773 | # Check if this is a pre-release version (contains dash) |
| 774 | if '-' in version: |
| 775 | # Split on the dash to separate numeric version from pre-release identifier |
| 776 | base_version, prerelease = version.split('-', 1) |
| 777 | base_parts = base_version.split('.') |
| 778 | |
| 779 | # Pad the base version to 3 parts |
| 780 | while len(base_parts) < 3: |
| 781 | base_parts.append('0') |
| 782 | |
| 783 | # Take only first 3 numeric parts, then rejoin with pre-release identifier |
| 784 | # This prevents "4.0.0-ALPHA.0" from being added |
| 785 | normalized_base = '.'.join(base_parts[:3]) |
| 786 | return f"{normalized_base}-{prerelease}" |
| 787 | else: |
| 788 | # Non-pre-release version - use original logic |
| 789 | parts = version.split('.') |
| 790 | |
| 791 | # If already 3+ parts, return as-is (complete version) |
| 792 | if len(parts) >= 3: |
| 793 | return version |
| 794 | |
| 795 | # If less than 3 parts, pad with zeros to make it 3 parts |
| 796 | while len(parts) < 3: |
| 797 | parts.append('0') |
| 798 | return '.'.join(parts) |
| 799 | |
| 800 | def _version_sort_key(self, version: str) -> tuple: |
| 801 | """Convert version string to sortable tuple for proper ordering.""" |