mirror of
				https://github.com/SoPat712/YTLitePlus.git
				synced 2025-10-30 20:34:03 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			219 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
			
		
		
	
	
			219 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
| //
 | |
| //  FLEXRuntimeKeyPathTokenizer.m
 | |
| //  FLEX
 | |
| //
 | |
| //  Created by Tanner on 3/22/17.
 | |
| //  Copyright © 2017 Tanner Bennett. All rights reserved.
 | |
| //
 | |
| 
 | |
| #import "FLEXRuntimeKeyPathTokenizer.h"
 | |
| 
 | |
| #define TBCountOfStringOccurence(target, str) ([target componentsSeparatedByString:str].count - 1)
 | |
| 
 | |
| @implementation FLEXRuntimeKeyPathTokenizer
 | |
| 
 | |
| #pragma mark Initialization
 | |
| 
 | |
| static NSCharacterSet *firstAllowed      = nil;
 | |
| static NSCharacterSet *identifierAllowed = nil;
 | |
| static NSCharacterSet *filenameAllowed   = nil;
 | |
| static NSCharacterSet *keyPathDisallowed = nil;
 | |
| static NSCharacterSet *methodAllowed     = nil;
 | |
| + (void)initialize {
 | |
|     if (self == [self class]) {
 | |
|         NSString *_methodFirstAllowed    = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$";
 | |
|         NSString *_identifierAllowed     = [_methodFirstAllowed stringByAppendingString:@"1234567890"];
 | |
|         NSString *_methodAllowedSansType = [_identifierAllowed stringByAppendingString:@":"];
 | |
|         NSString *_filenameNameAllowed   = [_identifierAllowed stringByAppendingString:@"-+?!"];
 | |
|         firstAllowed      = [NSCharacterSet characterSetWithCharactersInString:_methodFirstAllowed];
 | |
|         identifierAllowed = [NSCharacterSet characterSetWithCharactersInString:_identifierAllowed];
 | |
|         filenameAllowed   = [NSCharacterSet characterSetWithCharactersInString:_filenameNameAllowed];
 | |
|         methodAllowed     = [NSCharacterSet characterSetWithCharactersInString:_methodAllowedSansType];
 | |
| 
 | |
|         NSString *_kpDisallowed = [_identifierAllowed stringByAppendingString:@"-+:\\.*"];
 | |
|         keyPathDisallowed = [NSCharacterSet characterSetWithCharactersInString:_kpDisallowed].invertedSet;
 | |
|     }
 | |
| }
 | |
| 
 | |
| #pragma mark Public
 | |
| 
 | |
| + (FLEXRuntimeKeyPath *)tokenizeString:(NSString *)userInput {
 | |
|     if (!userInput.length) {
 | |
|         return nil;
 | |
|     }
 | |
| 
 | |
|     NSUInteger tokens = [self tokenCountOfString:userInput];
 | |
|     if (tokens == 0) {
 | |
|         return nil;
 | |
|     }
 | |
| 
 | |
|     if ([userInput containsString:@"**"]) {
 | |
|         @throw NSInternalInconsistencyException;
 | |
|     }
 | |
| 
 | |
|     NSNumber *instance = nil;
 | |
|     NSScanner *scanner = [NSScanner scannerWithString:userInput];
 | |
|     FLEXSearchToken *bundle    = [self scanToken:scanner allowed:filenameAllowed first:filenameAllowed];
 | |
|     FLEXSearchToken *cls       = [self scanToken:scanner allowed:identifierAllowed first:firstAllowed];
 | |
|     FLEXSearchToken *method    = tokens > 2 ? [self scanMethodToken:scanner instance:&instance] : nil;
 | |
| 
 | |
|     return [FLEXRuntimeKeyPath bundle:bundle
 | |
|                        class:cls
 | |
|                       method:method
 | |
|                   isInstance:instance
 | |
|                       string:userInput];
 | |
| }
 | |
| 
 | |
| + (BOOL)allowedInKeyPath:(NSString *)text {
 | |
|     if (!text.length) {
 | |
|         return YES;
 | |
|     }
 | |
|     
 | |
|     return [text rangeOfCharacterFromSet:keyPathDisallowed].location == NSNotFound;
 | |
| }
 | |
| 
 | |
| #pragma mark Private
 | |
| 
 | |
| + (NSUInteger)tokenCountOfString:(NSString *)userInput {
 | |
|     NSUInteger escapedCount = TBCountOfStringOccurence(userInput, @"\\.");
 | |
|     NSUInteger tokenCount  = TBCountOfStringOccurence(userInput, @".") - escapedCount + 1;
 | |
| 
 | |
|     return tokenCount;
 | |
| }
 | |
| 
 | |
