Browse Source

Add Alien Blue support

master
lint 4 years ago
parent
commit
2ce6b7f3f8
23 changed files with 4853 additions and 7 deletions
  1. +5
    -5
      Makefile
  2. +32
    -2
      prefs/Resources/Root.plist
  3. BIN
      tfdidthatsay.plist
  4. +60
    -0
      tweak/AlienBlue.h
  5. +216
    -0
      tweak/AlienBlue.xm
  6. +40
    -0
      tweak/assets/MMDocument.h
  7. +70
    -0
      tweak/assets/MMDocument.m
  8. +39
    -0
      tweak/assets/MMDocument_Private.h
  9. +92
    -0
      tweak/assets/MMElement.h
  10. +171
    -0
      tweak/assets/MMElement.m
  11. +37
    -0
      tweak/assets/MMGenerator.h
  12. +282
    -0
      tweak/assets/MMGenerator.m
  13. +40
    -0
      tweak/assets/MMHTMLParser.h
  14. +324
    -0
      tweak/assets/MMHTMLParser.m
  15. +7
    -0
      tweak/assets/MMMarkdown-Prefix.pch
  16. +82
    -0
      tweak/assets/MMMarkdown.h
  17. +73
    -0
      tweak/assets/MMMarkdown.m
  18. +41
    -0
      tweak/assets/MMParser.h
  19. +1321
    -0
      tweak/assets/MMParser.m
  20. +79
    -0
      tweak/assets/MMScanner.h
  21. +499
    -0
      tweak/assets/MMScanner.m
  22. +45
    -0
      tweak/assets/MMSpanParser.h
  23. +1298
    -0
      tweak/assets/MMSpanParser.m

+ 5
- 5
Makefile View File

@@ -3,18 +3,18 @@ ARCHS = arm64 arm64e
include $(THEOS)/makefiles/common.mk

