Skip to content

Instantly share code, notes, and snippets.

@aglee
Created December 22, 2013 18:53
Show Gist options
  • Save aglee/8086747 to your computer and use it in GitHub Desktop.
Save aglee/8086747 to your computer and use it in GitHub Desktop.
Category on NSApplication that makes it easy to use a Markdown-formatted Credits file, or to use your README as the Credits file.
//
// NSApplication+MarkdownCredits.h
// ALUtilities
//
// Copyright (c) 2013 Andy Lee
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so. Attribution is not required for either source or binary forms of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import <Cocoa/Cocoa.h>
/*!
* This category adds a showAboutPanelWithDerivedCredits: method, plus methods
* that support it. This allows you to easily use a Markdown-formatted file as
* your Credits file in the standard info panel. The easiest way to do this is:
*
* - Replace Credits.rtf with Credits.markdown, or Credits.mdown or Credits.md
* if you prefer.
* - Change the action of the "About" menu item to showAboutPanelWithDerivedCredits:.
* - Add the MMMarkdown project to your project. <https://github.com/mdiep/MMMarkdown>
*
* If you prefer a Markdown library other than MMMarkdown, you'll have to
* implement application:creditsStringFromData: in your app delegate to use that
* library. You can also implement it if you don't want to use Markdown at all,
* and have another way you'd like to generate the attributed string used for
* Credits.
*
* If there is no Credits.(markdown|mdown|md) file, showAboutPanelWithDerivedCredits:
* searches for a README file with one of those extensions. If you want your
* README to be your Credits file, remember to add it to your application's
* build target.
*
* You can specify a different file name altogether by implementing
* creditsFileNameForApplication:. This will be used regardless of whether
* Credits or README files are present.
*/
@interface NSApplication (MarkdownCredits)
/*!
* Used internally to provide the Credits string used by
* showAboutPanelWithDerivedCredits:. You might want to use this to implement
* your own info panel if you don't like the standard one.
*/
- (NSAttributedString *)derivedCreditsString;
#pragma mark - Action methods
/*!
* Opens the standard info panel, using different logic to derive the
* attributed string used for Credits.
*/
- (IBAction)showAboutPanelWithDerivedCredits:(id)sender;
@end
#pragma mark - Informal protocol that supports showAboutPanelWithDerivedCredits:
@interface NSObject (MarkdownCredits)
/*!
* File name to look for in the application bundle. showAboutPanelWithDerivedCredits:
* passes the contents of this file to application:creditsStringFromData:. If this
* method is not implemented or if it returns nil, a search is done for README.(markdown|mdown|md).
* If no credits file is found in the application bundle, we call orderFrontStandardAboutPanel:.
*/
- (NSString *)creditsFileNameForApplication:(NSApplication *)application;
/*!
* If the app delegate doesn't implement this, showAboutPanelWithDerivedCredits:
* checks whether the MMMarkdown library is present. If so, uses it to convert
* the data, on the assumption that it's in Markdown format.
*/
- (NSAttributedString *)application:(NSApplication *)application
creditsStringFromData:(NSData *)creditsData;
@end
//
// NSApplication+MarkdownCredits.m
// ALUtilities
//
// Copyright (c) 2013 Andy Lee
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so. Attribution is not required for either source or binary forms of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "NSApplication+MarkdownCredits.h"
@implementation NSApplication (MarkdownCredits)
- (NSAttributedString *)derivedCreditsString
{
// Load data from the credits file.
NSString *creditsFilePath = [self _creditsFilePath];
if (creditsFilePath == nil)
{
return nil;
}
NSData *creditsData = [NSData dataWithContentsOfFile:creditsFilePath];
if (creditsData == nil)
{
NSLog(@"+++ [ERROR] Could not load contents of file [%@].", creditsFilePath);
return nil;
}
// Convert the data to an attributed string.
NSAttributedString *creditsString;
if ([self.delegate respondsToSelector:@selector(application:creditsStringFromData:)])
{
creditsString = [(id)self.delegate application:self creditsStringFromData:creditsData];
}
else
{
creditsString = [self _creditsStringFromMarkdownData:creditsData];
}
if (creditsString == nil)
{
NSLog(@"+++ [ERROR] Could not convert contents of file [%@] to a Credits string.",
creditsFilePath);
}
return creditsString;
}
#pragma mark - Action methods
- (IBAction)showAboutPanelWithDerivedCredits:(id)sender
{
NSAttributedString *creditsString = [self derivedCreditsString];
if (creditsString == nil)
{
[self orderFrontStandardAboutPanel:sender];
}
else
{
[self orderFrontStandardAboutPanelWithOptions:@{ @"Credits" : creditsString }];
}
}
#pragma mark - Private methods
- (NSString *)_creditsFilePath
{
if ([self.delegate respondsToSelector:@selector(creditsFileNameForApplication:)])
{
NSString *creditsFileName = [(id)self.delegate creditsFileNameForApplication:self];
if (creditsFileName)
{
NSString *resourcePath = [self _pathForResourceWithFileName:creditsFileName];
if (resourcePath)
{
return resourcePath;
}
}
}
for (NSString *fileName in @[ @"Credits.markdown", @"Credits.mdown", @"Credits.md", @"README.markdown", @"README.mdown", @"README.md" ])
{
NSString *resourcePath = [self _pathForResourceWithFileName:fileName];
if (resourcePath)
{
return resourcePath;
}
}
// If we got this far, we failed.
return nil;
}
// Assume creditsData contains Markdown. Check for the presence of various
// Markdown libraries. If one is found, use it to generate the credits string.
- (NSAttributedString *)_creditsStringFromMarkdownData:(NSData *)markdownData
{
NSAttributedString *creditsString;
NSString *markdownString = [[NSString alloc] initWithData:markdownData
encoding:NSUTF8StringEncoding];
if (markdownString == nil)
{
return nil;
}
// See if the MMMarkdown library is present.
if ((creditsString = [self _mmmarkdownCreditsStringFromMarkdownString:markdownString]))
{
return creditsString;
}
//
// ...[agl] Check for other Markdown libraries -- what are some popular ones?...
//
// If we got this far, we failed.
return nil;
}
// This method is never called. It's just here to provide a method prototype.
- (NSString *)HTMLStringWithMarkdown:(NSString *)markdownString error:(NSError **)errorPtr
{
return nil;
}
- (NSAttributedString *)_mmmarkdownCreditsStringFromMarkdownString:(NSString *)markdownString
{
Class mmmarkdownClass = NSClassFromString(@"MMMarkdown");
if (mmmarkdownClass == nil)
{
return nil;
}
NSError *error;
NSString *htmlString = [(id)mmmarkdownClass HTMLStringWithMarkdown:markdownString error:&error];
if (htmlString == nil)
{
return nil;
}
return [self _creditsStringFromHTMLString:htmlString];
}
- (NSString *)_pathForResourceWithFileName:(NSString *)fileName
{
return [[NSBundle mainBundle] pathForResource:[fileName stringByDeletingPathExtension]
ofType:[fileName pathExtension]];
}
- (NSAttributedString *)_creditsStringFromHTMLString:(NSString *)htmlString
{
return [[NSAttributedString alloc] initWithHTML:[htmlString dataUsingEncoding:NSUTF8StringEncoding]
documentAttributes:NULL];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment