//
// UCVersionComparison.m
// UpdateCore - https://indie.miln.eu
//
// Copyright © Graham Miln. All rights reserved. https://miln.eu
//
// This package is subject to the terms of the Artistic License 2.0.
// If a copy of the Artistic-2.0 was not distributed with this file, you can
// obtain one at https://indie.miln.eu/licence
#import "UCVersionComparison.h"
@interface UCVersionComparison ()
- (NSComparisonResult)compareComponent:(NSString*)inLHS toComponent:(NSString*)inRHS;
- (NSArray<NSString*>*)versionComponentsFromString:(NSString *)aVersion;
@end
@implementation UCVersionComparison
- (BOOL)isUnstableVersion:(NSString*)inVersion {
if (inVersion == nil) {
return NO;
}
return ([inVersion rangeOfCharacterFromSet:NSCharacterSet.letterCharacterSet].location != NSNotFound);
}
- (NSComparisonResult)compareVersion:(NSString*)inLHS toVersion:(NSString*)inRHS {
if ((inLHS == nil) && (inRHS == nil)) {
return NSOrderedSame;
} else if ([inLHS isEqualToString:inRHS]) {
return NSOrderedSame;
} else if ((inLHS != nil) && (inRHS == nil)) {
return NSOrderedDescending;
} else if ((inLHS == nil) && (inRHS != nil)) {
return NSOrderedAscending;
}
if (self.allowUnstable == NO) {
BOOL lhsUnstable = [self isUnstableVersion:inLHS];
BOOL rhsUnstable = [self isUnstableVersion:inRHS];
if (lhsUnstable && !rhsUnstable) {
return NSOrderedAscending;
} else if (!lhsUnstable && rhsUnstable) {
return NSOrderedDescending;
}
}
NSComparisonResult result = NSOrderedSame;
NSArray<NSString*>* lhsComponents = [self versionComponentsFromString:inLHS];
NSArray<NSString*>* rhsComponents = [self versionComponentsFromString:inRHS];
NSInteger i = 0;
while((result == NSOrderedSame) && ((i < lhsComponents.count) || (i < rhsComponents.count))) {
NSString* lhsComponent = (i < lhsComponents.count ? lhsComponents[i] : nil);
NSString* rhsComponent = (i < rhsComponents.count ? rhsComponents[i] : nil);
result = [self compareComponent:lhsComponent toComponent:rhsComponent];
i++;
}
return result;
}
- (NSComparisonResult)compareComponent:(NSString*)inLHS toComponent:(NSString*)inRHS {
if ((inLHS != nil) && (inRHS == nil)) {
return (([inLHS rangeOfCharacterFromSet:NSCharacterSet.decimalDigitCharacterSet].location != NSNotFound) ? NSOrderedDescending : NSOrderedAscending);
} else if ((inLHS == nil) && (inRHS != nil)) {
return (([inRHS rangeOfCharacterFromSet:NSCharacterSet.decimalDigitCharacterSet].location != NSNotFound) ? NSOrderedAscending : NSOrderedDescending);
} else if ([inLHS isEqualToString:inRHS]) {
return NSOrderedSame;
} else {
BOOL lhsAlphaOnly = ([inLHS rangeOfCharacterFromSet:NSCharacterSet.decimalDigitCharacterSet].location != NSNotFound);
BOOL rhsAlphaOnly = ([inRHS rangeOfCharacterFromSet:NSCharacterSet.decimalDigitCharacterSet].location != NSNotFound);
if ((lhsAlphaOnly == YES) && (rhsAlphaOnly == NO)) {
return NSOrderedDescending;
} else if ((lhsAlphaOnly == NO) && (rhsAlphaOnly == YES)) {
return NSOrderedAscending;
}
}
return [inLHS compare:inRHS options:NSNumericSearch];
}
- (NSArray<NSString*>*)versionComponentsFromString:(NSString *)aVersion {
// Simple division of version by decimal points (.)
NSArray<NSString*>* pointComponents = [aVersion componentsSeparatedByString:@"."];
NSCharacterSet* setOfNonDecimalDigits = NSCharacterSet.decimalDigitCharacterSet.invertedSet;
NSMutableArray<NSString*>* components = [NSMutableArray new];
[pointComponents enumerateObjectsUsingBlock:^(NSString* inComponent,NSUInteger __unused inIndex,BOOL* __unused outShouldStop) {
NSString* component = [[inComponent stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet] lowercaseString];
// Does this component contain anything more than digits?
NSRange found = [component rangeOfCharacterFromSet:setOfNonDecimalDigits];
if (found.location == NSNotFound) {
// Fix for missing values before periods, .1
if ([component isEqualToString:@""]) {
component = @"0";
}
// Component is a plain number
[components addObject:component];
} else {
// Component contains something other than decimal digits
NSScanner* scanner = [NSScanner scannerWithString:component];
scanner.charactersToBeSkipped = NSCharacterSet.whitespaceAndNewlineCharacterSet;
NSString* digits = nil;
NSString* alpha = nil;
NSCharacterSet* digitsSet = NSCharacterSet.decimalDigitCharacterSet;
NSCharacterSet* nonDigitsSet = digitsSet.invertedSet;
while([scanner isAtEnd] == NO &&
([scanner scanCharactersFromSet:digitsSet intoString:&digits] ||
[scanner scanCharactersFromSet:nonDigitsSet intoString:&alpha])) {
if (digits != nil) {
[components addObject:[[digits stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet] lowercaseString]];
digits = nil;
}
if (alpha != nil) {
alpha = [[alpha stringByTrimmingCharactersInSet:NSCharacterSet.alphanumericCharacterSet.invertedSet] lowercaseString];
// Fix for punctuation separations such as brackets and hypens between digits, 1.0 (1234)
if ([alpha isEqualToString:@""] == YES) {
alpha = @"0";
}
[components addObject:alpha];
alpha = nil;
}
}
}
}];
return components;
}
@end
UCVersionComparison.m
This file can be downloaded as part of milnupdate.tbz.