TWEAK_NAME = tfdidthatsay
tfdidthatsay_FILES = $(wildcard tweak/*.xm)
tweak/Narwhal.xm_CFLAGS = -fobjc-arc
tfdidthatsay_EXTRA_FRAMEWORKS += Cephei
tfdidthatsay_FILES = $(wildcard tweak/*.xm tweak/assets/*.m)
tfdidthatsay_CFLAGS = -fobjc-arc
tweak/Reddit.xm_CFLAGS = -fno-objc-arc
tweak/Apollo.xm_CFLAGS = -fno-objc-arc

include $(THEOS_MAKE_PATH)/tweak.mk

SUBPROJECTS += prefs
include $(THEOS_MAKE_PATH)/aggregate.mk


after-install::
install.exec "killall -9 Reddit"
install.exec "killall -9 Apollo"
install.exec "killall -9 narwhal"
install.exec "killall -9 AlienBlue"

+ 32
- 2
prefs/Resources/Root.plist View File

@@ -54,11 +54,27 @@
<key>label</key>
<string>Enable Narwhal</string>
</dict>
<dict>
<key>PostNotification</key>
<string>com.lint.undelete.prefs.changed</string>
<key>cell</key>
<string>PSSwitchCell</string>
<key>default</key>
<true/>
<key>defaults</key>
<string>com.lint.undelete.prefs</string>
<key>key</key>
<string>isAlienBlueEnabled</string>
<key>label</key>
<string>Enable Alien Blue</string>
</dict>
<dict>
<key>cell</key>
<string>PSGroupCell</string>
<key>footerText</key>
<string>On apps that have the eye button rather than in the menu, enable or disable that button from appearing on only deleted comments/posts.</string>
<key>label</key>
<string>Apollo </string>
<string>visibility</string>
</dict>
<dict>
<key>PostNotification</key>
@@ -72,7 +88,21 @@
<key>key</key>
<string>isApolloDeletedCommentsOnly</string>
<key>label</key>
<string>Only display eye on deleted comments</string>
<string>Apollo | Only on deleted comments</string>
</dict>
<dict>
<key>PostNotification</key>
<string>com.lint.undelete.prefs.changed</string>
<key>cell</key>
<string>PSSwitchCell</string>
<key>default</key>
<true/>
<key>defaults</key>
<string>com.lint.undelete.prefs</string>
<key>key</key>
<string>isAlienBlueDeletedOnly</string>
<key>label</key>
<string>Alien Blue | Only on deleted comments/posts</string>
</dict>
<dict>
<key>cell</key>

BIN
tfdidthatsay.plist View File


+ 60
- 0
tweak/AlienBlue.h View File

@@ -0,0 +1,60 @@

/* -- Voteable Interfaces -- */

@interface VoteableElement
@property(strong, nonatomic) NSString *ident;
@property(strong, nonatomic) NSString *author;
@end

@interface Comment : VoteableElement
@property(strong, nonatomic) NSString *body;
@property(strong, nonatomic) NSString *bodyHTML;
@end

@interface Post : VoteableElement
@property(strong, nonatomic) NSString *selftext;
@property(strong, nonatomic) NSString *selftextHtml;
@property(assign, nonatomic) BOOL selfPost;
@end

/* -- UI Interfaces -- */

@interface NCommentCell
@property(strong, nonatomic) Comment *comment;
@end

@interface NCommentPostHeaderCell
@property(strong, nonatomic) Post *post;
@property(strong, nonatomic) id node;
@end

@interface CommentsViewController
-(void) respondToStyleChange;
@end

@interface CommentPostHeaderNode
@property(strong, nonatomic) Comment *comment;
@property(strong, nonatomic) Post *post;
@end

@interface CommentOptionsDrawerView
@property(strong, nonatomic) NSMutableArray *buttons;
@property(assign, nonatomic) BOOL isPostHeader;
@property(strong, nonatomic) id delegate;
-(void) addButton:(id) arg1;

//custom elements
@property(strong, nonatomic) Comment *comment;
@property(strong, nonatomic) Post *post;
@property(strong, nonatomic) CommentPostHeaderNode *commentNode;
@end

/* -- Other Interfaces -- */

@interface MarkupEngine
+(id) markDownHTML:(id) arg1 forSubreddit:(id) arg2;
@end

@interface Resources
+(BOOL) isNight;
@end

+ 216
- 0
tweak/AlienBlue.xm View File

@@ -0,0 +1,216 @@

#import "AlienBlue.h"
#import "assets/MMMarkdown.h"

static BOOL isAlienBlueEnabled;
static BOOL isAlienBlueDeletedOnly;
static CGFloat pushshiftRequestTimeoutValue;

%group AlienBlue

%hook NCommentCell

-(void) setDrawerView:(id) arg1 {
[arg1 setComment:[self comment]];
%orig;
}

%end

%hook NCommentPostHeaderCell

-(void) setDrawerView:(id) arg1 {
[arg1 setPost:[self post]];
[arg1 setCommentNode:[self node]];
%orig;
}

%end


%hook CommentOptionsDrawerView
%property(strong, nonatomic) Comment *comment;
%property(strong, nonatomic) Post *post;
%property(strong, nonatomic) CommentPostHeaderNode *commentNode;

-(id) initWithNode:(id) arg1 {
id orig = %orig;
NSString *body;
if ([self post]) {
body = [[self post] selftext];
} else if ([self comment]){
body = [[self comment] body];
}
if ((isAlienBlueDeletedOnly && ([body isEqualToString:@"[deleted]"] || [body isEqualToString:@"[removed]"])) || !isAlienBlueDeletedOnly) {
CGSize refSize = [[self buttons][0] frame].size;

UIButton *undeleteButton = [UIButton buttonWithType:UIButtonTypeCustom];
[undeleteButton setFrame:CGRectMake(0, 0, refSize.width, refSize.height)];
if ([self isPostHeader]){
[undeleteButton addTarget:self action:@selector(didTapPostUndeleteButton) forControlEvents:UIControlEventTouchUpInside];
} else {
[undeleteButton addTarget:self action:@selector(didTapCommentUndeleteButton) forControlEvents:UIControlEventTouchUpInside];
}
if ([%c(Resources) isNight]) {
[undeleteButton setImage:[UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160dark.png"] forState:UIControlStateNormal];
} else {
[undeleteButton setImage:[UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160light.png"] forState:UIControlStateNormal];
}
undeleteButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
undeleteButton.imageEdgeInsets = UIEdgeInsetsMake(10, 10, 10, 10);
[self addButton:undeleteButton];
}
return orig;

}

%new
-(void) didTapCommentUndeleteButton {
Comment *comment = [self comment];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

[request setURL:[NSURL URLWithString:[NSString stringWithFormat:@"https://api.pushshift.io/reddit/search/comment/?ids=%@&fields=author,body",[comment ident]]]];
[request setHTTPMethod:@"GET"];
[request setTimeoutInterval:pushshiftRequestTimeoutValue];

[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSString *author = @"[author]";
NSString *body = @"[body]";

if (data != nil && error == nil){
id jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if ([[jsonData objectForKey:@"data"] count] != 0){
author = [[jsonData objectForKey:@"data"][0] objectForKey:@"author"];
body = [[jsonData objectForKey:@"data"][0] objectForKey:@"body"];
if ([body isEqualToString:@"[deleted]"] || [body isEqualToString:@"[removed]"]){
body = @"[pushshift was unable to archive this]";
}
} else {
body = @"[pushshift has not archived this yet]";
}
} else if (error != nil || data == nil){
body = [NSString stringWithFormat:@"[an error occured while attempting to contact pushshift api (%@)]", [error localizedDescription]];
}
NSString *bodyHTML = [%c(MMMarkdown) HTMLStringWithMarkdown:body extensions:MMMarkdownExtensionsGitHubFlavored error:nil];
[comment setAuthor:author];
[comment setBody:body];
[comment setBodyHTML:bodyHTML];
[[self delegate] performSelectorOnMainThread:@selector(respondToStyleChange) withObject:nil waitUntilDone:NO];
}];
}

%new
-(void) didTapPostUndeleteButton {
Post *post = [self post];
Comment *postComment = [[self commentNode] comment]; //Don't know why he used a comment to store info about a post, but it exists
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

[request setURL:[NSURL URLWithString:[NSString stringWithFormat:@"https://api.pushshift.io/reddit/search/submission/?ids=%@&fields=author,selftext",[post ident]]]];
[request setHTTPMethod:@"GET"];
[request setTimeoutInterval:pushshiftRequestTimeoutValue];

[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSString *author = @"[author]";
NSString *body = @"[body]";

if (data != nil && error == nil){
id jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if ([[jsonData objectForKey:@"data"] count] != 0){
author = [[jsonData objectForKey:@"data"][0] objectForKey:@"author"];
body = [[jsonData objectForKey:@"data"][0] objectForKey:@"selftext"];
if ([body isEqualToString:@"[deleted]"] || [body isEqualToString:@"[removed]"]){
body = @"[pushshift was unable to archive this]";
}
} else {
body = @"[pushshift has not archived this yet]";
}
} else if (error != nil || data == nil){
body = [NSString stringWithFormat:@"[an error occured while attempting to contact pushshift api (%@)]", [error localizedDescription]];
}
NSString *bodyHTML = [%c(MMMarkdown) HTMLStringWithMarkdown:body extensions:MMMarkdownExtensionsGitHubFlavored error:nil];
[post setAuthor:author];
[post setSelftext:body];
[postComment setBodyHTML:bodyHTML];
[[self delegate] performSelectorOnMainThread:@selector(respondToStyleChange) withObject:nil waitUntilDone:NO];
}];
}

%end

%end


static void loadPrefs(){
NSMutableDictionary *prefs = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/User/Library/Preferences/com.lint.undelete.prefs.plist"];
if (prefs){
if ([prefs objectForKey:@"isAlienBlueEnabled"] != nil){
isAlienBlueEnabled = [[prefs objectForKey:@"isAlienBlueEnabled"] boolValue];
} else {
isAlienBlueEnabled = YES;
}
if ([prefs objectForKey:@"isAlienBlueDeletedOnly"] != nil){
isAlienBlueDeletedOnly = [[prefs objectForKey:@"isAlienBlueDeletedOnly"] boolValue];
} else {
isAlienBlueDeletedOnly = YES;
}
if ([prefs objectForKey:@"requestTimeoutValue"] != nil){
pushshiftRequestTimeoutValue = [[prefs objectForKey:@"requestTimeoutValue"] doubleValue];
} else {
pushshiftRequestTimeoutValue = 10;
}
} else {
isAlienBlueEnabled = YES;
isAlienBlueDeletedOnly = YES;
pushshiftRequestTimeoutValue = 10;
}
}

static void prefsChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
loadPrefs();
}


%ctor {
loadPrefs();
NSString* processName = [[NSProcessInfo processInfo] processName];

if ([processName isEqualToString:@"AlienBlue"]){
if (isAlienBlueEnabled){
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, prefsChanged, CFSTR("com.lint.undelete.prefs.changed"), NULL, CFNotificationSuspensionBehaviorDeliverImmediately);
%init(AlienBlue);
}
}
}


+ 40
- 0
tweak/assets/MMDocument.h View File

@@ -0,0 +1,40 @@
//
// MMDocument.h
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// 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, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions 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 <Foundation/Foundation.h>

@class MMElement;

NS_ASSUME_NONNULL_BEGIN
@interface MMDocument : NSObject

@property (copy, nonatomic, readonly) NSString *markdown;
@property (copy, nonatomic, readonly) NSArray<MMElement *> *elements;

+ (id)documentWithMarkdown:(NSString *)markdown;
- (id)initWithMarkdown:(NSString *)markdown;

@end
NS_ASSUME_NONNULL_END

+ 70
- 0
tweak/assets/MMDocument.m View File

@@ -0,0 +1,70 @@
//
// MMDocument.m
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// 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, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions 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 "MMDocument.h"
#import "MMDocument_Private.h"


@interface MMDocument ()
@property (copy, nonatomic) NSArray *elements;
@end

@implementation MMDocument
{
NSMutableArray *_elements;
}

#pragma mark - Public Methods

+ (id)documentWithMarkdown:(NSString *)markdown
{
return [[self.class alloc] initWithMarkdown:markdown];
}

- (id)initWithMarkdown:(NSString *)markdown
{
self = [super init];
if (self)
{
_markdown = markdown;
_elements = [NSMutableArray new];
}
return self;
}


#pragma mark - Private Methods

- (void)addElement:(MMElement *)anElement
{
[self willChangeValueForKey:@"elements"];
[_elements addObject:anElement];
[self didChangeValueForKey:@"elements"];
}


@end

+ 39
- 0
tweak/assets/MMDocument_Private.h View File

@@ -0,0 +1,39 @@
//
// MMDocument_Private.h
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// 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, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions 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 "MMDocument.h"


@class MMElement;

NS_ASSUME_NONNULL_BEGIN
@interface MMDocument (MMDocumentPrivate)

@property (copy, nonatomic) NSArray<MMElement *> *elements;

- (void)addElement:(MMElement *)anElement;

@end
NS_ASSUME_NONNULL_END

+ 92
- 0
tweak/assets/MMElement.h View File

@@ -0,0 +1,92 @@
//
// MMElement.h
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// 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, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions 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 <Foundation/Foundation.h>

@class MMElement;

typedef enum
{
MMElementTypeNone,
MMElementTypeHeader,
MMElementTypeParagraph,
MMElementTypeBlockquote,
MMElementTypeNumberedList,
MMElementTypeBulletedList,
MMElementTypeListItem,
MMElementTypeCodeBlock,
MMElementTypeHorizontalRule,
MMElementTypeHTML,
MMElementTypeLineBreak,
MMElementTypeStrikethrough,
MMElementTypeStrong,
MMElementTypeEm,
MMElementTypeCodeSpan,
MMElementTypeImage,
MMElementTypeLink,
MMElementTypeMailTo,
MMElementTypeDefinition,
MMElementTypeEntity,
MMElementTypeTable,
MMElementTypeTableHeader,
MMElementTypeTableHeaderCell,
MMElementTypeTableRow,
MMElementTypeTableRowCell,
} MMElementType;

typedef NS_ENUM(NSInteger, MMTableCellAlignment)
{
MMTableCellAlignmentNone,
MMTableCellAlignmentLeft,
MMTableCellAlignmentCenter,
MMTableCellAlignmentRight,
};

@interface MMElement : NSObject

@property (assign, nonatomic) NSRange range;
@property (assign, nonatomic) MMElementType type;
@property (copy, nonatomic) NSArray *innerRanges;

@property (assign, nonatomic) MMTableCellAlignment alignment;
@property (assign, nonatomic) NSUInteger level;
@property (copy, nonatomic) NSString *href;
@property (copy, nonatomic) NSString *title;
@property (copy, nonatomic) NSString *identifier;
@property (copy, nonatomic) NSString *stringValue;

@property (weak, nonatomic) MMElement *parent;
@property (copy, nonatomic) NSArray<MMElement *> *children;

@property (copy, nonatomic) NSString *language;

- (void)addInnerRange:(NSRange)aRange;
- (void)removeLastInnerRange;

- (void)addChild:(MMElement *)aChild;
- (void)removeChild:(MMElement *)aChild;
- (MMElement *)removeLastChild;

@end

+ 171
- 0
tweak/assets/MMElement.m View File

@@ -0,0 +1,171 @@
//
// MMElement.m
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// 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, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions 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 "MMElement.h"


static NSString * __MMStringFromElementType(MMElementType type)
{
switch (type)
{
case MMElementTypeNone:
return @"none";
case MMElementTypeHeader:
return @"header";
case MMElementTypeParagraph:
return @"paragraph";
case MMElementTypeBlockquote:
return @"blockquote";
case MMElementTypeNumberedList:
return @"ol";
case MMElementTypeBulletedList:
return @"ul";
case MMElementTypeListItem:
return @"li";
case MMElementTypeCodeBlock:
return @"code";
case MMElementTypeHorizontalRule:
return @"hr";
case MMElementTypeHTML:
return @"html";
case MMElementTypeLineBreak:
return @"br";
case MMElementTypeStrikethrough:
return @"del";
case MMElementTypeStrong:
return @"strong";
case MMElementTypeEm:
return @"em";
case MMElementTypeCodeSpan:
return @"code";
case MMElementTypeImage:
return @"image";
case MMElementTypeLink:
return @"link";
case MMElementTypeMailTo:
return @"mailto";
case MMElementTypeEntity:
return @"entity";
case MMElementTypeDefinition:
return @"definition";
default:
return @"unknown";
}
}

@implementation MMElement
{
NSMutableArray *_innerRanges;
NSMutableArray *_children;
}

#pragma mark - NSObject

- (id)init
{
self = [super init];
if (self)
{
_innerRanges = [NSMutableArray new];
_children = [NSMutableArray new];
}
return self;
}

- (void)dealloc
{
[self.children makeObjectsPerformSelector:@selector(setParent:) withObject:nil];
}

- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; type=%@; range=%@>",
NSStringFromClass(self.class), self, __MMStringFromElementType(self.type), NSStringFromRange(self.range)];
}


