diff --git a/Demo/NUIDemo.xcodeproj/project.pbxproj b/Demo/NUIDemo.xcodeproj/project.pbxproj index 5ab52acb..b719c51f 100644 --- a/Demo/NUIDemo.xcodeproj/project.pbxproj +++ b/Demo/NUIDemo.xcodeproj/project.pbxproj @@ -51,6 +51,7 @@ 0EE9CA7D191ECA0F00C84934 /* TestTheme.NUI.nss in Resources */ = {isa = PBXBuildFile; fileRef = 0EE9CA7B191EC96D00C84934 /* TestTheme.NUI.nss */; }; 165D32561BF2239900BF3069 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 165D32551BF2239900BF3069 /* Images.xcassets */; }; 3830C7C440594F829971E4C2 /* libPods-NUITests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CC7736AC8944034A5E10550 /* libPods-NUITests.a */; }; + 3AD433F41D2BD7B5008C4ED5 /* NUIPreprocessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AD433F31D2BD7B5008C4ED5 /* NUIPreprocessor.m */; }; 41A0DE98FD464C508748F886 /* libPods-NUIDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B8B8BB2BB5E6412588AE14FA /* libPods-NUIDemo.a */; }; 4A0B8636165EE59A005B5756 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A0B8635165EE59A005B5756 /* UIKit.framework */; }; 4A0B8638165EE59A005B5756 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A0B8637165EE59A005B5756 /* Foundation.framework */; }; @@ -198,6 +199,8 @@ 0EE9CA7B191EC96D00C84934 /* TestTheme.NUI.nss */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = TestTheme.NUI.nss; sourceTree = ""; }; 165D32551BF2239900BF3069 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 1B8E11A50FE5F75C66ACDBBD /* Pods-NUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-NUITests/Pods-NUITests.debug.xcconfig"; sourceTree = ""; }; + 3AD433F21D2BD7B5008C4ED5 /* NUIPreprocessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NUIPreprocessor.h; sourceTree = ""; }; + 3AD433F31D2BD7B5008C4ED5 /* NUIPreprocessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NUIPreprocessor.m; sourceTree = ""; }; 3AE80CFA17E0EB290065FB3D /* NUIConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NUIConstants.h; sourceTree = ""; }; 4A0B8631165EE59A005B5756 /* NUIDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NUIDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4A0B8635165EE59A005B5756 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; @@ -704,6 +707,8 @@ 4A744B18169904500068F3CF /* NUIFileMonitor.m */, 89B940271671418100850A9A /* NUIGraphics.h */, 89B940281671418100850A9A /* NUIGraphics.m */, + 3AD433F21D2BD7B5008C4ED5 /* NUIPreprocessor.h */, + 3AD433F31D2BD7B5008C4ED5 /* NUIPreprocessor.m */, 89B940291671418100850A9A /* NUIRenderer.h */, 89B9402A1671418100850A9A /* NUIRenderer.m */, 89B9402B1671418100850A9A /* NUISettings.h */, @@ -1185,6 +1190,7 @@ D983DEE8169DC05300A3A180 /* UIToolbar+NUI.m in Sources */, D983DF1C169DC84600A3A180 /* UISlider+NUI.m in Sources */, D983DF1F169DC87800A3A180 /* NUISliderRenderer.m in Sources */, + 3AD433F41D2BD7B5008C4ED5 /* NUIPreprocessor.m in Sources */, 4ADA71C616A0F15C00BDCF81 /* UISearchBar+NUI.m in Sources */, 4ADA71CA16A103CD00BDCF81 /* NUISearchBarRenderer.m in Sources */, D8E7168116B8AF4400686854 /* NUIControlRenderer.m in Sources */, diff --git a/NUI/Core/NUIFileMonitor.m b/NUI/Core/NUIFileMonitor.m index 983cbab5..0deda863 100644 --- a/NUI/Core/NUIFileMonitor.m +++ b/NUI/Core/NUIFileMonitor.m @@ -24,7 +24,6 @@ +(void)watch:(NSString*)path withCallback:(void(^)())callback if (flags) { dispatch_source_cancel(source); callback(); - [self watch:path withCallback:callback]; } }); dispatch_source_set_cancel_handler(source, ^(void) diff --git a/NUI/Core/NUIPreprocessor.h b/NUI/Core/NUIPreprocessor.h new file mode 100644 index 00000000..867ba69c --- /dev/null +++ b/NUI/Core/NUIPreprocessor.h @@ -0,0 +1,16 @@ +// +// NUIPreprocessor.h +// NUIDemo +// +// Created by imihaly on 04/07/16. +// Copyright © 2016 Tom Benner. All rights reserved. +// + +#import + +@interface NUIPreprocessor : NSObject + ++ (NSString *)preprocessFileAtPath:(NSString *)path; ++ (NSArray *)dependenciesOfFileAtPath:(NSString *)path; + +@end diff --git a/NUI/Core/NUIPreprocessor.m b/NUI/Core/NUIPreprocessor.m new file mode 100644 index 00000000..36748ef6 --- /dev/null +++ b/NUI/Core/NUIPreprocessor.m @@ -0,0 +1,110 @@ +// +// NUIPreprocessor.m +// NUIDemo +// +// Created by imihaly on 04/07/16. +// Copyright © 2016 Tom Benner. All rights reserved. +// + +#import "NUIPreprocessor.h" + +@implementation NUIPreprocessor + ++ (NSString *)preprocessFileAtPath:(NSString *)path { + NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; + return [self processString:content + path:path + dependencies:nil + importStack:@[path]]; +} + ++ (NSArray *)dependenciesOfFileAtPath:(NSString *)path { + NSMutableArray *dependencies = [NSMutableArray arrayWithObject:path]; + NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; + [self processString:content + path:path + dependencies:dependencies + importStack:@[path]]; + return dependencies; +} + ++ (NSString *)processString:(NSString *)string path:(NSString *)path dependencies:(NSMutableArray *)dependencies importStack:(NSArray *)importStack { + NSError *error = NULL; + NSUInteger pos = 0; + NSUInteger length = string.length; + NSMutableString *result = NSMutableString.string; + + NSRegularExpression *importRegex = [NSRegularExpression regularExpressionWithPattern:@"@import\\s+\'(.*)\'" + options:NSRegularExpressionCaseInsensitive + error:&error]; + + NSRegularExpression *blockCommentRegex = [NSRegularExpression regularExpressionWithPattern:@"/\\*.*\\*/" + options:NSRegularExpressionCaseInsensitive + error:&error]; + + NSRegularExpression *lineCommentRegex = [NSRegularExpression regularExpressionWithPattern:@"//.*\\n" + options:NSRegularExpressionCaseInsensitive + error:&error]; + + while(YES) { + NSTextCheckingResult* blockCommentMatch = [blockCommentRegex firstMatchInString:string options:0 range:NSMakeRange(pos, length - pos)]; + NSTextCheckingResult* lineCommentMatch = [lineCommentRegex firstMatchInString:string options:0 range:NSMakeRange(pos, length - pos)]; + NSTextCheckingResult* importMatch = [importRegex firstMatchInString:string options:0 range:NSMakeRange(pos, length - pos)]; + + NSTextCheckingResult* match = nil; + if(blockCommentMatch) { + match = match == nil || match.range.location > blockCommentMatch.range.location ? blockCommentMatch : match; + } + if(lineCommentMatch) { + match = match == nil || match.range.location > lineCommentMatch.range.location ? lineCommentMatch : match; + } + if(importMatch) { + match = match == nil || match.range.location > importMatch.range.location ? importMatch : match; + } + + if(!match) break; + if(match == importMatch) { + [result appendString:[string substringWithRange:NSMakeRange(pos, match.range.location - pos)]]; + pos = match.range.location + match.range.length; + + NSString *importedFileName = [string substringWithRange:(NSRange)[match rangeAtIndex:1]]; + NSString* importedPath = [self pathForIncludedFile:importedFileName inSourceFile:path]; + if(!importedPath) { + NSLog(@"Preprocessing error: file not found: `%@`, imported from: `%@`, skipping", importedFileName, path); + continue; + } + + if([importStack containsObject:importedPath]) { + NSLog(@"Preprocessing error: circular include: `%@`, imported from: `%@`, skipping", importedFileName, importStack); + continue; + } + + if(dependencies && ![dependencies containsObject:importedPath]) { + [dependencies addObject:importedPath]; + } + NSString *content = [NSString stringWithContentsOfFile:importedPath encoding:NSUTF8StringEncoding error:nil]; + if(!content) { + NSLog(@"Preprocessing error: could not read: `%@`, imported from: `%@`, skipping", importedFileName, path); + continue; + } + + [result appendString:[self processString:content path:importedPath dependencies:dependencies importStack:[importStack arrayByAddingObject:importedPath]]]; + } else { + // drop comments + [result appendString:[string substringWithRange:NSMakeRange(pos, match.range.location - pos)]]; + pos = match.range.location + match.range.length; + } + }; + + [result appendString:[string substringWithRange:NSMakeRange(pos, string.length - pos)]]; + return result; +} + ++ (NSString *)pathForIncludedFile:(NSString *)includedFile inSourceFile:(NSString *)sourceFilePath { + NSString *dir = [sourceFilePath stringByDeletingLastPathComponent]; + NSString *proposedPath = [dir stringByAppendingPathComponent:includedFile]; + if([NSFileManager.defaultManager fileExistsAtPath:proposedPath]) return proposedPath; + return [[NSBundle mainBundle] pathForResource:includedFile ofType:nil]; +} + +@end diff --git a/NUI/Core/NUIRenderer.m b/NUI/Core/NUIRenderer.m index de9c8252..51d5e81c 100644 --- a/NUI/Core/NUIRenderer.m +++ b/NUI/Core/NUIRenderer.m @@ -8,6 +8,7 @@ #import "NUIRenderer.h" #import "UIProgressView+NUI.h" +#import "NUIPreprocessor.h" @implementation NUIRenderer @@ -331,13 +332,7 @@ + (NUIRenderer*)getInstance @synchronized(self) { if (gInstance == nil) { gInstance = [NUIRenderer new]; - if ([NUISettings autoUpdateIsEnabled]) { - [NUIFileMonitor watch:[NUISettings autoUpdatePath] withCallback:^(){ - dispatch_async(dispatch_get_main_queue(), ^{ - [self stylesheetFileChanged]; - }); - }]; - } + [self resetFileMonitor]; } } return gInstance; @@ -355,9 +350,35 @@ + (void)orientationDidChange:(NSNotification *)notification + (void)stylesheetFileChanged { - [NUISettings loadStylesheetByPath:[NUISettings autoUpdatePath]]; + @try { + [NUISettings loadStylesheetByPath:[NUISettings autoUpdatePath]]; + } @catch (NSException *exception) { + NSLog(@"Error: %@", exception); + } [NUIRenderer rerender]; [CATransaction flush]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self resetFileMonitor]; + }); +} + ++ (void)resetFileMonitor { + if ([NUISettings autoUpdateIsEnabled]) { + NSArray *pathesToMonitor = @[[NUISettings autoUpdatePath]]; + @try { + pathesToMonitor = [NUIPreprocessor dependenciesOfFileAtPath:[NUISettings autoUpdatePath]]; + } @catch (NSException *exception) { + NSLog(@"Error: %@", exception); + } + + for(NSString *path in pathesToMonitor) { + [NUIFileMonitor watch:path withCallback:^(){ + dispatch_async(dispatch_get_main_queue(), ^{ + [self stylesheetFileChanged]; + }); + }]; + } + } } @end diff --git a/NUI/Core/NUIStyleParser.m b/NUI/Core/NUIStyleParser.m index 6dd37149..487bc27e 100644 --- a/NUI/Core/NUIStyleParser.m +++ b/NUI/Core/NUIStyleParser.m @@ -22,6 +22,7 @@ #import "NUIRenderer.h" #import "NUISettings.h" #import "NUIDefinition.h" +#import "NUIPreprocessor.h" @implementation NUIStyleParser @@ -29,14 +30,12 @@ - (NSMutableDictionary*)getStylesFromFile:(NSString*)fileName { NSString* path = [[NSBundle mainBundle] pathForResource:fileName ofType:@"nss"]; NSAssert1(path != nil, @"File \"%@\" does not exist", fileName); - NSString* content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; - NUIStyleSheet *styleSheet = [self parse:content]; - return [self consolidateRuleSets:styleSheet]; + return [self getStylesFromPath:path]; } - (NSMutableDictionary*)getStylesFromPath:(NSString*)path { - NSString* content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; + NSString* content = [NUIPreprocessor preprocessFileAtPath:path]; NUIStyleSheet *styleSheet = [self parse:content]; return [self consolidateRuleSets:styleSheet]; } @@ -119,10 +118,6 @@ - (NUIStyleSheet *)parse:(NSString *)styles [tokeniser addTokenRecogniser:[NUIPWhiteSpaceRecogniser whiteSpaceRecogniser]]; - [tokeniser addTokenRecogniser:[NUIPQuotedRecogniser quotedRecogniserWithStartQuote:@"/*" - endQuote:@"*/" - name:@"Comment"]]; - [tokeniser addTokenRecogniser:[NUIPKeywordRecogniser recogniserForKeyword:@"@media"]]; [tokeniser addTokenRecogniser:[NUIPKeywordRecogniser recogniserForKeyword:@"and"]]; diff --git a/NUI/Core/Parser/NUITokeniserDelegate.m b/NUI/Core/Parser/NUITokeniserDelegate.m index b11619b7..67dcf87d 100644 --- a/NUI/Core/Parser/NUITokeniserDelegate.m +++ b/NUI/Core/Parser/NUITokeniserDelegate.m @@ -17,7 +17,7 @@ - (BOOL)tokeniser:(NUIPTokeniser *)tokeniser shouldConsumeToken:(NUIPToken *)tok - (void)tokeniser:(NUIPTokeniser *)tokeniser requestsToken:(NUIPToken *)token pushedOntoStream:(NUIPTokenStream *)stream { - if ([token isWhiteSpaceToken] || [[token name] isEqualToString:@"Comment"]) + if ([token isWhiteSpaceToken]) return; if ([token isIdentifierToken]) {