diff --git a/Application/GBAppledocApplication.m b/Application/GBAppledocApplication.m index 1b9e46da..4f30113c 100644 --- a/Application/GBAppledocApplication.m +++ b/Application/GBAppledocApplication.m @@ -45,6 +45,7 @@ static char *kGBArgExitCodeThreshold = "exit-threshold"; static char *kGBArgDocsSectionTitle = "docs-section-title"; +static char *kGBArgSkipCodeBlockMarker = "skip-code-block-marker"; static char *kGBArgRepeatFirstParagraph = "repeat-first-par"; static char *kGBArgPreprocessHeaderDoc = "preprocess-headerdoc"; static char *kGBArgPrintInformationBlockTitles = "print-information-block-titles"; @@ -286,7 +287,8 @@ - (void)application:(DDCliApplication *)app willParseOptions:(DDGetoptLongParser { kGBArgCrossRefFormat, 0, DDGetoptRequiredArgument }, { kGBArgExplicitCrossRef, 0, DDGetoptNoArgument }, { GBNoArg(kGBArgExplicitCrossRef), 0, DDGetoptNoArgument }, - + + { kGBArgSkipCodeBlockMarker, 0, DDGetoptRequiredArgument }, { kGBArgKeepIntermediateFiles, 0, DDGetoptNoArgument }, { kGBArgKeepUndocumentedObjects, 0, DDGetoptNoArgument }, { kGBArgKeepUndocumentedMembers, 0, DDGetoptNoArgument }, @@ -807,6 +809,9 @@ - (void)setPublishDocset:(BOOL)value { - (void)setHtmlAnchors:(NSString *)value { self.settings.htmlAnchorFormat = GBHTMLAnchorFormatFromNSString(value); } +- (void)setSkipCodeBlockMarker:(NSString *)marker { + [self.settings addSkipCodeBlockMarker:marker]; +} - (void)setNoCleanOutput:(BOOL)value { self.settings.cleanupOutputPathBeforeRunning = !value; } - (void)setNoCreateHtml:(BOOL)value { [self setCreateHtml:!value]; } - (void)setNoCreateDocset:(BOOL)value { [self setCreateDocset:!value]; } @@ -953,7 +958,8 @@ - (void)printSettingsAndArguments:(NSArray *)arguments { ddprintf(@"--%s = %@\n", kGBArgDocSetXMLFilename, self.settings.docsetXMLFilename); ddprintf(@"--%s = %@\n", kGBArgDocSetPackageFilename, self.settings.docsetPackageFilename); ddprintf(@"\n"); - + + for (NSString *marker in self.settings.skipCodeBlockMarkers) ddprintf(@"--%s = %@\n", kGBArgSkipCodeBlockMarker, marker); ddprintf(@"--%s = %@\n", kGBArgCleanOutput, PRINT_BOOL(self.settings.cleanupOutputPathBeforeRunning)); ddprintf(@"--%s = %@\n", kGBArgCreateHTML, PRINT_BOOL(self.settings.createHTML)); ddprintf(@"--%s = %@\n", kGBArgCreateDocSet, PRINT_BOOL(self.settings.createDocSet)); @@ -1048,6 +1054,7 @@ - (void)printHelp { PRINT_USAGE(@" ", kGBArgCrossRefFormat, @"", @"Cross reference template regex"); PRINT_USAGE(@" ", kGBArgExitCodeThreshold, @"", @"Exit code threshold below which 0 is returned"); PRINT_USAGE(@" ", kGBArgDocsSectionTitle, @"", @"Title of the documentation section (defaults to \"Programming Guides\""); + PRINT_USAGE(@" ", kGBArgSkipCodeBlockMarker, @"", @"Text to mark begin/end of code block for which preprocessing is skipped"); ddprintf(@"\n"); ddprintf(@"WARNINGS\n"); PRINT_USAGE(@" ", kGBArgWarnOnMissingOutputPath, @"", @"[b] Warn if output path is not given"); diff --git a/Application/GBApplicationSettingsProvider.h b/Application/GBApplicationSettingsProvider.h index 81420e44..fdb85f8e 100644 --- a/Application/GBApplicationSettingsProvider.h +++ b/Application/GBApplicationSettingsProvider.h @@ -212,6 +212,31 @@ NSString *NSStringFromGBPublishedFeedFormats(GBPublishedFeedFormats format); /// @name Behavior handling ///--------------------------------------------------------------------------------------- +/** The markers to use when matching source code blocks that will forgo preprocessing. + + Text between these markers will be treated as source-code blocks, and not preprocessed. + */ +@property (strong, readonly) NSArray *skipCodeBlockMarkers; + +/** The actual regex patterns to use when matching source code blocks that will forgo preprocessing. + + The patterns are computed when the markers are added. + */ +@property (strong, readonly) NSArray *skipCodeBlockPatterns; + +/** + Add a marker to be used when searching for code blocks that should skip preprocessing. + + @param marker A marker. The marker defines the beginning and ending tokens that deliniate a source-code-block. The begin/end markers are separated by a forward-slash. If there is no forward-slash, then the same pattern is used for both the begin and end markers. + + When scanning the source text, any text that is between the two markers will be treated as source code, and will skip the preprocessing step. Specifically, reference links will not be discovered within that block. To match, the begin/end markers must be on lines all by themselves with nothing else but whitespace. + + For example, a marker of "@code/@endcode" will mark all the text in between as source code, and will not try to match reference links inside. Similarly, a marker of "~~~~~" will do the same thing for all the text between two lines with "~~~~~" on them. + + @note Markers are checked in the order in which they are added. Duplicates are dropped, and not added. + */ +- (void)addSkipCodeBlockMarker:(NSString *)marker; + /** Indicates whether HTML files should be generated or not. If `YES`, HTML files are generated in `outputPath` from parsed and processed data. If `NO`, input files are parsed and processed, but nothing is generated. diff --git a/Application/GBApplicationSettingsProvider.m b/Application/GBApplicationSettingsProvider.m index 8ce4184a..7f13413c 100644 --- a/Application/GBApplicationSettingsProvider.m +++ b/Application/GBApplicationSettingsProvider.m @@ -90,6 +90,10 @@ - (NSString *)sanitizeFileNameString:(NSString *)fileName; @property (readonly) NSDateFormatter *yearDateFormatter; @property (readonly) NSDateFormatter *yearToDayDateFormatter; +// Make these readwrite internally... +@property (strong, readwrite) NSArray *skipCodeBlockMarkers; +@property (strong, readwrite) NSArray *skipCodeBlockPatterns; + @end #pragma mark - @@ -128,7 +132,9 @@ - (id)init { self.includePaths = [NSMutableSet set]; self.ignoredPaths = [NSMutableSet set]; self.excludeOutputPaths = [NSMutableSet set]; - + + self.skipCodeBlockMarkers = [NSArray array]; + self.skipCodeBlockPatterns = [NSArray array]; self.createHTML = YES; self.createDocSet = YES; self.installDocSet = YES; @@ -197,6 +203,24 @@ - (id)init { #pragma mark Helper methods +- (void)addSkipCodeBlockMarker:(NSString *)marker { + NSString *begin, *end; + NSRange range = [marker rangeOfString:@"/"]; + if (range.location == NSNotFound) { + begin = end = marker; + } else { + begin = [marker substringToIndex:range.location]; + end = [marker substringFromIndex:range.location+1]; + } + + NSString *pattern = [NSString stringWithFormat:@"\\r?\\n(([ \\t]*(%@)[ \\t]*)\\r?\\n[\\s\\S]*?\\r?\\n([ \\t]*(%@)[ \\t]*)\\r?\\n)", begin, end]; + NSUInteger index = [self.skipCodeBlockPatterns indexOfObject:pattern]; + if (index == NSNotFound) { + self.skipCodeBlockMarkers = [self.skipCodeBlockMarkers arrayByAddingObject:marker]; + self.skipCodeBlockPatterns = [self.skipCodeBlockPatterns arrayByAddingObject:pattern]; + } +} + - (void)updateHelperClassesWithSettingsValues { } @@ -687,6 +711,8 @@ - (NSString *)versionIdentifier { @synthesize docsetXMLFilename; @synthesize docsetPackageFilename; +@synthesize skipCodeBlockMarkers; +@synthesize skipCodeBlockPatterns; @synthesize repeatFirstParagraphForMemberDescription; @synthesize preprocessHeaderDoc; @synthesize printInformationBlockTitles; diff --git a/Processing/GBCommentsProcessor+CodeBlockProcessing.m b/Processing/GBCommentsProcessor+CodeBlockProcessing.m index 6f907174..cbc4a49f 100644 --- a/Processing/GBCommentsProcessor+CodeBlockProcessing.m +++ b/Processing/GBCommentsProcessor+CodeBlockProcessing.m @@ -7,10 +7,41 @@ // #import "GBCommentsProcessor+CodeBlockProcessing.h" +#import "GBApplicationSettingsProvider.h" #import "RegexKitLite.h" +#import + +@interface GBCommentsProcessor () +@property (strong) GBApplicationSettingsProvider *settings; +@end @implementation GBCommentsProcessor (CodeBlockProcessing) +/** + Get any source-code-blocks that are to skip preprocessing, as defined by the settings. + + @param string the string to search + @param range the range of the string to be searched + + @return an array of component extracted from the matched text + - Index 0: The entire matched text + - Index 1: The source code text + - Index 2: The prefix text (stuff matched before the code block) + - Index 3: The "begin" marker + - Index 4: The postfix text (stuff matched after the code block) + - Index 5: The "end" marker + */ +- (NSArray*)codeBlockComponentsToSkipInString:(NSString*)string range:(NSRange)searchRange { + for (NSString *pattern in self.settings.skipCodeBlockPatterns) { + NSArray *captured = [string captureComponentsMatchedByRegex:pattern range:searchRange]; + if ([captured count]) { + NSAssert([captured count] == 6, @"Must match exactly 6 components"); + return captured; + } + } + return nil; +} + /** Fetch any source-code-block components from @a string @@ -32,14 +63,12 @@ @implementation GBCommentsProcessor (CodeBlockProcessing) - range the range for the code text, relative to the original @a string */ - (NSArray*)codeComponentsInString:(NSString*)string { - NSString *pattern = @"\\r?\\n(([ \\t]*(~~~|```|@code)[ \\t]*)\\r?\\n[\\s\\S]*?\\r?\\n([ \\t]*(\\3|@endcode)[ \\t]*)\\r?\\n)"; NSUInteger stringLength = [string length]; NSRange searchRange = NSMakeRange(0, stringLength); NSMutableArray *result = [[NSMutableArray alloc] init]; while (searchRange.length > 0) { - NSArray *captured = [string captureComponentsMatchedByRegex:pattern range:searchRange]; + NSArray *captured = [self codeBlockComponentsToSkipInString:string range:searchRange]; if ([captured count] > 0) { - NSAssert([captured count] == 6, @"Match didn't produce right number of components"); NSString *code = [captured objectAtIndex:1]; NSRange codeRange = [string rangeOfString:code options:0 range:searchRange]; NSAssert(codeRange.location != NSNotFound, @"Matched code string should be in original"); diff --git a/Testing/GBCommentsProcessor-PreprocessingTesting.m b/Testing/GBCommentsProcessor-PreprocessingTesting.m index cadf7eca..a6c6de8a 100644 --- a/Testing/GBCommentsProcessor-PreprocessingTesting.m +++ b/Testing/GBCommentsProcessor-PreprocessingTesting.m @@ -130,36 +130,43 @@ - (void)testStringByPreprocessingString_shouldKeepReferencesWithMarkersIntact { assertThat(result2, is(@"![test_test](http://www.example.com/test_test.html)")); } -- (void)testStringByPreprocessingString_shouldConvertCodeBlockToMarkdownBackticks { +- (void)testStringByPreprocessingString_shouldNotConvertCodeBlockToMarkdownBackticksIfNotInSettings { // setup GBCommentsProcessor *processor = [self defaultProcessor]; // execute NSString *result = [processor stringByPreprocessingString:@"\n @code \n[self doSomething];\n @endcode \n" withFlags:0]; // verify - assertThat(result, is(@"\n```\n[self doSomething];\n```\n")); + assertThat(result, is(@"\n @code \n[self doSomething];\n @endcode \n")); } -- (void)testStringByPreprocessingString_shouldConvertTildeCodeBlockToMarkdownBackticks { +- (void)testStringByPreprocessingString_shouldConvertSkipCodeBlockBeginEndMarkersToMarkdownBackticks { // setup - GBCommentsProcessor *processor = [self defaultProcessor]; + id settings = [GBTestObjectsRegistry realSettingsProvider]; + [settings addSkipCodeBlockMarker:@"@code/@endcode"]; + GBCommentsProcessor *processor = [GBCommentsProcessor processorWithSettingsProvider:settings]; // execute - NSString *result = [processor stringByPreprocessingString:@"\n ~~~ \n[self doSomething];\n ~~~ \n" withFlags:0]; + NSString *result = [processor stringByPreprocessingString:@"\n @code \n[self doSomething];\n @endcode \n" withFlags:0]; // verify assertThat(result, is(@"\n```\n[self doSomething];\n```\n")); } -- (void)testStringByPreprocessingString_shouldConvertBacktickCodeBlockToMarkdownBackticks { +- (void)testStringByPreprocessingString_shouldConvertSkipCodeBlockSingleMarkerToMarkdownBackticks { // setup - GBCommentsProcessor *processor = [self defaultProcessor]; + id settings = [GBTestObjectsRegistry realSettingsProvider]; + [settings addSkipCodeBlockMarker:@"~~~"]; + GBCommentsProcessor *processor = [GBCommentsProcessor processorWithSettingsProvider:settings]; // execute - NSString *result = [processor stringByPreprocessingString:@"\n ``` \n[self doSomething];\n ``` \n" withFlags:0]; + NSString *result = [processor stringByPreprocessingString:@"\n ~~~ \n[self doSomething];\n ~~~ \n" withFlags:0]; // verify assertThat(result, is(@"\n```\n[self doSomething];\n```\n")); } -- (void)testStringByPreprocessingString_shouldConvertMultipleCodeBlocksToMarkdownBackticks { +- (void)testStringByPreprocessingString_shouldConvertSkipCodeBlockMultipleMarkersToMarkdownBackticks { // setup - GBCommentsProcessor *processor = [self defaultProcessor]; + id settings = [GBTestObjectsRegistry realSettingsProvider]; + [settings addSkipCodeBlockMarker:@"```"]; + [settings addSkipCodeBlockMarker:@"@code/@endcode"]; + GBCommentsProcessor *processor = [GBCommentsProcessor processorWithSettingsProvider:settings]; NSString *raw = @"\n @code \n[self doSomething];\n @endcode \n\n @code \n[self doSomething];\n @endcode \n"; NSString *expected = @"\n```\n[self doSomething];\n```\n\n```\n[self doSomething];\n```\n"; // execute