#pragma mark - Public Methods

- (void)addInnerRange:(NSRange)aRange
{
[self willChangeValueForKey:@"innerRanges"];
[_innerRanges addObject:[NSValue valueWithRange:aRange]];
[self didChangeValueForKey:@"innerRanges"];
}

- (void)removeLastInnerRange
{
[self willChangeValueForKey:@"innerRanges"];
[_innerRanges removeLastObject];
[self didChangeValueForKey:@"innerRanges"];
}

- (void)addChild:(MMElement *)aChild
{
[self willChangeValueForKey:@"children"];
[_children addObject:aChild];
aChild.parent = self;
[self didChangeValueForKey:@"children"];
}

- (void)removeChild:(MMElement *)aChild
{
[self willChangeValueForKey:@"children"];
[_children removeObjectIdenticalTo:aChild];
aChild.parent = nil;
[self didChangeValueForKey:@"children"];
}

- (MMElement *)removeLastChild
{
MMElement *child = [self.children lastObject];
[_children removeLastObject];
return child;
}


#pragma mark - Public Properties

- (void)setInnerRanges:(NSArray *)innerRanges
{
_innerRanges = [innerRanges mutableCopy];
}

- (void)setChildren:(NSArray *)children
{
for (MMElement *child in _children) {
child.parent = nil;
}
_children = [children mutableCopy];
for (MMElement *child in _children) {
child.parent = self;
}
}


@end

+ 37
- 0
tweak/assets/MMGenerator.h View File

@@ -0,0 +1,37 @@
//
// MMGenerator.h
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// 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, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions 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 <Foundation/Foundation.h>


@class MMDocument;

NS_ASSUME_NONNULL_BEGIN
@interface MMGenerator : NSObject

- (NSString *)generateHTML:(MMDocument *)aDocument;

@end
NS_ASSUME_NONNULL_END

+ 282
- 0
tweak/assets/MMGenerator.m View File

@@ -0,0 +1,282 @@
//
// MMGenerator.m
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// 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, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions 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 "MMGenerator.h"


#import "MMDocument.h"
#import "MMElement.h"

// This value is used to estimate the length of the HTML output. The length of the markdown document
// is multplied by it to create an NSMutableString with an initial capacity.
static const Float64 kHTMLDocumentLengthMultiplier = 1.25;

