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 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
@@ -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");
@@ -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
///---------------------------------------------------------------------------------------
@@ -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;
@@ -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
///---------------------------------------------------------------------------------------
@@ -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 {
@@ -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.
@@ -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]]);
Oops, something went wrong.

0 comments on commit 97db241

Please sign in to comment.