| + (FLEXSearchToken *)scanToken:(NSScanner *)scanner allowed:(NSCharacterSet *)allowedChars first:(NSCharacterSet *)first {
 | |
|     if (scanner.isAtEnd) {
 | |
|         if ([scanner.string hasSuffix:@"."] && ![scanner.string hasSuffix:@"\\."]) {
 | |
|             return [FLEXSearchToken string:nil options:TBWildcardOptionsAny];
 | |
|         }
 | |
|         return nil;
 | |
|     }
 | |
| 
 | |
|     TBWildcardOptions options = TBWildcardOptionsNone;
 | |
|     NSMutableString *token = [NSMutableString new];
 | |
| 
 | |
|     // Token cannot start with '.'
 | |
|     if ([scanner scanString:@"." intoString:nil]) {
 | |
|         @throw NSInternalInconsistencyException;
 | |
|     }
 | |
| 
 | |
|     if ([scanner scanString:@"*." intoString:nil]) {
 | |
|         return [FLEXSearchToken string:nil options:TBWildcardOptionsAny];
 | |
|     } else if ([scanner scanString:@"*" intoString:nil]) {
 | |
|         if (scanner.isAtEnd) {
 | |
|             return FLEXSearchToken.any;
 | |
|         }
 | |
|         
 | |
|         options |= TBWildcardOptionsPrefix;
 | |
|     }
 | |
| 
 | |
|     NSString *tmp = nil;
 | |
|     BOOL stop = NO, didScanDelimiter = NO, didScanFirstAllowed = NO;
 | |
|     NSCharacterSet *disallowed = allowedChars.invertedSet;
 | |
|     while (!stop && ![scanner scanString:@"." intoString:&tmp] && !scanner.isAtEnd) {
 | |
|         // Scan word chars
 | |
|         // In this block, we have not scanned anything yet, except maybe leading '\' or '\.'
 | |
|         if (!didScanFirstAllowed) {
 | |
|             if ([scanner scanCharactersFromSet:first intoString:&tmp]) {
 | |
|                 [token appendString:tmp];
 | |
|                 didScanFirstAllowed = YES;
 | |
|             } else if ([scanner scanString:@"\\" intoString:nil]) {
 | |
|                 if (options == TBWildcardOptionsPrefix && [scanner scanString:@"." intoString:nil]) {
 | |
|                     [token appendString:@"."];
 | |
|                 } else if (scanner.isAtEnd && options == TBWildcardOptionsPrefix) {
 | |
|                     // Only allow standalone '\' if prefixed by '*'
 | |
|                     return FLEXSearchToken.any;
 | |
|                 } else {
 | |
|                     // Token starts with a number, period, or something else not allowed,
 | |
|                     // or token is a standalone '\' with no '*' prefix
 | |
|                     @throw NSInternalInconsistencyException;
 | |
|                 }
 | |
|             } else {
 | |
|                 // Token starts with a number, period, or something else not allowed
 | |
|                 @throw NSInternalInconsistencyException;
 | |
|             }
 | |
|         } else if ([scanner scanCharactersFromSet:allowedChars intoString:&tmp]) {
 | |
|             [token appendString:tmp];
 | |
|         }
 | |
|         // Scan '\.' or trailing '\'
 | |
|         else if ([scanner scanString:@"\\" intoString:nil]) {
 | |
|             if ([scanner scanString:@"." intoString:nil]) {
 | |
|                 [token appendString:@"."];
 | |
|             } else if (scanner.isAtEnd) {
 | |
|                 // Ignore forward slash not followed by period if at end
 | |
|                 return [FLEXSearchToken string:token options:options | TBWildcardOptionsSuffix];
 | |
|             } else {
 | |
|                 // Only periods can follow a forward slash
 | |
|                 @throw NSInternalInconsistencyException;
 | |
|             }
 | |
|         }
 | |
|         // Scan '*.'
 | |
|         else if ([scanner scanString:@"*." intoString:nil]) {
 | |
|             options |= TBWildcardOptionsSuffix;
 | |
|             stop = YES;
 | |
|             didScanDelimiter = YES;
 | |
|         }
 | |
|         // Scan '*' not followed by .
 | |
|         else if ([scanner scanString:@"*" intoString:nil]) {
 | |
|             if (!scanner.isAtEnd) {
 | |
|                 // Invalid token, wildcard in middle of token
 | |
|                 @throw NSInternalInconsistencyException;
 | |
|             }
 | |
|         } else if ([scanner scanCharactersFromSet:disallowed intoString:nil]) {
 | |
|             // Invalid token, invalid characters
 | |
|             @throw NSInternalInconsistencyException;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Did we scan a trailing, un-escsaped '.'?
 | |
|     if ([tmp isEqualToString:@"."]) {
 | |
|         didScanDelimiter = YES;
 | |
|     }
 | |
| 
 | |
|     if (!didScanDelimiter) {
 | |
|         options |= TBWildcardOptionsSuffix;
 | |
|     }
 | |
| 
 | |
|     return [FLEXSearchToken string:token options:options];
 | |
| }
 | |
| 
 | |
| + (FLEXSearchToken *)scanMethodToken:(NSScanner *)scanner instance:(NSNumber **)instance {
 | |
|     if (scanner.isAtEnd) {
 | |
|         if ([scanner.string hasSuffix:@"."]) {
 | |
|             return [FLEXSearchToken string:nil options:TBWildcardOptionsAny];
 | |
|         }
 | |
|         return nil;
 | |
|     }
 | |
| 
 | |
|     if ([scanner.string hasSuffix:@"."] && ![scanner.string hasSuffix:@"\\."]) {
 | |
|         // Methods cannot end with '.' except for '\.'
 | |
|         @throw NSInternalInconsistencyException;
 | |
|     }
 | |
|     
 | |
|     if ([scanner scanString:@"-" intoString:nil]) {
 | |
|         *instance = @YES;
 | |
|     } else if ([scanner scanString:@"+" intoString:nil]) {
 | |
|         *instance = @NO;
 | |
|     } else {
 | |
|         if ([scanner scanString:@"*" intoString:nil]) {
 | |
|             // Just checking... It has to start with one of these three!
 | |
|             scanner.scanLocation--;
 | |
|         } else {
 | |
|             @throw NSInternalInconsistencyException;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // -*foo not allowed
 | |
|     if (*instance && [scanner scanString:@"*" intoString:nil]) {
 | |
|         @throw NSInternalInconsistencyException;
 | |
|     }
 | |
| 
 | |
|     if (scanner.isAtEnd) {
 | |
|         return [FLEXSearchToken string:@"" options:TBWildcardOptionsSuffix];
 | |
|     }
 | |
| 
 | |
|     return [self scanToken:scanner allowed:methodAllowed first:firstAllowed];
 | |
| }
 | |
| 
 | |
| @end
 | 