static NSString * __HTMLEscapedString(NSString *aString)
{
NSMutableString *result = [aString mutableCopy];
[result replaceOccurrencesOfString:@"&"
withString:@"&amp;"
options:NSLiteralSearch
range:NSMakeRange(0, result.length)];
[result replaceOccurrencesOfString:@"\""
withString:@"&quot;"
options:NSLiteralSearch
range:NSMakeRange(0, result.length)];
return result;
}

static NSString *__obfuscatedEmailAddress(NSString *anAddress)
{
NSMutableString *result = [NSMutableString new];
NSString *(^decimal)(unichar c) = ^(unichar c){ return [NSString stringWithFormat:@"&#%d;", c]; };
NSString *(^hex)(unichar c) = ^(unichar c){ return [NSString stringWithFormat:@"&#x%x;", c]; };
NSString *(^raw)(unichar c) = ^(unichar c){ return [NSString stringWithCharacters:&c length:1]; };
NSArray *encoders = @[ decimal, hex, raw ];
for (NSUInteger idx=0; idx<anAddress.length; idx++)
{
unichar character = [anAddress characterAtIndex:idx];
NSString *(^encoder)(unichar c);
if (character == '@')
{
// Make sure that the @ gets encoded
encoder = [encoders objectAtIndex:arc4random_uniform(2)];
}
else
{
int r = arc4random_uniform(100);
encoder = encoders[(r >= 90) ? 2 : (r >= 45) ? 1 : 0];
}
[result appendString:encoder(character)];
}
return result;
}

static NSString * __HTMLStartTagForElement(MMElement *anElement)
{
switch (anElement.type)
{
case MMElementTypeHeader:
return [NSString stringWithFormat:@"<h%u>", (unsigned int)anElement.level];
case MMElementTypeParagraph:
return @"<p>";
case MMElementTypeBulletedList:
return @"<ul>\n";
case MMElementTypeNumberedList:
return @"<ol>\n";
case MMElementTypeListItem:
return @"<li>";
case MMElementTypeBlockquote:
return @"<blockquote>\n";
case MMElementTypeCodeBlock:
return anElement.language ? [NSString stringWithFormat:@"<pre><code class=\"%@\">", anElement.language] : @"<pre><code>";
case MMElementTypeLineBreak:
return @"<br />";
case MMElementTypeHorizontalRule:
return @"\n<hr />\n";
case MMElementTypeStrikethrough:
return @"<del>";
case MMElementTypeStrong:
return @"<strong>";
case MMElementTypeEm:
return @"<em>";
case MMElementTypeCodeSpan:
return @"<code>";
case MMElementTypeImage:
if (anElement.title != nil)
{
return [NSString stringWithFormat:@"<img src=\"%@\" alt=\"%@\" title=\"%@\" />",
__HTMLEscapedString(anElement.href),
__HTMLEscapedString(anElement.stringValue),
__HTMLEscapedString(anElement.title)];
}
return [NSString stringWithFormat:@"<img src=\"%@\" alt=\"%@\" />",
__HTMLEscapedString(anElement.href),
__HTMLEscapedString(anElement.stringValue)];
case MMElementTypeLink:
if (anElement.title != nil)
{
return [NSString stringWithFormat:@"<a title=\"%@\" href=\"%@\">",
__HTMLEscapedString(anElement.title), __HTMLEscapedString(anElement.href)];
}
return [NSString stringWithFormat:@"<a href=\"%@\">", __HTMLEscapedString(anElement.href)];
case MMElementTypeMailTo:
return [NSString stringWithFormat:@"<a href=\"%@\">%@</a>",
__obfuscatedEmailAddress([NSString stringWithFormat:@"mailto:%@", anElement.href]),
__obfuscatedEmailAddress(anElement.href)];
case MMElementTypeEntity:
return anElement.stringValue;
case MMElementTypeTable:
return @"<table>";
case MMElementTypeTableHeader:
return @"<thead><tr>";
case MMElementTypeTableHeaderCell:
return anElement.alignment == MMTableCellAlignmentCenter ? @"<th align='center'>"
: anElement.alignment == MMTableCellAlignmentLeft ? @"<th align='left'>"
: anElement.alignment == MMTableCellAlignmentRight ? @"<th align='right'>"
: @"<th>";
case MMElementTypeTableRow:
return @"<tr>";
case MMElementTypeTableRowCell:
return anElement.alignment == MMTableCellAlignmentCenter ? @"<td align='center'>"
: anElement.alignment == MMTableCellAlignmentLeft ? @"<td align='left'>"
: anElement.alignment == MMTableCellAlignmentRight ? @"<td align='right'>"
: @"<td>";
default:
return nil;
}
}

static NSString * __HTMLEndTagForElement(MMElement *anElement)
{
switch (anElement.type)
{
case MMElementTypeHeader:
return [NSString stringWithFormat:@"</h%u>\n", (unsigned int)anElement.level];
case MMElementTypeParagraph:
return @"</p>\n";
case MMElementTypeBulletedList:
return @"</ul>\n";
case MMElementTypeNumberedList:
return @"</ol>\n";
case MMElementTypeListItem:
return @"</li>\n";
case MMElementTypeBlockquote:
return @"</blockquote>\n";
case MMElementTypeCodeBlock:
return @"</code></pre>\n";
case MMElementTypeStrikethrough:
return @"</del>";
case MMElementTypeStrong:
return @"</strong>";
case MMElementTypeEm:
return @"</em>";
case MMElementTypeCodeSpan:
return @"</code>";
case MMElementTypeLink:
return @"</a>";
case MMElementTypeTable:
return @"</tbody></table>";
case MMElementTypeTableHeader:
return @"</tr></thead><tbody>";
case MMElementTypeTableHeaderCell:
return @"</th>";
case MMElementTypeTableRow:
return @"</tr>";
case MMElementTypeTableRowCell:
return @"</td>";
default:
return nil;
}
}

@interface MMGenerator ()
- (void) _generateHTMLForElement:(MMElement *)anElement
inDocument:(MMDocument *)aDocument
HTML:(NSMutableString *)theHTML
location:(NSUInteger *)aLocation;
@end

@implementation MMGenerator

#pragma mark - Public Methods

- (NSString *)generateHTML:(MMDocument *)aDocument
{
NSString *markdown = aDocument.markdown;
NSUInteger location = 0;
NSUInteger length = markdown.length;
NSMutableString *HTML = [NSMutableString stringWithCapacity:length * kHTMLDocumentLengthMultiplier];
for (MMElement *element in aDocument.elements)
{
if (element.type == MMElementTypeHTML)
{
[HTML appendString:[aDocument.markdown substringWithRange:element.range]];
}
else
{
[self _generateHTMLForElement:element
inDocument:aDocument
HTML:HTML
location:&location];
}
}
return HTML;
}


#pragma mark - Private Methods

