Permalink
Browse files

Implemented possibility to add arbitrary documentation. Closes #7.

This was one of much requested features. It allows adding arbitrary files to generated documentation. It's enabled with one or more `--include` switches. All files and directories specified are simply copied to `docs` subfolder within generated HTML files. So basically, you're free to write any HTML or whatever you want included with the generated documentation.

But the power lies in special "template" files. These are just normal text files which names end with `-template` (for example `document1-template.html). Extension of these files is not important - they will always be converted to .html! The files can reside on any subpath - the path will be preserved. All such files are processed using the same logic as any other comment, so you can use appledoc comment syntax, including cross referencing any object or member. You can also cross reference these files from "normal" comments (or other documents) by simply writing the filename without `-template` and extension. You don't have to deal with subpaths either, these will be automatically picked up for you! [Online documentation](http://tomaz.github.com/appledoc) is not yet updated, will do it shortly.

At this point, it's basic stuff only. As such it has much potential for future (like adding markdown syntax for headings, images and similar - for now just use HTML tags). But at least it's a start and get's work done for the moment.

Enjoy and let me know if you find something not working as expected :)
  • Loading branch information...
1 parent 50c90fe commit 97db2410848cef6fe2017c8609af978891e094a6 @tomaz committed Feb 11, 2011
Showing with 1,335 additions and 267 deletions.
  1. +6 −0 Application/GBAppledocApplication.m
  2. +66 −2 Application/GBApplicationSettingsProvider.h
  3. +71 −15 Application/GBApplicationSettingsProvider.m
  4. +7 −0 Application/GBApplicationStringsProvider.h
  5. +11 −0 Application/GBApplicationStringsProvider.m
  6. +9 −0 Application/GBCommentComponentsProvider.h
  7. +8 −0 Application/GBCommentComponentsProvider.m
  8. +1 −1 Developer Notes.markdown
  9. +47 −7 Generating/GBHTMLOutputGenerator.m
  10. +21 −0 Generating/GBHTMLTemplateVariablesProvider.h
  11. +20 −0 Generating/GBHTMLTemplateVariablesProvider.m
  12. +4 −67 Generating/GBOutputGenerator.h
  13. +5 −122 Generating/GBOutputGenerator.m
  14. +89 −0 Generating/GBTemplateFilesHandler.h
  15. +154 −0 Generating/GBTemplateFilesHandler.m
  16. +1 −0 Model/GBDataObjects.h
  17. +101 −0 Model/GBDocumentData.h
  18. +73 −0 Model/GBDocumentData.m
  19. +8 −0 Model/GBModelBase.h
  20. +4 −0 Model/GBModelBase.m
  21. +39 −7 Model/GBStore.h
  22. +21 −0 Model/GBStore.m
  23. +15 −1 Parsing/GBParser.h
  24. +94 −38 Parsing/GBParser.m
  25. +26 −0 Processing/GBCommentsProcessor.m
  26. +13 −1 Processing/GBProcessor.m
  27. +46 −0 Templates/html/document-template.html
  28. +124 −6 Testing/GBApplicationSettingsProviderTesting.m
  29. +20 −0 Testing/GBApplicationTesting.m
  30. +53 −0 Testing/GBCommentsProcessor-LinkItemsTesting.m
  31. +83 −0 Testing/GBDocumentDataTesting.m
  32. +24 −0 Testing/GBProcessor-CommentsTesting.m
  33. +40 −0 Testing/GBStoreTesting.m
  34. +1 −0 Testing/GBTestObjectsRegistry.h
  35. +10 −0 Testing/GBTestObjectsRegistry.m
  36. +20 −0 appledoc.xcodeproj/project.pbxproj
View
6 Application/GBAppledocApplication.m
@@ -20,6 +20,7 @@
static NSString *kGBArgTemplatesPath = @"templates";
static NSString *kGBArgDocSetInstallPath = @"docset-install-path";
static NSString *kGBArgDocSetUtilPath = @"docsetutil-path";
+static NSString *kGBArgIncludePath = @"include";
static NSString *kGBArgIgnorePath = @"ignore";
static NSString *kGBArgProjectName = @"project-name";
@@ -156,6 +157,7 @@ - (int)application:(DDCliApplication *)app runWithArguments:(NSArray *)arguments
GBLogNormal(@"Parsing source files...");
GBParser *parser = [GBParser parserWithSettingsProvider:self.settings];
[parser parseObjectsFromPaths:arguments toStore:store];
+ [parser parseDocumentsFromPaths:[self.settings.includePaths allObjects] toStore:store];
GBAbsoluteTime parseTime = GetCurrentTime();
NSUInteger timeForParsing = SubtractTime(parseTime, startTime) * 1000.0;
GBLogInfo(@"Finished parsing in %ldms.\n", timeForParsing);
@@ -193,6 +195,7 @@ - (void)application:(DDCliApplication *)app willParseOptions:(DDGetoptLongParser
{ kGBArgOutputPath, 'o', DDGetoptRequiredArgument },
{ kGBArgTemplatesPath, 't', DDGetoptRequiredArgument },
{ kGBArgIgnorePath, 'i', DDGetoptRequiredArgument },
+ { kGBArgIncludePath, 's', DDGetoptRequiredArgument },
{ kGBArgDocSetInstallPath, 0, DDGetoptRequiredArgument },
{ kGBArgDocSetUtilPath, 0, DDGetoptRequiredArgument },
@@ -448,6 +451,7 @@ - (void)setOutput:(NSString *)path { self.settings.outputPath = [self standardiz
- (void)setTemplates:(NSString *)path { self.settings.templatesPath = [self standardizeCurrentDirectoryForPath:path]; }
- (void)setDocsetInstallPath:(NSString *)path { self.settings.docsetInstallPath = [self standardizeCurrentDirectoryForPath:path]; }
- (void)setDocsetutilPath:(NSString *)path { self.settings.docsetUtilPath = [self standardizeCurrentDirectoryForPath:path]; }
+- (void)setInclude:(NSString *)path { [self.settings.includePaths addObject:[self standardizeCurrentDirectoryForPath:path]]; }
- (void)setIgnore:(NSString *)path {
if ([path hasPrefix:@"*"]) path = [path substringFromIndex:1];
[self.settings.ignoredPaths addObject:path];
@@ -557,6 +561,7 @@ - (void)printSettingsAndArguments:(NSArray *)arguments {
ddprintf(@"--%@ = %@\n", kGBArgTemplatesPath, self.settings.templatesPath);
ddprintf(@"--%@ = %@\n", kGBArgOutputPath, self.settings.outputPath);
+ for (NSString *path in self.settings.includePaths) ddprintf(@"--%@ = %@\n", kGBArgIncludePath, path);
for (NSString *path in self.settings.ignoredPaths) ddprintf(@"--%@ = %@\n", kGBArgIgnorePath, path);
ddprintf(@"--%@ = %@\n", kGBArgDocSetInstallPath, self.settings.docsetInstallPath);
ddprintf(@"--%@ = %@\n", kGBArgDocSetUtilPath, self.settings.docsetUtilPath);
@@ -628,6 +633,7 @@ - (void)printHelp {
PRINT_USAGE(@"-o,", kGBArgOutputPath, @"<path>", @"Output path");
PRINT_USAGE(@"-t,", kGBArgTemplatesPath, @"<path>", @"Template files path");
PRINT_USAGE(@" ", kGBArgDocSetInstallPath, @"<path>", @"DocSet installation path");
+ PRINT_USAGE(@"-s,", kGBArgIncludePath, @"<path>", @"Include static doc(s) at path");
PRINT_USAGE(@"-i,", kGBArgIgnorePath, @"<path>", @"Ignore given path");
ddprintf(@"\n");
ddprintf(@"PROJECT INFO\n");
View
68 Application/GBApplicationSettingsProvider.h
@@ -134,11 +134,21 @@
/** The path to `docsetutil` tool, including tool filename. */
@property (copy) NSString *docsetUtilPath;
+/** The list of all include paths containing static documentation.
+
+ The array contains full paths to either directories or files. In the first case, directories are recursively parsed for all template files (i.e. files with names ending with `-template` and arbitrary extension). Each file is processed the same as any other comment! All non-template files are simply copied over to destination without processing, preserving original directory structure. If the path represents a file, the same logic is applied: if it's a template file it's processed, otherwise it's simply copied over to destination unmodified.
+
+ @warning *Note:* All include paths are copied over to destination defined with `outputPath`, inside `docs` directory. If a path represents a directory, it's copied into a subdirectory of `docs` using the last path component name as the subdirectory name. For example: contents of `some/path/to/dir` would be copied to `docs/dir` within `outputPath` and `another/path` would be copied to `docs/path`. In case the path represents a file, it's simply copied inside `docs` directory at `outputPath`.
+
+ @warning *Important:* Make sure no duplicate directories or files are added to the list - appledoc will fail in such case! Also make sure to not add subpaths of an already added path - this will also fail while copying files!
+ */
+@property (retain) NSMutableSet *includePaths;
+
/** The list of all full or partial paths to be ignored.
It's recommended to check if a path string ends with any of the given paths before processing it. This should catch directory and file names properly as directories are processed first.
*/
-@property (retain) NSMutableArray *ignoredPaths;
+@property (retain) NSMutableSet *ignoredPaths;
///---------------------------------------------------------------------------------------
/// @name Behavior handling
@@ -327,7 +337,7 @@
/** Returns HTML reference name for the given object.
- This should only be used for creating anchors that need to be referenced from other parts of the same HTML file. The method works for top-level objects as well as their members.
+ This should only be used for creating anchors that need to be referenced from other parts of the same HTML file. The method works for static documents, top-level objects as well as their members.
@param object The object for which to return reference name.
@return Returns the reference name of the object.
@@ -367,16 +377,70 @@
@param object The object for which to generate the reference to.
@return Returns the reference string.
@exception NSException Thrown if object is `nil`.
+ @see htmlRelativePathToIndexFromObject:
@see htmlReferenceForObject:fromSource:
@see htmlReferenceNameForObject:
*/
- (NSString *)htmlReferenceForObjectFromIndex:(GBModelBase *)object;
+/** Returns relative HTML path from the given object to the index file location.
+
+ This is kind of reverse to `htmlReferenceForObjectFromIndex:`, except that it only returns the relative path, without index.html.
+
+ @param object The object from which to generate the path.
+ @return Returns relative path.
+ @exception NSException Thrown if object is `nil`.
+ @see htmlReferenceForObjectFromIndex:
+ @see htmlReferenceForObject:fromSource:
+ @see htmlReferenceNameForObject:
+ */
+- (NSString *)htmlRelativePathToIndexFromObject:(id)object;
+
+/** The subpath within `outputPath` where static documents are stored.
+ */
+@property (readonly) NSString *htmlStaticDocumentsSubpath;
+
/** The file extension for html files.
*/
@property (readonly) NSString *htmlExtension;
///---------------------------------------------------------------------------------------
+/// @name Application-wide template files helpers
+///---------------------------------------------------------------------------------------
+
+/** Determines if the given path represents a template file or not.
+
+ The method simply checks the if the name of the last path component ends with `-template` string.
+
+ @param path The path to check.
+ @return Returns `YES` if the given path represents a template file, `NO` otherwise.
+ @see outputFilenameForTemplatePath:
+ */
+- (BOOL)isPathRepresentingTemplateFile:(NSString *)path;
+
+/** Returns the actual filename of the output file from the given template path.
+
+ The method simply removes `-template` string from the file name and returns the resulting string. The result is the filename without path but with the same extension as the original path. If the given path doesn't represent a template file, the result is equivalent to sending `lastPathComponent` to the input path.
+
+ @param path The path to convert.
+ @return Returns filename that can be used for output.
+ @see isPathRepresentingTemplateFile
+ @see templateFilenameForOutputPath:
+ */
+- (NSString *)outputFilenameForTemplatePath:(NSString *)path;
+
+/** Returns the template name for the given filename.
+
+ This is reverse method for `outputFilenameForTemplatePath`. It adds `-template` string to the end of the given path filename, before the optional extension.
+
+ @param path The path to convert.
+ @return Returns template filename.
+ @see isPathRepresentingTemplateFile
+ @see outputFilenameForTemplatePath:
+ */
+- (NSString *)templateFilenameForOutputPath:(NSString *)path;
+
+///---------------------------------------------------------------------------------------
/// @name Helper methods
///---------------------------------------------------------------------------------------
View
86 Application/GBApplicationSettingsProvider.m
@@ -28,11 +28,10 @@
@interface GBApplicationSettingsProvider ()
+ (NSSet *)nonCopyableProperties;
-- (NSString *)outputPathForObject:(id)object withExtension:(NSString *)extension;
-- (NSString *)relativePathPrefixFromObject:(GBModelBase *)source toObject:(GBModelBase *)destination;
- (NSString *)htmlReferenceForObjectFromIndex:(GBModelBase *)object;
- (NSString *)htmlReferenceForTopLevelObject:(GBModelBase *)object fromTopLevelObject:(GBModelBase *)source;
-- (NSString *)htmlReferenceForMember:(GBModelBase *)member prefixedWith:(NSString *)prefix;
+- (NSString *)htmlReferenceForMember:(id)member prefixedWith:(id)prefix;
+- (NSString *)outputPathForObject:(id)object withExtension:(NSString *)extension;
- (NSString *)stringByNormalizingString:(NSString *)string;
@property (readonly) NSDateFormatter *yearDateFormatter;
@property (readonly) NSDateFormatter *yearToDayDateFormatter;
@@ -65,6 +64,7 @@ - (id)init {
self.templatesPath = nil;
self.docsetInstallPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Developer/Shared/Documentation/DocSets"];
self.docsetUtilPath = @"/Developer/usr/bin/docsetutil";
+ self.includePaths = [NSMutableSet set];
self.ignoredPaths = [NSMutableSet set];
self.createHTML = YES;
@@ -159,6 +159,7 @@ - (NSString *)stringByEscapingHTML:(NSString *)string {
- (NSString *)htmlReferenceNameForObject:(GBModelBase *)object {
NSParameterAssert(object != nil);
if (object.isTopLevelObject) return [self htmlReferenceForObject:object fromSource:object];
+ if (object.isStaticDocument) return [self htmlReferenceForObject:object fromSource:object];
return [self htmlReferenceForMember:object prefixedWith:@""];
}
@@ -169,15 +170,16 @@ - (NSString *)htmlReferenceForObject:(GBModelBase *)object fromSource:(GBModelBa
if (!source) {
// To top-level object.
if (object.isTopLevelObject) return [self htmlReferenceForObjectFromIndex:object];
+ if (object.isStaticDocument) return [self htmlReferenceForObjectFromIndex:object];
// To a member of top-level object.
NSString *path = [self htmlReferenceForObjectFromIndex:object.parentObject];
NSString *memberReference = [self htmlReferenceForMember:object prefixedWith:@"#"];
return [NSString stringWithFormat:@"%@%@", path, memberReference];
}
- // Generate hrefs from member to other objects:
- if (!source.isTopLevelObject) {
+ // Generate hrefs from members to other objects.
+ if (!source.isTopLevelObject && !source.isStaticDocument) {
GBModelBase *sourceParent = source.parentObject;
// To the parent or another top-level object.
@@ -192,12 +194,10 @@ - (NSString *)htmlReferenceForObject:(GBModelBase *)object fromSource:(GBModelBa
return [NSString stringWithFormat:@"%@%@", path, memberReference];
}
- // From top-level object to samo or another top level object.
- if (object == source || object.isTopLevelObject) {
- return [self htmlReferenceForTopLevelObject:object fromTopLevelObject:source];
- }
+ // From now on we're generating hrefs from top-level object or documents to other documents, top-level objects or their members. First handle links from any kind of object to itself and top-level object or document to top-level object. Handle links from document to document slighlty differently, they are more complicated due to arbitrary directory structure.
+ if (object == source || object.isTopLevelObject || object.isStaticDocument) return [self htmlReferenceForTopLevelObject:object fromTopLevelObject:source];
- // From top-level object to another top-level object member.
+ // From top-level object or document to top-level object member.
NSString *memberPath = [self htmlReferenceForMember:object prefixedWith:@"#"];
if (object.parentObject != source) {
NSString *objectPath = [self htmlReferenceForTopLevelObject:object.parentObject fromTopLevelObject:source];
@@ -212,10 +212,11 @@ - (NSString *)htmlReferenceForObjectFromIndex:(GBModelBase *)object {
return [self outputPathForObject:object withExtension:[self htmlExtension]];
}
-- (NSString *)htmlReferenceForTopLevelObject:(GBModelBase *)object fromTopLevelObject:(GBModelBase *)source {
+- (NSString *)htmlReferenceForTopLevelObject:(id)object fromTopLevelObject:(id)source {
+ // Handles top-level object or document to top-level object or document.
NSString *path = [self outputPathForObject:object withExtension:[self htmlExtension]];
- if ([object isKindOfClass:[source class]]) return [path lastPathComponent];
- NSString *prefix = [self relativePathPrefixFromObject:source toObject:object];
+ if (object == source) return [path lastPathComponent];
+ NSString *prefix = [self htmlRelativePathToIndexFromObject:source];
return [prefix stringByAppendingPathComponent:path];
}
@@ -229,10 +230,42 @@ - (NSString *)htmlReferenceForMember:(GBModelBase *)member prefixedWith:(NSStrin
return @"";
}
+- (NSString *)htmlStaticDocumentsSubpath {
+ return @"docs";
+}
+
- (NSString *)htmlExtension {
return @"html";
}
+#pragma mark Common template files helpers
+
+- (BOOL)isPathRepresentingTemplateFile:(NSString *)path {
+ NSString *filename = [[path lastPathComponent] stringByDeletingPathExtension];
+ if ([filename hasSuffix:@"-template"]) return YES;
+ return NO;
+}
+
+- (NSString *)outputFilenameForTemplatePath:(NSString *)path {
+ NSString *result = [path lastPathComponent];
+ return [result stringByReplacingOccurrencesOfString:@"-template" withString:@""];
+}
+
+- (NSString *)templateFilenameForOutputPath:(NSString *)path {
+ // If the path is already valid template, just return it.
+ if ([self isPathRepresentingTemplateFile:path]) return path;
+
+ // Get all components.
+ NSString *prefix = [path stringByDeletingLastPathComponent];
+ NSString *filename = [[[path lastPathComponent] stringByDeletingPathExtension] stringByAppendingString:@"-template"];
+ NSString *extension = [path pathExtension];
+
+ // Prepare the result.
+ NSString *result = [prefix stringByAppendingPathComponent:filename];
+ if ([extension length] > 0) result = [result stringByAppendingPathExtension:extension];
+ return result;
+}
+
#pragma mark Date and time helpers
- (NSString *)yearStringFromDate:(NSDate *)date {
@@ -264,6 +297,7 @@ - (NSDateFormatter *)yearToDayDateFormatter {
#pragma mark Paths helper methods
- (NSString *)outputPathForObject:(id)object withExtension:(NSString *)extension {
+ // Returns relative path to the given object from the output root (i.e. from the index file).
NSString *basePath = nil;
NSString *name = nil;
if ([object isKindOfClass:[GBClassData class]]) {
@@ -278,14 +312,35 @@ - (NSString *)outputPathForObject:(id)object withExtension:(NSString *)extension
basePath = @"Protocols";
name = [object nameOfProtocol];
}
+ else if ([object isKindOfClass:[GBDocumentData class]]) {
+ GBDocumentData *document = object;
+
+ // Get output filename (removing template suffix) and document subpath without filename. Note that we need to remove extension as we'll add html by default!
+ NSString *subpath = [document.subpathOfDocument stringByDeletingLastPathComponent];
+ NSString *filename = [self outputFilenameForTemplatePath:document.pathOfDocument];
+ filename = [filename stringByDeletingPathExtension];
+
+ // Prepare relative path from output path to the document now.
+ basePath = [self.htmlStaticDocumentsSubpath stringByAppendingPathComponent:subpath];
+ name = filename;
+ }
if (basePath == nil || name == nil) return nil;
basePath = [basePath stringByAppendingPathComponent:name];
return [basePath stringByAppendingPathExtension:extension];
}
-- (NSString *)relativePathPrefixFromObject:(GBModelBase *)source toObject:(GBModelBase *)destination {
- if ([source isKindOfClass:[destination class]]) return @"";
+- (NSString *)htmlRelativePathToIndexFromObject:(id)object {
+ // Returns relative path prefix from the given source to the given destination or empty string if both objects live in the same path. This is pretty simple except when either object is a document. In such case we need to handle arbitrary depth.
+ if ([object isStaticDocument]) {
+ NSString *subpath = [[object subpathOfDocument] stringByDeletingLastPathComponent];
+ if ([subpath length] > 0) {
+ NSArray *components = [subpath pathComponents];
+ NSMutableString *result = [NSMutableString stringWithString:@"../"];
+ for (NSUInteger i=0; i<[components count]; i++) [result appendString:@"../"];
+ return result;
+ }
+ }
return @"../";
}
@@ -359,6 +414,7 @@ - (NSString *)versionIdentifier {
@synthesize docsetInstallPath;
@synthesize docsetUtilPath;
@synthesize templatesPath;
+@synthesize includePaths;
@synthesize ignoredPaths;
@synthesize docsetBundleIdentifier;
View
7 Application/GBApplicationStringsProvider.h
@@ -42,6 +42,13 @@
@property (readonly) NSDictionary *objectMethods;
///---------------------------------------------------------------------------------------
+/// @name Document output strings
+///---------------------------------------------------------------------------------------
+
+/** Strings used for generating common page strings for static documents. */
+@property (readonly) NSDictionary *documentPage;
+
+///---------------------------------------------------------------------------------------
/// @name Index output strings
///---------------------------------------------------------------------------------------
View
11 Application/GBApplicationStringsProvider.m
@@ -81,6 +81,17 @@ - (NSDictionary *)objectMethods {
return result;
}
+#pragma mark Document output strings
+
+- (NSDictionary *)documentPage {
+ static NSMutableDictionary *result = nil;
+ if (!result) {
+ result = [[NSMutableDictionary alloc] init];
+ [result setObject:@"%@ Document" forKey:@"titleTemplate"];
+ }
+ return result;
+}
+
#pragma mark Index output strings
- (NSDictionary *)indexPage {
View
9 Application/GBCommentComponentsProvider.h
@@ -129,6 +129,15 @@
*/
- (NSString *)objectCrossReferenceRegex:(BOOL)templated;
+/** Returns the regex used for matching (possible) static document cross reference with capture 1 containing document name.
+
+ The result of the method depends on the templated value: if the value is `YES`, the string includes template from `crossReferenceMarkersTemplate`, otherwise it only contains "pure" regex. The first option should be used for in-text cross references detection, while the second for `crossReferenceRegex` matching.
+
+ @param templated If `YES` templated regex is returned, otherwise pure one.
+ @return Returns the regex used for matching cross reference.
+ */
+- (NSString *)documentCrossReferenceRegex:(BOOL)templated;
+
/** Returns the regex used for matching URL cross reference with caption 1 contining the URL itself.
The result of the method depends on the templated value: if the value is `YES`, the string includes template from `crossReferenceMarkersTemplate`, otherwise it only contains "pure" regex. The first option should be used for in-text cross references detection, while the second for `crossReferenceRegex` matching.
View
8 Application/GBCommentComponentsProvider.m
@@ -136,6 +136,14 @@ - (NSString *)objectCrossReferenceRegex:(BOOL)templated {
}
}
+- (NSString *)documentCrossReferenceRegex:(BOOL)templated {
+ if (templated) {
+ GBRETURN_ON_DEMAND([self crossReferenceRegexForRegex:[self objectCrossReferenceRegex:NO]]);
+ } else {
+ GBRETURN_ON_DEMAND(@"([^>,.:;!?()\\s]+)");
+ }
+}
+
- (NSString *)urlCrossReferenceRegex:(BOOL)templated {
if (templated) {
GBRETURN_ON_DEMAND([self crossReferenceRegexForRegex:[self urlCrossReferenceRegex:NO]]);
View
2 Developer Notes.markdown
@@ -24,7 +24,7 @@ Main classes and top-down overview
appledoc run session is managed through `GBAppledocApplication` Main responsibilities of the class are preparing settings for the session by combining factory defaults, global settings and command line switches and starting different generation phases, based on the settings: parsing source files, post processing and comments processing, generation of output files. Each phase relies on results of previous ones, so they need to be connected somehow. To avoid close coupling as well as allow additional phases in the future if need be, classes involved are not connected to each other. Instead a common class - `GBStore` is introduced. This class is a container of all information extracted from source files. Similarly, `GBApplicationSettingsProvider` contains all settings for the application. Both classes are passed around the rest of the application classes:
- `GBAppledocApplication`: Creates `GBApplicationSettingsProvider` with required settings, then creates `GBStore` instance. Then it invokes all phases handling classes as required.
-- `GBParser`: Parses all source code and converts it to various objects which are then registered to `GBStore`. After this class is done, store is ready for post processing. It includes all classes, categories and protocols together with their members, all represented as lists of objects in the store. Additionally, all comments are attached to the objects, but only as source strings at this point.
+- `GBParser`: Parses all source code and converts it to various objects which are then registered to `GBStore`. This is also where any static documentation that requires post-processing is searched for and registered to `GBStore`. After this class is done, store is ready for post processing. It includes all classes, categories, protocols and static documents together with their members, all represented as lists of objects in the store. Additionally, all comments are attached to the objects, but only as source strings at this point.
- `GBProcessor`: Post processes objects registered to `GBStore`. This phase requires all objects be registered to store so we can detect cross references and similar. This is where categories are merged to classes and all comments strings are converted into object representations among other things. Exact handling can be changed by various settings. The main responsibility of this phase is to prepare clean store, ready for output generation.
- `GBGenerator`: Uses all objects registered to `GBStore` to generate output as defined by settings.
View
54 Generating/GBHTMLOutputGenerator.m
@@ -20,15 +20,18 @@ - (BOOL)validateTemplates:(NSError **)error;
- (BOOL)processClasses:(NSError **)error;
- (BOOL)processCategories:(NSError **)error;
- (BOOL)processProtocols:(NSError **)error;
+- (BOOL)processDocuments:(NSError **)error;
- (BOOL)processIndex:(NSError **)error;
- (BOOL)processHierarchy:(NSError **)error;
- (NSString *)stringByCleaningHtml:(NSString *)string;
- (NSString *)htmlOutputPathForIndex;
- (NSString *)htmlOutputPathForHierarchy;
- (NSString *)htmlOutputPathForObject:(GBModelBase *)object;
+- (NSString *)htmlOutputPathForTemplateName:(NSString *)template;
@property (readonly) GBTemplateHandler *htmlObjectTemplate;
@property (readonly) GBTemplateHandler *htmlIndexTemplate;
@property (readonly) GBTemplateHandler *htmlHierarchyTemplate;
+@property (readonly) GBTemplateHandler *htmlDocumentTemplate;
@property (readonly) GBHTMLTemplateVariablesProvider *variablesProvider;
@end
@@ -45,6 +48,7 @@ - (BOOL)generateOutputWithStore:(id)store error:(NSError **)error {
if (![self processClasses:error]) return NO;
if (![self processCategories:error]) return NO;
if (![self processProtocols:error]) return NO;
+ if (![self processDocuments:error]) return NO;
if (![self processIndex:error]) return NO;
if (![self processHierarchy:error]) return NO;
return YES;
@@ -98,6 +102,35 @@ - (BOOL)processProtocols:(NSError **)error {
return YES;
}
+- (BOOL)processDocuments:(NSError **)error {
+ // First process all include paths by copying them over to the destination. Note that we do it even if no template is found - if the user specified some include path, we should use it...
+ NSString *docsUserPath = [self.outputUserPath stringByAppendingPathComponent:self.settings.htmlStaticDocumentsSubpath];
+ GBTemplateFilesHandler *handler = [[GBTemplateFilesHandler alloc] init];
+ for (NSString *path in self.settings.includePaths) {
+ GBLogInfo(@"Copying static documents from '%@'...", path);
+ NSString *lastComponent = [path lastPathComponent];
+ NSString *installPath = [docsUserPath stringByAppendingPathComponent:lastComponent];
+ handler.templateUserPath = path;
+ handler.outputUserPath = installPath;
+ if (![handler copyTemplateFilesToOutputPath:error]) return NO;
+ }
+
+ // Now process all documents.
+ for (GBDocumentData *document in self.store.documents) {
+ GBLogInfo(@"Generating output for document %@...", document);
+ NSDictionary *vars = [self.variablesProvider variablesForDocument:document withStore:self.store];
+ NSString *output = [self.htmlDocumentTemplate renderObject:vars];
+ NSString *cleaned = [self stringByCleaningHtml:output];
+ NSString *path = [self htmlOutputPathForObject:document];
+ if (![self writeString:cleaned toFile:[path stringByStandardizingPath] error:error]) {
+ GBLogWarn(@"Failed writting HTML for document %@ to '%@'!", document, path);
+ return NO;
+ }
+ GBLogDebug(@"Finished generating output for document %@.", document);
+ }
+ return YES;
+}
+
- (BOOL)processIndex:(NSError **)error {
GBLogInfo(@"Generating output for index...");
if ([self.store.classes count] > 0 || [self.store.protocols count] > 0 || [self.store.categories count] > 0) {
@@ -164,24 +197,27 @@ - (NSString *)stringByCleaningHtml:(NSString *)string {
- (NSString *)htmlOutputPathForIndex {
// Returns file name including full path for HTML file representing the main index.
- NSString *path = [self outputPathToTemplateEndingWith:@"index-template.html"];
- path = [path stringByAppendingPathComponent:@"index"];
- return [path stringByAppendingPathExtension:self.settings.htmlExtension];
+ return [self htmlOutputPathForTemplateName:@"index-template.html"];
}
- (NSString *)htmlOutputPathForHierarchy {
// Returns file name including full path for HTML file representing the main hierarchy.
- NSString *path = [self outputPathToTemplateEndingWith:@"hierarchy-template.html"];
- path = [path stringByAppendingPathComponent:@"hierarchy"];
- return [path stringByAppendingPathExtension:self.settings.htmlExtension];
+ return [self htmlOutputPathForTemplateName:@"hierarchy-template.html"];
}
- (NSString *)htmlOutputPathForObject:(GBModelBase *)object {
- // Returns file name including full path for HTML file representing the given top-level object. This works for any top-level object: class, category or protocol. The path is automatically determined regarding to the object class.
+ // Returns file name including full path for HTML file representing the given top-level object. This works for any top-level object: class, category or protocol. The path is automatically determined regarding to the object class. Note that we use the HTML reference to get us the actual path - we can't rely on template filename as it's the same for all objects...
NSString *inner = [self.settings htmlReferenceForObjectFromIndex:object];
return [self.outputUserPath stringByAppendingPathComponent:inner];
}
+- (NSString *)htmlOutputPathForTemplateName:(NSString *)template {
+ // Returns full path and actual file name corresponding to the given template.
+ NSString *path = [self outputPathToTemplateEndingWith:template];
+ NSString *filename = [self.settings outputFilenameForTemplatePath:template];
+ return [path stringByAppendingPathComponent:filename];
+}
+
- (GBHTMLTemplateVariablesProvider *)variablesProvider {
static GBHTMLTemplateVariablesProvider *result = nil;
if (!result) {
@@ -203,6 +239,10 @@ - (GBTemplateHandler *)htmlHierarchyTemplate {
return [self.templateFiles objectForKey:@"hierarchy-template.html"];
}
+- (GBTemplateHandler *)htmlDocumentTemplate {
+ return [self.templateFiles objectForKey:@"document-template.html"];
+}
+
#pragma mark Overriden methods
- (NSString *)outputSubpath {
View
21 Generating/GBHTMLTemplateVariablesProvider.h
@@ -54,6 +54,7 @@
@exception NSException Thrown if the given object or store is `nil`.
@see variablesForCategory:withStore:
@see variablesForProtocol:withStore:
+ @see variablesForDocument:withStore:
@see variablesForIndexWithStore:
@see variablesForHierarchyWithStore:
*/
@@ -69,6 +70,7 @@
@exception NSException Thrown if the given object or store is `nil`.
@see variablesForClass:withStore:
@see variablesForProtocol:withStore:
+ @see variablesForDocument:withStore:
@see variablesForIndexWithStore:
@see variablesForHierarchyWithStore:
*/
@@ -84,11 +86,28 @@
@exception NSException Thrown if the given object or store is `nil`.
@see variablesForClass:withStore:
@see variablesForCategory:withStore:
+ @see variablesForDocument:withStore:
@see variablesForIndexWithStore:
@see variablesForHierarchyWithStore:
*/
- (NSDictionary *)variablesForProtocol:(GBProtocolData *)object withStore:(id)store;
+/** Returns the variables for the given `GBDocumentData` using the given `GBStore` for links.
+
+ The result can be used with `GBTemplateHandler` to generate document specific output.
+
+ @param object The document for which to return variables.
+ @param store Store provider to be used for links generation.
+ @return Returns dictionary of all variables
+ @exception NSException Thrown if the given object or store is `nil`.
+ @see variablesForClass:withStore:
+ @see variablesForCategory:withStore:
+ @see variablesForProtocol:withStore:
+ @see variablesForIndexWithStore:
+ @see variablesForHierarchyWithStore:
+ */
+- (NSDictionary *)variablesForDocument:(GBDocumentData *)object withStore:(id)store;
+
/** Returns the variables for the index file using the given `GBStore` for links.
The result can be used with `GBTemplateHandler` to generate protocol specific output.
@@ -99,6 +118,7 @@
@see variablesForClass:withStore:
@see variablesForCategory:withStore:
@see variablesForProtocol:withStore:
+ @see variablesForDocument:withStore:
@see variablesForHierarchyWithStore:
*/
- (NSDictionary *)variablesForIndexWithStore:(id)store;
@@ -113,6 +133,7 @@
@see variablesForClass:withStore:
@see variablesForCategory:withStore:
@see variablesForProtocol:withStore:
+ @see variablesForDocument:withStore:
@see variablesForIndexWithStore:
*/
- (NSDictionary *)variablesForHierarchyWithStore:(id)store;
View
20 Generating/GBHTMLTemplateVariablesProvider.m
@@ -32,6 +32,7 @@ @interface GBHTMLTemplateVariablesProvider (ObjectVariables)
- (NSString *)pageTitleForClass:(GBClassData *)object;
- (NSString *)pageTitleForCategory:(GBCategoryData *)object;
- (NSString *)pageTitleForProtocol:(GBProtocolData *)object;
+- (NSString *)pageTitleForDocument:(GBDocumentData *)object;
- (NSDictionary *)specificationsForClass:(GBClassData *)object;
- (NSDictionary *)specificationsForCategory:(GBCategoryData *)object;
- (NSDictionary *)specificationsForProtocol:(GBProtocolData *)object;
@@ -128,6 +129,20 @@ - (NSDictionary *)variablesForProtocol:(GBProtocolData *)object withStore:(id)st
return result;
}
+- (NSDictionary *)variablesForDocument:(GBDocumentData *)object withStore:(id)store {
+ self.store = store;
+ NSString *path = [self.settings htmlRelativePathToIndexFromObject:object];
+ NSMutableDictionary *page = [NSMutableDictionary dictionary];
+ [page setObject:[self pageTitleForDocument:object] forKey:@"title"];
+ [page setObject:[path stringByAppendingPathComponent:@"css/styles.css"] forKey:@"cssPath"];
+ [self addFooterVarsToDictionary:page];
+ NSMutableDictionary *result = [NSMutableDictionary dictionary];
+ [result setObject:page forKey:@"page"];
+ [result setObject:object forKey:@"object"];
+ [result setObject:self.settings.stringTemplates forKey:@"strings"];
+ return result;
+}
+
#pragma mark Index variables handling
- (NSDictionary *)variablesForIndexWithStore:(id)store {
@@ -217,6 +232,11 @@ - (NSString *)pageTitleForProtocol:(GBProtocolData *)object {
return [NSString stringWithFormat:template, object.nameOfProtocol];
}
+- (NSString *)pageTitleForDocument:(GBDocumentData *)object {
+ NSString *template = [self.settings.stringTemplates valueForKeyPath:@"documentPage.titleTemplate"];
+ return [NSString stringWithFormat:template, [[object.nameOfDocument lastPathComponent] stringByDeletingPathExtension]];
+}
+
- (NSDictionary *)specificationsForClass:(GBClassData *)object {
NSMutableArray *result = [NSMutableArray array];
[self registerObjectInheritsFromSpecificationForClass:object toArray:result];
View
71 Generating/GBOutputGenerator.h
@@ -6,16 +6,18 @@
// Copyright 2010 Gentle Bytes. All rights reserved.
//
-#import <Foundation/Foundation.h>
+#import "GBTemplateFilesHandler.h"
@class GBStore;
@class GBApplicationSettingsProvider;
/** The base class for all output generators.
Output generator is an object that handles a specific spet while generating output. These are helper classes for `GBGenerator` class; each concrete subclass handles specifics for certain step. Generator just ties all of these together into properly ordered chain as required by command line parameters.
+
+ @warning *Implementation detail:* As this is a subclass of `GBTemplateFilesHandler`, it automatically inherits all template files processing functionality from it. However it overrides `templateUserPath` and `outputUserPath` to use values from `GBApplicationSettingsProvider`. This greatly simplifies output generators handling - instead of requiring clients to setup proper paths, subclasses only need to override `outputSubpath` and return subdirectory which is used for both: actual template files and output files location; in both cases, the subpath is appended to the locations from the settings.
*/
-@interface GBOutputGenerator : NSObject
+@interface GBOutputGenerator : GBTemplateFilesHandler
///---------------------------------------------------------------------------------------
/// @name Initialization & disposal
@@ -68,21 +70,6 @@
*/
- (BOOL)initializeDirectoryAtPath:(NSString *)path preserve:(NSArray *)preserve error:(NSError **)error;
-/** Copies all files from the templates path to the output path as defined in assigned `settings`, replicating the directory structure and stores all detected template files to `templateFiles` dictionary.
-
- The method uses `[GBApplicationSettingsProvider templatesPath]` as the base path for templates and `[GBApplicationSettingsProvider outputPath]` as the base path for output. In both cases, `outputSubpath` is used to determine the source and destination subdirectories. It then copies all files from template path to the output path, including the whole directory structure. If any special template file is found at source path, it is not copied! Template files are identified by having a `-template` suffix followed by optional extension. For example `object-template.html`. As this message prepares the ground for actual generation, it should be sent before any other messages (i.e. before `generateOutput:`).
-
- To further aid subclasses, the method reads out all template files in templates path and stores them to `templateFiles` dictionary. Each template file is stored with a key correspoding to it's filename, including the subdirectory within the base template path and extension.
-
- @warning *Note:* This message is intended to be sent from higher-level generator objects. Although it would present no error to run it several times, in most circumstances subclasses don't need to send it manually. If copying fails, a warning is logged and copying is stopped. Depending of type of failure, the method either returns `YES` or `NO`. If copying of all files is succesful, but reading or clearing template or ignored files fails, the operation is still considered succesful, so `YES` is returned. However if replicating the directory structure or copying files fails, this is considered an error and `NO` is returned. In such case, clients should abort further processing.
-
-
- @param error If copying fails, error description is returned here.
- @return Returns `YES` if all files were succesfully copied, `NO` otherwise.
- @see generateOutputWithStore:error:
- */
-- (BOOL)copyTemplateFilesToOutputPath:(NSError **)error;
-
/** Copies or moves directory or file from the given source path to the destination path.
This method takes into account `[GBApplicationSettings keepIntermediateFiles]` and either copies or moves files regarding it's value. The method is designed to be used from within subclasses.
@@ -122,47 +109,6 @@
/// @name Subclass parameters and helpers
///---------------------------------------------------------------------------------------
-/** Returns the full path to the template ending with the given string.
-
- The method searches `templateFiles` for a key ending with the given suffix and returns full path to the given template. This is useful for getting the template for which we only know filename, but not the whole path for example.
-
- @param suffix Template file suffix to search for.
- @return Returns template path to the given template or `nil` if not found.
- @see outputPathToTemplateEndingWith:
- @see templateFiles
- */
-- (NSString *)templatePathForTemplateEndingWith:(NSString *)suffix;
-
-/** Returns the path to the template ending with the given string.
-
- The method searches `templateFiles` for a key ending with the given suffix and returns the path to the output directory corresponding to the given template subpath. This is useful for generating actual template file names - just append the desired filename and you have output file name ready!
-
- @param suffix Template file suffix to search for.
- @return Returns output path corresponding to the given template or `nil` if not found.
- @see templatePathForTemplateEndingWith:
- @see templateFiles
- */
-- (NSString *)outputPathToTemplateEndingWith:(NSString *)suffix;
-
-/** The dictionary of all template files detected within `copyTemplateFilesToOutputPath:`.
-
- Each object has a key of template file name and relative path from `templateUserPath`. The keys are mapped to `GBTemplateHandler` instances associated with the template.
-
- This is intended to be used within subclasses only. Dictionary contents are automatically updated and should not be changed by subclasses.
-
- @see copyTemplateFilesToOutputPath:
- */
-@property (readonly) NSMutableDictionary *templateFiles;
-
-/** Returns user-friendly template path string including `outputSubpath`.
-
- This uses the same string as entered by the user when starting the application. Send `stringByStandardizingPath` message to the returned value before using it!
-
- @see inputUserPath
- @see outputUserPath
- */
-@property (readonly) NSString *templateUserPath;
-
/** Returns user-friendly input path string.
This is simply a shortcut for output path of previous generator. Internally it works by sending the `outputUserPath` message to `previousGenerator and returning the result. If previous generator is `nil` (i.e. this is the first generator), `nil` is returned. Send `stringByStandardizingPath` message to the returned value before using it!
@@ -173,15 +119,6 @@
*/
@property (readonly) NSString *inputUserPath;
-/** Returns the output path including `outputSubpath`.
-
- This uses the same string as entered by the user when starting the application. Send `stringByStandardizingPath` message to the returned value before using it!
-
- @see inputUserPath
- @see templateUserPath
- */
-@property (readonly) NSString *outputUserPath;
-
/** Returns the `GBOutputGenerator` that was used just before this one or `nil` if this is the first generator in this session.
This is useful for subclasses that rely on files generated by previous generator. For example, we can get the location of generated files so we can further manipulate them. If a concrete subclass requires previous generator, and none is provided, it should throw an exception from `generateOutputWithStore:error:`!
View
127 Generating/GBOutputGenerator.m
@@ -13,9 +13,6 @@
@interface GBOutputGenerator ()
-- (GBTemplateHandler *)templateHandlerFromTemplateFile:(NSString *)filename error:(NSError **)error;
-- (BOOL)isPathRepresentingTemplateFile:(NSString *)path;
-- (BOOL)isPathRepresentingIgnoredFile:(NSString *)path;
@property (readwrite, retain) GBStore *store;
@end
@@ -48,77 +45,6 @@ - (BOOL)generateOutputWithStore:(id)store error:(NSError **)error {
return YES;
}
-- (BOOL)copyTemplateFilesToOutputPath:(NSError **)error {
- // Remove all previous template files.
- [self.templateFiles removeAllObjects];
-
- // Prepare source and destination paths.
- NSString *sourceUserPath = self.templateUserPath;
- NSString *destUserPath = self.outputUserPath;
- NSString *sourcePath = [sourceUserPath stringByStandardizingPath];
- NSString *destPath = [destUserPath stringByStandardizingPath];
- GBLogVerbose(@"Copying template files from '%@' to '%@'...", sourceUserPath, destUserPath);
-
- // Remove destination path if it exists. Exit if we fail.
- if ([self.fileManager fileExistsAtPath:destPath]) {
- GBLogDebug(@"Removing output at '%@'...", destUserPath);
- if (![self.fileManager removeItemAtPath:destPath error:error]) {
- GBLogWarn(@"Failed removing output files at '%@'!", destUserPath);
- return NO;
- }
- }
-
- // Create directory hierarchy minus the last one. This is necessary if more than one component is missing at destination path; copyItemAtPath:toPath:error would fail in such case. Note that we can't create the last directory as mentioned method request is that the destination doesn't exist!
- NSString *createDestPath = [destPath stringByDeletingLastPathComponent];
- if (![self.fileManager createDirectoryAtPath:createDestPath withIntermediateDirectories:YES attributes:nil error:error]) {
- GBLogWarn(@"Failed creating directory '%@'!", createDestPath);
- return NO;
- }
-
- // If there's no source file, there also no need to copy anything, so exit. In fact, copying would probably just result in errors.
- if (![self.fileManager fileExistsAtPath:sourcePath]) {
- GBLogDebug(@"No template file found at '%@', no need to copy.", sourceUserPath);
- return YES;
- }
-
- // Copy the whole source directory over to output. Exit if we fail.
- GBLogDebug(@"Copying template files from '%@' to '%@'...", sourceUserPath, destUserPath);
- if (![self.fileManager copyItemAtPath:sourcePath toPath:destPath error:error]) {
- GBLogWarn(@"Failed copying templates from '%@' to '%@'!", sourceUserPath, destUserPath);
- return NO;
- }
-
- // Remove all ignored files and special template items from output. First enumerate all files. If this fails, report success; this step is only used to verscleanup the destination, we should still have valid output if these files are kept there. Note that we need to test for existing file before removing as it could happen file's parent dir was removed already in previous iterations so the file or subdir doesn't exist anymore - see https://github.com/tomaz/appledoc/issues#issue/59 for details.
- GBLogDebug(@"Removing temporary files from '%@'...", destUserPath);
- NSArray *items = [self.fileManager subpathsOfDirectoryAtPath:destPath error:error];
- if (!items) {
- GBLogWarn(@"Failed enumerating template files at '%@'!", destUserPath);
- return YES;
- }
- for (NSString *path in items) {
- BOOL delete = NO;
- if ([self isPathRepresentingIgnoredFile:path]) {
- GBLogDebug(@"Removing ignored file '%@' from output...", path);
- delete = YES;
- } else if ([self isPathRepresentingTemplateFile:path]) {
- GBTemplateHandler *handler = [self templateHandlerFromTemplateFile:path error:error];
- if (!handler) return NO;
- GBLogDebug(@"Removing template file '%@' from output...", path);
- [self.templateFiles setObject:handler forKey:path];
- delete = YES;
- }
-
- if (delete) {
- NSString *fullpath = [destPath stringByAppendingPathComponent:path];
- if ([self.fileManager fileExistsAtPath:fullpath] && ![self.fileManager removeItemAtPath:fullpath error:error]) {
- GBLogWarn(@"Can't clean leftover '%@' from '%@'.", path, destUserPath);
- }
- }
- }
-
- return YES;
-}
-
- (BOOL)initializeDirectoryAtPath:(NSString *)path error:(NSError **)error {
return [self initializeDirectoryAtPath:path preserve:nil error:error];
}
@@ -175,36 +101,8 @@ - (BOOL)copyOrMoveItemFromPath:(NSString *)source toPath:(NSString *)destination
return [self.fileManager moveItemAtPath:source toPath:destination error:error];
}
-- (BOOL)isPathRepresentingTemplateFile:(NSString *)path {
- NSString *filename = [[path lastPathComponent] stringByDeletingPathExtension];
- if ([filename hasSuffix:@"-template"]) return YES;
- return NO;
-}
-
-- (BOOL)isPathRepresentingIgnoredFile:(NSString *)path {
- NSString *filename = [path lastPathComponent];
- if ([filename hasPrefix:@"."]) return YES;
- return NO;
-}
-
#pragma mark Helper methods
-- (NSString *)templatePathForTemplateEndingWith:(NSString *)suffix {
- for (NSString *template in [self.templateFiles allKeys]) {
- if ([template hasSuffix:suffix]) return template;
- }
- return nil;
-}
-
-- (NSString *)outputPathToTemplateEndingWith:(NSString *)suffix {
- NSString *template = [self templatePathForTemplateEndingWith:suffix];
- if (template) {
- NSString *path = [template substringToIndex:[template length] - [suffix length]];
- return [self.outputUserPath stringByAppendingPathComponent:path];
- }
- return nil;
-}
-
- (BOOL)writeString:(NSString *)string toFile:(NSString *)path error:(NSError **)error {
NSString *standardized = [path stringByStandardizingPath];
NSString *directory = [standardized stringByDeletingLastPathComponent];
@@ -221,36 +119,21 @@ - (BOOL)writeString:(NSString *)string toFile:(NSString *)path error:(NSError **
return YES;
}
-- (GBTemplateHandler *)templateHandlerFromTemplateFile:(NSString *)filename error:(NSError **)error {
- NSString *path = [[self templateUserPath] stringByAppendingPathComponent:filename];
- GBLogDebug(@"Creating template handler for template file '%@'...", path);
- GBTemplateHandler *result = [GBTemplateHandler handler];
- if (![result parseTemplateFromPath:[path stringByStandardizingPath] error:error]) {
- GBLogWarn(@"Failed parsing template '%@'!", filename);
- return nil;
- }
- return result;
-}
-
#pragma mark Subclass helpers
- (NSString *)templateUserPath {
+ // Overriden to simplify handling.
return [self.settings.templatesPath stringByAppendingPathComponent:self.outputSubpath];
}
-- (NSString *)inputUserPath {
- if (!self.previousGenerator) return nil;
- return self.previousGenerator.outputUserPath;
-}
-
- (NSString *)outputUserPath {
+ // Overriden to simplify handling.
return [self.settings.outputPath stringByAppendingPathComponent:self.outputSubpath];
}
-- (NSMutableDictionary *)templateFiles {
- static NSMutableDictionary *result = nil;
- if (!result) result = [[NSMutableDictionary alloc] init];
- return result;
+- (NSString *)inputUserPath {
+ if (!self.previousGenerator) return nil;
+ return self.previousGenerator.outputUserPath;
}
#pragma mark Generation parameters
View
89 Generating/GBTemplateFilesHandler.h
@@ -0,0 +1,89 @@
+//
+// GBTemplateFilesHandler.h
+// appledoc
+//
+// Created by Tomaz Kragelj on 10.2.11.
+// Copyright 2011 Gentle Bytes. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+/** Implements common functionality for handling template files.
+
+ Template files handler as an object that takes templates path and output path and copies all files from template path to the output path collecting all template files during the process. Template files are all files which names end with `-template` string with arbitrary extension. Such files are collected for post processing, but are not copied over to the output path. It's up to the client or sublcass to handle these files as needed. The object preserves template directory structure.
+
+ */
+@interface GBTemplateFilesHandler : NSObject
+
+///---------------------------------------------------------------------------------------
+/// @name Template files handling
+///---------------------------------------------------------------------------------------
+
+/** Copies all files from the templates path to the output path as defined in assigned `settings`, replicating the directory structure and stores all detected template files to `templateFiles` dictionary.
+
+ The method uses `[GBApplicationSettingsProvider templatesPath]` as the base path for templates and `[GBApplicationSettingsProvider outputPath]` as the base path for output. In both cases, `outputSubpath` is used to determine the source and destination subdirectories. It then copies all files from template path to the output path, including the whole directory structure. If any special template file is found at source path, it is not copied! Template files are identified by having a `-template` suffix followed by optional extension. For example `object-template.html`. As this message prepares the ground for actual generation, it should be sent before any other messages (i.e. before `generateOutput:`).
+
+ To further aid subclasses, the method reads out all template files in templates path and stores them to `templateFiles` dictionary. Each template file is stored with a key correspoding to it's filename, including the subdirectory within the base template path and extension.
+
+ @warning *Note:* This message is intended to be sent from higher-level generator objects. Although it would present no error to run it several times, in most circumstances subclasses don't need to send it manually. If copying fails, a warning is logged and copying is stopped. Depending of type of failure, the method either returns `YES` or `NO`. If copying of all files is succesful, but reading or clearing template or ignored files fails, the operation is still considered succesful, so `YES` is returned. However if replicating the directory structure or copying files fails, this is considered an error and `NO` is returned. In such case, clients should abort further processing.
+
+
+ @param error If copying fails, error description is returned here.
+ @return Returns `YES` if all files were succesfully copied, `NO` otherwise.
+ @see generateOutputWithStore:error:
+ */
+- (BOOL)copyTemplateFilesToOutputPath:(NSError **)error;
+
+/** Returns the full path to the template ending with the given string.
+
+ The method searches `templateFiles` for a key ending with the given suffix and returns full path to the given template. This is useful for getting the template for which we only know filename, but not the whole path for example.
+
+ @param suffix Template file suffix to search for.
+ @return Returns template path to the given template or `nil` if not found.
+ @see outputPathToTemplateEndingWith:
+ @see templateFiles
+ */
+- (NSString *)templatePathForTemplateEndingWith:(NSString *)suffix;
+
+/** Returns the path to the template ending with the given string.
+
+ The method searches `templateFiles` for a key ending with the given suffix and returns the path to the output directory corresponding to the given template subpath. This is useful for generating actual template file names - just append the desired filename and you have output file name ready!
+
+ @param suffix Template file suffix to search for.
+ @return Returns output path corresponding to the given template or `nil` if not found.
+ @see templatePathForTemplateEndingWith:
+ @see templateFiles
+ */
+- (NSString *)outputPathToTemplateEndingWith:(NSString *)suffix;
+
+///---------------------------------------------------------------------------------------
+/// @name Parameters handling
+///---------------------------------------------------------------------------------------
+
+/** The dictionary of all template files detected within `copyTemplateFilesToOutputPath:`.
+
+ Each object has a key of template file name and relative path from `templateUserPath`. The keys are mapped to `GBTemplateHandler` instances associated with the template.
+
+ This is intended to be used within subclasses only. Dictionary contents are automatically updated and should not be changed by subclasses.
+
+ @see copyTemplateFilesToOutputPath:
+ */
+@property (retain) NSMutableDictionary *templateFiles;
+
+/** Returns user-friendly template path string including `outputSubpath`.
+
+ This must be set prior to any handling! Send `stringByStandardizingPath` message to the returned value before using it!
+
+ @see outputUserPath
+ */
+@property (copy) NSString *templateUserPath;
+
+/** Returns the output path including `outputSubpath`.
+
+ This must be set prior to any handling! Send `stringByStandardizingPath` message to the returned value before using it!
+
+ @see templateUserPath
+ */
+@property (copy) NSString *outputUserPath;
+
+@end
View
154 Generating/GBTemplateFilesHandler.m
@@ -0,0 +1,154 @@
+//
+// GBTemplateFilesHandler.m
+// appledoc
+//
+// Created by Tomaz Kragelj on 10.2.11.
+// Copyright 2011 Gentle Bytes. All rights reserved.
+//
+
+#import "GBTemplateHandler.h"
+#import "GBTemplateFilesHandler.h"
+
+@interface GBTemplateFilesHandler ()
+
+- (BOOL)isPathRepresentingTemplateFile:(NSString *)path;
+- (BOOL)isPathRepresentingIgnoredFile:(NSString *)path;
+- (GBTemplateHandler *)templateHandlerFromTemplateFile:(NSString *)filename error:(NSError **)error;
+
+@end
+
+#pragma mark -
+
+@implementation GBTemplateFilesHandler
+
+#pragma mark Initialization & disposal
+
+- (id)init {
+ self = [super init];
+ if (self) {
+ self.templateFiles = [NSMutableDictionary dictionary];
+ }
+ return self;
+}
+
+#pragma mark Template files handling
+
+- (BOOL)copyTemplateFilesToOutputPath:(NSError **)error {
+ // Remove all previous template files.
+ [self.templateFiles removeAllObjects];
+
+ // Prepare source and destination paths.
+ NSString *sourceUserPath = self.templateUserPath;
+ NSString *destUserPath = self.outputUserPath;
+ NSString *sourcePath = [sourceUserPath stringByStandardizingPath];
+ NSString *destPath = [destUserPath stringByStandardizingPath];
+ GBLogVerbose(@"Copying template files from '%@' to '%@'...", sourceUserPath, destUserPath);
+
+ // Remove destination path if it exists. Exit if we fail.
+ if ([self.fileManager fileExistsAtPath:destPath]) {
+ GBLogDebug(@"Removing output at '%@'...", destUserPath);
+ if (![self.fileManager removeItemAtPath:destPath error:error]) {
+ GBLogWarn(@"Failed removing output files at '%@'!", destUserPath);
+ return NO;
+ }
+ }
+
+ // Create directory hierarchy minus the last one. This is necessary if more than one component is missing at destination path; copyItemAtPath:toPath:error would fail in such case. Note that we can't create the last directory as mentioned method request is that the destination doesn't exist!
+ NSString *createDestPath = [destPath stringByDeletingLastPathComponent];
+ if (![self.fileManager createDirectoryAtPath:createDestPath withIntermediateDirectories:YES attributes:nil error:error]) {
+ GBLogWarn(@"Failed creating directory '%@'!", createDestPath);
+ return NO;
+ }
+
+ // If there's no source file, there also no need to copy anything, so exit. In fact, copying would probably just result in errors.
+ if (![self.fileManager fileExistsAtPath:sourcePath]) {
+ GBLogDebug(@"No template file found at '%@', no need to copy.", sourceUserPath);
+ return YES;
+ }
+
+ // Copy the whole source directory over to output. Exit if we fail.
+ GBLogDebug(@"Copying template files from '%@' to '%@'...", sourceUserPath, destUserPath);
+ if (![self.fileManager copyItemAtPath:sourcePath toPath:destPath error:error]) {
+ GBLogWarn(@"Failed copying templates from '%@' to '%@'!", sourceUserPath, destUserPath);
+ return NO;
+ }
+
+ // Remove all ignored files and special template items from output. First enumerate all files. If this fails, report success; this step is only used to verscleanup the destination, we should still have valid output if these files are kept there. Note that we need to test for existing file before removing as it could happen file's parent dir was removed already in previous iterations so the file or subdir doesn't exist anymore - see https://github.com/tomaz/appledoc/issues#issue/59 for details.
+ GBLogDebug(@"Removing temporary files from '%@'...", destUserPath);
+ NSArray *items = [self.fileManager subpathsOfDirectoryAtPath:destPath error:error];
+ if (!items) {
+ GBLogWarn(@"Failed enumerating template files at '%@'!", destUserPath);
+ return YES;
+ }
+ for (NSString *path in items) {
+ BOOL delete = NO;
+ if ([self isPathRepresentingIgnoredFile:path]) {
+ GBLogDebug(@"Removing ignored file '%@' from output...", path);
+ delete = YES;
+ } else if ([self isPathRepresentingTemplateFile:path]) {
+ GBTemplateHandler *handler = [self templateHandlerFromTemplateFile:path error:error];
+ if (!handler) return NO;
+ GBLogDebug(@"Removing template file '%@' from output...", path);
+ [self.templateFiles setObject:handler forKey:path];
+ delete = YES;
+ }
+
+ if (delete) {
+ NSString *fullpath = [destPath stringByAppendingPathComponent:path];
+ if ([self.fileManager fileExistsAtPath:fullpath] && ![self.fileManager removeItemAtPath:fullpath error:error]) {
+ GBLogWarn(@"Can't clean leftover '%@' from '%@'.", path, destUserPath);
+ }
+ }
+ }
+
+ return YES;
+}
+
+- (NSString *)templatePathForTemplateEndingWith:(NSString *)suffix {
+ for (NSString *template in [self.templateFiles allKeys]) {
+ if ([template hasSuffix:suffix]) return template;
+ }
+ return nil;
+}
+
+- (NSString *)outputPathToTemplateEndingWith:(NSString *)suffix {
+ NSString *template = [self templatePathForTemplateEndingWith:suffix];
+ if (template) {
+ NSString *path = [template substringToIndex:[template length] - [suffix length]];
+ return [self.outputUserPath stringByAppendingPathComponent:path];
+ }
+ return nil;
+}
+
+#pragma mark Helper methods
+
+- (GBTemplateHandler *)templateHandlerFromTemplateFile:(NSString *)filename error:(NSError **)error {
+ NSString *path = [[self templateUserPath] stringByAppendingPathComponent:filename];
+ GBLogDebug(@"Creating template handler for template file '%@'...", path);
+ GBTemplateHandler *result = [GBTemplateHandler handler];
+ if (![result parseTemplateFromPath:[path stringByStandardizingPath] error:error]) {
+ GBLogWarn(@"Failed parsing template '%@'!", filename);
+ return nil;
+ }
+ return result;
+}
+
+- (BOOL)isPathRepresentingTemplateFile:(NSString *)path {
+ NSString *filename = [[path lastPathComponent] stringByDeletingPathExtension];
+ if ([filename hasSuffix:@"-template"]) return YES;
+ return NO;
+}
+
+- (BOOL)isPathRepresentingIgnoredFile:(NSString *)path {
+ NSString *filename = [path lastPathComponent];
+ if ([filename hasPrefix:@"."]) return YES;
+ return NO;
+}
+
+#pragma mark Properties
+
+@synthesize templateFiles;
+@synthesize templateUserPath;
+@synthesize outputUserPath;
+
+@end
View
1 Model/GBDataObjects.h
@@ -19,6 +19,7 @@
#import "GBClassData.h"
#import "GBCategoryData.h"
#import "GBProtocolData.h"
+#import "GBDocumentData.h"
#import "GBIvarData.h"
#import "GBMethodData.h"
View
101 Model/GBDocumentData.h
@@ -0,0 +1,101 @@
+//
+// GBDocumentData.h
+// appledoc
+//
+// Created by Tomaz Kragelj on 10.2.11.
+// Copyright 2011 Gentle Bytes. All rights reserved.
+//
+
+#import "GBModelBase.h"
+#import "GBObjectDataProviding.h"
+
+@class GBAdoptedProtocolsProvider;
+@class GBMethodsProvider;
+
+/** Describes a static document.
+ */
+@interface GBDocumentData : GBModelBase <GBObjectDataProviding> {
+ @private
+ GBAdoptedProtocolsProvider *_adoptedProtocols;
+ GBMethodsProvider *_methods;
+}
+
+///---------------------------------------------------------------------------------------
+/// @name Initialization & disposal
+///---------------------------------------------------------------------------------------
+
+/** Returns autoreleased instance of the document data with the given contents.
+
+ @param contents Contents of the document as read from file.
+ @param path Full path to the document.
+ @return Returns initialized object.
+ @exception NSException Thrown if the given contents is `nil`.
+ */
++ (id)documentDataWithContents:(NSString *)contents path:(NSString *)path;
+
+/** Returns autoreleased instance of the document data with the given contents.
+
+ This is just a convenience initializer that also sets `basePathOfDocument`.
+
+ @param contents Contents of the document as read from file.
+ @param path Full path to the document.
+ @param basePath Full base path of the document as specified from the include switch.
+ @return Returns initialized object.
+ @exception NSException Thrown if the given contents is `nil`.
+ */
++ (id)documentDataWithContents:(NSString *)contents path:(NSString *)path basePath:(NSString *)basePath;
+
+/** Initializes the document with the given contents.
+
+ The initializer copies the given contents string into the assigned comment's string value which unifies post processing handling. This is the designated initializer.
+
+ @param contents Contents of the document as read from file.
+ @param path Full path to the document.
+ @return Returns initialized object.
+ @exception NSException Thrown if the given contents is `nil`.
+ */
+- (id)initWithContents:(NSString *)contents path:(NSString *)path;
+
+///---------------------------------------------------------------------------------------
+/// @name Properties
+///---------------------------------------------------------------------------------------
+
+/** The name of the document.
+
+ Name is automatically retrieved from the `pathOfDocument` inside the initializer.
+
+ @see pathOfDocument
+ @see basePathOfDocument
+ */
+@property (copy) NSString *nameOfDocument;
+
+/** Full path of the document source, including the file name and extension.
+
+ @see nameOfDocument
+ @see basePathOfDocument
+ @see subpathOfDocument
+ */
+@property (copy) NSString *pathOfDocument;
+
+/** Input base path from the setting at which the document was found.
+
+ This is used to prepare proper HTML references from the document to other objects among other things.
+
+ @warning *Important:* The value must be set by client in order for the object to be usable! The client must supply full, standardized path - i.e. use `stringByStandardizingPath` for the value assigned!
+
+ @see pathOfDocument
+ @see subpathOfDocument
+ @see nameOfDocument
+ */
+@property (copy) NSString *basePathOfDocument;
+
+/** Returns the subpath of the document including document's filename.
+
+ Subpath is simply the subpath within the `basePathOfDocument`. For example, if the value of `pathOfDocument` is `path/sub/document.ext` and value of `basePathOfDocument` is `path`, this returns `sub/document.ext`.
+
+ @see pathOfDocument
+ @see basePathOfDocument
+ */
+@property (readonly) NSString *subpathOfDocument;
+
+@end
View
73 Model/GBDocumentData.m
@@ -0,0 +1,73 @@
+//
+// GBDocumentData.m
+// appledoc
+//
+// Created by Tomaz Kragelj on 10.2.11.
+// Copyright 2011 Gentle Bytes. All rights reserved.
+//
+
+#import "GBApplicationSettingsProvider.h"
+#import "GBDataObjects.h"
+#import "GBDocumentData.h"
+
+@implementation GBDocumentData
+
+#pragma mark Initialization & disposal
+
++ (id)documentDataWithContents:(NSString *)contents path:(NSString *)path {
+ return [[[self alloc] initWithContents:contents path:path] autorelease];
+}
+
++ (id)documentDataWithContents:(NSString *)contents path:(NSString *)path basePath:(NSString *)basePath {
+ id result = [self documentDataWithContents:contents path:path];
+ [result setBasePathOfDocument:basePath];
+ return result;
+}
+
+- (id)initWithContents:(NSString *)contents path:(NSString *)path {
+ NSParameterAssert(contents != nil);
+ GBLogDebug(@"Initializing document with contents %@...", [contents normalizedDescription]);
+ self = [super init];
+ if (self) {
+ GBSourceInfo *info = [GBSourceInfo infoWithFilename:[path lastPathComponent] lineNumber:1];
+ [self registerSourceInfo:info];
+ self.nameOfDocument = [path lastPathComponent];
+ self.pathOfDocument = path;
+ self.basePathOfDocument = @"";
+ self.comment = [GBComment commentWithStringValue:contents];
+ self.comment.sourceInfo = info;
+ _adoptedProtocols = [[GBAdoptedProtocolsProvider alloc] initWithParentObject:self];
+ _methods = [[GBMethodsProvider alloc] initWithParentObject:self];
+ }
+ return self;
+}
+
+#pragma mark Overriden methods
+
+- (NSString *)description {
+ return self.nameOfDocument;
+}
+
+- (NSString *)debugDescription {
+ return [NSString stringWithFormat:@"document %@", self.nameOfDocument];
+}
+
+- (BOOL)isStaticDocument {
+ return YES;
+}
+
+#pragma mark Properties
+
+- (NSString *)subpathOfDocument {
+ NSString *result = [self.pathOfDocument stringByReplacingOccurrencesOfString:self.basePathOfDocument withString:@""];
+ if ([result hasPrefix:@"/"]) result = [result substringFromIndex:1];
+ return result;
+}
+
+@synthesize nameOfDocument;
+@synthesize pathOfDocument;
+@synthesize basePathOfDocument;
+@synthesize adoptedProtocols = _adoptedProtocols;
+@synthesize methods = _methods;
+
+@end
View
8 Model/GBModelBase.h
@@ -92,9 +92,17 @@
*/
@property (retain) id parentObject;
+/** Specifies whether this is a static object or not.
+
+ @see isTopLevelObject
+ */
+@property (readonly) BOOL isStaticDocument;
+
/** Specifies whether this is a top level object or not.
Top level objects are classes, categories and protocols.
+
+ @see isStaticDocument;
*/
@property (readonly) BOOL isTopLevelObject;
View
4 Model/GBModelBase.m
@@ -88,6 +88,10 @@ - (NSArray *)sourceInfosSortedByName {
#pragma Helper methods
+- (BOOL)isStaticDocument {
+ return NO;
+}
+
- (BOOL)isTopLevelObject {
return NO;
}
View
46 Model/GBStore.h
@@ -11,6 +11,7 @@
@class GBClassData;
@class GBCategoryData;
@class GBProtocolData;
+@class GBDocumentData;
/** Implements the application's in-memory objects data store.
@@ -24,15 +25,17 @@
NSMutableDictionary *_categoriesByName;
NSMutableSet *_protocols;
NSMutableDictionary *_protocolsByName;
+ NSMutableSet *_documents;
+ NSMutableDictionary *_documentsByName;
}
///---------------------------------------------------------------------------------------
/// @name Registrations handling
///---------------------------------------------------------------------------------------
-/** Registers the given class to the providers data.
+/** Registers the given class to the store data.
- If provider doesn't yet have the given class instance registered, the object is added to `classes` list. If the same object is already registered, nothing happens.
+ If store doesn't yet have the given class instance registered, the object is added to `classes` list. If the same instance is already registered, nothing happens.
@warning *Note:* If another instance of the class with the same name is registered, an exception is thrown.
@@ -46,9 +49,9 @@
*/
- (void)registerClass:(GBClassData *)class;
-/** Registers the given category to the providers data.
+/** Registers the given category to the store data.
- If provider doesn't yet have the given category instance registered, the object is added to `categories` list. If the same object is already registered, nothing happens.
+ If store doesn't yet have the given category instance registered, the object is added to `categories` list. If the same instance is already registered, nothing happens.
@warning *Note:* If another instance of the category with the same name/class name is registered, an exception is thrown.
@@ -62,11 +65,11 @@
*/
- (void)registerCategory:(GBCategoryData *)category;
-/** Registers the given protocol to the providers data.
+/** Registers the given protocol to the store data.
- If provider doesn't yet have the given protocol instance registered, the object is added to `protocols` list. If the same object is already registered, nothing happens.
+ If store doesn't yet have the given protocol instance registered, the object is added to `protocols` list. If the same instance is already registered, nothing happens.
- @warning *Note:* If another instance of the protocol with the same name name is registered, an exception is thrown.
+ @warning *Note:* If another instance of the protocol with the same name is registered, an exception is thrown.
@param protocol The protocol to register.
@exception NSException Thrown if the given protocol is already registered.
@@ -78,6 +81,19 @@
*/
- (void)registerProtocol:(GBProtocolData *)protocol;
+/** Registers the given static document to the store data.
+
+ If store doesn't yet have the given document instance registered, the object is added to `documents` list. If the same instance is already regsitered, nothing happens.
+
+ @warning *Note:* If another instance of the document with the same path is registered, an exception is thrown.
+
+ @param document The document to register.
+ @exception NSException Thrown if the given document is already registered.
+ @see documentWithPath:
+ @see documents
+ */
+- (void)registerDocument:(GBDocumentData *)document;
+
/** Unregisters the given class, category or protocol.
If the object is not part of the store, nothing happens.
@@ -129,6 +145,16 @@
*/
- (GBProtocolData *)protocolWithName:(NSString *)name;
+/** Returns the document instance that matches the given path.
+
+ If no registered document matches the given path, `nil` is returned.
+
+ @param path Full path of the document to return.
+ @return Returns document instance or `nil` if no match is found.
+ @see documents
+ */
+- (GBDocumentData *)documentWithName:(NSString *)path;
+
/** The list of all registered classes as instances of `GBClassData`.
@see classWithName:
@@ -150,6 +176,12 @@
*/
@property (readonly) NSSet *protocols;
+/** The list of all registered documents as instances of `GBDocumentData`.
+
+ @see registerDocument:
+ */
+@property (readonly) NSSet *documents;
+
///---------------------------------------------------------------------------------------
/// @name Helper methods
///---------------------------------------------------------------------------------------
View
21 Model/GBStore.m
@@ -22,6 +22,8 @@ - (id)init {
_categoriesByName = [[NSMutableDictionary alloc] init];
_protocols = [[NSMutableSet alloc] init];
_protocolsByName = [[NSMutableDictionary alloc] init];
+ _documents = [[NSMutableSet alloc] init];
+ _documentsByName = [[NSMutableDictionary alloc] init];
}
return self;
}
@@ -93,6 +95,20 @@ - (void)registerProtocol:(GBProtocolData *)protocol {
[_protocolsByName setObject:protocol forKey:protocol.nameOfProtocol];
}
+- (void)registerDocument:(GBDocumentData *)document {
+ NSParameterAssert(document != nil);
+ GBLogDebug(@"Registering document %@...", document);
+ if ([_documents containsObject:document]) return;
+ NSString *name = [document.nameOfDocument stringByDeletingPathExtension];
+ GBDocumentData *existingDocument = [_documentsByName objectForKey:name];
+ if (existingDocument) {
+ [NSException raise:@"Document with name %@ is already registered!", name];
+ return;
+ }
+ [_documents addObject:document];
+ [_documentsByName setObject:document forKey:name];
+}
+
- (void)unregisterTopLevelObject:(id)object {
if ([_classes containsObject:object]) {
[_classes removeObject:object];
@@ -125,8 +141,13 @@ - (GBProtocolData *)protocolWithName:(NSString *)name {
return [_protocolsByName objectForKey:name];
}
+- (GBDocumentData *)documentWithName:(NSString *)path {
+ return [_documentsByName objectForKey:path];
+}
+
@synthesize classes = _classes;
@synthesize categories = _categories;
@synthesize protocols = _protocols;
+@synthesize documents = _documents;
@end
View
16 Parsing/GBParser.h
@@ -42,7 +42,7 @@
/** Scans the given array of paths and parses all code files into in-memory objects.
- This is the main parsing method. It is intended to be invoked from the top level application code. It accepts an array of paths - either directories or file names - and parses them for code. If it detects an object within any file, it's data is parsed into in-memory representation suited for further processing. Parsed data is registered to the given `GBStore`.
+ This is the main method for source code parsing. It is intended to be invoked from the top level application code. It accepts an array of paths - either directories or file names - and parses them for code. If it detects an object within any file, it's data is parsed into in-memory representation suited for further processing. Parsed data is registered to the given `GBStore`.
If any kind of inconsistency is detected in source code, a warning is logged and parsing continues. This allows us to extract as much information as possible, while ignoring problems.
@@ -51,7 +51,21 @@
@param paths An array of strings representing paths to parse.
@param store The store to add objects to.
@exception NSException Thrown if a serious problem is detected which prevents us from parsing.
+ @see parseDocumentsFromPaths:toStore:
*/
- (void)parseObjectsFromPaths:(NSArray *)paths toStore:(id)store;
+/** Scans the given array of paths and parses all static document files into in-memory objects.
+
+ This is the main method for static documents parsing. It is intended to be invoked from the top level application code. Is accepts an array of paths - either directories or file names - and parses them for static documents requiring post processing. If it detects such a document, it's contents are loaded into in-memory representation suited for furhter processing. Parsed data is registered to the given `GBStore`. If the document is empty, a warning is issued, but it's still registered; the result is an empty html file generated at output path.
+
+ @warning *Note:* The method expects the given array contains `NSString`s representing existing directorry or file names. The method itself doesn't validate this and may result in unpredictable behavior in case an invalid path is passed in. The paths don't have to be standardized, expanded or similar though.
+
+ @param paths An array of strings representing paths to parse.
+ @param store The store to add objects to.
+ @exception NSException Thrown if a serious problem is detected which prevents us from parsing.
+ @see parseObjectsFromPaths:toStore:
+ */
+- (void)parseDocumentsFromPaths:(NSArray *)paths toStore:(id)store;
+
@end
View
132 Parsing/GBParser.m
@@ -7,19 +7,23 @@
//
#import "GBStore.h"
+#import "GBDataObjects.h"
#import "GBApplicationSettingsProvider.h"
#import "GBObjectiveCParser.h"
#import "GBParser.h"
@interface GBParser ()
-- (void)parseDirectory:(NSString *)path;
-- (void)parseFile:(NSString *)path;
+- (void)parsePath:(NSString *)input usingBlock:(void (^)(NSString *path))block;
+- (void)parseDirectory:(NSString *)input usingBlock:(void (^)(NSString *path))block;
+- (void)parseFile:(NSString *)input usingBlock:(void (^)(NSString *path))block;
- (BOOL)isPathIgnored:(NSString *)path;
- (BOOL)isFileIgnored:(NSString *)filename;
- (BOOL)isDirectoryIgnored:(NSString *)filename;
- (BOOL)isSourceCodeFile:(NSString *)path;
+- (BOOL)isDocumentFile:(NSString *)path;
@property (assign) NSUInteger numberOfParsedFiles;
+@property (assign) NSUInteger numberOfParsedDocuments;
@property (retain) GBObjectiveCParser *objectiveCParser;
@property (retain) GBStore *store;
@property (retain) GBApplicationSettingsProvider *settings;
@@ -52,71 +56,117 @@ - (id)initWithSettingsProvider:(id)settingsProvider {
- (void)parseObjectsFromPaths:(NSArray *)paths toStore:(id)store {
NSParameterAssert(paths != nil);
NSParameterAssert(store != nil);
- GBLogVerbose(@"Parsing objects from %u paths...", [paths count]);
+ GBLogVerbose(@"Parsing objects from %lu paths...", [paths count]);
self.store = store;
self.numberOfParsedFiles = 0;
- for (NSString *path in paths) {
- GBLogVerbose(@"Parsing '%@'...", path);
- NSString *standardized = [path stringByStandardizingPath];
- if ([self.fileManager isPathDirectory:[standardized stringByStandardizingPath]]) {
- [self parseDirectory:standardized];
- } else {
- [self parseFile:standardized];
- }
+ for (NSString *input in paths) {
+ [self parsePath:input usingBlock:^(NSString *path) {
+ if (![self isSourceCodeFile:path]) return;
+
+ GBLogInfo(@"Parsing source code from '%@'...", path);
+ NSError *error = nil;
+ NSString *contents = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
+ if (error) {
+ GBLogNSError(error, @"Failed reading contents of source file '%@'!", path);
+ return;
+ }
+
+ [self.objectiveCParser parseObjectsFromString:contents sourceFile:[path lastPathComponent] toStore:self.store];
+ self.numberOfParsedFiles++;
+ }];
}
+ GBLogVerbose(@"Parsed %lu source files.", self.numberOfParsedFiles);
}
-- (void)parseDirectory:(NSString *)path {
- GBLogDebug(@"Parsing path '%@'...", path);
- if ([self isPathIgnored:path]) {
- GBLogNormal(@"Ignoring path '%@'...", path);
+- (void)parseDocumentsFromPaths:(NSArray *)paths toStore:(id)store {
+ NSParameterAssert(paths != nil);
+ NSParameterAssert(store != nil);
+ GBLogVerbose(@"Parsing static documents from %u paths...", [paths count]);
+ self.store = store;
+ self.numberOfParsedDocuments = 0;
+ for (NSString *input in paths) {
+ [self parsePath:input usingBlock:^(NSString *path) {
+ if (![self isDocumentFile:path]) return;
+
+ GBLogInfo(@"Parsing static document from '%@'...", path);
+ NSError *error = nil;
+ NSString *contents = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
+ if (error) {
+ GBLogNSError(error, @"Failed reading contents of static document '%@'...", path);
+ return;
+ }
+ if ([contents length] == 0) GBLogWarn(@"Empty static document found at '%@'!", path);
+
+ GBDocumentData *document = [GBDocumentData documentDataWithContents:contents path:path];
+ document.basePathOfDocument = [input stringByStandardizingPath];
+ [self.store registerDocument:document];
+
+ self.numberOfParsedDocuments++;
+ }];
+ }
+ GBLogVerbose(@"Parsed %lu static document files.", self.numberOfParsedDocuments);
+}
+
+#pragma mark Parsing helpers
+
+- (void)parsePath:(NSString *)input usingBlock:(void (^)(NSString *path))block {
+ GBLogDebug(@"Parsing '%@'...", input);
+ NSString *standardized = [input stringByStandardizingPath];
+ if ([self.fileManager isPathDirectory:[standardized stringByStandardizingPath]])
+ [self parseDirectory:standardized usingBlock:block];
+ else
+ [self parseFile:standardized usingBlock:block];
+}
+
+- (void)parseDirectory:(NSString *)input usingBlock:(void (^)(NSString *path))block {
+ GBLogDebug(@"Parsing path '%@'...", input);
+
+ // Skip directory if found in --ignore paths.
+ if ([self isPathIgnored:input]) {
+ GBLogNormal(@"Ignoring path '%@'...", input);
return;
}
+ // Enumerate directory contents (non-recursive).
NSError *error = nil;
- NSArray *contents = [self.fileManager contentsOfDirectoryAtPath:path error:&error];
+ NSArray *contents = [self.fileManager contentsOfDirectoryAtPath:input error:&error];
if (error) {
- GBLogNSError(error, @"Failed fetching contents of '%@'!", path);
+ GBLogNSError(error, @"Failed fetching contents of '%@'!", input);
return;
}
- // First process files. Skip ignored files.
+ // First process files. Skip system files such as .DS_Store and similar.
for (NSString *subpath in contents) {
- NSString *fullPath = [path stringByAppendingPathComponent:subpath];
+ NSString *fullPath = [input stringByAppendingPathComponent:subpath];
if ([self.fileManager isPathDirectory:fullPath]) continue;
if ([self isFileIgnored:subpath]) continue;
- [self parseFile:fullPath];
+ [self parseFile:fullPath usingBlock:block];
}
- // Now process all subdirectories. Skip ignored directories.
+ // Now process all subdirectories. Skip directories such as .git, .svn, .hg and similar.
for (NSString *subpath in contents) {
- NSString *fullPath = [path stringByAppendingPathComponent:subpath];
+ NSString *fullPath = [input stringByAppendingPathComponent:subpath];
if (![self.fileManager isPathDirectory:fullPath]) continue;
if ([self isDirectoryIgnored:subpath]) continue;
- [self parseDirectory:fullPath];
+ [self parseDirectory:fullPath usingBlock:block];
}
}
-- (void)parseFile:(NSString *)path {
- GBLogDebug(@"Parsing file '%@'...", path);
- if ([self isPathIgnored:path]) {
- GBLogNormal(@"Ignoring file '%@'...", path);
- return;
- }
- if (![self isSourceCodeFile:path]) return;
-
- GBLogInfo(@"Parsing source code from '%@'...", path);
- NSError *error = nil;
- NSString *input = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
- if (error) {
- GBLogNSError(error, @"Failed reading contents of file '%@'!", path);
+- (void)parseFile:(NSString *)input usingBlock:(void (^)(NSString *path))block {
+ GBLogDebug(@"Parsing file '%@'...", input);
+
+ // Skip file if found in --ignore paths.
+ if ([self isPathIgnored:input]) {
+ GBLogNormal(@"Ignoring file '%@'...", input);
return;
}
- [self.objectiveCParser parseObjectsFromString:input sourceFile:[path lastPathComponent] toStore:self.store];
- self.numberOfParsedFiles++;
+ // Pass the path to the client via the block.
+ block(input);
}
+#pragma mark Helper methods
+
- (BOOL)isPathIgnored:(NSString *)path {
for (NSString *ignored in self.settings.ignoredPaths) {
if ([path hasSuffix:ignored]) return YES;
@@ -132,6 +182,7 @@ - (BOOL)isFileIgnored:(NSString *)filename {
- (BOOL)isDirectoryIgnored:(NSString *)filename {
if ([filename isEqualToString:@".git"]) return YES;
if ([filename isEqualToString:@".svn"]) return YES;
+ if ([filename isEqualToString:@".hg"]) return YES;
return NO;
}
@@ -144,9 +195,14 @@ - (BOOL)isSourceCodeFile:(NSString *)path {
return NO;
}
+- (BOOL)isDocumentFile:(NSString *)path {
+ return [self.settings isPathRepresentingTemplateFile:path];
+}
+
#pragma mark Properties
@synthesize numberOfParsedFiles;
+@synthesize numberOfParsedDocuments;
@synthesize objectiveCParser;
@synthesize settings;
@synthesize store;
View
26 Processing/GBCommentsProcessor.m
@@ -34,6 +34,7 @@ - (id)localMemberLinkFromString:(NSString *)string range:(NSRange *)range templa
- (id)classLinkFromString:(NSString *)string range:(NSRange *)range templated:(BOOL)templated;
- (id)categoryLinkFromString:(NSString *)string range:(NSRange *)range templated:(BOOL)templated;
- (id)protocolLinkFromString:(NSString *)string range:(NSRange *)range templated:(BOOL)templated;
+- (id)documentLinkFromString:(NSString *)string range:(NSRange *)range templated:(BOOL)templated;
- (id)urlLinkItemFromString:(NSString *)string range:(NSRange *)range templated:(BOOL)templated;
- (GBCommentParagraph *)pushParagraphIfStackIsEmpty;
@@ -576,6 +577,8 @@ - (id)linkItemFromString:(NSString *)string range:(NSRange *)range description:(
desc = @"class";
} else if ((result = [self protocolLinkFromString:string range:range templated:templated])) {
desc = @"protocol";
+ } else if ((result = [self documentLinkFromString:string range:range templated:templated])) {
+ desc = @"document";
} else if ((result = [self remoteMemberLinkItemFromString:string range:range templated:templated])) {
desc = @"remote member";
} else if ((result = [self localMemberLinkFromString:string range:range templated:templated])) {
@@ -719,6 +722,29 @@ - (id)protocolLinkFromString:(NSString *)string range:(NSRange *)range templated
return result;
}
+- (id)documentLinkFromString:(NSString *)string range:(NSRange *)range templated:(BOOL)templated {
+ // Matches the beginning of the string for document cross reference. If found, GBParagraphLinkItem is prepared and returned. NOTE: The range argument is used to return the range of all link text, including optional <> markers.
+ NSArray *components = [string captureComponentsMatchedByRegex:[self.components documentCrossReferenceRegex:templated]];
+ if ([components count] == 0) return nil;
+
+ // Get link components. Index 0 contains full text, including optional <>, index 1 just the object name.
+ NSString *linkText = [components objectAtIndex:0];
+ NSString *objectName = [components objectAtIndex:1];
+
+ // Get the document from the store - note that we need to add -template extension! If not found, exit.
+ NSString *objectID = [self.settings templateFilenameForOutputPath:objectName];
+ GBDocumentData *referencedObject = [self.store documentWithName:objectID];
+ if (!referencedObject) return nil;
+
+ // Ok, we have valid method, return the link item.
+ GBParagraphLinkItem *result = [GBParagraphLinkItem paragraphItemWithStringValue:objectName];
+ result.href = [self.settings htmlReferenceForObject:referencedObject fromSource:self.currentContext];
+ result.context = referencedObject;
+ result.isLocal = (referencedObject == self.currentContext);
+ if (range) *range = [string rangeOfString:linkText];
+ return result;
+}
+
- (id)urlLinkItemFromString:(NSString *)string range:(NSRange *)range templated:(BOOL)templated {
// Matches the beginning of the string for URL cross reference. If found, GBParagraphLinkItem is prepared and returned. NOTE: The range argument is used to return the range of all link text, including optional <> markers.
NSArray *components = [string captureComponentsMatchedByRegex:[self.components urlCrossReferenceRegex:templated]];
View
14 Processing/GBProcessor.m
@@ -18,6 +18,8 @@ @interface GBProcessor ()
- (void)processClasses;
- (void)processCategories;
- (void)processProtocols;
+- (void)processDocuments;
+
- (void)processMethodsFromProvider:(GBMethodsProvider *)provider;
- (void)processCommentForObject:(GBModelBase *)object;
- (void)processParametersFromComment:(GBComment *)comment matchingMethod:(GBMethodData *)method;
@@ -36,7 +38,7 @@ - (void)validateCommentsForObjectAndMembers:(GBModelBase *)object;
- (BOOL)isCommentValid:(GBComment *)comment;
@property (retain) GBCommentsProcessor *commentsProcessor;
-@property (retain) id<GBObjectDataProviding> currentContext;
+@property (retain) id currentContext;
@property (retain) GBStore *store;
@property (retain) GBApplicationSettingsProvider *settings;
@@ -75,6 +77,7 @@ - (void)processObjectsFromStore:(id)store {
[self processClasses];
[self processCategories];
[self processProtocols];
+ [self processDocuments];
}
- (void)processClasses {
@@ -123,6 +126,15 @@ - (void)processProtocols {
}
}
+- (void)processDocuments {
+ for (GBDocumentData *document in self.store.documents) {
+ GBLogInfo(@"Processing static document %@...", document);
+ self.currentContext = document;
+ [self processCommentForObject:document];
+ GBLogDebug(@"Finished processing document %@.", document);
+ }
+}
+
#pragma mark Common data processing
- (void)processMethodsFromProvider:(GBMethodsProvider *)provider {
View
46 Templates/html/document-template.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>{{page/title}}</title>
+ <meta http-equiv="Content-Type" content="application/xhtml+xml;charset=utf-8" />
+ <meta id="xcode-display" name="xcode-display" content="render"/>
+ <meta name="viewport" content="width=550" />
+ <link rel="stylesheet" type="text/css" href="{{page/cssPath}}" />
+ {{#strings/appledocData}}<meta name="generator" content="{{tool}} {{version}} (build {{build}})" />{{/strings/appledocData}}
+ </head>
+ <body>
+ <article>
+ <a title="{{page/title}}" name="top"></a>
+ <div id="container">
+ {{#object/comment}}{{>GBComment}}{{/object/comment}}
+ </div>
+ </article>
+ </body>
+</html>
+
+
+Section GBComment
+{{#paragraphs}}
+<p>{{>GBCommentParagraph}}</p>
+{{/paragraphs}}
+EndSection
+
+Section GBCommentParagraph
+{{#paragraphItems}}{{>GBParagraphItem}}{{/paragraphItems}}
+EndSection
+
+Section GBParagraphItem
+{{#isTextItem}}{{&stringValue}}{{/isTextItem}}{{#isOrderedListItem}}<ol>{{>GBParagraphListItem}}</ol>{{/isOrderedListItem}}{{#isUnorderedListItem}}<ul>{{>GBParagraphListItem}}</ul>{{/isUnorderedListItem}}{{#isWarningSpecialItem}}<p class="warning">{{>GBParagraphSpecialItem}}</p>{{/isWarningSpecialItem}}{{#isBugSpecialItem}}<p class="bug">{{>GBParagraphSpecialItem}}</p>{{/isBugSpecialItem}}{{#isExampleSpecialItem}}<pre>{{>GBParagraphSpecialItem}}</pre>{{/isExampleSpecialItem}}{{#isBoldDecoratorItem}}<strong>{{>GBParagraphDecoratorItem}}</strong>{{/isBoldDecoratorItem}}{{#isItalicsDecoratorItem}}<em>{{>GBParagraphDecoratorItem}}</em>{{/isItalicsDecoratorItem}}{{#isCodeDecoratorItem}}<code>{{>GBParagraphDecoratorItem}}</code>{{/isCodeDecoratorItem}}{{#isLinkItem}}<a href="{{&href}}">{{&stringValue}}</a>{{/isLinkItem}}
+EndSection
+
+Section GBParagraphListItem
+{{#listItems}}<li>{{>GBCommentParagraph}}</li>{{/listItems}}
+EndSection
+
+Section GBParagraphSpecialItem
+{{#specialItemDescription}}{{>GBCommentParagraph}}{{/specialItemDescription}}
+EndSection
+
+Section GBParagraphDecoratorItem
+{{#decoratedItems}}{{>GBParagraphItem}}{{/decoratedItems}}
+EndSection
View
130 Testing/GBApplicationSettingsProviderTesting.m
@@ -150,6 +150,21 @@ - (void)testHtmlReferenceNameForObject_shouldReturnProperValueForTopLevelObjects
assertThat([settings htmlReferenceNameForObject:protocol], is(@"Protocol.html"));
}
+- (void)testHtmlReferenceNameForObject_shouldReturnProperValueForDocuments {
+ // setup
+ GBApplicationSettingsProvider *settings = [GBApplicationSettingsProvider provider];
+ settings.outputPath = @"anything :)";
+ GBDocumentData *document1 = [GBDocumentData documentDataWithContents:@"c" path:@"document-template.html" basePath:@""];
+ GBDocumentData *document2 = [GBDocumentData documentDataWithContents:@"c" path:@"path/document-template.html" basePath:@""];
+ GBDocumentData *document3 = [GBDocumentData documentDataWithContents:@"c" path:@"path/sub/document-template.html" basePath:@""];
+ GBDocumentData *document4 = [GBDocumentData documentDataWithContents:@"c" path:@"path/sub/document-template.html" basePath:@"path"];
+ // verify
+ assertThat([settings htmlReferenceNameForObject:document1], is(@"document.html"));
+ assertThat([settings htmlReferenceNameForObject:document2], is(@"document.html"));
+ assertThat([settings htmlReferenceNameForObject:document3], is(@"document.html"));
+ assertThat([settings htmlReferenceNameForObject:document4], is(@"document.html"));
+}
+
- (void)testHtmlReferenceNameForObject_shouldReturnProperValueForMethods {
// setup
GBApplicationSettingsProvider *settings = [GBApplicationSettingsProvider provider];
@@ -206,15 +221,36 @@ - (void)testHtmlReferenceForObjectFromSource_shouldReturnProperValueForProtocolF
assertThat([settings htmlReferenceForObject:method fromSource:nil], is(@"Protocols/Protocol.html#//api/name/method:"));
}
+- (void)testHtmlReferenceForObjectFromSource_shouldReturnProperValueForDocumentFromIndex {
+ // setup
+ GBApplicationSettingsProvider *settings = [GBApplicationSettingsProvider provider];
+ settings.outputPath = @"anything :)";
+ GBDocumentData *document1 = [GBDocumentData documentDataWithContents:@"c" path:@"document-template.html" basePath:@""];
+ GBDocumentData *document2 = [GBDocumentData documentDataWithContents:@"c" path:@"path/document-template.html" basePath:@""];
+ GBDocumentData *document3 = [GBDocumentData documentDataWithContents:@"c" path:@"path/sub/document-template.html" basePath:@""];
+ GBDocumentData *document4 = [GBDocumentData documentDataWithContents:@"c" path:@"path/sub/document-template.html" basePath:@"path"];
+ // verify
+ assertThat([settings htmlReferenceForObject:document1 fromSource:nil], is(@"docs/document.html"));
+ assertThat([settings htmlReferenceForObject:document2 fromSource:nil], is(@"docs/path/document.html"));
+ assertThat([settings htmlReferenceForObject:document3 fromSource:nil], is(@"docs/path/sub/document.html"));
+ assertThat([settings htmlReferenceForObject:document4 fromSource:nil], is(@"docs/sub/document.html"));
+}
+
#pragma mark HTML href references handling - top level to top level
- (void)testHtmlReferenceForObjectFromSource_shouldReturnProperValueForTopLevelObjectToSameObjectReference {
// setup
GBApplicationSettingsProvider *settings = [GBApplicationSettingsProvider provider];
settings.outputPath = @"anything :)";
GBClassData *class = [GBClassData classDataWithName:@"Cla