- (void)_generateHTMLForElement:(MMElement *)anElement
inDocument:(MMDocument *)aDocument
HTML:(NSMutableString *)theHTML
location:(NSUInteger *)aLocation
{
NSString *startTag = __HTMLStartTagForElement(anElement);
NSString *endTag = __HTMLEndTagForElement(anElement);
if (startTag)
[theHTML appendString:startTag];
for (MMElement *child in anElement.children)
{
if (child.type == MMElementTypeNone)
{
NSString *markdown = aDocument.markdown;
if (child.range.length == 0)
{
[theHTML appendString:@"\n"];
}
else
{
[theHTML appendString:[markdown substringWithRange:child.range]];
}
}
else if (child.type == MMElementTypeHTML)
{
[theHTML appendString:[aDocument.markdown substringWithRange:child.range]];
}
else
{
[self _generateHTMLForElement:child
inDocument:aDocument
HTML:theHTML
location:aLocation];
}
}
if (endTag)
[theHTML appendString:endTag];
}


@end

+ 40
- 0
tweak/assets/MMHTMLParser.h View File

@@ -0,0 +1,40 @@
//
// MMHTMLParser.h
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// 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, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions 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 <Foundation/Foundation.h>


@class MMElement;
@class MMScanner;

NS_ASSUME_NONNULL_BEGIN
@interface MMHTMLParser : NSObject

- (nullable MMElement *)parseBlockTagWithScanner:(MMScanner *)scanner;
- (nullable MMElement *)parseCommentWithScanner:(MMScanner *)scanner;
- (nullable MMElement *)parseInlineTagWithScanner:(MMScanner *)scanner;

@end
NS_ASSUME_NONNULL_END

+ 324
- 0
tweak/assets/MMHTMLParser.m View File

@@ -0,0 +1,324 @@
//
// MMHTMLParser.m
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// 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, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions 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 "MMHTMLParser.h"


#import "MMElement.h"
#import "MMScanner.h"

@implementation MMHTMLParser

#pragma mark - Public Methods

- (MMElement *)parseBlockTagWithScanner:(MMScanner *)scanner
{
[scanner beginTransaction];
MMElement *element = [self _parseStrictBlockTagWithScanner:scanner];
[scanner commitTransaction:element != nil];
if (element)
return element;
return [self _parseLenientBlockTagWithScanner:scanner];
}

- (MMElement *)parseCommentWithScanner:(MMScanner *)scanner
{
if (![scanner matchString:@"<!--"])
return nil;
NSCharacterSet *setToSkip = [[NSCharacterSet characterSetWithCharactersInString:@"-"] invertedSet];
while (!scanner.atEndOfString)
{
if (scanner.atEndOfLine)
[scanner advanceToNextLine];
else
{
[scanner skipCharactersFromSet:setToSkip];
if ([scanner matchString:@"-->"])
{
MMElement *element = [MMElement new];
element.type = MMElementTypeHTML;
element.range = NSMakeRange(scanner.startLocation, scanner.location-scanner.startLocation);
return element;
}
[scanner advance];
}
}
return nil;
}

- (MMElement *)parseInlineTagWithScanner:(MMScanner *)scanner
{
if (scanner.nextCharacter != '<')
return nil;
[scanner advance];
if (scanner.nextCharacter == '/')
[scanner advance];
NSRange tagNameRange = [self _parseNameWithScanner:scanner];
if (tagNameRange.length == 0)
return nil;
[self _parseAttributesWithScanner:scanner];
[scanner skipWhitespace];
if (scanner.nextCharacter == '/')
[scanner advance];
if (scanner.nextCharacter != '>')
return nil;
[scanner advance];
MMElement *element = [MMElement new];
element.type = MMElementTypeHTML;
element.range = NSMakeRange(scanner.startLocation, scanner.location-scanner.startLocation);
element.stringValue = [scanner.string substringWithRange:tagNameRange];
return element;
}


#pragma mark - Private Methods

- (MMElement *)_parseStrictBlockTagWithScanner:(MMScanner *)scanner
{
// which starts with a '<'
if (scanner.nextCharacter != '<')
return nil;
[scanner advance];
NSSet *htmlBlockTags = [NSSet setWithObjects:
@"p", @"div", @"h1", @"h2", @"h3", @"h4", @"h5", @"h6",
@"blockquote", @"pre", @"table", @"dl", @"ol", @"ul",
@"script", @"noscript", @"form", @"fieldset", @"iframe",
@"math", @"ins", @"del", nil];
NSString *tagName = [scanner nextWord];
if (![htmlBlockTags containsObject:tagName])
return nil;
scanner.location += tagName.length;
[self _parseAttributesWithScanner:scanner];
[scanner skipWhitespace];
if (scanner.nextCharacter != '>')
return nil;
[scanner advance];
NSCharacterSet *boringChars = [[NSCharacterSet characterSetWithCharactersInString:@"<"] invertedSet];
while (1)
{
if (scanner.atEndOfString)
return nil;
[scanner skipCharactersFromSet:boringChars];
if (scanner.atEndOfLine)
{
[scanner advanceToNextLine];
continue;
}
[scanner beginTransaction];
if ([self _parseEndTag:tagName withScanner:scanner])
{
[scanner commitTransaction:YES];
break;
}
[scanner commitTransaction:NO];
MMElement *element;
[scanner beginTransaction];
element = [self _parseStrictBlockTagWithScanner:scanner];
[scanner commitTransaction:element != nil];
if (element)
continue;
[scanner beginTransaction];
element = [self parseCommentWithScanner:scanner];
[scanner commitTransaction:element != nil];
if (element)
continue;
[scanner beginTransaction];
element = [self parseInlineTagWithScanner:scanner];
[scanner commitTransaction:element != nil];
if (element)
continue;
return nil;
}
MMElement *element = [MMElement new];
element.type = MMElementTypeHTML;
element.range = NSMakeRange(scanner.startLocation, scanner.location-scanner.startLocation);
return element;
}

- (BOOL)_parseEndTag:(NSString *)tagName withScanner:(MMScanner *)scanner
{
if (scanner.nextCharacter != '<')
return NO;
[scanner advance];
if (scanner.nextCharacter != '/')
return NO;
[scanner advance];
[scanner skipWhitespace];
if (![scanner matchString:tagName])
return NO;
[scanner skipWhitespace];
if (scanner.nextCharacter != '>')
return NO;
[scanner advance];
return YES;
}

- (MMElement *)_parseLenientBlockTagWithScanner:(MMScanner *)scanner
{
// which starts with a '<'
if (scanner.nextCharacter != '<')
return nil;
[scanner advance];
NSSet *htmlBlockTags = [NSSet setWithObjects:
@"p", @"div", @"h1", @"h2", @"h3", @"h4", @"h5", @"h6",
@"blockquote", @"pre", @"table", @"dl", @"ol", @"ul",
@"script", @"noscript", @"form", @"fieldset", @"iframe",
@"math", @"ins", @"del", nil];
NSString *tagName = scanner.nextWord;
if (![htmlBlockTags containsObject:tagName])
return nil;
scanner.location += tagName.length;
// Find a '>'
while (scanner.nextCharacter != '>')
{
if (scanner.atEndOfString)
return nil;
else if (scanner.atEndOfLine)
[scanner advanceToNextLine];
else
[scanner advance];
}
// Skip lines until we come across a blank line
while (!scanner.atEndOfLine)
{
[scanner advanceToNextLine];
}
MMElement *element = [MMElement new];
element.type = MMElementTypeHTML;
element.range = NSMakeRange(scanner.startLocation, scanner.location-scanner.startLocation);
return element;
}

- (NSRange)_parseNameWithScanner:(MMScanner *)scanner
{
NSMutableCharacterSet *nameSet = [NSMutableCharacterSet alphanumericCharacterSet];
[nameSet addCharactersInString:@":-"];
NSRange result = NSMakeRange(scanner.location, 0);
result.length = [scanner skipCharactersFromSet:nameSet];
return result;
}

- (BOOL)_parseStringWithScanner:(MMScanner *)scanner
{
unichar nextChar = [scanner nextCharacter];
if (nextChar != '"' && nextChar != '\'')
return NO;
[scanner advance];
while (scanner.nextCharacter != nextChar)
{
if (scanner.atEndOfString)
return NO;
else if (scanner.atEndOfLine)
[scanner advanceToNextLine];
else
[scanner advance];
}
// skip over the closing quotation mark
[scanner advance];
return YES;
}

- (BOOL)_parseAttributeValueWithScanner:(MMScanner *)scanner
{
NSMutableCharacterSet *characters = [[NSCharacterSet.whitespaceCharacterSet invertedSet] mutableCopy];
[characters removeCharactersInString:@"\"'=><`"];
return [scanner skipCharactersFromSet:characters] > 0;
}

- (void)_parseAttributesWithScanner:(MMScanner *)scanner
{
while ([scanner skipWhitespaceAndNewlines] > 0)
{
NSRange range;
range = [self _parseNameWithScanner:scanner];
if (range.length == 0)
break;
[scanner beginTransaction];
[scanner skipWhitespace];
if (scanner.nextCharacter == '=')
{
[scanner commitTransaction:YES];
[scanner advance];
[scanner skipWhitespace];
if ([self _parseStringWithScanner:scanner])
;
else if ([self _parseAttributeValueWithScanner:scanner])
;
else
break;
}
else
{
[scanner commitTransaction:NO];
}
}
}


@end

+ 7
- 0
tweak/assets/MMMarkdown-Prefix.pch View File

@@ -0,0 +1,7 @@
//
// Prefix header for all source files of the 'MMMarkdown' target in the 'MMMarkdown' project
//

#ifdef __OBJC__
#import <Foundation/Foundation.h>
#endif

+ 82
- 0
tweak/assets/MMMarkdown.h View File

@@ -0,0 +1,82 @@
//
// MMMarkdown.h
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// 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, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions 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 <Foundation/Foundation.h>

//! Project version number for MMMarkdown.
FOUNDATION_EXPORT double MMMarkdownVersionNumber;

//! Project version string for MMMarkdown.
FOUNDATION_EXPORT const unsigned char MMMarkdownVersionString[];

typedef NS_OPTIONS(NSUInteger, MMMarkdownExtensions)
{
MMMarkdownExtensionsNone = 0,
MMMarkdownExtensionsAutolinkedURLs = 1 << 0,
// MMMarkdownExtensionsCrossReferences = 1 << 1,
// MMMarkdownExtensionsCustomAttributes = 1 << 2,
MMMarkdownExtensionsFencedCodeBlocks = 1 << 3,
// MMMarkdownExtensionsFootnotes = 1 << 4,
MMMarkdownExtensionsHardNewlines = 1 << 5,
MMMarkdownExtensionsStrikethroughs = 1 << 6,
// MMMarkdownExtensionsTableCaptions = 1 << 7,
MMMarkdownExtensionsTables = 1 << 8,
MMMarkdownExtensionsUnderscoresInWords = 1 << 9,
MMMarkdownExtensionsGitHubFlavored = MMMarkdownExtensionsAutolinkedURLs|MMMarkdownExtensionsFencedCodeBlocks|MMMarkdownExtensionsHardNewlines|MMMarkdownExtensionsStrikethroughs|MMMarkdownExtensionsTables|MMMarkdownExtensionsUnderscoresInWords,
};

NS_ASSUME_NONNULL_BEGIN
@interface MMMarkdown : NSObject

/*!
Convert a Markdown string to HTML.
@param string
A Markdown string. Must not be nil.
@param error
Out parameter used if an error occurs while parsing the Markdown. May be NULL.
@result
Returns an HTML string.
*/
+ (NSString *)HTMLStringWithMarkdown:(NSString *)string error:(NSError * __autoreleasing * _Nullable)error;

/*!
Convert a Markdown string to HTML.
@param string
A Markdown string. Must not be nil.
@param extensions
The extensions to enable.
@param error
Out parameter used if an error occurs while parsing the Markdown. May be NULL.
@result
Returns an HTML string.
*/
+ (NSString *)HTMLStringWithMarkdown:(NSString *)string extensions:(MMMarkdownExtensions)extensions error:(NSError * __autoreleasing * _Nullable)error;

@end
NS_ASSUME_NONNULL_END

+ 73
- 0
tweak/assets/MMMarkdown.m View File

@@ -0,0 +1,73 @@
//
// MMMarkdown.m
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// 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, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions 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 "MMMarkdown.h"


#import "MMParser.h"
#import "MMGenerator.h"

@implementation MMMarkdown

#pragma mark - Public Methods

+ (NSString *)HTMLStringWithMarkdown:(NSString *)string error:(__autoreleasing NSError **)error
{
return [self HTMLStringWithMarkdown:string extensions:MMMarkdownExtensionsNone fromSelector:_cmd error:error];
}

+ (NSString *)HTMLStringWithMarkdown:(NSString *)string extensions:(MMMarkdownExtensions)extensions error:(NSError *__autoreleasing *)error
{
return [self HTMLStringWithMarkdown:string extensions:extensions fromSelector:_cmd error:error];
}


#pragma mark - Private Methods

+ (NSString *)HTMLStringWithMarkdown:(NSString *)string
extensions:(MMMarkdownExtensions)extensions
fromSelector:(SEL)selector
error:(__autoreleasing NSError **)error
{
if (string == nil)
{
NSString *reason = [NSString stringWithFormat:@"[%@ %@]: nil argument for markdown",
NSStringFromClass(self.class), NSStringFromSelector(selector)];
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:reason userInfo:nil];
}
if (string.length == 0)
return @"";
MMParser *parser = [[MMParser alloc] initWithExtensions:extensions];
MMGenerator *generator = [MMGenerator new];
MMDocument *document = [parser parseMarkdown:string error:error];
return [generator generateHTML:document];
}


@end

+ 41
- 0
tweak/assets/MMParser.h View File

@@ -0,0 +1,41 @@
//
// MMParser.h
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// 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, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions 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 <Foundation/Foundation.h>


#import "MMMarkdown.h"

@class MMDocument;

NS_ASSUME_NONNULL_BEGIN
@interface MMParser : NSObject

- (id)initWithExtensions:(MMMarkdownExtensions)extensions;

- (MMDocument *)parseMarkdown:(NSString *)markdown error:(NSError * __autoreleasing * _Nullable)error;

@end
NS_ASSUME_NONNULL_END

+ 1321
- 0
tweak/assets/MMParser.m
File diff suppressed because it is too large
View File


+ 79
- 0
tweak/assets/MMScanner.h View File

@@ -0,0 +1,79 @@
//
// MMScanner.h
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// 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, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions 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 <Foundation/Foundation.h>


@interface MMScanner : NSObject

// Constant info
@property (strong, nonatomic, readonly) NSString *string;
@property (copy, nonatomic, readonly) NSArray *lineRanges;

// Changing
@property (assign, nonatomic, readonly) NSUInteger startLocation;
@property (assign, nonatomic, readonly) NSRange currentRange;

// Settable
@property (assign, nonatomic) NSUInteger location;

+ (id)scannerWithString:(NSString *)aString;
- (id)initWithString:(NSString *)aString;

+ (id)scannerWithString:(NSString *)aString lineRanges:(NSArray *)theLineRanges;
- (id)initWithString:(NSString *)aString lineRanges:(NSArray *)theLineRanges;

- (void)beginTransaction;
- (void)commitTransaction:(BOOL)shouldSave;

- (BOOL)atBeginningOfLine;
- (BOOL)atEndOfLine;
- (BOOL)atEndOfString;

- (unichar)previousCharacter;
- (unichar)nextCharacter;

- (NSString *)previousWord;
- (NSString *)nextWord;

- (NSString *)previousWordWithCharactersFromSet:(NSCharacterSet *)set;
- (NSString *)nextWordWithCharactersFromSet:(NSCharacterSet *)set;

- (void)advance;
- (void)advanceToNextLine;

- (BOOL)matchString:(NSString *)string;

- (NSUInteger)skipCharactersFromSet:(NSCharacterSet *)aSet;
- (NSUInteger)skipCharactersFromSet:(NSCharacterSet *)aSet max:(NSUInteger)maxToSkip;
- (NSUInteger)skipEmptyLines;
- (NSUInteger)skipIndentationUpTo:(NSUInteger)maxSpacesToSkip;
- (NSUInteger)skipNestedBracketsWithDelimiter:(unichar)delimiter;
- (NSUInteger)skipToEndOfLine;
- (NSUInteger)skipToLastCharacterOfLine;
- (NSUInteger)skipWhitespace;
- (NSUInteger)skipWhitespaceAndNewlines;

@end

+ 499
- 0
tweak/assets/MMScanner.m View File

@@ -0,0 +1,499 @@
//
// MMScanner.m
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// 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, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions 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 "MMScanner.h"


static NSString *__delimitersForCharacter(unichar character)
{
switch (character)
{
case '[':
case ']':
return @"[]";
case '(':
case ')':
return @"()";
case '<':
case '>':
return @"<>";
case '{':
case '}':
return @"{}";
default:
[NSException raise:@"Invalid delimiter character"
format:@"Character '%C' is not a valid delimiter", character];
return nil;
}
}

@interface MMScanner ()
@property (assign, nonatomic) NSUInteger startLocation;
@property (assign, nonatomic) NSRange currentRange;

@property (assign, nonatomic, readonly) NSRange currentLineRange;
@property (assign, nonatomic) NSUInteger rangeIndex;
@property (strong, nonatomic, readonly) NSMutableArray *transactions;
@end

@implementation MMScanner

#pragma mark - Public Methods

+ (id)scannerWithString:(NSString *)aString
{
return [[self.class alloc] initWithString:aString];
}

- (id)initWithString:(NSString *)aString
{
NSArray *lineRanges = [self _lineRangesForString:aString];
return [self initWithString:aString lineRanges:lineRanges];
}

+ (id)scannerWithString:(NSString *)aString lineRanges:(NSArray *)theLineRanges
{
return [[self.class alloc] initWithString:aString lineRanges:theLineRanges];
}

- (id)initWithString:(NSString *)aString lineRanges:(NSArray *)theLineRanges
{
NSParameterAssert(theLineRanges.count > 0);
self = [super init];
if (self)
{
_string = aString;
_lineRanges = theLineRanges;
_rangeIndex = 0;
_transactions = [NSMutableArray new];
self.startLocation = 0;
self.currentRange = self.currentLineRange;
}
return self;
}

- (void)beginTransaction
{
NSDictionary *transaction = @{
@"rangeIndex": @(self.rangeIndex),
@"location": @(self.location),
@"startLocation": @(self.startLocation),
};
[self.transactions addObject:transaction];
self.startLocation = self.location;
}

- (void)commitTransaction:(BOOL)shouldSave
{
if (!self.transactions.count)
[NSException raise:@"Transaction underflow" format:@"Could not commit transaction because the stack is empty"];
NSDictionary *transaction = [self.transactions lastObject];
[self.transactions removeLastObject];
self.startLocation = [[transaction objectForKey:@"startLocation"] unsignedIntegerValue];
if (!shouldSave)
{
self.rangeIndex = [[transaction objectForKey:@"rangeIndex"] unsignedIntegerValue];
self.location = [[transaction objectForKey:@"location"] unsignedIntegerValue];
}
}

- (BOOL)atBeginningOfLine
{
return self.location == self.currentLineRange.location;
}

- (BOOL)atEndOfLine
{
return self.location == NSMaxRange(self.currentLineRange);
}

- (BOOL)atEndOfString
{
return self.atEndOfLine && self.rangeIndex == self.lineRanges.count - 1;
}

- (unichar)previousCharacter
{
if (self.atBeginningOfLine)
return '\0';
return [self.string characterAtIndex:self.location - 1];
}

- (unichar)nextCharacter
{
if (self.atEndOfLine)
return '\n';
return [self.string characterAtIndex:self.location];
}

- (NSString *)previousWord
{
return [self previousWordWithCharactersFromSet:NSCharacterSet.alphanumericCharacterSet];
}

- (NSString *)nextWord
{
return [self nextWordWithCharactersFromSet:NSCharacterSet.alphanumericCharacterSet];
}

- (NSString *)previousWordWithCharactersFromSet:(NSCharacterSet *)set
{
NSUInteger start = MAX(self.currentLineRange.location, self.startLocation);
NSUInteger end = self.currentRange.location;
NSRange range = NSMakeRange(start, end-start);
NSRange result = [self.string rangeOfCharacterFromSet:set.invertedSet
options:NSBackwardsSearch
range:range];
if (result.location == NSNotFound)
return [self.string substringWithRange:range];
NSUInteger wordLocation = NSMaxRange(result);
NSRange wordRange = NSMakeRange(wordLocation, end-wordLocation);
return [self.string substringWithRange:wordRange];
}

- (NSString *)nextWordWithCharactersFromSet:(NSCharacterSet *)set
{
NSRange result = [self.string rangeOfCharacterFromSet:set.invertedSet
options:0
range:self.currentRange];
if (result.location == NSNotFound)
return [self.string substringWithRange:self.currentRange];
NSRange wordRange = self.currentRange;
wordRange.length = result.location - wordRange.location;
return [self.string substringWithRange:wordRange];
}

- (void)advance
{
if (self.atEndOfLine)
return;
self.location += 1;
}

- (void)advanceToNextLine
{
// If at the last line, just go to the end of the line
if (self.rangeIndex == self.lineRanges.count - 1)
{
self.location = NSMaxRange(self.currentLineRange);
}
// Otherwise actually go to the next line
else
{
self.rangeIndex += 1;
self.currentRange = self.currentLineRange;
}
}

- (BOOL)matchString:(NSString *)string
{
if (self.currentRange.length < string.length)
return NO;
NSUInteger location = self.location;
for (NSUInteger idx=0; idx<string.length; idx++)
{
if ([string characterAtIndex:idx] != [self.string characterAtIndex:location+idx])
return NO;
}
self.location += string.length;
return YES;
}

- (NSUInteger)skipCharactersFromSet:(NSCharacterSet *)aSet
{
NSRange searchRange = self.currentRange;
NSRange range = [self.string rangeOfCharacterFromSet:[aSet invertedSet]
options:0
range:searchRange];
NSUInteger current = self.location;
if (range.location == NSNotFound)
{
self.currentRange = NSMakeRange(NSMaxRange(self.currentRange), 0);
}
else
{
self.currentRange = NSMakeRange(range.location, NSMaxRange(self.currentRange)-range.location);
}
return self.location - current;
}

- (NSUInteger)skipCharactersFromSet:(NSCharacterSet *)aSet max:(NSUInteger)maxToSkip
{
NSUInteger idx=0;
for (; idx<maxToSkip; idx++)
{
unichar character = [self nextCharacter];
if ([aSet characterIsMember:character])
[self advance];
else
break;
}
return idx;
}

- (NSUInteger)skipEmptyLines
{
NSUInteger skipped = 0;
while (![self atEndOfString])
{
[self beginTransaction];
[self skipWhitespace];
if (!self.atEndOfLine)
{
[self commitTransaction:NO];
break;
}
[self commitTransaction:YES];
[self advanceToNextLine];
skipped++;
}
return skipped;
}

- (NSUInteger)skipIndentationUpTo:(NSUInteger)maxSpacesToSkip
{
NSUInteger skipped = 0;
[self beginTransaction];
while (!self.atEndOfLine && skipped < maxSpacesToSkip)
{
unichar character = self.nextCharacter;
if (character == ' ')
skipped += 1;
else if (character == '\t')
{
skipped -= skipped % 4;
skipped += 4;
}
else
break;
[self advance];
}
[self commitTransaction:skipped <= maxSpacesToSkip];
return skipped;
}

- (NSUInteger)skipNestedBracketsWithDelimiter:(unichar)delimiter
{
NSString *delimiters = __delimitersForCharacter(delimiter);

if (delimiters == nil)
return 0;

unichar openDelimiter = [delimiters characterAtIndex:0];
unichar closeDelimeter = [delimiters characterAtIndex:1];
if ([self nextCharacter] != openDelimiter)
return 0;
[self beginTransaction];
NSUInteger location = self.location;
[self advance];
NSString *specialChars = [NSString stringWithFormat:@"%@\\", delimiters];
NSCharacterSet *boringChars = [[NSCharacterSet characterSetWithCharactersInString:specialChars] invertedSet];
NSUInteger nestingLevel = 1;
while (nestingLevel > 0)
{
if (self.atEndOfLine)
{
[self commitTransaction:NO];
return 0;
}
[self skipCharactersFromSet:boringChars];
unichar nextChar = self.nextCharacter;
[self advance];
if (nextChar == openDelimiter)
{
nestingLevel++;
}
else if (nextChar == closeDelimeter)
{
nestingLevel--;
}
else if (nextChar == '\\')
{
// skip a second character after a backslash
[self advance];
}
}
[self commitTransaction:YES];
return self.location - location;
}

- (NSUInteger)skipToEndOfLine
{
NSUInteger length = self.currentRange.length;
self.location = NSMaxRange(self.currentRange);
return length;
}

- (NSUInteger)skipToLastCharacterOfLine
{
NSUInteger length = self.currentRange.length - 1;
self.location = NSMaxRange(self.currentRange) - 1;
return length;
}

- (NSUInteger)skipWhitespace
{
return [self skipCharactersFromSet:NSCharacterSet.whitespaceCharacterSet];
}

- (NSUInteger)skipWhitespaceAndNewlines
{
NSCharacterSet *whitespaceSet = NSCharacterSet.whitespaceCharacterSet;
NSUInteger length = 0;
while (!self.atEndOfString)
{
if (self.atEndOfLine)
{
[self advanceToNextLine];
length++;
}
else
{
NSUInteger spaces = [self skipCharactersFromSet:whitespaceSet];
if (spaces == 0)
break;
length += spaces;
}
}
return length;
}


#pragma mark - Public Properties

- (NSUInteger)location
{
return self.currentRange.location;
}

- (void)setLocation:(NSUInteger)location
{
// If the new location isn't a part of the current range, then find the range it belongs to.
if (!NSLocationInRange(location, self.currentLineRange))
{
__block NSUInteger index = 0;
[self.lineRanges enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
NSRange range = [obj rangeValue];
*stop = NSLocationInRange(location, range) || location == NSMaxRange(range);
index = idx;
}];
self.rangeIndex = index;
}
self.currentRange = NSMakeRange(location, NSMaxRange(self.currentLineRange)-location);
}


#pragma mark - Private Methods

- (NSArray *)_lineRangesForString:(NSString *)aString
{
NSMutableArray *result = [NSMutableArray array];
NSUInteger location = 0;
NSUInteger idx;
for (idx=0; idx<aString.length; idx++)
{
unichar character = [aString characterAtIndex:idx];
if (character == '\r' || character == '\n')
{
NSRange range = NSMakeRange(location, idx-location);
[result addObject:[NSValue valueWithRange:range]];
// If it's a carriage return, check for a line feed too
if (character == '\r')
{
if (idx + 1 < aString.length && [aString characterAtIndex:idx + 1] == '\n')
{
idx += 1;
}
}
location = idx + 1;
}
}
// Add the final line if the string doesn't end with a newline
if (location < aString.length)
{
NSRange range = NSMakeRange(location, aString.length-location);
[result addObject:[NSValue valueWithRange:range]];
}
return result;
}

- (NSUInteger)_locationOfCharacter:(unichar)character inRange:(NSRange)range
{
NSString *characterString = [NSString stringWithCharacters:&character length:1];
NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:characterString];
NSRange result = [self.string rangeOfCharacterFromSet:characterSet options:0 range:range];
return result.location;
}

#pragma mark - Private Properties

- (NSRange)currentLineRange
{
return [self.lineRanges[self.rangeIndex] rangeValue];
}

@end

+ 45
- 0
tweak/assets/MMSpanParser.h View File

@@ -0,0 +1,45 @@
//
// MMSpanParser.h
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// 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, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions 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 <Foundation/Foundation.h>


#import "MMMarkdown.h"
#import "MMParser.h"

@class MMElement;
@class MMScanner;

NS_ASSUME_NONNULL_BEGIN
@interface MMSpanParser : NSObject

- (id)initWithExtensions:(MMMarkdownExtensions)extensions;

- (nullable NSArray *)parseSpansInBlockElement:(MMElement *)block withScanner:(MMScanner *)scanner;

- (nullable NSArray *)parseSpansInTableColumns:(NSArray *)columns withScanner:(MMScanner *)scanner;

@end
NS_ASSUME_NONNULL_END

+ 1298
- 0
tweak/assets/MMSpanParser.m
File diff suppressed because it is too large
View File


Loading…
Cancel
Save