Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dde451ab4e | |||
| ae97f0fbea | |||
| 040d7896fe | |||
| 403285e46f | |||
| af94dd615e | |||
| 99ded8446b | |||
| 2bbf1213a6 | |||
| aa748f2cdd | |||
| 851b547dcd | |||
| 5b0a1e7633 | |||
| 13955aac92 | |||
| 0b40036eda | |||
| 2e3d59324a | |||
| 9d1457a96a | |||
| eb9742b518 | |||
| 569daaf779 | |||
| 649d7690e1 | |||
| 2b8df10a0d | |||
| c9fcecafc3 | |||
| 6c64c16475 | |||
| d87627c957 | |||
| 1ecc196398 | |||
| 6103068a26 | |||
| 429d557c9c | |||
| 2e69439003 | |||
| a93ccedac5 | |||
| bc81245371 | |||
| ecebb6d8b8 | |||
| 1df35418f2 | |||
| 5e8c7d03ff | |||
| 6ab69d3d82 | |||
| 9cf9d6bfbe | |||
| f4529f5ff9 | |||
| 4592cdd4fa | |||
| 17a2b6b5ff | |||
| 20d2827573 | |||
| 98f6beced3 | |||
| fab59935e2 | |||
| 54e1f56cad | |||
| abc0768701 | |||
| 95c1e59344 | |||
| 4581712c5e | |||
| b0e9e36c6b | |||
| 61490b1e13 | |||
| bcbf3bd38c | |||
| 79467395b5 | |||
| 4e2f5c234e | |||
| 898a1ca2ed | |||
| ba5d8ce9c6 | |||
| 68336ab630 | |||
| 368f0da628 | |||
| 72ff31b6ff | |||
| 924d06bcfe | |||
| a1aa1a0eac | |||
| 71a64dee21 | |||
| 71eb29ada0 | |||
| 711850056d | |||
| d2cbd0ce96 | |||
| 61c6701518 | |||
| 8518dfb406 | |||
| a06d690000 | |||
| 09d4166025 | |||
| 1f162f124e | |||
| 79f35fa4aa | |||
| 095b2d3a6c | |||
| 8a77770f4d | |||
| 6389a8cbe5 | |||
| e467e992e2 | |||
| f68e64505b | |||
| d2c2af66fd | |||
| b7f451993b | |||
| b095feaca5 | |||
| f963bfec5b | |||
| 36f0c4d9c7 | |||
| 3b9f770ea9 | |||
| a1dfe192af | |||
| 050ca10694 | |||
| 45d03f88b7 | |||
| 586cecff2f | |||
| 124e0f0a48 | |||
| d510f69d10 | |||
| 19c473e81d | |||
| a209277d97 |
@@ -36,6 +36,7 @@ playground.xcworkspace
|
||||
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
|
||||
# Packages/
|
||||
.build/
|
||||
.swiftpm/
|
||||
|
||||
# CocoaPods
|
||||
#
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// AppDelegate.swift
|
||||
// SwiftyMarkdownExample macOS
|
||||
//
|
||||
// Created by Simon Fairbairn on 01/02/2020.
|
||||
// Copyright © 2020 Voyage Travel Apps. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
@NSApplicationMain
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
|
||||
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
// Insert code here to initialize your application
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ aNotification: Notification) {
|
||||
// Insert code here to tear down your application
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "16x16",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "16x16",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "32x32",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "32x32",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "128x128",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "128x128",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "256x256",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "256x256",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "512x512",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "512x512",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,717 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="11134" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11134"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Application-->
|
||||
<scene sceneID="JPo-4y-FX3">
|
||||
<objects>
|
||||
<application id="hnw-xV-0zn" sceneMemberID="viewController">
|
||||
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
||||
<items>
|
||||
<menuItem title="SwiftyMarkdownExample macOS" id="1Xt-HY-uBw">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="SwiftyMarkdownExample macOS" systemMenu="apple" id="uQy-DD-JDr">
|
||||
<items>
|
||||
<menuItem title="About SwiftyMarkdownExample macOS" id="5kV-Vb-QxS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
|
||||
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
|
||||
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
|
||||
<menuItem title="Services" id="NMo-om-nkz">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
|
||||
<menuItem title="Hide SwiftyMarkdownExample macOS" keyEquivalent="h" id="Olw-nP-bQN">
|
||||
<connections>
|
||||
<action selector="hide:" target="Ady-hI-5gd" id="PnN-Uc-m68"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="hideOtherApplications:" target="Ady-hI-5gd" id="VT4-aY-XCT"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Show All" id="Kd2-mp-pUS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="unhideAllApplications:" target="Ady-hI-5gd" id="Dhg-Le-xox"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
|
||||
<menuItem title="Quit SwiftyMarkdownExample macOS" keyEquivalent="q" id="4sb-4s-VLi">
|
||||
<connections>
|
||||
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="File" id="dMs-cI-mzQ">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="File" id="bib-Uj-vzu">
|
||||
<items>
|
||||
<menuItem title="New" keyEquivalent="n" id="Was-JA-tGl">
|
||||
<connections>
|
||||
<action selector="newDocument:" target="Ady-hI-5gd" id="4Si-XN-c54"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Open…" keyEquivalent="o" id="IAo-SY-fd9">
|
||||
<connections>
|
||||
<action selector="openDocument:" target="Ady-hI-5gd" id="bVn-NM-KNZ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Open Recent" id="tXI-mr-wws">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Open Recent" systemMenu="recentDocuments" id="oas-Oc-fiZ">
|
||||
<items>
|
||||
<menuItem title="Clear Menu" id="vNY-rz-j42">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="clearRecentDocuments:" target="Ady-hI-5gd" id="Daa-9d-B3U"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
|
||||
<menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
|
||||
<connections>
|
||||
<action selector="performClose:" target="Ady-hI-5gd" id="HmO-Ls-i7Q"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Save…" keyEquivalent="s" id="pxx-59-PXV">
|
||||
<connections>
|
||||
<action selector="saveDocument:" target="Ady-hI-5gd" id="teZ-XB-qJY"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Save As…" keyEquivalent="S" id="Bw7-FT-i3A">
|
||||
<connections>
|
||||
<action selector="saveDocumentAs:" target="Ady-hI-5gd" id="mDf-zr-I0C"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Revert to Saved" keyEquivalent="r" id="KaW-ft-85H">
|
||||
<connections>
|
||||
<action selector="revertDocumentToSaved:" target="Ady-hI-5gd" id="iJ3-Pv-kwq"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="aJh-i4-bef"/>
|
||||
<menuItem title="Page Setup…" keyEquivalent="P" id="qIS-W8-SiK">
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="runPageLayout:" target="Ady-hI-5gd" id="Din-rz-gC5"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Print…" keyEquivalent="p" id="aTl-1u-JFS">
|
||||
<connections>
|
||||
<action selector="print:" target="Ady-hI-5gd" id="qaZ-4w-aoO"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Edit" id="5QF-Oa-p0T">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
|
||||
<items>
|
||||
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
|
||||
<connections>
|
||||
<action selector="undo:" target="Ady-hI-5gd" id="M6e-cu-g7V"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
|
||||
<connections>
|
||||
<action selector="redo:" target="Ady-hI-5gd" id="oIA-Rs-6OD"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
|
||||
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
|
||||
<connections>
|
||||
<action selector="cut:" target="Ady-hI-5gd" id="YJe-68-I9s"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
|
||||
<connections>
|
||||
<action selector="copy:" target="Ady-hI-5gd" id="G1f-GL-Joy"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
|
||||
<connections>
|
||||
<action selector="paste:" target="Ady-hI-5gd" id="UvS-8e-Qdg"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="pasteAsPlainText:" target="Ady-hI-5gd" id="cEh-KX-wJQ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Delete" id="pa3-QI-u2k">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="delete:" target="Ady-hI-5gd" id="0Mk-Ml-PaM"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
|
||||
<connections>
|
||||
<action selector="selectAll:" target="Ady-hI-5gd" id="VNm-Mi-diN"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
|
||||
<menuItem title="Find" id="4EN-yA-p0u">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Find" id="1b7-l0-nxx">
|
||||
<items>
|
||||
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="cD7-Qs-BN4"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="WD3-Gg-5AJ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="NDo-RZ-v9R"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="HOh-sY-3ay"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="U76-nv-p5D"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
|
||||
<connections>
|
||||
<action selector="centerSelectionInVisibleArea:" target="Ady-hI-5gd" id="IOG-6D-g5B"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
|
||||
<items>
|
||||
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
|
||||
<connections>
|
||||
<action selector="showGuessPanel:" target="Ady-hI-5gd" id="vFj-Ks-hy3"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
|
||||
<connections>
|
||||
<action selector="checkSpelling:" target="Ady-hI-5gd" id="fz7-VC-reM"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
|
||||
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleContinuousSpellChecking:" target="Ady-hI-5gd" id="7w6-Qz-0kB"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleGrammarChecking:" target="Ady-hI-5gd" id="muD-Qn-j4w"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticSpellingCorrection:" target="Ady-hI-5gd" id="2lM-Qi-WAP"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Substitutions" id="9ic-FL-obx">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
|
||||
<items>
|
||||
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="orderFrontSubstitutionsPanel:" target="Ady-hI-5gd" id="oku-mr-iSq"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
|
||||
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleSmartInsertDelete:" target="Ady-hI-5gd" id="3IJ-Se-DZD"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticQuoteSubstitution:" target="Ady-hI-5gd" id="ptq-xd-QOA"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticDashSubstitution:" target="Ady-hI-5gd" id="oCt-pO-9gS"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Smart Links" id="cwL-P1-jid">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticLinkDetection:" target="Ady-hI-5gd" id="Gip-E3-Fov"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Data Detectors" id="tRr-pd-1PS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticDataDetection:" target="Ady-hI-5gd" id="R1I-Nq-Kbl"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticTextReplacement:" target="Ady-hI-5gd" id="DvP-Fe-Py6"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Transformations" id="2oI-Rn-ZJC">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
|
||||
<items>
|
||||
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="uppercaseWord:" target="Ady-hI-5gd" id="sPh-Tk-edu"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="lowercaseWord:" target="Ady-hI-5gd" id="iUZ-b5-hil"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="capitalizeWord:" target="Ady-hI-5gd" id="26H-TL-nsh"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Speech" id="xrE-MZ-jX0">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
|
||||
<items>
|
||||
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="startSpeaking:" target="Ady-hI-5gd" id="654-Ng-kyl"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="stopSpeaking:" target="Ady-hI-5gd" id="dX8-6p-jy9"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Format" id="jxT-CU-nIS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Format" id="GEO-Iw-cKr">
|
||||
<items>
|
||||
<menuItem title="Font" id="Gi5-1S-RQB">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Font" systemMenu="font" id="aXa-aM-Jaq">
|
||||
<items>
|
||||
<menuItem title="Show Fonts" keyEquivalent="t" id="Q5e-8K-NDq">
|
||||
<connections>
|
||||
<action selector="orderFrontFontPanel:" target="YLy-65-1bz" id="WHr-nq-2xA"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Bold" tag="2" keyEquivalent="b" id="GB9-OM-e27">
|
||||
<connections>
|
||||
<action selector="addFontTrait:" target="YLy-65-1bz" id="hqk-hr-sYV"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Italic" tag="1" keyEquivalent="i" id="Vjx-xi-njq">
|
||||
<connections>
|
||||
<action selector="addFontTrait:" target="YLy-65-1bz" id="IHV-OB-c03"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Underline" keyEquivalent="u" id="WRG-CD-K1S">
|
||||
<connections>
|
||||
<action selector="underline:" target="Ady-hI-5gd" id="FYS-2b-JAY"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="5gT-KC-WSO"/>
|
||||
<menuItem title="Bigger" tag="3" keyEquivalent="+" id="Ptp-SP-VEL">
|
||||
<connections>
|
||||
<action selector="modifyFont:" target="YLy-65-1bz" id="Uc7-di-UnL"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Smaller" tag="4" keyEquivalent="-" id="i1d-Er-qST">
|
||||
<connections>
|
||||
<action selector="modifyFont:" target="YLy-65-1bz" id="HcX-Lf-eNd"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="kx3-Dk-x3B"/>
|
||||
<menuItem title="Kern" id="jBQ-r6-VK2">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Kern" id="tlD-Oa-oAM">
|
||||
<items>
|
||||
<menuItem title="Use Default" id="GUa-eO-cwY">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="useStandardKerning:" target="Ady-hI-5gd" id="6dk-9l-Ckg"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Use None" id="cDB-IK-hbR">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="turnOffKerning:" target="Ady-hI-5gd" id="U8a-gz-Maa"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Tighten" id="46P-cB-AYj">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="tightenKerning:" target="Ady-hI-5gd" id="hr7-Nz-8ro"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Loosen" id="ogc-rX-tC1">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="loosenKerning:" target="Ady-hI-5gd" id="8i4-f9-FKE"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Ligatures" id="o6e-r0-MWq">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Ligatures" id="w0m-vy-SC9">
|
||||
<items>
|
||||
<menuItem title="Use Default" id="agt-UL-0e3">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="useStandardLigatures:" target="Ady-hI-5gd" id="7uR-wd-Dx6"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Use None" id="J7y-lM-qPV">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="turnOffLigatures:" target="Ady-hI-5gd" id="iX2-gA-Ilz"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Use All" id="xQD-1f-W4t">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="useAllLigatures:" target="Ady-hI-5gd" id="KcB-kA-TuK"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Baseline" id="OaQ-X3-Vso">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Baseline" id="ijk-EB-dga">
|
||||
<items>
|
||||
<menuItem title="Use Default" id="3Om-Ey-2VK">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="unscript:" target="Ady-hI-5gd" id="0vZ-95-Ywn"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Superscript" id="Rqc-34-cIF">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="superscript:" target="Ady-hI-5gd" id="3qV-fo-wpU"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Subscript" id="I0S-gh-46l">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="subscript:" target="Ady-hI-5gd" id="Q6W-4W-IGz"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Raise" id="2h7-ER-AoG">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="raiseBaseline:" target="Ady-hI-5gd" id="4sk-31-7Q9"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Lower" id="1tx-W0-xDw">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="lowerBaseline:" target="Ady-hI-5gd" id="OF1-bc-KW4"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="Ndw-q3-faq"/>
|
||||
<menuItem title="Show Colors" keyEquivalent="C" id="bgn-CT-cEk">
|
||||
<connections>
|
||||
<action selector="orderFrontColorPanel:" target="Ady-hI-5gd" id="mSX-Xz-DV3"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="iMs-zA-UFJ"/>
|
||||
<menuItem title="Copy Style" keyEquivalent="c" id="5Vv-lz-BsD">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="copyFont:" target="Ady-hI-5gd" id="GJO-xA-L4q"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Paste Style" keyEquivalent="v" id="vKC-jM-MkH">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="pasteFont:" target="Ady-hI-5gd" id="JfD-CL-leO"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Text" id="Fal-I4-PZk">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Text" id="d9c-me-L2H">
|
||||
<items>
|
||||
<menuItem title="Align Left" keyEquivalent="{" id="ZM1-6Q-yy1">
|
||||
<connections>
|
||||
<action selector="alignLeft:" target="Ady-hI-5gd" id="zUv-R1-uAa"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Center" keyEquivalent="|" id="VIY-Ag-zcb">
|
||||
<connections>
|
||||
<action selector="alignCenter:" target="Ady-hI-5gd" id="spX-mk-kcS"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Justify" id="J5U-5w-g23">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="alignJustified:" target="Ady-hI-5gd" id="ljL-7U-jND"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Align Right" keyEquivalent="}" id="wb2-vD-lq4">
|
||||
<connections>
|
||||
<action selector="alignRight:" target="Ady-hI-5gd" id="r48-bG-YeY"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="4s2-GY-VfK"/>
|
||||
<menuItem title="Writing Direction" id="H1b-Si-o9J">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Writing Direction" id="8mr-sm-Yjd">
|
||||
<items>
|
||||
<menuItem title="Paragraph" enabled="NO" id="ZvO-Gk-QUH">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem id="YGs-j5-SAR">
|
||||
<string key="title"> Default</string>
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="makeBaseWritingDirectionNatural:" target="Ady-hI-5gd" id="qtV-5e-UBP"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem id="Lbh-J2-qVU">
|
||||
<string key="title"> Left to Right</string>
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="makeBaseWritingDirectionLeftToRight:" target="Ady-hI-5gd" id="S0X-9S-QSf"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem id="jFq-tB-4Kx">
|
||||
<string key="title"> Right to Left</string>
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="makeBaseWritingDirectionRightToLeft:" target="Ady-hI-5gd" id="5fk-qB-AqJ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="swp-gr-a21"/>
|
||||
<menuItem title="Selection" enabled="NO" id="cqv-fj-IhA">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem id="Nop-cj-93Q">
|
||||
<string key="title"> Default</string>
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="makeTextWritingDirectionNatural:" target="Ady-hI-5gd" id="lPI-Se-ZHp"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem id="BgM-ve-c93">
|
||||
<string key="title"> Left to Right</string>
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="makeTextWritingDirectionLeftToRight:" target="Ady-hI-5gd" id="caW-Bv-w94"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem id="RB4-Sm-HuC">
|
||||
<string key="title"> Right to Left</string>
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="makeTextWritingDirectionRightToLeft:" target="Ady-hI-5gd" id="EXD-6r-ZUu"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="fKy-g9-1gm"/>
|
||||
<menuItem title="Show Ruler" id="vLm-3I-IUL">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleRuler:" target="Ady-hI-5gd" id="FOx-HJ-KwY"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Copy Ruler" keyEquivalent="c" id="MkV-Pr-PK5">
|
||||
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="copyRuler:" target="Ady-hI-5gd" id="71i-fW-3W2"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Paste Ruler" keyEquivalent="v" id="LVM-kO-fVI">
|
||||
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="pasteRuler:" target="Ady-hI-5gd" id="cSh-wd-qM2"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="View" id="H8h-7b-M4v">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="View" id="HyV-fh-RgO">
|
||||
<items>
|
||||
<menuItem title="Show Toolbar" keyEquivalent="t" id="snW-S8-Cw5">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="toggleToolbarShown:" target="Ady-hI-5gd" id="BXY-wc-z0C"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Customize Toolbar…" id="1UK-8n-QPP">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="runToolbarCustomizationPalette:" target="Ady-hI-5gd" id="pQI-g3-MTW"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="hB3-LF-h0Y"/>
|
||||
<menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE">
|
||||
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="toggleSidebar:" target="Ady-hI-5gd" id="iwa-gc-5KM"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
|
||||
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="toggleFullScreen:" target="Ady-hI-5gd" id="dU3-MA-1Rq"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Window" id="aUF-d1-5bR">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
|
||||
<items>
|
||||
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
|
||||
<connections>
|
||||
<action selector="performMiniaturize:" target="Ady-hI-5gd" id="VwT-WD-YPe"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Zoom" id="R4o-n2-Eq4">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="performZoom:" target="Ady-hI-5gd" id="DIl-cC-cCs"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
|
||||
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="arrangeInFront:" target="Ady-hI-5gd" id="DRN-fu-gQh"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Help" id="wpr-3q-Mcd">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
|
||||
<items>
|
||||
<menuItem title="SwiftyMarkdownExample macOS Help" keyEquivalent="?" id="FKE-Sm-Kum">
|
||||
<connections>
|
||||
<action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
|
||||
</connections>
|
||||
</application>
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModuleProvider="target"/>
|
||||
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="75" y="0.0"/>
|
||||
</scene>
|
||||
<!--Window Controller-->
|
||||
<scene sceneID="R2V-B0-nI4">
|
||||
<objects>
|
||||
<windowController id="B8D-0N-5wS" sceneMemberID="viewController">
|
||||
<window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="IQv-IB-iLA">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="480" height="270"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="B8D-0N-5wS" id="98r-iN-zZc"/>
|
||||
</connections>
|
||||
</window>
|
||||
<connections>
|
||||
<segue destination="XfG-lQ-9wD" kind="relationship" relationship="window.shadowedContentViewController" id="cq2-FE-JQM"/>
|
||||
</connections>
|
||||
</windowController>
|
||||
<customObject id="Oky-zY-oP4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="75" y="250"/>
|
||||
</scene>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="hIz-AP-VOD">
|
||||
<objects>
|
||||
<viewController id="XfG-lQ-9wD" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" id="m2S-Jp-Qdl">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="75" y="655"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -3,9 +3,11 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string></string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
@@ -13,12 +15,22 @@
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>19</string>
|
||||
<string>1</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2020 Voyage Travel Apps. All rights reserved.</string>
|
||||
<key>NSMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>NSSupportsAutomaticTermination</key>
|
||||
<true/>
|
||||
<key>NSSupportsSuddenTermination</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// ViewController.swift
|
||||
// SwiftyMarkdownExample macOS
|
||||
//
|
||||
// Created by Simon Fairbairn on 01/02/2020.
|
||||
// Copyright © 2020 Voyage Travel Apps. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import SwiftyMarkdown
|
||||
|
||||
class ViewController: NSViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
}
|
||||
|
||||
override var representedObject: Any? {
|
||||
didSet {
|
||||
// Update the view, if already loaded.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+240
-66
@@ -3,11 +3,15 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objectVersion = 52;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
F421DD991C8AF4E900B86D66 /* example.md in Resources */ = {isa = PBXBuildFile; fileRef = F421DD951C8AF34F00B86D66 /* example.md */; };
|
||||
F4B4A44C23E4E17400550249 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4B4A44B23E4E17400550249 /* AppDelegate.swift */; };
|
||||
F4B4A44E23E4E17400550249 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4B4A44D23E4E17400550249 /* ViewController.swift */; };
|
||||
F4B4A45023E4E17400550249 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F4B4A44F23E4E17400550249 /* Assets.xcassets */; };
|
||||
F4B4A45323E4E17400550249 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F4B4A45123E4E17400550249 /* Main.storyboard */; };
|
||||
F4B4A46023E4E3DC00550249 /* SwiftyMarkdown in Frameworks */ = {isa = PBXBuildFile; productRef = F4B4A45F23E4E3DC00550249 /* SwiftyMarkdown */; };
|
||||
F4CE98AC1C8AEF7D00D735C1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4CE98AB1C8AEF7D00D735C1 /* AppDelegate.swift */; };
|
||||
F4CE98AE1C8AEF7D00D735C1 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4CE98AD1C8AEF7D00D735C1 /* ViewController.swift */; };
|
||||
F4CE98B11C8AEF7D00D735C1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F4CE98AF1C8AEF7D00D735C1 /* Main.storyboard */; };
|
||||
@@ -15,8 +19,7 @@
|
||||
F4CE98B61C8AEF7D00D735C1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F4CE98B41C8AEF7D00D735C1 /* LaunchScreen.storyboard */; };
|
||||
F4CE98C11C8AEF7D00D735C1 /* SwiftyMarkdownExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4CE98C01C8AEF7D00D735C1 /* SwiftyMarkdownExampleTests.swift */; };
|
||||
F4CE98CC1C8AEF7D00D735C1 /* SwiftyMarkdownExampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4CE98CB1C8AEF7D00D735C1 /* SwiftyMarkdownExampleUITests.swift */; };
|
||||
F4CE98E31C8AEFEC00D735C1 /* SwiftyMarkdown.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F4CE98E01C8AEFE200D735C1 /* SwiftyMarkdown.framework */; };
|
||||
F4CE98E51C8AEFFB00D735C1 /* SwiftyMarkdown.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = F4CE98E01C8AEFE200D735C1 /* SwiftyMarkdown.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
F4EAB653244179FE00206782 /* example.md in Resources */ = {isa = PBXBuildFile; fileRef = F4576C2E2437F67B0013E2B6 /* example.md */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -34,37 +37,40 @@
|
||||
remoteGlobalIDString = F4CE98A71C8AEF7D00D735C1;
|
||||
remoteInfo = SwiftyMarkdownExample;
|
||||
};
|
||||
F4CE98DF1C8AEFE200D735C1 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = F4CE98DA1C8AEFE200D735C1 /* SwiftyMarkdown.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = F4CE98811C8A921300D735C1;
|
||||
remoteInfo = SwiftyMarkdown;
|
||||
};
|
||||
F4CE98E11C8AEFE200D735C1 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = F4CE98DA1C8AEFE200D735C1 /* SwiftyMarkdown.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = F4CE988B1C8A921300D735C1;
|
||||
remoteInfo = SwiftyMarkdownTests;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
F4B4A45C23E4E18800550249 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F4CE98E41C8AEFF000D735C1 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
F4CE98E51C8AEFFB00D735C1 /* SwiftyMarkdown.framework in CopyFiles */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
F421DD951C8AF34F00B86D66 /* example.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = example.md; sourceTree = "<group>"; };
|
||||
F4576C2E2437F67B0013E2B6 /* example.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = example.md; sourceTree = "<group>"; };
|
||||
F4B4A44923E4E17400550249 /* SwiftyMarkdownExample macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SwiftyMarkdownExample macOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
F4B4A44B23E4E17400550249 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
F4B4A44D23E4E17400550249 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||
F4B4A44F23E4E17400550249 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
F4B4A45223E4E17400550249 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
F4B4A45423E4E17400550249 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
F4B4A45523E4E17400550249 /* SwiftyMarkdownExample_macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftyMarkdownExample_macOS.entitlements; sourceTree = "<group>"; };
|
||||
F4B4A46123E4E5DD00550249 /* SwiftyMarkdown */ = {isa = PBXFileReference; lastKnownFileType = folder; name = SwiftyMarkdown; path = ../../../Developer/SwiftyMarkdown; sourceTree = "<group>"; };
|
||||
F4CE98A81C8AEF7D00D735C1 /* SwiftyMarkdownExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftyMarkdownExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
F4CE98AB1C8AEF7D00D735C1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
F4CE98AD1C8AEF7D00D735C1 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||
@@ -78,15 +84,21 @@
|
||||
F4CE98C71C8AEF7D00D735C1 /* SwiftyMarkdownExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftyMarkdownExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
F4CE98CB1C8AEF7D00D735C1 /* SwiftyMarkdownExampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyMarkdownExampleUITests.swift; sourceTree = "<group>"; };
|
||||
F4CE98CD1C8AEF7D00D735C1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
F4CE98DA1C8AEFE200D735C1 /* SwiftyMarkdown.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SwiftyMarkdown.xcodeproj; path = ../SwiftyMarkdown.xcodeproj; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
F4B4A44623E4E17400550249 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F4CE98A51C8AEF7D00D735C1 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F4CE98E31C8AEFEC00D735C1 /* SwiftyMarkdown.framework in Frameworks */,
|
||||
F4B4A46023E4E3DC00550249 /* SwiftyMarkdown in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -107,14 +119,36 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
F4B4A44A23E4E17400550249 /* SwiftyMarkdownExample macOS */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F4B4A44B23E4E17400550249 /* AppDelegate.swift */,
|
||||
F4B4A44D23E4E17400550249 /* ViewController.swift */,
|
||||
F4B4A44F23E4E17400550249 /* Assets.xcassets */,
|
||||
F4B4A45123E4E17400550249 /* Main.storyboard */,
|
||||
F4B4A45423E4E17400550249 /* Info.plist */,
|
||||
F4B4A45523E4E17400550249 /* SwiftyMarkdownExample_macOS.entitlements */,
|
||||
);
|
||||
path = "SwiftyMarkdownExample macOS";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F4B4A45923E4E18800550249 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F4CE989F1C8AEF7D00D735C1 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F4CE98DA1C8AEFE200D735C1 /* SwiftyMarkdown.xcodeproj */,
|
||||
F4B4A46123E4E5DD00550249 /* SwiftyMarkdown */,
|
||||
F4CE98AA1C8AEF7D00D735C1 /* SwiftyMarkdownExample */,
|
||||
F4CE98BF1C8AEF7D00D735C1 /* SwiftyMarkdownExampleTests */,
|
||||
F4CE98CA1C8AEF7D00D735C1 /* SwiftyMarkdownExampleUITests */,
|
||||
F4B4A44A23E4E17400550249 /* SwiftyMarkdownExample macOS */,
|
||||
F4CE98A91C8AEF7D00D735C1 /* Products */,
|
||||
F4B4A45923E4E18800550249 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -124,6 +158,7 @@
|
||||
F4CE98A81C8AEF7D00D735C1 /* SwiftyMarkdownExample.app */,
|
||||
F4CE98BC1C8AEF7D00D735C1 /* SwiftyMarkdownExampleTests.xctest */,
|
||||
F4CE98C71C8AEF7D00D735C1 /* SwiftyMarkdownExampleUITests.xctest */,
|
||||
F4B4A44923E4E17400550249 /* SwiftyMarkdownExample macOS.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -137,7 +172,7 @@
|
||||
F4CE98B21C8AEF7D00D735C1 /* Assets.xcassets */,
|
||||
F4CE98B41C8AEF7D00D735C1 /* LaunchScreen.storyboard */,
|
||||
F4CE98B71C8AEF7D00D735C1 /* Info.plist */,
|
||||
F421DD951C8AF34F00B86D66 /* example.md */,
|
||||
F4576C2E2437F67B0013E2B6 /* example.md */,
|
||||
);
|
||||
path = SwiftyMarkdownExample;
|
||||
sourceTree = "<group>";
|
||||
@@ -160,18 +195,27 @@
|
||||
path = SwiftyMarkdownExampleUITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F4CE98DB1C8AEFE200D735C1 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F4CE98E01C8AEFE200D735C1 /* SwiftyMarkdown.framework */,
|
||||
F4CE98E21C8AEFE200D735C1 /* SwiftyMarkdownTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
F4B4A44823E4E17400550249 /* SwiftyMarkdownExample macOS */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = F4B4A45623E4E17400550249 /* Build configuration list for PBXNativeTarget "SwiftyMarkdownExample macOS" */;
|
||||
buildPhases = (
|
||||
F4B4A44523E4E17400550249 /* Sources */,
|
||||
F4B4A44623E4E17400550249 /* Frameworks */,
|
||||
F4B4A44723E4E17400550249 /* Resources */,
|
||||
F4B4A45C23E4E18800550249 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "SwiftyMarkdownExample macOS";
|
||||
productName = "SwiftyMarkdownExample macOS";
|
||||
productReference = F4B4A44923E4E17400550249 /* SwiftyMarkdownExample macOS.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
F4CE98A71C8AEF7D00D735C1 /* SwiftyMarkdownExample */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = F4CE98D01C8AEF7D00D735C1 /* Build configuration list for PBXNativeTarget "SwiftyMarkdownExample" */;
|
||||
@@ -186,6 +230,9 @@
|
||||
dependencies = (
|
||||
);
|
||||
name = SwiftyMarkdownExample;
|
||||
packageProductDependencies = (
|
||||
F4B4A45F23E4E3DC00550249 /* SwiftyMarkdown */,
|
||||
);
|
||||
productName = SwiftyMarkdownExample;
|
||||
productReference = F4CE98A81C8AEF7D00D735C1 /* SwiftyMarkdownExample.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
@@ -232,10 +279,15 @@
|
||||
F4CE98A01C8AEF7D00D735C1 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0720;
|
||||
LastUpgradeCheck = 1120;
|
||||
LastSwiftUpdateCheck = 1130;
|
||||
LastUpgradeCheck = 1130;
|
||||
ORGANIZATIONNAME = "Voyage Travel Apps";
|
||||
TargetAttributes = {
|
||||
F4B4A44823E4E17400550249 = {
|
||||
CreatedOnToolsVersion = 11.3.1;
|
||||
DevelopmentTeam = 52T262DA8V;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
F4CE98A71C8AEF7D00D735C1 = {
|
||||
CreatedOnToolsVersion = 7.2.1;
|
||||
LastSwiftMigration = 1120;
|
||||
@@ -261,48 +313,38 @@
|
||||
Base,
|
||||
);
|
||||
mainGroup = F4CE989F1C8AEF7D00D735C1;
|
||||
packageReferences = (
|
||||
F4B4A45E23E4E3DC00550249 /* XCRemoteSwiftPackageReference "SwiftyMarkdown" */,
|
||||
);
|
||||
productRefGroup = F4CE98A91C8AEF7D00D735C1 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectReferences = (
|
||||
{
|
||||
ProductGroup = F4CE98DB1C8AEFE200D735C1 /* Products */;
|
||||
ProjectRef = F4CE98DA1C8AEFE200D735C1 /* SwiftyMarkdown.xcodeproj */;
|
||||
},
|
||||
);
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
F4CE98A71C8AEF7D00D735C1 /* SwiftyMarkdownExample */,
|
||||
F4CE98BB1C8AEF7D00D735C1 /* SwiftyMarkdownExampleTests */,
|
||||
F4CE98C61C8AEF7D00D735C1 /* SwiftyMarkdownExampleUITests */,
|
||||
F4B4A44823E4E17400550249 /* SwiftyMarkdownExample macOS */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXReferenceProxy section */
|
||||
F4CE98E01C8AEFE200D735C1 /* SwiftyMarkdown.framework */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.framework;
|
||||
path = SwiftyMarkdown.framework;
|
||||
remoteRef = F4CE98DF1C8AEFE200D735C1 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
F4CE98E21C8AEFE200D735C1 /* SwiftyMarkdownTests.xctest */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.cfbundle;
|
||||
path = SwiftyMarkdownTests.xctest;
|
||||
remoteRef = F4CE98E11C8AEFE200D735C1 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
/* End PBXReferenceProxy section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
F4B4A44723E4E17400550249 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F4B4A45023E4E17400550249 /* Assets.xcassets in Resources */,
|
||||
F4B4A45323E4E17400550249 /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F4CE98A61C8AEF7D00D735C1 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F4CE98B61C8AEF7D00D735C1 /* LaunchScreen.storyboard in Resources */,
|
||||
F4EAB653244179FE00206782 /* example.md in Resources */,
|
||||
F4CE98B31C8AEF7D00D735C1 /* Assets.xcassets in Resources */,
|
||||
F421DD991C8AF4E900B86D66 /* example.md in Resources */,
|
||||
F4CE98B11C8AEF7D00D735C1 /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -324,6 +366,15 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
F4B4A44523E4E17400550249 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F4B4A44E23E4E17400550249 /* ViewController.swift in Sources */,
|
||||
F4B4A44C23E4E17400550249 /* AppDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F4CE98A41C8AEF7D00D735C1 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -365,6 +416,14 @@
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
F4B4A45123E4E17400550249 /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
F4B4A45223E4E17400550249 /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F4CE98AF1C8AEF7D00D735C1 /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
@@ -384,6 +443,70 @@
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
F4B4A45723E4E17400550249 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = "SwiftyMarkdownExample macOS/SwiftyMarkdownExample_macOS.entitlements";
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = 52T262DA8V;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = "SwiftyMarkdownExample macOS/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.voyagetravelapps.SwiftyMarkdownExample-macOS";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
F4B4A45823E4E17400550249 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = "SwiftyMarkdownExample macOS/SwiftyMarkdownExample_macOS.entitlements";
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = 52T262DA8V;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = "SwiftyMarkdownExample macOS/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.voyagetravelapps.SwiftyMarkdownExample-macOS";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
F4CE98CE1C8AEF7D00D735C1 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@@ -484,7 +607,8 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
@@ -495,7 +619,10 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
INFOPLIST_FILE = SwiftyMarkdownExample/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.voyagetravelapps.SwiftyMarkdownExample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
@@ -508,7 +635,10 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
INFOPLIST_FILE = SwiftyMarkdownExample/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.voyagetravelapps.SwiftyMarkdownExample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
@@ -521,7 +651,11 @@
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
INFOPLIST_FILE = SwiftyMarkdownExampleTests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.voyagetravelapps.SwiftyMarkdownExampleTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
@@ -535,7 +669,11 @@
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
INFOPLIST_FILE = SwiftyMarkdownExampleTests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.voyagetravelapps.SwiftyMarkdownExampleTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
@@ -548,7 +686,11 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
INFOPLIST_FILE = SwiftyMarkdownExampleUITests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.voyagetravelapps.SwiftyMarkdownExampleUITests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
@@ -562,7 +704,11 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
INFOPLIST_FILE = SwiftyMarkdownExampleUITests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.voyagetravelapps.SwiftyMarkdownExampleUITests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
@@ -575,6 +721,15 @@
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
F4B4A45623E4E17400550249 /* Build configuration list for PBXNativeTarget "SwiftyMarkdownExample macOS" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
F4B4A45723E4E17400550249 /* Debug */,
|
||||
F4B4A45823E4E17400550249 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
F4CE98A31C8AEF7D00D735C1 /* Build configuration list for PBXProject "SwiftyMarkdownExample" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
@@ -612,6 +767,25 @@
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
F4B4A45E23E4E3DC00550249 /* XCRemoteSwiftPackageReference "SwiftyMarkdown" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/SimonFairbairn/SwiftyMarkdown.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 1.0.2;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
F4B4A45F23E4E3DC00550249 /* SwiftyMarkdown */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = F4B4A45E23E4E3DC00550249 /* XCRemoteSwiftPackageReference "SwiftyMarkdown" */;
|
||||
productName = SwiftyMarkdown;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = F4CE98A01C8AEF7D00D735C1 /* Project object */;
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "SwiftyMarkdown",
|
||||
"repositoryURL": "https://github.com/SimonFairbairn/SwiftyMarkdown.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "36f0c4d9c772a57f72941e7b51f0f04fb57fd79b",
|
||||
"version": "1.0.2"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
+4
-3
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<device id="retina4_7" orientation="portrait" appearance="dark"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@@ -21,6 +21,7 @@
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" usesAttributedText="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qZP-CU-74n">
|
||||
<rect key="frame" x="16" y="0.0" width="343" height="583"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<attributedString key="attributedText"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
<dataDetectorType key="dataDetectorTypes" link="YES"/>
|
||||
@@ -55,7 +56,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="qZP-CU-74n" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" id="1yU-8N-26a"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="qZP-CU-74n" secondAttribute="trailing" id="F5p-iG-zTB"/>
|
||||
+8
-1
@@ -37,11 +37,18 @@ class ViewController: UIViewController {
|
||||
|
||||
if let url = Bundle.main.url(forResource: "example", withExtension: "md"), let md = SwiftyMarkdown(url: url) {
|
||||
md.h2.fontName = "AvenirNextCondensed-Bold"
|
||||
md.h2.color = UIColor.red
|
||||
md.h2.color = UIColor.blue
|
||||
md.h2.alignment = .center
|
||||
|
||||
md.code.fontName = "CourierNewPSMT"
|
||||
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
md.strikethrough.color = .tertiaryLabel
|
||||
} else {
|
||||
md.strikethrough.color = .lightGray
|
||||
}
|
||||
|
||||
md.blockquotes.fontStyle = .italic
|
||||
|
||||
md.underlineLinks = true
|
||||
+24
-5
@@ -14,18 +14,37 @@ Customise fonts and colors easily in a Swift-like way:
|
||||
md.h2.color = UIColor.redColor()
|
||||
md.h2.alignment = .center
|
||||
|
||||
It supports the standard Markdown syntax, like *italics*, _underline italics_, **bold**, `backticks for code` and headings.
|
||||
It supports the standard Markdown syntax, like *italics*, _underline italics_, **bold**, `backticks for code`, ~~strikethrough~~, and headings.
|
||||
|
||||
It ignores random * and correctly handles escaped \*asterisks\* and \_underlines\_ and \`backticks\`. It also supports inline Markdown [Links](http://voyagetravelapps.com/).
|
||||
|
||||
> It also now supports blockquotes
|
||||
> and it supports whole-line italic and bold styles.
|
||||
> and it supports whole-line italic and bold styles so you can go completely wild with styling! Wow! Such styles! Much fun!
|
||||
|
||||
- And
|
||||
**Lists**
|
||||
|
||||
- It Supports
|
||||
- Unordered
|
||||
- Lists
|
||||
|
||||
|
||||
- Indented item with a longer string to make sure indentation is consistent
|
||||
- Second level indent with a longer string to make sure indentation is consistent
|
||||
- List item with a longer string to make sure indentation is consistent
|
||||
|
||||
1. And
|
||||
1. Ordered
|
||||
1. Lists
|
||||
1. Indented item
|
||||
1. Second level indent
|
||||
1. (Use `1.` as the list item identifier)
|
||||
1. List item
|
||||
1. List item
|
||||
- Mix
|
||||
- List styles
|
||||
1. List item with a longer string to make sure indentation is consistent
|
||||
1. List item
|
||||
1. List item
|
||||
1. List item
|
||||
1. List item
|
||||
|
||||
|
||||
|
||||
+179
-112
@@ -1,106 +1,152 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.2)
|
||||
activesupport (4.2.11.1)
|
||||
i18n (~> 0.7)
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.7.0)
|
||||
CFPropertyList (3.0.5)
|
||||
rexml
|
||||
activesupport (6.1.6)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
zeitwerk (~> 2.3)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
algoliasearch (1.27.1)
|
||||
algoliasearch (1.27.5)
|
||||
httpclient (~> 2.8, >= 2.8.3)
|
||||
json (>= 1.5.1)
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
babosa (1.0.3)
|
||||
claide (1.0.3)
|
||||
cocoapods (1.8.4)
|
||||
activesupport (>= 4.0.2, < 5)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.588.0)
|
||||
aws-sdk-core (3.131.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.525.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.57.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.114.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.4)
|
||||
aws-sigv4 (1.5.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
claide (1.1.0)
|
||||
cocoapods (1.11.3)
|
||||
addressable (~> 2.8)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
cocoapods-core (= 1.8.4)
|
||||
cocoapods-core (= 1.11.3)
|
||||
cocoapods-deintegrate (>= 1.0.3, < 2.0)
|
||||
cocoapods-downloader (>= 1.2.2, < 2.0)
|
||||
cocoapods-downloader (>= 1.4.0, < 2.0)
|
||||
cocoapods-plugins (>= 1.0.0, < 2.0)
|
||||
cocoapods-search (>= 1.0.0, < 2.0)
|
||||
cocoapods-stats (>= 1.0.0, < 2.0)
|
||||
cocoapods-trunk (>= 1.4.0, < 2.0)
|
||||
cocoapods-try (>= 1.1.0, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
escape (~> 0.0.4)
|
||||
fourflusher (>= 2.3.0, < 3.0)
|
||||
gh_inspector (~> 1.0)
|
||||
molinillo (~> 0.6.6)
|
||||
molinillo (~> 0.8.0)
|
||||
nap (~> 1.0)
|
||||
ruby-macho (~> 1.4)
|
||||
xcodeproj (>= 1.11.1, < 2.0)
|
||||
cocoapods-core (1.8.4)
|
||||
activesupport (>= 4.0.2, < 6)
|
||||
ruby-macho (>= 1.0, < 3.0)
|
||||
xcodeproj (>= 1.21.0, < 2.0)
|
||||
cocoapods-core (1.11.3)
|
||||
activesupport (>= 5.0, < 7)
|
||||
addressable (~> 2.8)
|
||||
algoliasearch (~> 1.0)
|
||||
concurrent-ruby (~> 1.1)
|
||||
fuzzy_match (~> 2.0.4)
|
||||
nap (~> 1.0)
|
||||
cocoapods-deintegrate (1.0.4)
|
||||
cocoapods-downloader (1.3.0)
|
||||
netrc (~> 0.11)
|
||||
public_suffix (~> 4.0)
|
||||
typhoeus (~> 1.0)
|
||||
cocoapods-deintegrate (1.0.5)
|
||||
cocoapods-downloader (1.6.3)
|
||||
cocoapods-plugins (1.0.0)
|
||||
nap
|
||||
cocoapods-search (1.0.0)
|
||||
cocoapods-stats (1.1.0)
|
||||
cocoapods-trunk (1.4.1)
|
||||
cocoapods-search (1.0.1)
|
||||
cocoapods-trunk (1.6.0)
|
||||
nap (>= 0.8, < 2.0)
|
||||
netrc (~> 0.11)
|
||||
cocoapods-try (1.1.0)
|
||||
cocoapods-try (1.2.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander-fastlane (4.4.6)
|
||||
highline (~> 1.7.2)
|
||||
concurrent-ruby (1.1.5)
|
||||
declarative (0.0.10)
|
||||
declarative-option (0.1.0)
|
||||
digest-crc (0.4.1)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
concurrent-ruby (1.1.10)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.6.4)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.5)
|
||||
emoji_regex (1.0.1)
|
||||
dotenv (2.7.6)
|
||||
emoji_regex (3.2.3)
|
||||
escape (0.0.4)
|
||||
excon (0.71.1)
|
||||
faraday (0.17.1)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-cookie_jar (0.0.6)
|
||||
faraday (>= 0.7.4)
|
||||
ethon (0.15.0)
|
||||
ffi (>= 1.15.0)
|
||||
excon (0.92.3)
|
||||
faraday (1.10.0)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday_middleware (0.13.1)
|
||||
faraday (>= 0.7.4, < 1.0)
|
||||
fastimage (2.1.7)
|
||||
fastlane (2.138.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.3)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.6)
|
||||
fastlane (2.206.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.3, < 3.0.0)
|
||||
babosa (>= 1.0.2, < 2.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored
|
||||
commander-fastlane (>= 4.4.6, < 5.0.0)
|
||||
commander (~> 4.6)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 2.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
faraday (~> 0.17)
|
||||
faraday (~> 1.0)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 0.13.1)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-api-client (>= 0.21.2, < 0.24.0)
|
||||
google-cloud-storage (>= 1.15.0, < 2.0.0)
|
||||
highline (>= 1.7.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
json (< 3.0.0)
|
||||
jwt (~> 2.1.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multi_xml (~> 0.5)
|
||||
multipart-post (~> 2.0.0)
|
||||
naturally (~> 2.2)
|
||||
optparse (~> 0.1.1)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
public_suffix (~> 2.0.0)
|
||||
rubyzip (>= 1.3.0, < 2.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.3)
|
||||
simctl (~> 1.6.3)
|
||||
slack-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (>= 1.4.5, < 2.0.0)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
@@ -109,102 +155,123 @@ GEM
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
ffi (1.15.5)
|
||||
fourflusher (2.3.1)
|
||||
fuzzy_match (2.0.4)
|
||||
gh_inspector (1.1.3)
|
||||
google-api-client (0.23.9)
|
||||
google-apis-androidpublisher_v3 (0.21.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-core (0.5.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.5, < 0.7.0)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
mime-types (~> 3.0)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
signet (~> 0.9)
|
||||
google-cloud-core (1.4.1)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.10.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.7.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-storage_v1 (0.14.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-env (1.3.0)
|
||||
faraday (~> 0.11)
|
||||
google-cloud-storage (1.16.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.2.0)
|
||||
google-cloud-storage (1.36.2)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-api-client (~> 0.23)
|
||||
google-cloud-core (~> 1.2)
|
||||
googleauth (>= 0.6.2, < 0.10.0)
|
||||
googleauth (0.6.7)
|
||||
faraday (~> 0.12)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.1)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.1.3)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (~> 0.7)
|
||||
highline (1.7.10)
|
||||
http-cookie (1.0.3)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.4)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
i18n (0.9.5)
|
||||
i18n (1.10.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
json (2.3.0)
|
||||
jwt (2.1.0)
|
||||
jmespath (1.6.1)
|
||||
json (2.6.2)
|
||||
jwt (2.3.0)
|
||||
memoist (0.16.2)
|
||||
mime-types (3.3)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2019.1009)
|
||||
mini_magick (4.9.5)
|
||||
minitest (5.13.0)
|
||||
molinillo (0.6.6)
|
||||
multi_json (1.14.1)
|
||||
multi_xml (0.6.0)
|
||||
mini_magick (4.11.0)
|
||||
mini_mime (1.1.2)
|
||||
minitest (5.15.0)
|
||||
molinillo (0.8.0)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.0.0)
|
||||
nanaimo (0.2.6)
|
||||
nanaimo (0.3.0)
|
||||
nap (1.1.0)
|
||||
naturally (2.2.0)
|
||||
naturally (2.2.1)
|
||||
netrc (0.11.0)
|
||||
os (1.0.1)
|
||||
plist (3.5.0)
|
||||
public_suffix (2.0.5)
|
||||
representable (3.0.4)
|
||||
optparse (0.1.1)
|
||||
os (1.1.4)
|
||||
plist (3.6.0)
|
||||
public_suffix (4.0.7)
|
||||
rake (13.0.6)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.2.5)
|
||||
rouge (2.0.7)
|
||||
ruby-macho (1.4.0)
|
||||
rubyzip (1.3.0)
|
||||
ruby-macho (2.5.1)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.3)
|
||||
signet (0.12.0)
|
||||
addressable (~> 2.3)
|
||||
faraday (~> 0.9)
|
||||
signet (0.16.1)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.0)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.7)
|
||||
simctl (1.6.8)
|
||||
CFPropertyList
|
||||
naturally
|
||||
slack-notifier (2.3.2)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
thread_safe (0.3.6)
|
||||
tty-cursor (0.7.0)
|
||||
tty-screen (0.7.0)
|
||||
tty-spinner (0.9.2)
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.1)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
tzinfo (1.2.5)
|
||||
thread_safe (~> 0.1)
|
||||
typhoeus (1.4.0)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (2.0.4)
|
||||
concurrent-ruby (~> 1.0)
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.6)
|
||||
unicode-display_width (1.6.0)
|
||||
unf_ext (0.0.8.1)
|
||||
unicode-display_width (1.8.0)
|
||||
webrick (1.7.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.14.0)
|
||||
xcodeproj (1.21.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.2.6)
|
||||
nanaimo (~> 0.3.0)
|
||||
rexml (~> 3.2.4)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty-travis-formatter (1.0.0)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
zeitwerk (2.5.4)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
@@ -214,4 +281,4 @@ DEPENDENCIES
|
||||
fastlane
|
||||
|
||||
BUNDLED WITH
|
||||
2.0.2
|
||||
2.3.8
|
||||
|
||||
+3
-2
@@ -1,4 +1,4 @@
|
||||
// swift-tools-version:5.0
|
||||
// swift-tools-version:5.1
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
@@ -13,6 +13,7 @@ let package = Package(
|
||||
.library(name: "SwiftyMarkdown", targets: ["SwiftyMarkdown"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "SwiftyMarkdown", path: "SwiftyMarkdown"),
|
||||
.target(name: "SwiftyMarkdown"),
|
||||
.testTarget(name: "SwiftyMarkdownTests", dependencies: ["SwiftyMarkdown"])
|
||||
]
|
||||
)
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,285 @@
|
||||
//: [Previous](@previous)
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
func repeating( _ max : Int ) -> String {
|
||||
var output = self
|
||||
for _ in 1..<max {
|
||||
output += self
|
||||
}
|
||||
return output
|
||||
}
|
||||
}
|
||||
|
||||
enum TagState {
|
||||
case none
|
||||
case open
|
||||
case intermediate
|
||||
case closed
|
||||
}
|
||||
|
||||
struct TagString {
|
||||
var state : TagState = .none
|
||||
var preOpenString = ""
|
||||
var openTagString = ""
|
||||
var intermediateString = ""
|
||||
var intermediateTagString = ""
|
||||
var metadataString = ""
|
||||
var closedTagString = ""
|
||||
var postClosedString = ""
|
||||
|
||||
let rule : Rule
|
||||
|
||||
init( with rule : Rule ) {
|
||||
self.rule = rule
|
||||
}
|
||||
|
||||
mutating func append( _ string : String? ) {
|
||||
guard let existentString = string else {
|
||||
return
|
||||
}
|
||||
switch self.state {
|
||||
case .none:
|
||||
self.preOpenString += existentString
|
||||
case .open:
|
||||
self.intermediateString += existentString
|
||||
case .intermediate:
|
||||
self.metadataString += existentString
|
||||
case .closed:
|
||||
self.postClosedString += existentString
|
||||
}
|
||||
}
|
||||
|
||||
mutating func append( contentsOf tokenGroup: [TokenGroup] ) {
|
||||
print(tokenGroup)
|
||||
for token in tokenGroup {
|
||||
switch token.state {
|
||||
case .none:
|
||||
self.append(token.string)
|
||||
case .open:
|
||||
if self.state != .none {
|
||||
self.preOpenString += token.string
|
||||
} else {
|
||||
self.openTagString += token.string
|
||||
}
|
||||
case .intermediate:
|
||||
if self.state != .open {
|
||||
self.intermediateString += token.string
|
||||
} else {
|
||||
self.intermediateTagString += token.string
|
||||
}
|
||||
case .closed:
|
||||
if self.rule.intermediateTag != nil && self.state != .intermediate {
|
||||
self.metadataString += token.string
|
||||
} else {
|
||||
self.closedTagString += token.string
|
||||
}
|
||||
}
|
||||
self.state = token.state
|
||||
}
|
||||
}
|
||||
|
||||
mutating func tokens() -> [Token] {
|
||||
print(self)
|
||||
var tokens : [Token] = []
|
||||
|
||||
if !self.preOpenString.isEmpty {
|
||||
tokens.append(Token(type: .string, inputString: self.preOpenString))
|
||||
}
|
||||
if !self.openTagString.isEmpty {
|
||||
tokens.append(Token(type: .openTag, inputString: self.openTagString))
|
||||
}
|
||||
if !self.intermediateString.isEmpty {
|
||||
var token = Token(type: .string, inputString: self.intermediateString)
|
||||
token.metadataString = self.metadataString
|
||||
tokens.append(token)
|
||||
}
|
||||
if !self.intermediateTagString.isEmpty {
|
||||
tokens.append(Token(type: .intermediateTag, inputString: self.intermediateTagString))
|
||||
}
|
||||
if !self.metadataString.isEmpty {
|
||||
tokens.append(Token(type: .metadata, inputString: self.metadataString))
|
||||
}
|
||||
if !self.closedTagString.isEmpty {
|
||||
tokens.append(Token(type: .closeTag, inputString: self.closedTagString))
|
||||
}
|
||||
|
||||
self.preOpenString = ""
|
||||
self.openTagString = ""
|
||||
self.intermediateString = ""
|
||||
self.intermediateTagString = ""
|
||||
self.metadataString = ""
|
||||
self.closedTagString = ""
|
||||
self.postClosedString = ""
|
||||
|
||||
self.state = .none
|
||||
|
||||
return tokens
|
||||
}
|
||||
}
|
||||
|
||||
struct TokenGroup {
|
||||
enum TokenGroupType {
|
||||
case string
|
||||
case tag
|
||||
case escape
|
||||
}
|
||||
|
||||
let string : String
|
||||
let isEscaped : Bool
|
||||
let type : TokenGroupType
|
||||
var state : TagState = .none
|
||||
}
|
||||
|
||||
|
||||
|
||||
func getTokenGroups( for string : inout String, with rule : Rule, shouldEmpty : Bool = false ) -> [TokenGroup] {
|
||||
if string.isEmpty {
|
||||
return []
|
||||
}
|
||||
let maxCount = rule.openTag.count * rule.maxTags
|
||||
var groups : [TokenGroup] = []
|
||||
|
||||
let maxTag = rule.openTag.repeating(rule.maxTags)
|
||||
|
||||
if maxTag.contains(string) {
|
||||
if string.count == maxCount || shouldEmpty {
|
||||
var token = TokenGroup(string: string, isEscaped: false, type: .tag)
|
||||
token.state = .open
|
||||
groups.append(token)
|
||||
string.removeAll()
|
||||
}
|
||||
|
||||
} else if string == rule.intermediateTag {
|
||||
var token = TokenGroup(string: string, isEscaped: false, type: .tag)
|
||||
token.state = .intermediate
|
||||
groups.append(token)
|
||||
string.removeAll()
|
||||
} else if string == rule.closingTag {
|
||||
var token = TokenGroup(string: string, isEscaped: false, type: .tag)
|
||||
token.state = .closed
|
||||
groups.append(token)
|
||||
string.removeAll()
|
||||
}
|
||||
|
||||
if shouldEmpty && !string.isEmpty {
|
||||
let token = TokenGroup(string: string, isEscaped: false, type: .tag)
|
||||
groups.append(token)
|
||||
string.removeAll()
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
func scan( _ string : String, with rule : Rule) -> [Token] {
|
||||
let scanner = Scanner(string: string)
|
||||
scanner.charactersToBeSkipped = nil
|
||||
var tokens : [Token] = []
|
||||
var set = CharacterSet(charactersIn: "\(rule.openTag)\(rule.intermediateTag ?? "")\(rule.closingTag ?? "")")
|
||||
if let existentEscape = rule.escapeCharacter {
|
||||
set.insert(charactersIn: String(existentEscape))
|
||||
}
|
||||
|
||||
var openTag = rule.openTag.repeating(rule.maxTags)
|
||||
|
||||
var tagString = TagString(with: rule)
|
||||
|
||||
var openTagFound : TagState = .none
|
||||
var regularCharacters = ""
|
||||
var tagGroupCount = 0
|
||||
while !scanner.isAtEnd {
|
||||
tagGroupCount += 1
|
||||
|
||||
if #available(iOS 13.0, OSX 10.15, watchOS 6.0, tvOS 13.0, *) {
|
||||
if let start = scanner.scanUpToCharacters(from: set) {
|
||||
tagString.append(start)
|
||||
}
|
||||
} else {
|
||||
var string : NSString?
|
||||
scanner.scanUpToCharacters(from: set, into: &string)
|
||||
if let existentString = string as String? {
|
||||
tagString.append(existentString)
|
||||
}
|
||||
}
|
||||
|
||||
// The end of the string
|
||||
let maybeFoundChars = scanner.scanCharacters(from: set )
|
||||
guard let foundTag = maybeFoundChars else {
|
||||
continue
|
||||
}
|
||||
|
||||
if foundTag == rule.openTag && foundTag.count < rule.minTags {
|
||||
tagString.append(foundTag)
|
||||
continue
|
||||
}
|
||||
|
||||
//:--
|
||||
print(foundTag)
|
||||
var tokenGroups : [TokenGroup] = []
|
||||
var escapeCharacter : Character? = nil
|
||||
var cumulatedString = ""
|
||||
for char in foundTag {
|
||||
if let existentEscapeCharacter = escapeCharacter {
|
||||
|
||||
// If any of the tags feature the current character
|
||||
let escape = String(existentEscapeCharacter)
|
||||
let nextTagCharacter = String(char)
|
||||
if rule.openTag.contains(nextTagCharacter) || rule.intermediateTag?.contains(nextTagCharacter) ?? false || rule.closingTag?.contains(nextTagCharacter) ?? false {
|
||||
tokenGroups.append(TokenGroup(string: nextTagCharacter, isEscaped: true, type: .tag))
|
||||
escapeCharacter = nil
|
||||
} else if nextTagCharacter == escape {
|
||||
// Doesn't apply to this rule
|
||||
tokenGroups.append(TokenGroup(string: nextTagCharacter, isEscaped: false, type: .escape))
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
if let existentEscape = rule.escapeCharacter {
|
||||
if char == existentEscape {
|
||||
tokenGroups.append(contentsOf: getTokenGroups(for: &cumulatedString, with: rule, shouldEmpty: true))
|
||||
escapeCharacter = char
|
||||
continue
|
||||
}
|
||||
}
|
||||
cumulatedString.append(char)
|
||||
tokenGroups.append(contentsOf: getTokenGroups(for: &cumulatedString, with: rule))
|
||||
|
||||
}
|
||||
if let remainingEscape = escapeCharacter {
|
||||
tokenGroups.append(TokenGroup(string: String(remainingEscape), isEscaped: false, type: .escape))
|
||||
}
|
||||
|
||||
tokenGroups.append(contentsOf: getTokenGroups(for: &cumulatedString, with: rule, shouldEmpty: true))
|
||||
tagString.append(contentsOf: tokenGroups)
|
||||
|
||||
if tagString.state == .closed {
|
||||
tokens.append(contentsOf: tagString.tokens())
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
tokens.append(contentsOf: tagString.tokens())
|
||||
|
||||
|
||||
return tokens
|
||||
}
|
||||
|
||||
//: [Next](@next)
|
||||
|
||||
|
||||
|
||||
var string = "[]([[\\[Some Link]\\]](\\(\\(\\url) [Regular link](url)"
|
||||
//string = "Text before [Regular link](url) Text after"
|
||||
var output = "[]([[Some Link]] Regular link"
|
||||
|
||||
var tokens = scan(string, with: LinkRule())
|
||||
print( tokens.filter( { $0.type == .string }).map({ $0.outputString }).joined())
|
||||
//print( tokens )
|
||||
|
||||
//string = "**\\*\\Bold\\*\\***"
|
||||
//output = "*\\Bold**"
|
||||
|
||||
//tokens = scan(string, with: AsteriskRule())
|
||||
//print( tokens )
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import Foundation
|
||||
|
||||
|
||||
|
||||
public protocol Rule {
|
||||
var escapeCharacter : Character? { get }
|
||||
var openTag : String { get }
|
||||
var intermediateTag : String? { get }
|
||||
var closingTag : String? { get }
|
||||
var maxTags : Int { get }
|
||||
var minTags : Int { get }
|
||||
}
|
||||
|
||||
public struct LinkRule : Rule {
|
||||
public let escapeCharacter : Character? = "\\"
|
||||
public let openTag : String = "["
|
||||
public let intermediateTag : String? = "]("
|
||||
public let closingTag : String? = ")"
|
||||
public let maxTags : Int = 1
|
||||
public let minTags : Int = 1
|
||||
public init() { }
|
||||
}
|
||||
|
||||
public struct AsteriskRule : Rule {
|
||||
public let escapeCharacter : Character? = "\\"
|
||||
public let openTag : String = "*"
|
||||
public let intermediateTag : String? = nil
|
||||
public let closingTag : String? = nil
|
||||
public let maxTags : Int = 3
|
||||
public let minTags : Int = 1
|
||||
public init() { }
|
||||
}
|
||||
+1
@@ -74,6 +74,7 @@ public struct Token {
|
||||
public let inputString : String
|
||||
public var metadataString : String? = nil
|
||||
public var characterStyles : [CharacterStyling] = []
|
||||
public var group : Int = 0
|
||||
public var count : Int = 0
|
||||
public var shouldSkip : Bool = false
|
||||
public var outputString : String {
|
||||
+2
@@ -5,5 +5,7 @@
|
||||
<page name='Line Processing'/>
|
||||
<page name='Tokenising'/>
|
||||
<page name='Attributed String'/>
|
||||
<page name='SKLabelNode'/>
|
||||
<page name='Groups'/>
|
||||
</pages>
|
||||
</playground>
|
||||
@@ -6,6 +6,7 @@ SwiftyMarkdown converts Markdown files and strings into `NSAttributedString`s us
|
||||
- [Installation](#installation)
|
||||
- [How to Use](#how-to-use-swiftymarkdown)
|
||||
- [Screenshot](#screenshot)
|
||||
- [Front Matter](#front-matter)
|
||||
- [Appendix](#appendix)
|
||||
|
||||
## Fully Rebuilt For 2020!
|
||||
@@ -64,37 +65,53 @@ label.attributedText = md.attributedString()
|
||||
|
||||
*italics* or _italics_
|
||||
**bold** or __bold__
|
||||
~~Linethrough~~Strikethroughs.
|
||||
`code`
|
||||
|
||||
# Header 1
|
||||
|
||||
or
|
||||
|
||||
Header 1
|
||||
====
|
||||
Header 1
|
||||
====
|
||||
|
||||
## Header 2
|
||||
|
||||
or
|
||||
|
||||
Header 2
|
||||
---
|
||||
Header 2
|
||||
---
|
||||
|
||||
### Header 3
|
||||
#### Header 4
|
||||
##### Header 5 #####
|
||||
###### Header 6 ######
|
||||
|
||||
`code`
|
||||
|
||||
Indented code blocks (spaces or tabs)
|
||||
|
||||
[Links](http://voyagetravelapps.com/)
|
||||

|
||||
|
||||
[Referenced Links][1]
|
||||
![Referenced Images][2]
|
||||
|
||||
[1]: http://voyagetravelapps.com/
|
||||
[2]: <Name of asset in bundle>
|
||||
|
||||
> Blockquotes
|
||||
|
||||
- Bulleted
|
||||
- Lists
|
||||
- Including indented lists
|
||||
- Up to three levels
|
||||
- Neat!
|
||||
|
||||
1. Ordered
|
||||
1. Lists
|
||||
1. Including indented lists
|
||||
- Up to three levels
|
||||
|
||||
|
||||
|
||||
Compound rules also work, for example:
|
||||
|
||||
@@ -102,7 +119,7 @@ Compound rules also work, for example:
|
||||
|
||||
Or [**Bold Links**](http://voyagetravelapps.com/)
|
||||
|
||||
Images will be inserted into the returned `NSAttributedString` as an `NSTextAttachment` (sadly, this will not work on watchOS as `NSTextAttachment` is not available).
|
||||
Images will be inserted into the returned `NSAttributedString` as an `NSTextAttachment` (sadly, this will not work on watchOS as `NSTextAttachment` is not available).
|
||||
|
||||
## Customisation
|
||||
|
||||
@@ -119,6 +136,8 @@ md.h1.alignmnent = .center
|
||||
md.italic.color = UIColor.blueColor()
|
||||
|
||||
md.underlineLinks = true
|
||||
|
||||
md.bullet = "🍏"
|
||||
```
|
||||
|
||||
On iOS, Specified font sizes will be adjusted relative to the the user's dynamic type settings.
|
||||
@@ -127,7 +146,11 @@ On iOS, Specified font sizes will be adjusted relative to the the user's dynamic
|
||||
|
||||

|
||||
|
||||
There's an example project included in the repository. Open the `.xcworkspace` file to get started.
|
||||
There's an example project included in the repository. Open the `Example/SwiftyMarkdown.xcodeproj` file to get started.
|
||||
|
||||
## Front Matter
|
||||
|
||||
SwiftyMarkdown recognises YAML front matter and will populate the `frontMatterAttributes` property with the key-value pairs that it fines.
|
||||
|
||||
## Appendix
|
||||
|
||||
@@ -202,12 +225,22 @@ code.fontSize : CGFloat
|
||||
code.color : UI/NSColor
|
||||
code.fontStyle : FontStyle
|
||||
|
||||
strikethrough.fontName : String
|
||||
strikethrough.fontSize : CGFloat
|
||||
strikethrough.color : UI/NSColor
|
||||
strikethrough.fontStyle : FontStyle
|
||||
|
||||
underlineLinks : Bool
|
||||
|
||||
bullet : String
|
||||
```
|
||||
|
||||
`FontStyle` is an enum with these cases: `normal`, `bold`, `italic`, and `bolditalic` to give you more precise control over how lines and character styles should look.
|
||||
`FontStyle` is an enum with these cases: `normal`, `bold`, `italic`, and `bolditalic` to give you more precise control over how lines and character styles should look. For example, perhaps you want blockquotes to default to having the italic style:
|
||||
|
||||
If you like a bit of chaos:
|
||||
```swift
|
||||
md.blockquotes.fontStyle = .italic
|
||||
```
|
||||
Or, if you like a bit of chaos:
|
||||
|
||||
```swift
|
||||
md.bold.fontStyle = .italic
|
||||
@@ -265,13 +298,46 @@ enum CharacterStyle : CharacterStyling {
|
||||
}
|
||||
|
||||
static public var characterRules = [
|
||||
CharacterRule(openTag: "[", intermediateTag: "](", closingTag: ")", escapeCharacter: "\\", styles: [1 : [CharacterStyle.link]], maxTags: 1),
|
||||
CharacterRule(openTag: "`", intermediateTag: nil, closingTag: nil, escapeCharacter: "\\", styles: [1 : [CharacterStyle.code]], maxTags: 1),
|
||||
CharacterRule(openTag: "*", intermediateTag: nil, closingTag: nil, escapeCharacter: "\\", styles: [1 : [CharacterStyle.italic], 2 : [CharacterStyle.bold], 3 : [CharacterStyle.bold, CharacterStyle.italic]], maxTags: 3),
|
||||
CharacterRule(openTag: "_", intermediateTag: nil, closingTag: nil, escapeCharacter: "\\", styles: [1 : [CharacterStyle.italic], 2 : [CharacterStyle.bold], 3 : [CharacterStyle.bold, CharacterStyle.italic]], maxTags: 3)
|
||||
CharacterRule(primaryTag: CharacterRuleTag(tag: "[", type: .open), otherTags: [
|
||||
CharacterRuleTag(tag: "]", type: .close),
|
||||
CharacterRuleTag(tag: "[", type: .metadataOpen),
|
||||
CharacterRuleTag(tag: "]", type: .metadataClose)
|
||||
], styles: [1 : CharacterStyle.link], metadataLookup: true, definesBoundary: true),
|
||||
CharacterRule(primaryTag: CharacterRuleTag(tag: "`", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.code], shouldCancelRemainingTags: true, balancedTags: true),
|
||||
CharacterRule(primaryTag: CharacterRuleTag(tag: "*", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.italic, 2 : CharacterStyle.bold], minTags:1 , maxTags:2),
|
||||
CharacterRule(primaryTag: CharacterRuleTag(tag: "_", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.italic, 2 : CharacterStyle.bold], minTags:1 , maxTags:2)
|
||||
]
|
||||
```
|
||||
|
||||
These Character Rules are defined by SwiftyMarkdown:
|
||||
|
||||
public struct CharacterRule : CustomStringConvertible {
|
||||
|
||||
public let primaryTag : CharacterRuleTag
|
||||
public let tags : [CharacterRuleTag]
|
||||
public let escapeCharacters : [Character]
|
||||
public let styles : [Int : CharacterStyling]
|
||||
public let minTags : Int
|
||||
public let maxTags : Int
|
||||
public var metadataLookup : Bool = false
|
||||
public var definesBoundary = false
|
||||
public var shouldCancelRemainingRules = false
|
||||
public var balancedTags = false
|
||||
}
|
||||
|
||||
1. `primaryTag`: Each rule must have at least one tag and it can be one of `repeating`, `open`, `close`, `metadataOpen`, or `metadataClose`. `repeating` tags are tags that have identical open and close characters (and often have more than 1 style depending on how many are in a group). For example, the `*` tag used in Markdown.
|
||||
2. `tags`: An array of other tags that the rule can look for. This is where you would put the `close` tag for a custom rule, for example.
|
||||
3. `escapeCharacters`: The characters that appear prior to any of the tag characters that tell the scanner to ignore the tag.
|
||||
4. `styles`: The styles that should be applied to every character between the opening and closing tags.
|
||||
5. `minTags`: The minimum number of repeating characters to be considered a successful match. For example, setting the `primaryTag` to `*` and the `minTag` to 2 would mean that `**foo**` would be a successful match wheras `*bar*` would not.
|
||||
6. `maxTags`: The maximum number of repeating characters to be considered a successful match.
|
||||
7. `metadataLookup`: Used for Markdown reference links. Tells the scanner to try to look up the metadata from this dictionary, rather than from the inline result.
|
||||
8. `definesBoundary`: In order for open and close tags to be successful, the `boundaryCount` for a given location in the string needs to be the same. Setting this property to `true` means that this rule will increase the `boundaryCount` for every character between its opening and closing tags. For example, the `[` rule defines a boundary. After it is applied, the string `*foo[bar*]` becomes `*foobar*` with a boundaryCount `00001111`. Applying the `*` rule results in the output `*foobar*` because the opening `*` tag and the closing `*` tag now have different `boundaryCount` values. It's basically a way to fix the `**[should not be bold**](url)` problem in Markdown.
|
||||
9. `shouldCancelRemainingTags`: A successful match will mark every character between the opening and closing tags as complete, thereby preventing any further rules from being applied to those characters.
|
||||
10. `balancedTags`: This flag requires that the opening and closing tags be of exactly equal length. E.g. If this is set to true, `**foo*` would result in `**foo*`. If it was false, the output would be `*foo`.
|
||||
|
||||
|
||||
|
||||
#### Rule Subsets
|
||||
|
||||
If you want to only support a small subset of Markdown, it's now easy to do.
|
||||
@@ -281,8 +347,8 @@ This example would only process strings with `*` and `_` characters, ignoring li
|
||||
SwiftyMarkdown.lineRules = []
|
||||
|
||||
SwiftyMarkdown.characterRules = [
|
||||
CharacterRule(openTag: "*", intermediateTag: nil, closingTag: nil, escapeCharacter: "\\", styles: [1 : [CharacterStyle.italic], 2 : [CharacterStyle.bold], 3 : [CharacterStyle.bold, CharacterStyle.italic]], maxTags: 3),
|
||||
CharacterRule(openTag: "_", intermediateTag: nil, closingTag: nil, escapeCharacter: "\\", styles: [1 : [CharacterStyle.italic], 2 : [CharacterStyle.bold], 3 : [CharacterStyle.bold, CharacterStyle.italic]], maxTags: 3)
|
||||
CharacterRule(primaryTag: CharacterRuleTag(tag: "*", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.italic, 2 : CharacterStyle.bold], minTags:1 , maxTags:2),
|
||||
CharacterRule(primaryTag: CharacterRuleTag(tag: "_", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.italic, 2 : CharacterStyle.bold], minTags:1 , maxTags:2)
|
||||
]
|
||||
```
|
||||
|
||||
@@ -303,7 +369,7 @@ enum Characters : CharacterStyling {
|
||||
}
|
||||
|
||||
let characterRules = [
|
||||
CharacterRule(openTag: "%", intermediateTag: nil, closingTag: nil, escapeCharacter: "\\", styles: [1 : [CharacterStyle.elf]], maxTags: 1)
|
||||
CharacterRule(primaryTag: CharacterRuleTag(tag: "%", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.elf])
|
||||
]
|
||||
|
||||
let processor = SwiftyTokeniser( with : characterRules )
|
||||
@@ -333,4 +399,5 @@ let label = SKLabelNode()
|
||||
label.preferredMaxLayoutWidth = 500
|
||||
label.numberOfLines = 0
|
||||
label.attributedText = smd.attributedString()
|
||||
```
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
layout: page
|
||||
title: "Trail Wallet FAQ"
|
||||
date: 2015-04-22 10:59
|
||||
comments: true
|
||||
sharing: true
|
||||
liking: false
|
||||
footer: true
|
||||
sidebar: false
|
||||
---
|
||||
|
||||
# Good Day To You, Walleteer!
|
||||
|
||||
We are Erin and Simon from [Never Ending Voyage][1] and we want to thank you for trying out our app. We have been travelling non-stop for seven years and part of how we support ourselves is through Trail Wallet.
|
||||
@@ -0,0 +1,129 @@
|
||||
# SwiftyMarkdown 1.0
|
||||
|
||||
SwiftyMarkdown converts Markdown files and strings into `NSAttributedString`s using sensible defaults and a Swift-style syntax. It uses dynamic type to set the font size correctly with whatever font you'd like to use.
|
||||
|
||||
## Fully Rebuilt For 2020!
|
||||
|
||||
SwiftyMarkdown now features a more robust and reliable rules-based line processing and tokenisation engine. It has added support for images stored in the bundle (``), codeblocks, blockquotes, and unordered lists!
|
||||
|
||||
Line-level attributes can now have a paragraph alignment applied to them (e.g. `h2.aligment = .center`), and links can be underlined by setting underlineLinks to `true`.
|
||||
|
||||
It also uses the system color `.label` as the default font color on iOS 13 and above for Dark Mode support out of the box.
|
||||
|
||||
## Installation
|
||||
|
||||
### CocoaPods:
|
||||
|
||||
`pod 'SwiftyMarkdown'`
|
||||
|
||||
### SPM:
|
||||
|
||||
In Xcode, `File -> Swift Packages -> Add Package Dependency` and add the GitHub URL.
|
||||
|
||||
*italics* or _italics_
|
||||
**bold** or __bold__
|
||||
~~Linethrough~~Strikethroughs.
|
||||
`code`
|
||||
|
||||
# Header 1
|
||||
|
||||
or
|
||||
|
||||
Header 1
|
||||
====
|
||||
|
||||
## Header 2
|
||||
|
||||
or
|
||||
|
||||
Header 2
|
||||
---
|
||||
|
||||
### Header 3
|
||||
#### Header 4
|
||||
##### Header 5 #####
|
||||
###### Header 6 ######
|
||||
|
||||
Indented code blocks (spaces or tabs)
|
||||
|
||||
[Links](http://voyagetravelapps.com/)
|
||||

|
||||
|
||||
> Blockquotes
|
||||
|
||||
- Bulleted
|
||||
- Lists
|
||||
- Including indented lists
|
||||
- Up to three levels
|
||||
- Neat!
|
||||
|
||||
1. Ordered
|
||||
1. Lists
|
||||
1. Including indented lists
|
||||
- Up to three levels
|
||||
1. Neat!
|
||||
|
||||
# SwiftyMarkdown 1.0
|
||||
|
||||
SwiftyMarkdown converts Markdown files and strings into `NSAttributedString`s using sensible defaults and a Swift-style syntax. It uses dynamic type to set the font size correctly with whatever font you'd like to use.
|
||||
|
||||
## Fully Rebuilt For 2020!
|
||||
|
||||
SwiftyMarkdown now features a more robust and reliable rules-based line processing and tokenisation engine. It has added support for images stored in the bundle (``), codeblocks, blockquotes, and unordered lists!
|
||||
|
||||
Line-level attributes can now have a paragraph alignment applied to them (e.g. `h2.aligment = .center`), and links can be underlined by setting underlineLinks to `true`.
|
||||
|
||||
It also uses the system color `.label` as the default font color on iOS 13 and above for Dark Mode support out of the box.
|
||||
|
||||
## Installation
|
||||
|
||||
### CocoaPods:
|
||||
|
||||
`pod 'SwiftyMarkdown'`
|
||||
|
||||
### SPM:
|
||||
|
||||
In Xcode, `File -> Swift Packages -> Add Package Dependency` and add the GitHub URL.
|
||||
|
||||
*italics* or _italics_
|
||||
**bold** or __bold__
|
||||
~~Linethrough~~Strikethroughs.
|
||||
`code`
|
||||
|
||||
# Header 1
|
||||
|
||||
or
|
||||
|
||||
Header 1
|
||||
====
|
||||
|
||||
## Header 2
|
||||
|
||||
or
|
||||
|
||||
Header 2
|
||||
---
|
||||
|
||||
### Header 3
|
||||
#### Header 4
|
||||
##### Header 5 #####
|
||||
###### Header 6 ######
|
||||
|
||||
Indented code blocks (spaces or tabs)
|
||||
|
||||
[Links](http://voyagetravelapps.com/)
|
||||

|
||||
|
||||
> Blockquotes
|
||||
|
||||
- Bulleted
|
||||
- Lists
|
||||
- Including indented lists
|
||||
- Up to three levels
|
||||
- Neat!
|
||||
|
||||
1. Ordered
|
||||
1. Lists
|
||||
1. Including indented lists
|
||||
- Up to three levels
|
||||
1. Neat!
|
||||
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// CharacterRule.swift
|
||||
// SwiftyMarkdown
|
||||
//
|
||||
// Created by Simon Fairbairn on 04/02/2020.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum SpaceAllowed {
|
||||
case no
|
||||
case bothSides
|
||||
case oneSide
|
||||
case leadingSide
|
||||
case trailingSide
|
||||
}
|
||||
|
||||
public enum Cancel {
|
||||
case none
|
||||
case allRemaining
|
||||
case currentSet
|
||||
}
|
||||
|
||||
public enum CharacterRuleTagType {
|
||||
case open
|
||||
case close
|
||||
case metadataOpen
|
||||
case metadataClose
|
||||
case repeating
|
||||
}
|
||||
|
||||
|
||||
public struct CharacterRuleTag {
|
||||
let tag : String
|
||||
let type : CharacterRuleTagType
|
||||
|
||||
public init( tag : String, type : CharacterRuleTagType ) {
|
||||
self.tag = tag
|
||||
self.type = type
|
||||
}
|
||||
}
|
||||
|
||||
public struct CharacterRule : CustomStringConvertible {
|
||||
|
||||
public let primaryTag : CharacterRuleTag
|
||||
public let tags : [CharacterRuleTag]
|
||||
public let escapeCharacters : [Character]
|
||||
public let styles : [Int : CharacterStyling]
|
||||
public let minTags : Int
|
||||
public let maxTags : Int
|
||||
public var metadataLookup : Bool = false
|
||||
public var isRepeatingTag : Bool {
|
||||
return self.primaryTag.type == .repeating
|
||||
}
|
||||
public var definesBoundary = false
|
||||
public var shouldCancelRemainingRules = false
|
||||
public var balancedTags = false
|
||||
|
||||
public var description: String {
|
||||
return "Character Rule with Open tag: \(self.primaryTag.tag) and current styles : \(self.styles) "
|
||||
}
|
||||
|
||||
public func tag( for type : CharacterRuleTagType ) -> CharacterRuleTag? {
|
||||
return self.tags.filter({ $0.type == type }).first ?? nil
|
||||
}
|
||||
|
||||
public init(primaryTag: CharacterRuleTag, otherTags: [CharacterRuleTag], escapeCharacters : [Character] = ["\\"], styles: [Int : CharacterStyling] = [:], minTags : Int = 1, maxTags : Int = 1, metadataLookup : Bool = false, definesBoundary : Bool = false, shouldCancelRemainingRules : Bool = false, balancedTags : Bool = false) {
|
||||
self.primaryTag = primaryTag
|
||||
self.tags = otherTags
|
||||
self.escapeCharacters = escapeCharacters
|
||||
self.styles = styles
|
||||
self.metadataLookup = metadataLookup
|
||||
self.definesBoundary = definesBoundary
|
||||
self.shouldCancelRemainingRules = shouldCancelRemainingRules
|
||||
self.minTags = maxTags < minTags ? maxTags : minTags
|
||||
self.maxTags = minTags > maxTags ? minTags : maxTags
|
||||
self.balancedTags = balancedTags
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
enum ElementType {
|
||||
case tag
|
||||
case escape
|
||||
case string
|
||||
case space
|
||||
case newline
|
||||
case metadata
|
||||
}
|
||||
|
||||
struct Element {
|
||||
let character : Character
|
||||
var type : ElementType
|
||||
var boundaryCount : Int = 0
|
||||
var isComplete : Bool = false
|
||||
var styles : [CharacterStyling] = []
|
||||
var metadata : [String] = []
|
||||
}
|
||||
|
||||
extension CharacterSet {
|
||||
func containsUnicodeScalars(of character: Character) -> Bool {
|
||||
return character.unicodeScalars.allSatisfy(contains(_:))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// PerfomanceLog.swift
|
||||
// SwiftyMarkdown
|
||||
//
|
||||
// Created by Simon Fairbairn on 04/02/2020.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
class PerformanceLog {
|
||||
var timer : TimeInterval = 0
|
||||
let enablePerfomanceLog : Bool
|
||||
let log : OSLog
|
||||
let identifier : String
|
||||
|
||||
init( with environmentVariableName : String, identifier : String, log : OSLog ) {
|
||||
self.log = log
|
||||
self.enablePerfomanceLog = (ProcessInfo.processInfo.environment[environmentVariableName] != nil)
|
||||
self.identifier = identifier
|
||||
}
|
||||
|
||||
func start() {
|
||||
guard enablePerfomanceLog else { return }
|
||||
self.timer = Date().timeIntervalSinceReferenceDate
|
||||
os_log("--- TIMER %{public}@ began", log: self.log, type: .info, self.identifier)
|
||||
}
|
||||
|
||||
func tag( with string : String) {
|
||||
guard enablePerfomanceLog else { return }
|
||||
if timer == 0 {
|
||||
self.start()
|
||||
}
|
||||
os_log("TIMER %{public}@: %f %@", log: self.log, type: .info, self.identifier, Date().timeIntervalSinceReferenceDate - self.timer, string)
|
||||
}
|
||||
|
||||
func end() {
|
||||
guard enablePerfomanceLog else { return }
|
||||
self.timer = Date().timeIntervalSinceReferenceDate
|
||||
os_log("--- TIMER %{public}@ finished. Total time: %f", log: self.log, type: .info, self.identifier, Date().timeIntervalSinceReferenceDate - self.timer)
|
||||
self.timer = 0
|
||||
|
||||
}
|
||||
}
|
||||
+108
-16
@@ -7,6 +7,12 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
extension OSLog {
|
||||
private static var subsystem = "SwiftyLineProcessor"
|
||||
static let swiftyLineProcessorPerformance = OSLog(subsystem: subsystem, category: "Swifty Line Processor Performance")
|
||||
}
|
||||
|
||||
public protocol LineStyling {
|
||||
var shouldTokeniseLine : Bool { get }
|
||||
@@ -38,6 +44,13 @@ public enum Remove {
|
||||
public enum ChangeApplication {
|
||||
case current
|
||||
case previous
|
||||
case untilClose
|
||||
}
|
||||
|
||||
public struct FrontMatterRule {
|
||||
let openTag : String
|
||||
let closeTag : String
|
||||
let keyValueSeparator : Character
|
||||
}
|
||||
|
||||
public struct LineRule {
|
||||
@@ -58,13 +71,21 @@ public struct LineRule {
|
||||
|
||||
public class SwiftyLineProcessor {
|
||||
|
||||
public var processEmptyStrings : LineStyling?
|
||||
public internal(set) var frontMatterAttributes : [String : String] = [:]
|
||||
|
||||
var closeToken : String? = nil
|
||||
let defaultType : LineStyling
|
||||
public var processEmptyStrings : LineStyling?
|
||||
let lineRules : [LineRule]
|
||||
|
||||
public init( rules : [LineRule], defaultRule: LineStyling) {
|
||||
let lineRules : [LineRule]
|
||||
let frontMatterRules : [FrontMatterRule]
|
||||
|
||||
let perfomanceLog = PerformanceLog(with: "SwiftyLineProcessorPerformanceLogging", identifier: "Line Processor", log: OSLog.swiftyLineProcessorPerformance)
|
||||
|
||||
public init( rules : [LineRule], defaultRule: LineStyling, frontMatterRules : [FrontMatterRule] = []) {
|
||||
self.lineRules = rules
|
||||
self.defaultType = defaultRule
|
||||
self.frontMatterRules = frontMatterRules
|
||||
}
|
||||
|
||||
func findLeadingLineElement( _ element : LineRule, in string : String ) -> String {
|
||||
@@ -87,25 +108,27 @@ public class SwiftyLineProcessor {
|
||||
return output
|
||||
}
|
||||
|
||||
func processLineLevelAttributes( _ text : String ) -> SwiftyLine {
|
||||
func processLineLevelAttributes( _ text : String ) -> SwiftyLine? {
|
||||
if text.isEmpty, let style = processEmptyStrings {
|
||||
return SwiftyLine(line: "", lineStyle: style)
|
||||
}
|
||||
let previousLines = lineRules.filter({ $0.changeAppliesTo == .previous })
|
||||
for element in previousLines {
|
||||
let output = (element.shouldTrim) ? text.trimmingCharacters(in: .whitespaces) : text
|
||||
let charSet = CharacterSet(charactersIn: element.token )
|
||||
if output.unicodeScalars.allSatisfy({ charSet.contains($0) }) {
|
||||
return SwiftyLine(line: "", lineStyle: element.type)
|
||||
}
|
||||
}
|
||||
|
||||
for element in lineRules {
|
||||
guard element.token.count > 0 else {
|
||||
continue
|
||||
}
|
||||
var output : String = (element.shouldTrim) ? text.trimmingCharacters(in: .whitespaces) : text
|
||||
let unprocessed = output
|
||||
|
||||
if let hasToken = self.closeToken, unprocessed != hasToken {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !text.contains(element.token) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch element.removeFrom {
|
||||
case .leading:
|
||||
output = findLeadingLineElement(element, in: output)
|
||||
@@ -114,6 +137,9 @@ public class SwiftyLineProcessor {
|
||||
case .both:
|
||||
output = findLeadingLineElement(element, in: output)
|
||||
output = findTrailingLineElement(element, in: output)
|
||||
case .entireLine:
|
||||
let maybeOutput = output.replacingOccurrences(of: element.token, with: "")
|
||||
output = ( maybeOutput.isEmpty ) ? maybeOutput : output
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -121,25 +147,89 @@ public class SwiftyLineProcessor {
|
||||
guard unprocessed != output else {
|
||||
continue
|
||||
}
|
||||
if element.changeAppliesTo == .untilClose {
|
||||
self.closeToken = (self.closeToken == nil) ? element.token : nil
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
output = (element.shouldTrim) ? output.trimmingCharacters(in: .whitespaces) : output
|
||||
return SwiftyLine(line: output, lineStyle: element.type)
|
||||
|
||||
}
|
||||
|
||||
for element in previousLines {
|
||||
let output = (element.shouldTrim) ? text.trimmingCharacters(in: .whitespaces) : text
|
||||
let charSet = CharacterSet(charactersIn: element.token )
|
||||
if output.unicodeScalars.allSatisfy({ charSet.contains($0) }) {
|
||||
return SwiftyLine(line: "", lineStyle: element.type)
|
||||
}
|
||||
}
|
||||
|
||||
return SwiftyLine(line: text.trimmingCharacters(in: .whitespaces), lineStyle: defaultType)
|
||||
}
|
||||
|
||||
func processFrontMatter( _ strings : [String] ) -> [String] {
|
||||
guard let firstString = strings.first?.trimmingCharacters(in: .whitespacesAndNewlines) else {
|
||||
return strings
|
||||
}
|
||||
var rulesToApply : FrontMatterRule? = nil
|
||||
for matter in self.frontMatterRules {
|
||||
if firstString == matter.openTag {
|
||||
rulesToApply = matter
|
||||
break
|
||||
}
|
||||
}
|
||||
guard let existentRules = rulesToApply else {
|
||||
return strings
|
||||
}
|
||||
var outputString = strings
|
||||
// Remove the first line, which is the front matter opening tag
|
||||
let _ = outputString.removeFirst()
|
||||
var closeFound = false
|
||||
while !closeFound {
|
||||
let nextString = outputString.removeFirst()
|
||||
if nextString == existentRules.closeTag {
|
||||
closeFound = true
|
||||
continue
|
||||
}
|
||||
var keyValue = nextString.components(separatedBy: "\(existentRules.keyValueSeparator)")
|
||||
if keyValue.count < 2 {
|
||||
continue
|
||||
}
|
||||
let key = keyValue.removeFirst()
|
||||
let value = keyValue.joined()
|
||||
self.frontMatterAttributes[key] = value
|
||||
}
|
||||
while outputString.first?.isEmpty ?? false {
|
||||
outputString.removeFirst()
|
||||
}
|
||||
return outputString
|
||||
}
|
||||
|
||||
public func process( _ string : String ) -> [SwiftyLine] {
|
||||
var foundAttributes : [SwiftyLine] = []
|
||||
for heading in string.components(separatedBy: CharacterSet.newlines) {
|
||||
|
||||
|
||||
self.perfomanceLog.start()
|
||||
|
||||
var lines = string.components(separatedBy: CharacterSet.newlines)
|
||||
lines = self.processFrontMatter(lines)
|
||||
|
||||
self.perfomanceLog.tag(with: "(Front matter completed)")
|
||||
|
||||
|
||||
for heading in lines {
|
||||
|
||||
if processEmptyStrings == nil && heading.isEmpty {
|
||||
continue
|
||||
}
|
||||
|
||||
let input : SwiftyLine
|
||||
input = processLineLevelAttributes(String(heading))
|
||||
|
||||
|
||||
guard let input = processLineLevelAttributes(String(heading)) else {
|
||||
continue
|
||||
}
|
||||
|
||||
if let existentPrevious = input.lineStyle.styleIfFoundStyleAffectsPreviousLine(), foundAttributes.count > 0 {
|
||||
if let idx = foundAttributes.firstIndex(of: foundAttributes.last!) {
|
||||
let updatedPrevious = foundAttributes.last!
|
||||
@@ -148,6 +238,8 @@ public class SwiftyLineProcessor {
|
||||
continue
|
||||
}
|
||||
foundAttributes.append(input)
|
||||
|
||||
self.perfomanceLog.tag(with: "(line completed: \(heading)")
|
||||
}
|
||||
return foundAttributes
|
||||
}
|
||||
+10
-3
@@ -99,6 +99,9 @@ extension SwiftyMarkdown {
|
||||
fontName = italic.fontName ?? fontName
|
||||
fontSize = italic.fontSize
|
||||
globalItalic = true
|
||||
case .strikethrough:
|
||||
fontName = strikethrough.fontName ?? fontName
|
||||
fontSize = strikethrough.fontSize
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -127,10 +130,10 @@ extension SwiftyMarkdown {
|
||||
}
|
||||
|
||||
if globalItalic, let italicDescriptor = font.fontDescriptor.withSymbolicTraits(.traitItalic) {
|
||||
font = UIFont(descriptor: italicDescriptor, size: 0)
|
||||
font = UIFont(descriptor: italicDescriptor, size: fontSize ?? 0)
|
||||
}
|
||||
if globalBold, let boldDescriptor = font.fontDescriptor.withSymbolicTraits(.traitBold) {
|
||||
font = UIFont(descriptor: boldDescriptor, size: 0)
|
||||
font = UIFont(descriptor: boldDescriptor, size: fontSize ?? 0)
|
||||
}
|
||||
|
||||
return font
|
||||
@@ -140,6 +143,8 @@ extension SwiftyMarkdown {
|
||||
func color( for line : SwiftyLine ) -> UIColor {
|
||||
// What type are we and is there a font name set?
|
||||
switch line.lineStyle as! MarkdownLineStyle {
|
||||
case .yaml:
|
||||
return body.color
|
||||
case .h1, .previousH1:
|
||||
return h1.color
|
||||
case .h2, .previousH2:
|
||||
@@ -158,8 +163,10 @@ extension SwiftyMarkdown {
|
||||
return code.color
|
||||
case .blockquote:
|
||||
return blockquotes.color
|
||||
case .unorderedList:
|
||||
case .unorderedList, .unorderedListIndentFirstOrder, .unorderedListIndentSecondOrder, .orderedList, .orderedListIndentFirstOrder, .orderedListIndentSecondOrder:
|
||||
return body.color
|
||||
case .referencedLink:
|
||||
return link.color
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -133,7 +133,11 @@ extension SwiftyMarkdown {
|
||||
return code.color
|
||||
case .blockquote:
|
||||
return blockquotes.color
|
||||
case .unorderedList:
|
||||
case .unorderedList, .unorderedListIndentFirstOrder, .unorderedListIndentSecondOrder, .orderedList, .orderedListIndentFirstOrder, .orderedListIndentSecondOrder:
|
||||
return body.color
|
||||
case .yaml:
|
||||
return body.color
|
||||
case .referencedLink:
|
||||
return body.color
|
||||
}
|
||||
}
|
||||
+223
-47
@@ -5,22 +5,30 @@
|
||||
// Created by Simon Fairbairn on 05/03/2016.
|
||||
// Copyright © 2016 Voyage Travel Apps. All rights reserved.
|
||||
//
|
||||
|
||||
import os.log
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
enum CharacterStyle : CharacterStyling {
|
||||
extension OSLog {
|
||||
private static var subsystem = "SwiftyMarkdown"
|
||||
static let swiftyMarkdownPerformance = OSLog(subsystem: subsystem, category: "Swifty Markdown Performance")
|
||||
}
|
||||
|
||||
public enum CharacterStyle : CharacterStyling {
|
||||
case none
|
||||
case bold
|
||||
case italic
|
||||
case code
|
||||
case link
|
||||
case image
|
||||
case referencedLink
|
||||
case referencedImage
|
||||
case strikethrough
|
||||
|
||||
func isEqualTo(_ other: CharacterStyling) -> Bool {
|
||||
public func isEqualTo(_ other: CharacterStyling) -> Bool {
|
||||
guard let other = other as? CharacterStyle else {
|
||||
return false
|
||||
}
|
||||
@@ -38,7 +46,7 @@ enum MarkdownLineStyle : LineStyling {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case yaml
|
||||
case h1
|
||||
case h2
|
||||
case h3
|
||||
@@ -51,6 +59,13 @@ enum MarkdownLineStyle : LineStyling {
|
||||
case blockquote
|
||||
case codeblock
|
||||
case unorderedList
|
||||
case unorderedListIndentFirstOrder
|
||||
case unorderedListIndentSecondOrder
|
||||
case orderedList
|
||||
case orderedListIndentFirstOrder
|
||||
case orderedListIndentSecondOrder
|
||||
case referencedLink
|
||||
|
||||
func styleIfFoundStyleAffectsPreviousLine() -> LineStyling? {
|
||||
switch self {
|
||||
case .previousH1:
|
||||
@@ -89,6 +104,8 @@ enum MarkdownLineStyle : LineStyling {
|
||||
|
||||
@objc public protocol LineProperties {
|
||||
var alignment : NSTextAlignment { get set }
|
||||
var lineSpacing: CGFloat { get set }
|
||||
var paragraphSpacing: CGFloat { get set }
|
||||
}
|
||||
|
||||
|
||||
@@ -118,19 +135,41 @@ If that is not set, then the system default will be used.
|
||||
public var fontSize : CGFloat = 0.0
|
||||
public var fontStyle : FontStyle = .normal
|
||||
public var alignment: NSTextAlignment = .left
|
||||
public var lineSpacing : CGFloat = 0.0
|
||||
public var paragraphSpacing : CGFloat = 0.0
|
||||
}
|
||||
|
||||
|
||||
@objc open class LinkStyles : BasicStyles {
|
||||
public var underlineStyle: NSUnderlineStyle = .single
|
||||
#if os(macOS)
|
||||
public lazy var underlineColor = self.color
|
||||
#else
|
||||
public lazy var underlineColor = self.color
|
||||
#endif
|
||||
}
|
||||
|
||||
/// A class that takes a [Markdown](https://daringfireball.net/projects/markdown/) string or file and returns an NSAttributedString with the applied styles. Supports Dynamic Type.
|
||||
@objc open class SwiftyMarkdown: NSObject {
|
||||
|
||||
static public var frontMatterRules = [
|
||||
FrontMatterRule(openTag: "---", closeTag: "---", keyValueSeparator: ":")
|
||||
]
|
||||
|
||||
static public var lineRules = [
|
||||
LineRule(token: "=", type: MarkdownLineStyle.previousH1, removeFrom: .entireLine, changeAppliesTo: .previous),
|
||||
LineRule(token: "-", type: MarkdownLineStyle.previousH2, removeFrom: .entireLine, changeAppliesTo: .previous),
|
||||
LineRule(token: "\t\t- ", type: MarkdownLineStyle.unorderedListIndentSecondOrder, removeFrom: .leading, shouldTrim: false),
|
||||
LineRule(token: "\t- ", type: MarkdownLineStyle.unorderedListIndentFirstOrder, removeFrom: .leading, shouldTrim: false),
|
||||
LineRule(token: "- ",type : MarkdownLineStyle.unorderedList, removeFrom: .leading),
|
||||
LineRule(token: "\t\t* ", type: MarkdownLineStyle.unorderedListIndentSecondOrder, removeFrom: .leading, shouldTrim: false),
|
||||
LineRule(token: "\t* ", type: MarkdownLineStyle.unorderedListIndentFirstOrder, removeFrom: .leading, shouldTrim: false),
|
||||
LineRule(token: "\t\t1. ", type: MarkdownLineStyle.orderedListIndentSecondOrder, removeFrom: .leading, shouldTrim: false),
|
||||
LineRule(token: "\t1. ", type: MarkdownLineStyle.orderedListIndentFirstOrder, removeFrom: .leading, shouldTrim: false),
|
||||
LineRule(token: "1. ",type : MarkdownLineStyle.orderedList, removeFrom: .leading),
|
||||
LineRule(token: "* ",type : MarkdownLineStyle.unorderedList, removeFrom: .leading),
|
||||
LineRule(token: " ", type: MarkdownLineStyle.codeblock, removeFrom: .leading, shouldTrim: false),
|
||||
LineRule(token: "\t", type: MarkdownLineStyle.codeblock, removeFrom: .leading, shouldTrim: false),
|
||||
LineRule(token: ">",type : MarkdownLineStyle.blockquote, removeFrom: .leading),
|
||||
LineRule(token: "- ",type : MarkdownLineStyle.unorderedList, removeFrom: .leading),
|
||||
LineRule(token: "###### ",type : MarkdownLineStyle.h6, removeFrom: .both),
|
||||
LineRule(token: "##### ",type : MarkdownLineStyle.h5, removeFrom: .both),
|
||||
LineRule(token: "#### ",type : MarkdownLineStyle.h4, removeFrom: .both),
|
||||
@@ -140,14 +179,33 @@ If that is not set, then the system default will be used.
|
||||
]
|
||||
|
||||
static public var characterRules = [
|
||||
CharacterRule(openTag: "", escapeCharacter: "\\", styles: [1 : [CharacterStyle.image]], maxTags: 1),
|
||||
CharacterRule(openTag: "[", intermediateTag: "](", closingTag: ")", escapeCharacter: "\\", styles: [1 : [CharacterStyle.link]], maxTags: 1),
|
||||
CharacterRule(openTag: "`", intermediateTag: nil, closingTag: nil, escapeCharacter: "\\", styles: [1 : [CharacterStyle.code]], maxTags: 1, cancels: .allRemaining),
|
||||
CharacterRule(openTag: "*", intermediateTag: nil, closingTag: nil, escapeCharacter: "\\", styles: [1 : [CharacterStyle.italic], 2 : [CharacterStyle.bold], 3 : [CharacterStyle.bold, CharacterStyle.italic]], maxTags: 3),
|
||||
CharacterRule(openTag: "_", intermediateTag: nil, closingTag: nil, escapeCharacter: "\\", styles: [1 : [CharacterStyle.italic], 2 : [CharacterStyle.bold], 3 : [CharacterStyle.bold, CharacterStyle.italic]], maxTags: 3)
|
||||
CharacterRule(primaryTag: CharacterRuleTag(tag: "![", type: .open), otherTags: [
|
||||
CharacterRuleTag(tag: "]", type: .close),
|
||||
CharacterRuleTag(tag: "[", type: .metadataOpen),
|
||||
CharacterRuleTag(tag: "]", type: .metadataClose)
|
||||
], styles: [1 : CharacterStyle.image], metadataLookup: true, definesBoundary: true),
|
||||
CharacterRule(primaryTag: CharacterRuleTag(tag: "![", type: .open), otherTags: [
|
||||
CharacterRuleTag(tag: "]", type: .close),
|
||||
CharacterRuleTag(tag: "(", type: .metadataOpen),
|
||||
CharacterRuleTag(tag: ")", type: .metadataClose)
|
||||
], styles: [1 : CharacterStyle.image], metadataLookup: false, definesBoundary: true),
|
||||
CharacterRule(primaryTag: CharacterRuleTag(tag: "[", type: .open), otherTags: [
|
||||
CharacterRuleTag(tag: "]", type: .close),
|
||||
CharacterRuleTag(tag: "[", type: .metadataOpen),
|
||||
CharacterRuleTag(tag: "]", type: .metadataClose)
|
||||
], styles: [1 : CharacterStyle.link], metadataLookup: true, definesBoundary: true),
|
||||
CharacterRule(primaryTag: CharacterRuleTag(tag: "[", type: .open), otherTags: [
|
||||
CharacterRuleTag(tag: "]", type: .close),
|
||||
CharacterRuleTag(tag: "(", type: .metadataOpen),
|
||||
CharacterRuleTag(tag: ")", type: .metadataClose)
|
||||
], styles: [1 : CharacterStyle.link], metadataLookup: false, definesBoundary: true),
|
||||
CharacterRule(primaryTag: CharacterRuleTag(tag: "`", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.code], shouldCancelRemainingRules: true, balancedTags: true),
|
||||
CharacterRule(primaryTag:CharacterRuleTag(tag: "~", type: .repeating), otherTags : [], styles: [2 : CharacterStyle.strikethrough], minTags:2 , maxTags:2),
|
||||
CharacterRule(primaryTag: CharacterRuleTag(tag: "*", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.italic, 2 : CharacterStyle.bold], minTags:1 , maxTags:2),
|
||||
CharacterRule(primaryTag: CharacterRuleTag(tag: "_", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.italic, 2 : CharacterStyle.bold], minTags:1 , maxTags:2)
|
||||
]
|
||||
|
||||
let lineProcessor = SwiftyLineProcessor(rules: SwiftyMarkdown.lineRules, defaultRule: MarkdownLineStyle.body)
|
||||
let lineProcessor = SwiftyLineProcessor(rules: SwiftyMarkdown.lineRules, defaultRule: MarkdownLineStyle.body, frontMatterRules: SwiftyMarkdown.frontMatterRules)
|
||||
let tokeniser = SwiftyTokeniser(with: SwiftyMarkdown.characterRules)
|
||||
|
||||
/// The styles to apply to any H1 headers found in the Markdown
|
||||
@@ -175,7 +233,7 @@ If that is not set, then the system default will be used.
|
||||
open var blockquotes = LineStyles()
|
||||
|
||||
/// The styles to apply to any links found in the Markdown
|
||||
open var link = BasicStyles()
|
||||
open var link = LinkStyles()
|
||||
|
||||
/// The styles to apply to any bold text found in the Markdown
|
||||
open var bold = BasicStyles()
|
||||
@@ -186,19 +244,32 @@ If that is not set, then the system default will be used.
|
||||
/// The styles to apply to any code blocks or inline code text found in the Markdown
|
||||
open var code = BasicStyles()
|
||||
|
||||
|
||||
open var strikethrough = BasicStyles()
|
||||
|
||||
public var bullet : String = "・"
|
||||
|
||||
public var underlineLinks : Bool = false
|
||||
|
||||
public var frontMatterAttributes : [String : String] {
|
||||
get {
|
||||
return self.lineProcessor.frontMatterAttributes
|
||||
}
|
||||
}
|
||||
|
||||
var currentType : MarkdownLineStyle = .body
|
||||
|
||||
|
||||
var string : String
|
||||
|
||||
let tagList = "!\\_*`[]()"
|
||||
let validMarkdownTags = CharacterSet(charactersIn: "!\\_*`[]()")
|
||||
|
||||
var orderedListCount = 0
|
||||
var orderedListIndentFirstOrderCount = 0
|
||||
var orderedListIndentSecondOrderCount = 0
|
||||
|
||||
var previouslyFoundTokens : [Token] = []
|
||||
|
||||
var applyAttachments = true
|
||||
|
||||
let perfomanceLog = PerformanceLog(with: "SwiftyMarkdownPerformanceLogging", identifier: "Swifty Markdown", log: .swiftyMarkdownPerformance)
|
||||
|
||||
/**
|
||||
|
||||
- parameter string: A string containing [Markdown](https://daringfireball.net/projects/markdown/) syntax to be converted to an NSAttributedString
|
||||
@@ -208,13 +279,7 @@ If that is not set, then the system default will be used.
|
||||
public init(string : String ) {
|
||||
self.string = string
|
||||
super.init()
|
||||
#if os(macOS)
|
||||
self.setFontColorForAllStyles(with: .labelColor)
|
||||
#elseif !os(watchOS)
|
||||
if #available(iOS 13.0, tvOS 13.0, *) {
|
||||
self.setFontColorForAllStyles(with: .label)
|
||||
}
|
||||
#endif
|
||||
self.setup()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,6 +299,10 @@ If that is not set, then the system default will be used.
|
||||
return nil
|
||||
}
|
||||
super.init()
|
||||
self.setup()
|
||||
}
|
||||
|
||||
func setup() {
|
||||
#if os(macOS)
|
||||
self.setFontColorForAllStyles(with: .labelColor)
|
||||
#elseif !os(watchOS)
|
||||
@@ -261,6 +330,7 @@ If that is not set, then the system default will be used.
|
||||
code.fontSize = size
|
||||
link.fontSize = size
|
||||
link.fontSize = size
|
||||
strikethrough.fontSize = size
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
@@ -277,6 +347,7 @@ If that is not set, then the system default will be used.
|
||||
code.color = color
|
||||
link.color = color
|
||||
blockquotes.color = color
|
||||
strikethrough.color = color
|
||||
}
|
||||
#else
|
||||
open func setFontColorForAllStyles(with color: UIColor) {
|
||||
@@ -292,6 +363,7 @@ If that is not set, then the system default will be used.
|
||||
code.color = color
|
||||
link.color = color
|
||||
blockquotes.color = color
|
||||
strikethrough.color = color
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -308,31 +380,62 @@ If that is not set, then the system default will be used.
|
||||
code.fontName = name
|
||||
link.fontName = name
|
||||
blockquotes.fontName = name
|
||||
strikethrough.fontName = name
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Generates an NSAttributedString from the string or URL passed at initialisation. Custom fonts or styles are applied to the appropriate elements when this method is called.
|
||||
|
||||
- returns: An NSAttributedString with the styles applied
|
||||
*/
|
||||
open func attributedString(from markdownString : String? = nil) -> NSAttributedString {
|
||||
|
||||
self.previouslyFoundTokens.removeAll()
|
||||
self.perfomanceLog.start()
|
||||
|
||||
if let existentMarkdownString = markdownString {
|
||||
self.string = existentMarkdownString
|
||||
}
|
||||
let attributedString = NSMutableAttributedString(string: "")
|
||||
self.lineProcessor.processEmptyStrings = MarkdownLineStyle.body
|
||||
let foundAttributes : [SwiftyLine] = lineProcessor.process(self.string)
|
||||
|
||||
for (idx, line) in foundAttributes.enumerated() {
|
||||
|
||||
let references : [SwiftyLine] = foundAttributes.filter({ $0.line.starts(with: "[") && $0.line.contains("]:") })
|
||||
let referencesRemoved : [SwiftyLine] = foundAttributes.filter({ !($0.line.starts(with: "[") && $0.line.contains("]:") ) })
|
||||
var keyValuePairs : [String : String] = [:]
|
||||
for line in references {
|
||||
let strings = line.line.components(separatedBy: "]:")
|
||||
guard strings.count >= 2 else {
|
||||
continue
|
||||
}
|
||||
var key : String = strings[0]
|
||||
if !key.isEmpty {
|
||||
let newstart = key.index(key.startIndex, offsetBy: 1)
|
||||
let range : Range<String.Index> = newstart..<key.endIndex
|
||||
key = String(key[range]).trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
keyValuePairs[key] = strings[1].trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
|
||||
self.perfomanceLog.tag(with: "(line processing complete)")
|
||||
|
||||
self.tokeniser.metadataLookup = keyValuePairs
|
||||
|
||||
for (idx, line) in referencesRemoved.enumerated() {
|
||||
if idx > 0 {
|
||||
attributedString.append(NSAttributedString(string: "\n"))
|
||||
}
|
||||
let finalTokens = self.tokeniser.process(line.line)
|
||||
self.previouslyFoundTokens.append(contentsOf: finalTokens)
|
||||
self.perfomanceLog.tag(with: "(tokenising complete for line \(idx)")
|
||||
|
||||
attributedString.append(attributedStringFor(tokens: finalTokens, in: line))
|
||||
|
||||
}
|
||||
|
||||
self.perfomanceLog.end()
|
||||
|
||||
return attributedString
|
||||
}
|
||||
|
||||
@@ -346,9 +449,38 @@ extension SwiftyMarkdown {
|
||||
let finalAttributedString = NSMutableAttributedString()
|
||||
var attributes : [NSAttributedString.Key : AnyObject] = [:]
|
||||
|
||||
guard let markdownLineStyle = line.lineStyle as? MarkdownLineStyle else {
|
||||
preconditionFailure("The passed line style is not a valid Markdown Line Style")
|
||||
}
|
||||
|
||||
var listItem = self.bullet
|
||||
switch markdownLineStyle {
|
||||
case .orderedList:
|
||||
self.orderedListCount += 1
|
||||
self.orderedListIndentFirstOrderCount = 0
|
||||
self.orderedListIndentSecondOrderCount = 0
|
||||
listItem = "\(self.orderedListCount)."
|
||||
case .orderedListIndentFirstOrder, .unorderedListIndentFirstOrder:
|
||||
self.orderedListIndentFirstOrderCount += 1
|
||||
self.orderedListIndentSecondOrderCount = 0
|
||||
if markdownLineStyle == .orderedListIndentFirstOrder {
|
||||
listItem = "\(self.orderedListIndentFirstOrderCount)."
|
||||
}
|
||||
|
||||
case .orderedListIndentSecondOrder, .unorderedListIndentSecondOrder:
|
||||
self.orderedListIndentSecondOrderCount += 1
|
||||
if markdownLineStyle == .orderedListIndentSecondOrder {
|
||||
listItem = "\(self.orderedListIndentSecondOrderCount)."
|
||||
}
|
||||
|
||||
default:
|
||||
self.orderedListCount = 0
|
||||
self.orderedListIndentFirstOrderCount = 0
|
||||
self.orderedListIndentSecondOrderCount = 0
|
||||
}
|
||||
|
||||
let lineProperties : LineProperties
|
||||
switch line.lineStyle as! MarkdownLineStyle {
|
||||
switch markdownLineStyle {
|
||||
case .h1:
|
||||
lineProperties = self.h1
|
||||
case .h2:
|
||||
@@ -361,7 +493,6 @@ extension SwiftyMarkdown {
|
||||
lineProperties = self.h5
|
||||
case .h6:
|
||||
lineProperties = self.h6
|
||||
|
||||
case .codeblock:
|
||||
lineProperties = body
|
||||
let paragraphStyle = NSMutableParagraphStyle()
|
||||
@@ -371,26 +502,61 @@ extension SwiftyMarkdown {
|
||||
lineProperties = self.blockquotes
|
||||
let paragraphStyle = NSMutableParagraphStyle()
|
||||
paragraphStyle.firstLineHeadIndent = 20.0
|
||||
paragraphStyle.headIndent = 20.0
|
||||
attributes[.paragraphStyle] = paragraphStyle
|
||||
case .unorderedList:
|
||||
case .unorderedList, .unorderedListIndentFirstOrder, .unorderedListIndentSecondOrder, .orderedList, .orderedListIndentFirstOrder, .orderedListIndentSecondOrder:
|
||||
|
||||
let interval : CGFloat = 30
|
||||
var addition = interval
|
||||
var indent = ""
|
||||
switch line.lineStyle as! MarkdownLineStyle {
|
||||
case .unorderedListIndentFirstOrder, .orderedListIndentFirstOrder:
|
||||
addition = interval * 2
|
||||
indent = "\t"
|
||||
case .unorderedListIndentSecondOrder, .orderedListIndentSecondOrder:
|
||||
addition = interval * 3
|
||||
indent = "\t\t"
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
lineProperties = body
|
||||
finalTokens.insert(Token(type: .string, inputString: "・ "), at: 0)
|
||||
default:
|
||||
|
||||
let paragraphStyle = NSMutableParagraphStyle()
|
||||
paragraphStyle.tabStops = [NSTextTab(textAlignment: .left, location: interval, options: [:]), NSTextTab(textAlignment: .left, location: interval, options: [:])]
|
||||
paragraphStyle.defaultTabInterval = interval
|
||||
paragraphStyle.headIndent = addition
|
||||
|
||||
attributes[.paragraphStyle] = paragraphStyle
|
||||
finalTokens.insert(Token(type: .string, inputString: "\(indent)\(listItem)\t"), at: 0)
|
||||
|
||||
case .yaml:
|
||||
lineProperties = body
|
||||
case .previousH1:
|
||||
lineProperties = body
|
||||
case .previousH2:
|
||||
lineProperties = body
|
||||
case .body:
|
||||
lineProperties = body
|
||||
case .referencedLink:
|
||||
lineProperties = body
|
||||
break
|
||||
}
|
||||
|
||||
let paragraphStyle = attributes[.paragraphStyle] as? NSMutableParagraphStyle ?? NSMutableParagraphStyle()
|
||||
if lineProperties.alignment != .left {
|
||||
let paragraphStyle = NSMutableParagraphStyle()
|
||||
paragraphStyle.alignment = lineProperties.alignment
|
||||
attributes[.paragraphStyle] = paragraphStyle
|
||||
}
|
||||
paragraphStyle.lineSpacing = lineProperties.lineSpacing
|
||||
paragraphStyle.paragraphSpacing = lineProperties.paragraphSpacing
|
||||
attributes[.paragraphStyle] = paragraphStyle
|
||||
|
||||
|
||||
for token in finalTokens {
|
||||
attributes[.font] = self.font(for: line)
|
||||
attributes[.link] = nil
|
||||
attributes[.strikethroughStyle] = nil
|
||||
attributes[.foregroundColor] = self.color(for: line)
|
||||
attributes[.underlineStyle] = nil
|
||||
guard let styles = token.characterStyles as? [CharacterStyle] else {
|
||||
continue
|
||||
}
|
||||
@@ -403,26 +569,36 @@ extension SwiftyMarkdown {
|
||||
attributes[.foregroundColor] = self.bold.color
|
||||
}
|
||||
|
||||
if styles.contains(.link), let url = token.metadataString {
|
||||
attributes[.foregroundColor] = self.link.color
|
||||
attributes[.font] = self.font(for: line, characterOverride: .link)
|
||||
attributes[.link] = url as AnyObject
|
||||
|
||||
if underlineLinks {
|
||||
attributes[.underlineStyle] = NSUnderlineStyle.single.rawValue as AnyObject
|
||||
}
|
||||
if let linkIdx = styles.firstIndex(of: .link), linkIdx < token.metadataStrings.count {
|
||||
attributes[.foregroundColor] = self.link.color
|
||||
attributes[.font] = self.font(for: line, characterOverride: .link)
|
||||
attributes[.link] = token.metadataStrings[linkIdx] as AnyObject
|
||||
|
||||
if underlineLinks {
|
||||
attributes[.underlineStyle] = self.link.underlineStyle.rawValue as AnyObject
|
||||
attributes[.underlineColor] = self.link.underlineColor
|
||||
}
|
||||
}
|
||||
|
||||
if styles.contains(.strikethrough) {
|
||||
attributes[.font] = self.font(for: line, characterOverride: .strikethrough)
|
||||
attributes[.strikethroughStyle] = NSUnderlineStyle.single.rawValue as AnyObject
|
||||
attributes[.foregroundColor] = self.strikethrough.color
|
||||
}
|
||||
|
||||
#if !os(watchOS)
|
||||
if styles.contains(.image), let imageName = token.metadataString {
|
||||
if let imgIdx = styles.firstIndex(of: .image), imgIdx < token.metadataStrings.count {
|
||||
if !self.applyAttachments {
|
||||
continue
|
||||
}
|
||||
#if !os(macOS)
|
||||
let image1Attachment = NSTextAttachment()
|
||||
image1Attachment.image = UIImage(named: imageName)
|
||||
image1Attachment.image = UIImage(named: token.metadataStrings[imgIdx])
|
||||
let str = NSAttributedString(attachment: image1Attachment)
|
||||
finalAttributedString.append(str)
|
||||
#elseif !os(watchOS)
|
||||
let image1Attachment = NSTextAttachment()
|
||||
image1Attachment.image = NSImage(named: imageName)
|
||||
image1Attachment.image = NSImage(named: token.metadataStrings[imgIdx])
|
||||
let str = NSAttributedString(attachment: image1Attachment)
|
||||
finalAttributedString.append(str)
|
||||
#endif
|
||||
@@ -0,0 +1,564 @@
|
||||
//
|
||||
// SwiftyScanner.swift
|
||||
//
|
||||
//
|
||||
// Created by Simon Fairbairn on 04/04/2020.
|
||||
//
|
||||
|
||||
//
|
||||
// SwiftyScanner.swift
|
||||
// SwiftyMarkdown
|
||||
//
|
||||
// Created by Simon Fairbairn on 04/02/2020.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
extension OSLog {
|
||||
private static var subsystem = "SwiftyScanner"
|
||||
static let swiftyScanner = OSLog(subsystem: subsystem, category: "Swifty Scanner Scanner")
|
||||
static let swiftyScannerPerformance = OSLog(subsystem: subsystem, category: "Swifty Scanner Scanner Peformance")
|
||||
}
|
||||
|
||||
enum RepeatingTagType {
|
||||
case open
|
||||
case either
|
||||
case close
|
||||
case neither
|
||||
}
|
||||
|
||||
struct TagGroup {
|
||||
let groupID = UUID().uuidString
|
||||
var tagRanges : [ClosedRange<Int>]
|
||||
var tagType : RepeatingTagType = .open
|
||||
var count = 1
|
||||
}
|
||||
|
||||
class SwiftyScanner {
|
||||
var elements : [Element]
|
||||
let rule : CharacterRule
|
||||
let metadata : [String : String]
|
||||
var pointer : Int = 0
|
||||
|
||||
var spaceAndNewLine = CharacterSet.whitespacesAndNewlines
|
||||
var tagGroups : [TagGroup] = []
|
||||
|
||||
var isMetadataOpen = false
|
||||
|
||||
|
||||
var enableLog = (ProcessInfo.processInfo.environment["SwiftyScannerScanner"] != nil)
|
||||
|
||||
let currentPerfomanceLog = PerformanceLog(with: "SwiftyScannerScannerPerformanceLogging", identifier: "Scanner", log: OSLog.swiftyScannerPerformance)
|
||||
let log = PerformanceLog(with: "SwiftyScannerScanner", identifier: "Scanner", log: OSLog.swiftyScanner)
|
||||
|
||||
|
||||
|
||||
enum Position {
|
||||
case forward(Int)
|
||||
case backward(Int)
|
||||
}
|
||||
|
||||
init( withElements elements : [Element], rule : CharacterRule, metadata : [String : String]) {
|
||||
self.elements = elements
|
||||
self.rule = rule
|
||||
self.currentPerfomanceLog.start()
|
||||
self.metadata = metadata
|
||||
}
|
||||
|
||||
func elementsBetweenCurrentPosition( and newPosition : Position ) -> [Element]? {
|
||||
|
||||
let newIdx : Int
|
||||
var isForward = true
|
||||
switch newPosition {
|
||||
case .backward(let positions):
|
||||
isForward = false
|
||||
newIdx = pointer - positions
|
||||
if newIdx < 0 {
|
||||
return nil
|
||||
}
|
||||
case .forward(let positions):
|
||||
newIdx = pointer + positions
|
||||
if newIdx >= self.elements.count {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let range : ClosedRange<Int> = ( isForward ) ? self.pointer...newIdx : newIdx...self.pointer
|
||||
return Array(self.elements[range])
|
||||
}
|
||||
|
||||
|
||||
func element( for position : Position ) -> Element? {
|
||||
let newIdx : Int
|
||||
switch position {
|
||||
case .backward(let positions):
|
||||
newIdx = pointer - positions
|
||||
if newIdx < 0 {
|
||||
return nil
|
||||
}
|
||||
case .forward(let positions):
|
||||
newIdx = pointer + positions
|
||||
if newIdx >= self.elements.count {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return self.elements[newIdx]
|
||||
}
|
||||
|
||||
|
||||
func positionIsEqualTo( character : Character, direction : Position ) -> Bool {
|
||||
guard let validElement = self.element(for: direction) else {
|
||||
return false
|
||||
}
|
||||
return validElement.character == character
|
||||
}
|
||||
|
||||
func positionContains( characters : [Character], direction : Position ) -> Bool {
|
||||
guard let validElement = self.element(for: direction) else {
|
||||
return false
|
||||
}
|
||||
return characters.contains(validElement.character)
|
||||
}
|
||||
|
||||
func isEscaped() -> Bool {
|
||||
let isEscaped = self.positionContains(characters: self.rule.escapeCharacters, direction: .backward(1))
|
||||
if isEscaped {
|
||||
self.elements[self.pointer - 1].type = .escape
|
||||
}
|
||||
return isEscaped
|
||||
}
|
||||
|
||||
func range( for tag : String? ) -> ClosedRange<Int>? {
|
||||
|
||||
guard let tag = tag else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let openChar = tag.first else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if self.pointer == self.elements.count {
|
||||
return nil
|
||||
}
|
||||
|
||||
if self.elements[self.pointer].character != openChar {
|
||||
return nil
|
||||
}
|
||||
|
||||
if isEscaped() {
|
||||
return nil
|
||||
}
|
||||
|
||||
let range : ClosedRange<Int>
|
||||
if tag.count > 1 {
|
||||
guard let elements = self.elementsBetweenCurrentPosition(and: .forward(tag.count - 1) ) else {
|
||||
return nil
|
||||
}
|
||||
// If it's already a tag, then it should be ignored
|
||||
if elements.filter({ $0.type != .string }).count > 0 {
|
||||
return nil
|
||||
}
|
||||
if elements.map( { String($0.character) }).joined() != tag {
|
||||
return nil
|
||||
}
|
||||
let endIdx = (self.pointer + tag.count - 1)
|
||||
for i in self.pointer...endIdx {
|
||||
self.elements[i].type = .tag
|
||||
}
|
||||
range = self.pointer...endIdx
|
||||
self.pointer += tag.count
|
||||
} else {
|
||||
// If it's already a tag, then it should be ignored
|
||||
if self.elements[self.pointer].type != .string {
|
||||
return nil
|
||||
}
|
||||
self.elements[self.pointer].type = .tag
|
||||
range = self.pointer...self.pointer
|
||||
self.pointer += 1
|
||||
}
|
||||
return range
|
||||
}
|
||||
|
||||
|
||||
func resetTagGroup( withID id : String ) {
|
||||
if let idx = self.tagGroups.firstIndex(where: { $0.groupID == id }) {
|
||||
for range in self.tagGroups[idx].tagRanges {
|
||||
self.resetTag(in: range)
|
||||
}
|
||||
self.tagGroups.remove(at: idx)
|
||||
}
|
||||
self.isMetadataOpen = false
|
||||
}
|
||||
|
||||
func resetTag( in range : ClosedRange<Int>) {
|
||||
for idx in range {
|
||||
self.elements[idx].type = .string
|
||||
}
|
||||
}
|
||||
|
||||
func resetLastTag( for range : inout [ClosedRange<Int>]) {
|
||||
guard let last = range.last else {
|
||||
return
|
||||
}
|
||||
for idx in last {
|
||||
self.elements[idx].type = .string
|
||||
}
|
||||
}
|
||||
|
||||
func closeTag( _ tag : String, withGroupID id : String ) {
|
||||
|
||||
guard let tagIdx = self.tagGroups.firstIndex(where: { $0.groupID == id }) else {
|
||||
return
|
||||
}
|
||||
|
||||
var metadataString = ""
|
||||
if self.isMetadataOpen {
|
||||
let metadataCloseRange = self.tagGroups[tagIdx].tagRanges.removeLast()
|
||||
let metadataOpenRange = self.tagGroups[tagIdx].tagRanges.removeLast()
|
||||
|
||||
if metadataOpenRange.upperBound + 1 == (metadataCloseRange.lowerBound) {
|
||||
if self.enableLog {
|
||||
os_log("Nothing between the tags", log: OSLog.swiftyScanner, type:.info , self.rule.description)
|
||||
}
|
||||
} else {
|
||||
for idx in (metadataOpenRange.upperBound)...(metadataCloseRange.lowerBound) {
|
||||
self.elements[idx].type = .metadata
|
||||
if self.rule.definesBoundary {
|
||||
self.elements[idx].boundaryCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let key = self.elements[metadataOpenRange.upperBound + 1..<metadataCloseRange.lowerBound].map( { String( $0.character )}).joined()
|
||||
if self.rule.metadataLookup {
|
||||
metadataString = self.metadata[key] ?? ""
|
||||
} else {
|
||||
metadataString = key
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let closeRange = self.tagGroups[tagIdx].tagRanges.removeLast()
|
||||
let openRange = self.tagGroups[tagIdx].tagRanges.removeLast()
|
||||
|
||||
if self.rule.balancedTags && closeRange.count != openRange.count {
|
||||
self.tagGroups[tagIdx].tagRanges.append(openRange)
|
||||
self.tagGroups[tagIdx].tagRanges.append(closeRange)
|
||||
return
|
||||
}
|
||||
|
||||
var shouldRemove = true
|
||||
var styles : [CharacterStyling] = []
|
||||
if openRange.upperBound + 1 == (closeRange.lowerBound) {
|
||||
if self.enableLog {
|
||||
os_log("Nothing between the tags", log: OSLog.swiftyScanner, type:.info , self.rule.description)
|
||||
}
|
||||
} else {
|
||||
var remainingTags = min(openRange.upperBound - openRange.lowerBound, closeRange.upperBound - closeRange.lowerBound) + 1
|
||||
while remainingTags > 0 {
|
||||
if remainingTags >= self.rule.maxTags {
|
||||
remainingTags -= self.rule.maxTags
|
||||
if let style = self.rule.styles[ self.rule.maxTags ] {
|
||||
if !styles.contains(where: { $0.isEqualTo(style)}) {
|
||||
styles.append(style)
|
||||
}
|
||||
}
|
||||
}
|
||||
if let style = self.rule.styles[remainingTags] {
|
||||
remainingTags -= remainingTags
|
||||
if !styles.contains(where: { $0.isEqualTo(style)}) {
|
||||
styles.append(style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for idx in (openRange.upperBound)...(closeRange.lowerBound) {
|
||||
self.elements[idx].styles.append(contentsOf: styles)
|
||||
self.elements[idx].metadata.append(metadataString)
|
||||
if self.rule.definesBoundary {
|
||||
self.elements[idx].boundaryCount += 1
|
||||
}
|
||||
if self.rule.shouldCancelRemainingRules {
|
||||
self.elements[idx].boundaryCount = 1000
|
||||
}
|
||||
}
|
||||
|
||||
if self.rule.isRepeatingTag {
|
||||
let difference = ( openRange.upperBound - openRange.lowerBound ) - (closeRange.upperBound - closeRange.lowerBound)
|
||||
switch difference {
|
||||
case 1...:
|
||||
shouldRemove = false
|
||||
self.tagGroups[tagIdx].count = difference
|
||||
self.tagGroups[tagIdx].tagRanges.append( openRange.upperBound - (abs(difference) - 1)...openRange.upperBound )
|
||||
case ...(-1):
|
||||
for idx in closeRange.upperBound - (abs(difference) - 1)...closeRange.upperBound {
|
||||
self.elements[idx].type = .string
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if shouldRemove {
|
||||
self.tagGroups.removeAll(where: { $0.groupID == id })
|
||||
}
|
||||
self.isMetadataOpen = false
|
||||
}
|
||||
|
||||
func emptyRanges( _ ranges : inout [ClosedRange<Int>] ) {
|
||||
while !ranges.isEmpty {
|
||||
self.resetLastTag(for: &ranges)
|
||||
ranges.removeLast()
|
||||
}
|
||||
}
|
||||
|
||||
func scanNonRepeatingTags() {
|
||||
var groupID = ""
|
||||
let closeTag = self.rule.tag(for: .close)?.tag
|
||||
let metadataOpen = self.rule.tag(for: .metadataOpen)?.tag
|
||||
let metadataClose = self.rule.tag(for: .metadataClose)?.tag
|
||||
|
||||
while self.pointer < self.elements.count {
|
||||
if self.enableLog {
|
||||
os_log("CHARACTER: %@", log: OSLog.swiftyScanner, type:.info , String(self.elements[self.pointer].character))
|
||||
}
|
||||
|
||||
if let range = self.range(for: metadataClose) {
|
||||
if self.isMetadataOpen {
|
||||
guard let groupIdx = self.tagGroups.firstIndex(where: { $0.groupID == groupID }) else {
|
||||
self.pointer += 1
|
||||
continue
|
||||
}
|
||||
|
||||
guard !self.tagGroups.isEmpty else {
|
||||
self.resetTagGroup(withID: groupID)
|
||||
continue
|
||||
}
|
||||
|
||||
guard self.isMetadataOpen else {
|
||||
|
||||
self.resetTagGroup(withID: groupID)
|
||||
continue
|
||||
}
|
||||
if self.enableLog {
|
||||
os_log("Closing metadata tag found. Closing tag with ID %@", log: OSLog.swiftyScanner, type:.info , groupID)
|
||||
}
|
||||
self.tagGroups[groupIdx].tagRanges.append(range)
|
||||
self.closeTag(closeTag!, withGroupID: groupID)
|
||||
self.isMetadataOpen = false
|
||||
continue
|
||||
} else {
|
||||
self.resetTag(in: range)
|
||||
self.pointer -= metadataClose!.count
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if let openRange = self.range(for: self.rule.primaryTag.tag) {
|
||||
if self.isMetadataOpen {
|
||||
self.resetTagGroup(withID: groupID)
|
||||
}
|
||||
|
||||
let tagGroup = TagGroup(tagRanges: [openRange])
|
||||
groupID = tagGroup.groupID
|
||||
if self.enableLog {
|
||||
os_log("New open tag found. Starting new Group with ID %@", log: OSLog.swiftyScanner, type:.info , groupID)
|
||||
}
|
||||
if self.rule.isRepeatingTag {
|
||||
|
||||
}
|
||||
|
||||
self.tagGroups.append(tagGroup)
|
||||
continue
|
||||
}
|
||||
|
||||
if let range = self.range(for: closeTag) {
|
||||
guard !self.tagGroups.isEmpty else {
|
||||
if self.enableLog {
|
||||
os_log("No open tags exist, resetting this close tag", log: OSLog.swiftyScanner, type:.info)
|
||||
}
|
||||
self.resetTag(in: range)
|
||||
continue
|
||||
}
|
||||
self.tagGroups[self.tagGroups.count - 1].tagRanges.append(range)
|
||||
groupID = self.tagGroups[self.tagGroups.count - 1].groupID
|
||||
if self.enableLog {
|
||||
os_log("New close tag found. Appending to group with ID %@", log: OSLog.swiftyScanner, type:.info , groupID)
|
||||
}
|
||||
guard metadataOpen != nil else {
|
||||
if self.enableLog {
|
||||
os_log("No metadata tags exist, closing valid tag with ID %@", log: OSLog.swiftyScanner, type:.info , groupID)
|
||||
}
|
||||
self.closeTag(closeTag!, withGroupID: groupID)
|
||||
continue
|
||||
}
|
||||
|
||||
guard self.pointer != self.elements.count else {
|
||||
continue
|
||||
}
|
||||
|
||||
guard let range = self.range(for: metadataOpen) else {
|
||||
if self.enableLog {
|
||||
os_log("No metadata tag found, resetting group with ID %@", log: OSLog.swiftyScanner, type:.info , groupID)
|
||||
}
|
||||
self.resetTagGroup(withID: groupID)
|
||||
continue
|
||||
}
|
||||
self.tagGroups[self.tagGroups.count - 1].tagRanges.append(range)
|
||||
self.isMetadataOpen = true
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
if let range = self.range(for: metadataOpen) {
|
||||
if self.enableLog {
|
||||
os_log("Multiple open metadata tags found!", log: OSLog.swiftyScanner, type:.info , groupID)
|
||||
}
|
||||
self.resetTag(in: range)
|
||||
self.resetTagGroup(withID: groupID)
|
||||
self.isMetadataOpen = false
|
||||
continue
|
||||
}
|
||||
self.pointer += 1
|
||||
}
|
||||
}
|
||||
|
||||
func scanRepeatingTags() {
|
||||
|
||||
var groupID = ""
|
||||
let escapeCharacters = "" //self.rule.escapeCharacters.map( { String( $0 ) }).joined()
|
||||
let unionSet = spaceAndNewLine.union(CharacterSet(charactersIn: escapeCharacters))
|
||||
while self.pointer < self.elements.count {
|
||||
if self.enableLog {
|
||||
os_log("CHARACTER: %@", log: OSLog.swiftyScanner, type:.info , String(self.elements[self.pointer].character))
|
||||
}
|
||||
|
||||
if var openRange = self.range(for: self.rule.primaryTag.tag) {
|
||||
|
||||
if self.elements[openRange].first?.boundaryCount == 1000 {
|
||||
self.resetTag(in: openRange)
|
||||
continue
|
||||
}
|
||||
|
||||
var count = 1
|
||||
var tagType : RepeatingTagType = .open
|
||||
if let prevElement = self.element(for: .backward(self.rule.primaryTag.tag.count + 1)) {
|
||||
if !unionSet.containsUnicodeScalars(of: prevElement.character) {
|
||||
tagType = .either
|
||||
}
|
||||
} else {
|
||||
tagType = .open
|
||||
}
|
||||
|
||||
while let nextRange = self.range(for: self.rule.primaryTag.tag) {
|
||||
count += 1
|
||||
openRange = openRange.lowerBound...nextRange.upperBound
|
||||
}
|
||||
|
||||
if self.rule.minTags > 1 {
|
||||
if (openRange.upperBound - openRange.lowerBound) + 1 < self.rule.minTags {
|
||||
self.resetTag(in: openRange)
|
||||
os_log("Tag does not meet minimum length", log: .swiftyScanner, type: .info)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
var validTagGroup = true
|
||||
if let nextElement = self.element(for: .forward(0)) {
|
||||
if unionSet.containsUnicodeScalars(of: nextElement.character) {
|
||||
if tagType == .either {
|
||||
tagType = .close
|
||||
} else {
|
||||
validTagGroup = tagType != .open
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if tagType == .either {
|
||||
tagType = .close
|
||||
} else {
|
||||
validTagGroup = tagType != .open
|
||||
}
|
||||
}
|
||||
|
||||
if !validTagGroup {
|
||||
if self.enableLog {
|
||||
os_log("Tag has whitespace on both sides", log: .swiftyScanner, type: .info)
|
||||
}
|
||||
self.resetTag(in: openRange)
|
||||
continue
|
||||
}
|
||||
|
||||
if let idx = tagGroups.firstIndex(where: { $0.groupID == groupID }) {
|
||||
if tagType == .either {
|
||||
if tagGroups[idx].count == count {
|
||||
self.tagGroups[idx].tagRanges.append(openRange)
|
||||
self.closeTag(self.rule.primaryTag.tag, withGroupID: groupID)
|
||||
|
||||
if let last = self.tagGroups.last {
|
||||
groupID = last.groupID
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if let prevRange = tagGroups[idx].tagRanges.first {
|
||||
if self.elements[prevRange].first?.boundaryCount == self.elements[openRange].first?.boundaryCount {
|
||||
self.tagGroups[idx].tagRanges.append(openRange)
|
||||
self.closeTag(self.rule.primaryTag.tag, withGroupID: groupID)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
var tagGroup = TagGroup(tagRanges: [openRange])
|
||||
groupID = tagGroup.groupID
|
||||
tagGroup.tagType = tagType
|
||||
tagGroup.count = count
|
||||
|
||||
if self.enableLog {
|
||||
os_log("New open tag found with characters %@. Starting new Group with ID %@", log: OSLog.swiftyScanner, type:.info, self.elements[openRange].map( { String($0.character) }).joined(), groupID)
|
||||
}
|
||||
|
||||
self.tagGroups.append(tagGroup)
|
||||
continue
|
||||
}
|
||||
|
||||
self.pointer += 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func scan() -> [Element] {
|
||||
|
||||
guard self.elements.filter({ $0.type == .string }).map({ String($0.character) }).joined().contains(self.rule.primaryTag.tag) else {
|
||||
return self.elements
|
||||
}
|
||||
|
||||
self.currentPerfomanceLog.tag(with: "Beginning \(self.rule.primaryTag.tag)")
|
||||
|
||||
if self.enableLog {
|
||||
os_log("RULE: %@", log: OSLog.swiftyScanner, type:.info , self.rule.description)
|
||||
}
|
||||
|
||||
if self.rule.isRepeatingTag {
|
||||
self.scanRepeatingTags()
|
||||
} else {
|
||||
self.scanNonRepeatingTags()
|
||||
}
|
||||
|
||||
for tagGroup in self.tagGroups {
|
||||
self.resetTagGroup(withID: tagGroup.groupID)
|
||||
}
|
||||
|
||||
if self.enableLog {
|
||||
for element in self.elements {
|
||||
print(element)
|
||||
}
|
||||
}
|
||||
return self.elements
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
//
|
||||
// SwiftyTokeniser.swift
|
||||
// SwiftyMarkdown
|
||||
//
|
||||
// Created by Simon Fairbairn on 16/12/2019.
|
||||
// Copyright © 2019 Voyage Travel Apps. All rights reserved.
|
||||
//
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
extension OSLog {
|
||||
private static var subsystem = "SwiftyTokeniser"
|
||||
static let tokenising = OSLog(subsystem: subsystem, category: "Tokenising")
|
||||
static let styling = OSLog(subsystem: subsystem, category: "Styling")
|
||||
static let performance = OSLog(subsystem: subsystem, category: "Peformance")
|
||||
}
|
||||
|
||||
public class SwiftyTokeniser {
|
||||
let rules : [CharacterRule]
|
||||
var replacements : [String : [Token]] = [:]
|
||||
|
||||
var enableLog = (ProcessInfo.processInfo.environment["SwiftyTokeniserLogging"] != nil)
|
||||
let totalPerfomanceLog = PerformanceLog(with: "SwiftyTokeniserPerformanceLogging", identifier: "Tokeniser Total Run Time", log: OSLog.performance)
|
||||
let currentPerfomanceLog = PerformanceLog(with: "SwiftyTokeniserPerformanceLogging", identifier: "Tokeniser Current", log: OSLog.performance)
|
||||
|
||||
public var metadataLookup : [String : String] = [:]
|
||||
|
||||
let newlines = CharacterSet.newlines
|
||||
let spaces = CharacterSet.whitespaces
|
||||
|
||||
|
||||
public init( with rules : [CharacterRule] ) {
|
||||
self.rules = rules
|
||||
|
||||
self.totalPerfomanceLog.start()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.totalPerfomanceLog.end()
|
||||
}
|
||||
|
||||
|
||||
/// This goes through every CharacterRule in order and applies it to the input string, tokenising the string
|
||||
/// if there are any matches.
|
||||
///
|
||||
/// The for loop in the while loop (yeah, I know) is there to separate strings from within tags to
|
||||
/// those outside them.
|
||||
///
|
||||
/// e.g. "A string with a \[link\]\(url\) tag" would have the "link" text tokenised separately.
|
||||
///
|
||||
/// This is to prevent situations like **\[link**\](url) from returing a bold string.
|
||||
///
|
||||
/// - Parameter inputString: A string to have the CharacterRules in `self.rules` applied to
|
||||
public func process( _ inputString : String ) -> [Token] {
|
||||
let currentTokens = [Token(type: .string, inputString: inputString)]
|
||||
guard rules.count > 0 else {
|
||||
return currentTokens
|
||||
}
|
||||
var mutableRules = self.rules
|
||||
|
||||
if inputString.isEmpty {
|
||||
return [Token(type: .string, inputString: "", characterStyles: [])]
|
||||
}
|
||||
|
||||
self.currentPerfomanceLog.start()
|
||||
|
||||
var elementArray : [Element] = []
|
||||
for char in inputString {
|
||||
if newlines.containsUnicodeScalars(of: char) {
|
||||
let element = Element(character: char, type: .newline)
|
||||
elementArray.append(element)
|
||||
continue
|
||||
}
|
||||
if spaces.containsUnicodeScalars(of: char) {
|
||||
let element = Element(character: char, type: .space)
|
||||
elementArray.append(element)
|
||||
continue
|
||||
}
|
||||
let element = Element(character: char, type: .string)
|
||||
elementArray.append(element)
|
||||
}
|
||||
|
||||
while !mutableRules.isEmpty {
|
||||
let nextRule = mutableRules.removeFirst()
|
||||
if enableLog {
|
||||
os_log("------------------------------", log: .tokenising, type: .info)
|
||||
os_log("RULE: %@", log: OSLog.tokenising, type:.info , nextRule.description)
|
||||
}
|
||||
self.currentPerfomanceLog.tag(with: "(start rule %@)")
|
||||
|
||||
let scanner = SwiftyScanner(withElements: elementArray, rule: nextRule, metadata: self.metadataLookup)
|
||||
elementArray = scanner.scan()
|
||||
}
|
||||
|
||||
var output : [Token] = []
|
||||
var lastElement = elementArray.first!
|
||||
|
||||
func empty( _ string : inout String, into tokens : inout [Token] ) {
|
||||
guard !string.isEmpty else {
|
||||
return
|
||||
}
|
||||
var token = Token(type: .string, inputString: string)
|
||||
token.metadataStrings.append(contentsOf: lastElement.metadata)
|
||||
token.characterStyles = lastElement.styles
|
||||
string.removeAll()
|
||||
tokens.append(token)
|
||||
}
|
||||
|
||||
var accumulatedString = ""
|
||||
for element in elementArray {
|
||||
guard element.type != .escape else {
|
||||
continue
|
||||
}
|
||||
|
||||
guard element.type == .string || element.type == .space || element.type == .newline else {
|
||||
empty(&accumulatedString, into: &output)
|
||||
continue
|
||||
}
|
||||
if lastElement.styles as? [CharacterStyle] != element.styles as? [CharacterStyle] {
|
||||
empty(&accumulatedString, into: &output)
|
||||
}
|
||||
accumulatedString.append(element.character)
|
||||
lastElement = element
|
||||
}
|
||||
empty(&accumulatedString, into: &output)
|
||||
|
||||
self.currentPerfomanceLog.tag(with: "(finished all rules)")
|
||||
|
||||
if enableLog {
|
||||
os_log("=====RULE PROCESSING COMPLETE=====", log: .tokenising, type: .info)
|
||||
os_log("==================================", log: .tokenising, type: .info)
|
||||
}
|
||||
return output
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension String {
|
||||
func repeating( _ max : Int ) -> String {
|
||||
var output = self
|
||||
for _ in 1..<max {
|
||||
output += self
|
||||
}
|
||||
return output
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// Token.swift
|
||||
// SwiftyMarkdown
|
||||
//
|
||||
// Created by Simon Fairbairn on 04/02/2020.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// Tag definition
|
||||
public protocol CharacterStyling {
|
||||
func isEqualTo( _ other : CharacterStyling ) -> Bool
|
||||
}
|
||||
|
||||
// Token definition
|
||||
public enum TokenType {
|
||||
case repeatingTag
|
||||
case openTag
|
||||
case intermediateTag
|
||||
case closeTag
|
||||
case string
|
||||
case escape
|
||||
case replacement
|
||||
}
|
||||
|
||||
public struct Token {
|
||||
public let id = UUID().uuidString
|
||||
public let type : TokenType
|
||||
public let inputString : String
|
||||
public var metadataStrings : [String] = []
|
||||
public internal(set) var group : Int = 0
|
||||
public internal(set) var characterStyles : [CharacterStyling] = []
|
||||
public internal(set) var count : Int = 0
|
||||
public internal(set) var shouldSkip : Bool = false
|
||||
public internal(set) var tokenIndex : Int = -1
|
||||
public internal(set) var isProcessed : Bool = false
|
||||
public internal(set) var isMetadata : Bool = false
|
||||
public var children : [Token] = []
|
||||
|
||||
public var outputString : String {
|
||||
get {
|
||||
switch self.type {
|
||||
case .repeatingTag:
|
||||
if count <= 0 {
|
||||
return ""
|
||||
} else {
|
||||
let range = inputString.startIndex..<inputString.index(inputString.startIndex, offsetBy: self.count)
|
||||
return String(inputString[range])
|
||||
}
|
||||
case .openTag, .closeTag, .intermediateTag:
|
||||
return (self.isProcessed || self.isMetadata) ? "" : inputString
|
||||
case .escape, .string:
|
||||
return (self.isProcessed || self.isMetadata) ? "" : inputString
|
||||
case .replacement:
|
||||
return self.inputString
|
||||
}
|
||||
}
|
||||
}
|
||||
public init( type : TokenType, inputString : String, characterStyles : [CharacterStyling] = []) {
|
||||
self.type = type
|
||||
self.inputString = inputString
|
||||
self.characterStyles = characterStyles
|
||||
if type == .repeatingTag {
|
||||
self.count = inputString.count
|
||||
}
|
||||
}
|
||||
|
||||
func newToken( fromSubstring string: String, isReplacement : Bool) -> Token {
|
||||
var newToken = Token(type: (isReplacement) ? .replacement : .string , inputString: string, characterStyles: self.characterStyles)
|
||||
newToken.metadataStrings = self.metadataStrings
|
||||
newToken.isMetadata = self.isMetadata
|
||||
newToken.isProcessed = self.isProcessed
|
||||
return newToken
|
||||
}
|
||||
}
|
||||
|
||||
extension Sequence where Iterator.Element == Token {
|
||||
var oslogDisplay: String {
|
||||
return "[\"\(self.map( { ($0.outputString.isEmpty) ? "\($0.type): \($0.inputString)" : $0.outputString }).joined(separator: "\", \""))\"]"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "SwiftyMarkdown"
|
||||
s.version = "1.0.1"
|
||||
s.version = "1.2.4"
|
||||
s.summary = "Converts Markdown to NSAttributed String"
|
||||
s.homepage = "https://github.com/SimonFairbairn/SwiftyMarkdown"
|
||||
s.license = 'MIT'
|
||||
@@ -14,7 +14,7 @@ s.osx.deployment_target = "10.12"
|
||||
s.watchos.deployment_target = "4.0"
|
||||
s.requires_arc = true
|
||||
|
||||
s.source_files = 'SwiftyMarkdown/'
|
||||
s.source_files = 'Sources/SwiftyMarkdown/**/*'
|
||||
|
||||
s.swift_version = "5.0"
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.2.4</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -15,11 +15,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.1</string>
|
||||
<string>1.2.4</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>19</string>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
@@ -6,149 +6,207 @@
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXAggregateTarget section */
|
||||
"SwiftyMarkdown::SwiftyMarkdownPackageTests::ProductTarget" /* SwiftyMarkdownPackageTests */ = {
|
||||
isa = PBXAggregateTarget;
|
||||
buildConfigurationList = OBJ_54 /* Build configuration list for PBXAggregateTarget "SwiftyMarkdownPackageTests" */;
|
||||
buildPhases = (
|
||||
);
|
||||
dependencies = (
|
||||
OBJ_57 /* PBXTargetDependency */,
|
||||
);
|
||||
name = SwiftyMarkdownPackageTests;
|
||||
productName = SwiftyMarkdownPackageTests;
|
||||
};
|
||||
/* End PBXAggregateTarget section */
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
F40D3A7F23A807F60085CF6E /* SwiftyMarkdownCharacterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40D3A7E23A807F60085CF6E /* SwiftyMarkdownCharacterTests.swift */; };
|
||||
F40D3A8123A8085F0085CF6E /* XCTest+SwiftyMarkdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40D3A8023A8085F0085CF6E /* XCTest+SwiftyMarkdown.swift */; };
|
||||
F40D3A8423A813240085CF6E /* SwiftyMarkdownPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40D3A8323A813240085CF6E /* SwiftyMarkdownPerformanceTests.swift */; };
|
||||
F4440ABC23A8585C00F21DD0 /* SwiftyMarkdown+macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4440ABB23A8585C00F21DD0 /* SwiftyMarkdown+macOS.swift */; };
|
||||
F4440ABE23A8587C00F21DD0 /* SwiftyMarkdown+iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4440ABD23A8587C00F21DD0 /* SwiftyMarkdown+iOS.swift */; };
|
||||
F44E3F46239C365A00508290 /* String+SwiftyMarkdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44E3F45239C365A00508290 /* String+SwiftyMarkdown.swift */; };
|
||||
F49A0DA623A711920005B8B2 /* SwiftyLineProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F49A0DA523A711910005B8B2 /* SwiftyLineProcessor.swift */; };
|
||||
F49A0DA823A71A2C0005B8B2 /* SwiftyTokeniser.swift in Sources */ = {isa = PBXBuildFile; fileRef = F49A0DA723A71A2C0005B8B2 /* SwiftyTokeniser.swift */; };
|
||||
F4A48AB923A8516E00713437 /* SwiftyMarkdownAttributedStringTests copy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A48AB823A8516E00713437 /* SwiftyMarkdownAttributedStringTests copy.swift */; };
|
||||
F4A48ABB23A8521C00713437 /* test.md in Resources */ = {isa = PBXBuildFile; fileRef = F4A48ABA23A8518900713437 /* test.md */; };
|
||||
F4CE98851C8A921300D735C1 /* SwiftyMarkdown.h in Headers */ = {isa = PBXBuildFile; fileRef = F4CE98841C8A921300D735C1 /* SwiftyMarkdown.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
F4CE988C1C8A921300D735C1 /* SwiftyMarkdown.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F4CE98811C8A921300D735C1 /* SwiftyMarkdown.framework */; };
|
||||
F4CE98911C8A921300D735C1 /* SwiftyMarkdownLineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4CE98901C8A921300D735C1 /* SwiftyMarkdownLineTests.swift */; };
|
||||
F4CE989C1C8A922E00D735C1 /* SwiftyMarkdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4CE989B1C8A922E00D735C1 /* SwiftyMarkdown.swift */; };
|
||||
F4CE98E91C8AF01300D735C1 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = F4CE98E61C8AF01300D735C1 /* LICENSE */; };
|
||||
F4CE98EB1C8AF01300D735C1 /* SwiftyMarkdown.podspec in Resources */ = {isa = PBXBuildFile; fileRef = F4CE98E81C8AF01300D735C1 /* SwiftyMarkdown.podspec */; };
|
||||
F4ACB6CB23E8A5C500EA665D /* SwiftyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4ACB6CA23E8A5C500EA665D /* SwiftyScanner.swift */; };
|
||||
F4ACB6CE23E8A88400EA665D /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4ACB6CD23E8A88400EA665D /* Token.swift */; };
|
||||
F4ACB6D023E8A8A500EA665D /* CharacterRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4ACB6CF23E8A8A500EA665D /* CharacterRule.swift */; };
|
||||
F4ACB6D223E8B08400EA665D /* PerfomanceLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4ACB6D123E8B08400EA665D /* PerfomanceLog.swift */; };
|
||||
OBJ_40 /* String+SwiftyMarkdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* String+SwiftyMarkdown.swift */; };
|
||||
OBJ_41 /* SwiftyLineProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* SwiftyLineProcessor.swift */; };
|
||||
OBJ_42 /* SwiftyMarkdown+iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* SwiftyMarkdown+iOS.swift */; };
|
||||
OBJ_43 /* SwiftyMarkdown+macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* SwiftyMarkdown+macOS.swift */; };
|
||||
OBJ_44 /* SwiftyMarkdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* SwiftyMarkdown.swift */; };
|
||||
OBJ_45 /* SwiftyTokeniser.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* SwiftyTokeniser.swift */; };
|
||||
OBJ_52 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; };
|
||||
OBJ_63 /* SwiftyMarkdownAttributedStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* SwiftyMarkdownAttributedStringTests.swift */; };
|
||||
OBJ_64 /* SwiftyMarkdownCharacterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* SwiftyMarkdownCharacterTests.swift */; };
|
||||
OBJ_65 /* SwiftyMarkdownLineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_19 /* SwiftyMarkdownLineTests.swift */; };
|
||||
OBJ_66 /* SwiftyMarkdownLinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_20 /* SwiftyMarkdownLinkTests.swift */; };
|
||||
OBJ_67 /* SwiftyMarkdownPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* SwiftyMarkdownPerformanceTests.swift */; };
|
||||
OBJ_68 /* XCTest+SwiftyMarkdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_22 /* XCTest+SwiftyMarkdown.swift */; };
|
||||
OBJ_70 /* SwiftyMarkdown.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "SwiftyMarkdown::SwiftyMarkdown::Product" /* SwiftyMarkdown.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
F4CE988D1C8A921300D735C1 /* PBXContainerItemProxy */ = {
|
||||
F4ACB6CC23E8A5C600EA665D /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = F4CE98781C8A921300D735C1 /* Project object */;
|
||||
containerPortal = OBJ_1 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = F4CE98801C8A921300D735C1;
|
||||
remoteGlobalIDString = "SwiftyMarkdown::SwiftyMarkdownTests";
|
||||
remoteInfo = SwiftyMarkdownTests;
|
||||
};
|
||||
F4B37A0723E507C900833479 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = OBJ_1 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = "SwiftyMarkdown::SwiftyMarkdown";
|
||||
remoteInfo = SwiftyMarkdown;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
F40D3A7E23A807F60085CF6E /* SwiftyMarkdownCharacterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyMarkdownCharacterTests.swift; sourceTree = "<group>"; };
|
||||
F40D3A8023A8085F0085CF6E /* XCTest+SwiftyMarkdown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTest+SwiftyMarkdown.swift"; sourceTree = "<group>"; };
|
||||
F40D3A8323A813240085CF6E /* SwiftyMarkdownPerformanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyMarkdownPerformanceTests.swift; sourceTree = "<group>"; };
|
||||
F4440ABB23A8585C00F21DD0 /* SwiftyMarkdown+macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftyMarkdown+macOS.swift"; sourceTree = "<group>"; };
|
||||
F4440ABD23A8587C00F21DD0 /* SwiftyMarkdown+iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftyMarkdown+iOS.swift"; sourceTree = "<group>"; };
|
||||
F44E3F45239C365A00508290 /* String+SwiftyMarkdown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+SwiftyMarkdown.swift"; sourceTree = "<group>"; };
|
||||
F49A0DA523A711910005B8B2 /* SwiftyLineProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyLineProcessor.swift; sourceTree = "<group>"; };
|
||||
F49A0DA723A71A2C0005B8B2 /* SwiftyTokeniser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyTokeniser.swift; sourceTree = "<group>"; };
|
||||
F4A48AB823A8516E00713437 /* SwiftyMarkdownAttributedStringTests copy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftyMarkdownAttributedStringTests copy.swift"; sourceTree = "<group>"; };
|
||||
F4A48ABA23A8518900713437 /* test.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = test.md; sourceTree = "<group>"; };
|
||||
F4CE98811C8A921300D735C1 /* SwiftyMarkdown.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftyMarkdown.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
F4CE98841C8A921300D735C1 /* SwiftyMarkdown.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftyMarkdown.h; sourceTree = "<group>"; };
|
||||
F4CE98861C8A921300D735C1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
F4CE988B1C8A921300D735C1 /* SwiftyMarkdownTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftyMarkdownTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
F4CE98901C8A921300D735C1 /* SwiftyMarkdownLineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyMarkdownLineTests.swift; sourceTree = "<group>"; };
|
||||
F4CE98921C8A921300D735C1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
F4CE989B1C8A922E00D735C1 /* SwiftyMarkdown.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyMarkdown.swift; sourceTree = "<group>"; };
|
||||
F4CE98E61C8AF01300D735C1 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
|
||||
F4CE98E71C8AF01300D735C1 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
F4CE98E81C8AF01300D735C1 /* SwiftyMarkdown.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SwiftyMarkdown.podspec; sourceTree = "<group>"; };
|
||||
F4ACB6CA23E8A5C500EA665D /* SwiftyScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyScanner.swift; sourceTree = "<group>"; };
|
||||
F4ACB6CD23E8A88400EA665D /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = "<group>"; };
|
||||
F4ACB6CF23E8A8A500EA665D /* CharacterRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterRule.swift; sourceTree = "<group>"; };
|
||||
F4ACB6D123E8B08400EA665D /* PerfomanceLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerfomanceLog.swift; sourceTree = "<group>"; };
|
||||
OBJ_10 /* SwiftyLineProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyLineProcessor.swift; sourceTree = "<group>"; };
|
||||
OBJ_11 /* SwiftyMarkdown+iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftyMarkdown+iOS.swift"; sourceTree = "<group>"; };
|
||||
OBJ_12 /* SwiftyMarkdown+macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftyMarkdown+macOS.swift"; sourceTree = "<group>"; };
|
||||
OBJ_13 /* SwiftyMarkdown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyMarkdown.swift; sourceTree = "<group>"; };
|
||||
OBJ_14 /* SwiftyTokeniser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyTokeniser.swift; sourceTree = "<group>"; };
|
||||
OBJ_17 /* SwiftyMarkdownAttributedStringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyMarkdownAttributedStringTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_18 /* SwiftyMarkdownCharacterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyMarkdownCharacterTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_19 /* SwiftyMarkdownLineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyMarkdownLineTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_20 /* SwiftyMarkdownLinkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyMarkdownLinkTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_21 /* SwiftyMarkdownPerformanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyMarkdownPerformanceTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_22 /* XCTest+SwiftyMarkdown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTest+SwiftyMarkdown.swift"; sourceTree = "<group>"; };
|
||||
OBJ_26 /* Playground */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Playground; sourceTree = SOURCE_ROOT; };
|
||||
OBJ_27 /* Example */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Example; sourceTree = SOURCE_ROOT; };
|
||||
OBJ_28 /* Resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Resources; sourceTree = SOURCE_ROOT; };
|
||||
OBJ_29 /* fastlane */ = {isa = PBXFileReference; lastKnownFileType = folder; path = fastlane; sourceTree = SOURCE_ROOT; };
|
||||
OBJ_30 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
|
||||
OBJ_31 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
OBJ_32 /* Gemfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Gemfile; sourceTree = "<group>"; };
|
||||
OBJ_33 /* Gemfile.lock */ = {isa = PBXFileReference; lastKnownFileType = text; path = Gemfile.lock; sourceTree = "<group>"; };
|
||||
OBJ_34 /* SwiftyMarkdown.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = SwiftyMarkdown.podspec; sourceTree = "<group>"; };
|
||||
OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
|
||||
OBJ_9 /* String+SwiftyMarkdown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+SwiftyMarkdown.swift"; sourceTree = "<group>"; };
|
||||
"SwiftyMarkdown::SwiftyMarkdown::Product" /* SwiftyMarkdown.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftyMarkdown.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
"SwiftyMarkdown::SwiftyMarkdownTests::Product" /* SwiftyMarkdownTests.xctest */ = {isa = PBXFileReference; lastKnownFileType = file; path = SwiftyMarkdownTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
F4CE987D1C8A921300D735C1 /* Frameworks */ = {
|
||||
OBJ_46 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
buildActionMask = 0;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F4CE98881C8A921300D735C1 /* Frameworks */ = {
|
||||
OBJ_69 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
buildActionMask = 0;
|
||||
files = (
|
||||
F4CE988C1C8A921300D735C1 /* SwiftyMarkdown.framework in Frameworks */,
|
||||
OBJ_70 /* SwiftyMarkdown.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
F4CE98771C8A921300D735C1 = {
|
||||
OBJ_15 /* Tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F4CE98831C8A921300D735C1 /* SwiftyMarkdown */,
|
||||
F4CE988F1C8A921300D735C1 /* SwiftyMarkdownTests */,
|
||||
F4CE98821C8A921300D735C1 /* Products */,
|
||||
F4CE98E61C8AF01300D735C1 /* LICENSE */,
|
||||
F4CE98E71C8AF01300D735C1 /* README.md */,
|
||||
F4CE98E81C8AF01300D735C1 /* SwiftyMarkdown.podspec */,
|
||||
OBJ_16 /* SwiftyMarkdownTests */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
name = Tests;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
F4CE98821C8A921300D735C1 /* Products */ = {
|
||||
OBJ_16 /* SwiftyMarkdownTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F4CE98811C8A921300D735C1 /* SwiftyMarkdown.framework */,
|
||||
F4CE988B1C8A921300D735C1 /* SwiftyMarkdownTests.xctest */,
|
||||
OBJ_17 /* SwiftyMarkdownAttributedStringTests.swift */,
|
||||
OBJ_18 /* SwiftyMarkdownCharacterTests.swift */,
|
||||
OBJ_19 /* SwiftyMarkdownLineTests.swift */,
|
||||
OBJ_20 /* SwiftyMarkdownLinkTests.swift */,
|
||||
OBJ_21 /* SwiftyMarkdownPerformanceTests.swift */,
|
||||
OBJ_22 /* XCTest+SwiftyMarkdown.swift */,
|
||||
);
|
||||
name = SwiftyMarkdownTests;
|
||||
path = Tests/SwiftyMarkdownTests;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
OBJ_23 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
"SwiftyMarkdown::SwiftyMarkdown::Product" /* SwiftyMarkdown.framework */,
|
||||
"SwiftyMarkdown::SwiftyMarkdownTests::Product" /* SwiftyMarkdownTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
F4CE98831C8A921300D735C1 /* SwiftyMarkdown */ = {
|
||||
OBJ_5 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F4CE98841C8A921300D735C1 /* SwiftyMarkdown.h */,
|
||||
F4CE98861C8A921300D735C1 /* Info.plist */,
|
||||
F4CE989B1C8A922E00D735C1 /* SwiftyMarkdown.swift */,
|
||||
F44E3F45239C365A00508290 /* String+SwiftyMarkdown.swift */,
|
||||
F49A0DA523A711910005B8B2 /* SwiftyLineProcessor.swift */,
|
||||
F49A0DA723A71A2C0005B8B2 /* SwiftyTokeniser.swift */,
|
||||
F4440ABD23A8587C00F21DD0 /* SwiftyMarkdown+iOS.swift */,
|
||||
F4440ABB23A8585C00F21DD0 /* SwiftyMarkdown+macOS.swift */,
|
||||
OBJ_6 /* Package.swift */,
|
||||
OBJ_7 /* Sources */,
|
||||
OBJ_15 /* Tests */,
|
||||
OBJ_23 /* Products */,
|
||||
OBJ_26 /* Playground */,
|
||||
OBJ_27 /* Example */,
|
||||
OBJ_28 /* Resources */,
|
||||
OBJ_29 /* fastlane */,
|
||||
OBJ_30 /* LICENSE */,
|
||||
OBJ_31 /* README.md */,
|
||||
OBJ_32 /* Gemfile */,
|
||||
OBJ_33 /* Gemfile.lock */,
|
||||
OBJ_34 /* SwiftyMarkdown.podspec */,
|
||||
);
|
||||
path = SwiftyMarkdown;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F4CE988F1C8A921300D735C1 /* SwiftyMarkdownTests */ = {
|
||||
OBJ_7 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F4A48ABA23A8518900713437 /* test.md */,
|
||||
F4CE98901C8A921300D735C1 /* SwiftyMarkdownLineTests.swift */,
|
||||
F40D3A7E23A807F60085CF6E /* SwiftyMarkdownCharacterTests.swift */,
|
||||
F4CE98921C8A921300D735C1 /* Info.plist */,
|
||||
F40D3A8023A8085F0085CF6E /* XCTest+SwiftyMarkdown.swift */,
|
||||
F4A48AB823A8516E00713437 /* SwiftyMarkdownAttributedStringTests copy.swift */,
|
||||
F40D3A8323A813240085CF6E /* SwiftyMarkdownPerformanceTests.swift */,
|
||||
OBJ_8 /* SwiftyMarkdown */,
|
||||
);
|
||||
path = SwiftyMarkdownTests;
|
||||
sourceTree = "<group>";
|
||||
name = Sources;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
OBJ_8 /* SwiftyMarkdown */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
OBJ_9 /* String+SwiftyMarkdown.swift */,
|
||||
OBJ_10 /* SwiftyLineProcessor.swift */,
|
||||
OBJ_11 /* SwiftyMarkdown+iOS.swift */,
|
||||
OBJ_12 /* SwiftyMarkdown+macOS.swift */,
|
||||
OBJ_13 /* SwiftyMarkdown.swift */,
|
||||
OBJ_14 /* SwiftyTokeniser.swift */,
|
||||
F4ACB6CD23E8A88400EA665D /* Token.swift */,
|
||||
F4ACB6CA23E8A5C500EA665D /* SwiftyScanner.swift */,
|
||||
F4ACB6CF23E8A8A500EA665D /* CharacterRule.swift */,
|
||||
F4ACB6D123E8B08400EA665D /* PerfomanceLog.swift */,
|
||||
);
|
||||
name = SwiftyMarkdown;
|
||||
path = Sources/SwiftyMarkdown;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
F4CE987E1C8A921300D735C1 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F4CE98851C8A921300D735C1 /* SwiftyMarkdown.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
F4CE98801C8A921300D735C1 /* SwiftyMarkdown */ = {
|
||||
"SwiftyMarkdown::SwiftPMPackageDescription" /* SwiftyMarkdownPackageDescription */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = F4CE98951C8A921300D735C1 /* Build configuration list for PBXNativeTarget "SwiftyMarkdown" */;
|
||||
buildConfigurationList = OBJ_48 /* Build configuration list for PBXNativeTarget "SwiftyMarkdownPackageDescription" */;
|
||||
buildPhases = (
|
||||
F4CE987C1C8A921300D735C1 /* Sources */,
|
||||
F4CE987D1C8A921300D735C1 /* Frameworks */,
|
||||
F4CE987E1C8A921300D735C1 /* Headers */,
|
||||
F4CE987F1C8A921300D735C1 /* Resources */,
|
||||
OBJ_51 /* Sources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = SwiftyMarkdownPackageDescription;
|
||||
productName = SwiftyMarkdownPackageDescription;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
"SwiftyMarkdown::SwiftyMarkdown" /* SwiftyMarkdown */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = OBJ_36 /* Build configuration list for PBXNativeTarget "SwiftyMarkdown" */;
|
||||
buildPhases = (
|
||||
OBJ_39 /* Sources */,
|
||||
OBJ_46 /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -156,343 +214,348 @@
|
||||
);
|
||||
name = SwiftyMarkdown;
|
||||
productName = SwiftyMarkdown;
|
||||
productReference = F4CE98811C8A921300D735C1 /* SwiftyMarkdown.framework */;
|
||||
productReference = "SwiftyMarkdown::SwiftyMarkdown::Product" /* SwiftyMarkdown.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
F4CE988A1C8A921300D735C1 /* SwiftyMarkdownTests */ = {
|
||||
"SwiftyMarkdown::SwiftyMarkdownTests" /* SwiftyMarkdownTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = F4CE98981C8A921300D735C1 /* Build configuration list for PBXNativeTarget "SwiftyMarkdownTests" */;
|
||||
buildConfigurationList = OBJ_59 /* Build configuration list for PBXNativeTarget "SwiftyMarkdownTests" */;
|
||||
buildPhases = (
|
||||
F4CE98871C8A921300D735C1 /* Sources */,
|
||||
F4CE98881C8A921300D735C1 /* Frameworks */,
|
||||
F4CE98891C8A921300D735C1 /* Resources */,
|
||||
OBJ_62 /* Sources */,
|
||||
OBJ_69 /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
F4CE988E1C8A921300D735C1 /* PBXTargetDependency */,
|
||||
OBJ_71 /* PBXTargetDependency */,
|
||||
);
|
||||
name = SwiftyMarkdownTests;
|
||||
productName = SwiftyMarkdownTests;
|
||||
productReference = F4CE988B1C8A921300D735C1 /* SwiftyMarkdownTests.xctest */;
|
||||
productReference = "SwiftyMarkdown::SwiftyMarkdownTests::Product" /* SwiftyMarkdownTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
F4CE98781C8A921300D735C1 /* Project object */ = {
|
||||
OBJ_1 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0720;
|
||||
LastUpgradeCheck = 1120;
|
||||
ORGANIZATIONNAME = "Voyage Travel Apps";
|
||||
TargetAttributes = {
|
||||
F4CE98801C8A921300D735C1 = {
|
||||
CreatedOnToolsVersion = 7.2.1;
|
||||
LastSwiftMigration = 1120;
|
||||
};
|
||||
F4CE988A1C8A921300D735C1 = {
|
||||
CreatedOnToolsVersion = 7.2.1;
|
||||
DevelopmentTeam = 52T262DA8V;
|
||||
LastSwiftMigration = 1120;
|
||||
};
|
||||
};
|
||||
LastSwiftMigration = 9999;
|
||||
LastUpgradeCheck = 9999;
|
||||
};
|
||||
buildConfigurationList = F4CE987B1C8A921300D735C1 /* Build configuration list for PBXProject "SwiftyMarkdown" */;
|
||||
buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "SwiftyMarkdown" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = F4CE98771C8A921300D735C1;
|
||||
productRefGroup = F4CE98821C8A921300D735C1 /* Products */;
|
||||
mainGroup = OBJ_5;
|
||||
productRefGroup = OBJ_23 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
F4CE98801C8A921300D735C1 /* SwiftyMarkdown */,
|
||||
F4CE988A1C8A921300D735C1 /* SwiftyMarkdownTests */,
|
||||
"SwiftyMarkdown::SwiftyMarkdown" /* SwiftyMarkdown */,
|
||||
"SwiftyMarkdown::SwiftPMPackageDescription" /* SwiftyMarkdownPackageDescription */,
|
||||
"SwiftyMarkdown::SwiftyMarkdownPackageTests::ProductTarget" /* SwiftyMarkdownPackageTests */,
|
||||
"SwiftyMarkdown::SwiftyMarkdownTests" /* SwiftyMarkdownTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
F4CE987F1C8A921300D735C1 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F4CE98E91C8AF01300D735C1 /* LICENSE in Resources */,
|
||||
F4CE98EB1C8AF01300D735C1 /* SwiftyMarkdown.podspec in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F4CE98891C8A921300D735C1 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F4A48ABB23A8521C00713437 /* test.md in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
F4CE987C1C8A921300D735C1 /* Sources */ = {
|
||||
OBJ_39 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
buildActionMask = 0;
|
||||
files = (
|
||||
F4440ABC23A8585C00F21DD0 /* SwiftyMarkdown+macOS.swift in Sources */,
|
||||
F49A0DA623A711920005B8B2 /* SwiftyLineProcessor.swift in Sources */,
|
||||
F4440ABE23A8587C00F21DD0 /* SwiftyMarkdown+iOS.swift in Sources */,
|
||||
F4CE989C1C8A922E00D735C1 /* SwiftyMarkdown.swift in Sources */,
|
||||
F49A0DA823A71A2C0005B8B2 /* SwiftyTokeniser.swift in Sources */,
|
||||
F44E3F46239C365A00508290 /* String+SwiftyMarkdown.swift in Sources */,
|
||||
OBJ_40 /* String+SwiftyMarkdown.swift in Sources */,
|
||||
OBJ_41 /* SwiftyLineProcessor.swift in Sources */,
|
||||
F4ACB6D223E8B08400EA665D /* PerfomanceLog.swift in Sources */,
|
||||
F4ACB6CB23E8A5C500EA665D /* SwiftyScanner.swift in Sources */,
|
||||
OBJ_42 /* SwiftyMarkdown+iOS.swift in Sources */,
|
||||
OBJ_43 /* SwiftyMarkdown+macOS.swift in Sources */,
|
||||
OBJ_44 /* SwiftyMarkdown.swift in Sources */,
|
||||
OBJ_45 /* SwiftyTokeniser.swift in Sources */,
|
||||
F4ACB6D023E8A8A500EA665D /* CharacterRule.swift in Sources */,
|
||||
F4ACB6CE23E8A88400EA665D /* Token.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F4CE98871C8A921300D735C1 /* Sources */ = {
|
||||
OBJ_51 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
buildActionMask = 0;
|
||||
files = (
|
||||
F40D3A8123A8085F0085CF6E /* XCTest+SwiftyMarkdown.swift in Sources */,
|
||||
F4A48AB923A8516E00713437 /* SwiftyMarkdownAttributedStringTests copy.swift in Sources */,
|
||||
F40D3A7F23A807F60085CF6E /* SwiftyMarkdownCharacterTests.swift in Sources */,
|
||||
F4CE98911C8A921300D735C1 /* SwiftyMarkdownLineTests.swift in Sources */,
|
||||
F40D3A8423A813240085CF6E /* SwiftyMarkdownPerformanceTests.swift in Sources */,
|
||||
OBJ_52 /* Package.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
OBJ_62 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 0;
|
||||
files = (
|
||||
OBJ_63 /* SwiftyMarkdownAttributedStringTests.swift in Sources */,
|
||||
OBJ_64 /* SwiftyMarkdownCharacterTests.swift in Sources */,
|
||||
OBJ_65 /* SwiftyMarkdownLineTests.swift in Sources */,
|
||||
OBJ_66 /* SwiftyMarkdownLinkTests.swift in Sources */,
|
||||
OBJ_67 /* SwiftyMarkdownPerformanceTests.swift in Sources */,
|
||||
OBJ_68 /* XCTest+SwiftyMarkdown.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
F4CE988E1C8A921300D735C1 /* PBXTargetDependency */ = {
|
||||
OBJ_57 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = F4CE98801C8A921300D735C1 /* SwiftyMarkdown */;
|
||||
targetProxy = F4CE988D1C8A921300D735C1 /* PBXContainerItemProxy */;
|
||||
target = "SwiftyMarkdown::SwiftyMarkdownTests" /* SwiftyMarkdownTests */;
|
||||
targetProxy = F4ACB6CC23E8A5C600EA665D /* PBXContainerItemProxy */;
|
||||
};
|
||||
OBJ_71 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = "SwiftyMarkdown::SwiftyMarkdown" /* SwiftyMarkdown */;
|
||||
targetProxy = F4B37A0723E507C900833479 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
F4CE98931C8A921300D735C1 /* Debug */ = {
|
||||
OBJ_3 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 19;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_NS_ASSERTIONS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
"SWIFT_PACKAGE=1",
|
||||
"DEBUG=1",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -DXcode";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE DEBUG";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
USE_HEADERMAP = NO;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
F4CE98941C8A921300D735C1 /* Release */ = {
|
||||
OBJ_37 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = "$(inherited)";
|
||||
INFOPLIST_FILE = SwiftyMarkdown.xcodeproj/SwiftyMarkdown_Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = SwiftyMarkdown;
|
||||
PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGET_NAME = SwiftyMarkdown;
|
||||
TVOS_DEPLOYMENT_TARGET = 11.0;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 4.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
OBJ_38 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = "$(inherited)";
|
||||
INFOPLIST_FILE = SwiftyMarkdown.xcodeproj/SwiftyMarkdown_Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = SwiftyMarkdown;
|
||||
PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGET_NAME = SwiftyMarkdown;
|
||||
TVOS_DEPLOYMENT_TARGET = 11.0;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 4.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
OBJ_4 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 19;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
GCC_OPTIMIZATION_LEVEL = s;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"SWIFT_PACKAGE=1",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -DXcode";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
USE_HEADERMAP = NO;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
F4CE98961C8A921300D735C1 /* Debug */ = {
|
||||
OBJ_49 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BITCODE_GENERATION_MODE = bitcode;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 19;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = SwiftyMarkdown/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
OTHER_CFLAGS = "-fembed-bitcode";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.voyagetravelapps.SwiftyMarkdown;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
LD = /usr/bin/true;
|
||||
OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -package-description-version 5.1";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
F4CE98971C8A921300D735C1 /* Release */ = {
|
||||
OBJ_50 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BITCODE_GENERATION_MODE = bitcode;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 19;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = SwiftyMarkdown/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
OTHER_CFLAGS = "-fembed-bitcode";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.voyagetravelapps.SwiftyMarkdown;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
LD = /usr/bin/true;
|
||||
OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -package-description-version 5.1";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
F4CE98991C8A921300D735C1 /* Debug */ = {
|
||||
OBJ_55 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
DEVELOPMENT_TEAM = 52T262DA8V;
|
||||
INFOPLIST_FILE = SwiftyMarkdownTests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.voyagetravelapps.SwiftyMarkdownTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
F4CE989A1C8A921300D735C1 /* Release */ = {
|
||||
OBJ_56 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
DEVELOPMENT_TEAM = 52T262DA8V;
|
||||
INFOPLIST_FILE = SwiftyMarkdownTests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.voyagetravelapps.SwiftyMarkdownTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
OBJ_60 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = "$(inherited)";
|
||||
INFOPLIST_FILE = SwiftyMarkdown.xcodeproj/SwiftyMarkdownTests_Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGET_NAME = SwiftyMarkdownTests;
|
||||
TVOS_DEPLOYMENT_TARGET = 11.0;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 4.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
OBJ_61 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = "$(inherited)";
|
||||
INFOPLIST_FILE = SwiftyMarkdown.xcodeproj/SwiftyMarkdownTests_Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGET_NAME = SwiftyMarkdownTests;
|
||||
TVOS_DEPLOYMENT_TARGET = 11.0;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 4.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
F4CE987B1C8A921300D735C1 /* Build configuration list for PBXProject "SwiftyMarkdown" */ = {
|
||||
OBJ_2 /* Build configuration list for PBXProject "SwiftyMarkdown" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
F4CE98931C8A921300D735C1 /* Debug */,
|
||||
F4CE98941C8A921300D735C1 /* Release */,
|
||||
OBJ_3 /* Debug */,
|
||||
OBJ_4 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
F4CE98951C8A921300D735C1 /* Build configuration list for PBXNativeTarget "SwiftyMarkdown" */ = {
|
||||
OBJ_36 /* Build configuration list for PBXNativeTarget "SwiftyMarkdown" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
F4CE98961C8A921300D735C1 /* Debug */,
|
||||
F4CE98971C8A921300D735C1 /* Release */,
|
||||
OBJ_37 /* Debug */,
|
||||
OBJ_38 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
F4CE98981C8A921300D735C1 /* Build configuration list for PBXNativeTarget "SwiftyMarkdownTests" */ = {
|
||||
OBJ_48 /* Build configuration list for PBXNativeTarget "SwiftyMarkdownPackageDescription" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
F4CE98991C8A921300D735C1 /* Debug */,
|
||||
F4CE989A1C8A921300D735C1 /* Release */,
|
||||
OBJ_49 /* Debug */,
|
||||
OBJ_50 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
OBJ_54 /* Build configuration list for PBXAggregateTarget "SwiftyMarkdownPackageTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
OBJ_55 /* Debug */,
|
||||
OBJ_56 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
OBJ_59 /* Build configuration list for PBXNativeTarget "SwiftyMarkdownTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
OBJ_60 /* Debug */,
|
||||
OBJ_61 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = F4CE98781C8A921300D735C1 /* Project object */;
|
||||
rootObject = OBJ_1 /* Project object */;
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
+12
-2
@@ -11,7 +11,7 @@
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.01346</real>
|
||||
<real>0.1</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
@@ -21,7 +21,17 @@
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.0064581</real>
|
||||
<real>0.1</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testThatVeryLongStringsAreProcessedQuickly()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.1</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
+4
-6
@@ -11,11 +11,11 @@
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.1</real>
|
||||
<real>0.212</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
<key>maxPercentRelativeStandardDeviation</key>
|
||||
<real>5</real>
|
||||
<real>10</real>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testThatStringsAreProcessedQuickly()</key>
|
||||
@@ -23,11 +23,9 @@
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.01</real>
|
||||
<real>0.016</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
<key>maxPercentRelativeStandardDeviation</key>
|
||||
<real>5</real>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testThatVeryLongStringsAreProcessedQuickly()</key>
|
||||
@@ -35,7 +33,7 @@
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.0392</real>
|
||||
<real>0.016</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
+3
-17
@@ -4,7 +4,7 @@
|
||||
<dict>
|
||||
<key>runDestinationsByUUID</key>
|
||||
<dict>
|
||||
<key>3EC61D89-CC18-40D5-9AC6-F4504ECE42B5</key>
|
||||
<key>88991ED5-B954-422F-B610-BDC9A4AEC008</key>
|
||||
<dict>
|
||||
<key>localComputer</key>
|
||||
<dict>
|
||||
@@ -26,16 +26,9 @@
|
||||
<string>com.apple.platform.macosx</string>
|
||||
</dict>
|
||||
<key>targetArchitecture</key>
|
||||
<string>x86_64</string>
|
||||
<key>targetDevice</key>
|
||||
<dict>
|
||||
<key>modelCode</key>
|
||||
<string>iPhone10,6</string>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.iphonesimulator</string>
|
||||
</dict>
|
||||
<string>x86_64h</string>
|
||||
</dict>
|
||||
<key>8451F51C-30BE-4B7B-ACFD-E9C42A8D0DC4</key>
|
||||
<key>AD1DF83E-20BC-4E7E-8C14-683818ED0A26</key>
|
||||
<dict>
|
||||
<key>localComputer</key>
|
||||
<dict>
|
||||
@@ -58,13 +51,6 @@
|
||||
</dict>
|
||||
<key>targetArchitecture</key>
|
||||
<string>x86_64</string>
|
||||
<key>targetDevice</key>
|
||||
<dict>
|
||||
<key>modelCode</key>
|
||||
<string>iPhone11,8</string>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.iphonesimulator</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
+40
-30
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1120"
|
||||
LastUpgradeVersion = "9999"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -14,7 +14,7 @@
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F4CE98801C8A921300D735C1"
|
||||
BlueprintIdentifier = "SwiftyMarkdown::SwiftyMarkdown"
|
||||
BuildableName = "SwiftyMarkdown.framework"
|
||||
BlueprintName = "SwiftyMarkdown"
|
||||
ReferencedContainer = "container:SwiftyMarkdown.xcodeproj">
|
||||
@@ -27,21 +27,12 @@
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F4CE98801C8A921300D735C1"
|
||||
BuildableName = "SwiftyMarkdown.framework"
|
||||
BlueprintName = "SwiftyMarkdown"
|
||||
ReferencedContainer = "container:SwiftyMarkdown.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F4CE988A1C8A921300D735C1"
|
||||
BlueprintIdentifier = "SwiftyMarkdown::SwiftyMarkdownTests"
|
||||
BuildableName = "SwiftyMarkdownTests.xctest"
|
||||
BlueprintName = "SwiftyMarkdownTests"
|
||||
ReferencedContainer = "container:SwiftyMarkdown.xcodeproj">
|
||||
@@ -59,15 +50,43 @@
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F4CE98801C8A921300D735C1"
|
||||
BuildableName = "SwiftyMarkdown.framework"
|
||||
BlueprintName = "SwiftyMarkdown"
|
||||
ReferencedContainer = "container:SwiftyMarkdown.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<EnvironmentVariables>
|
||||
<EnvironmentVariable
|
||||
key = "SwiftyTokeniserLogging"
|
||||
value = ""
|
||||
isEnabled = "NO">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "SwiftyScannerScanner"
|
||||
value = ""
|
||||
isEnabled = "NO">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "SwiftyScannerScannerPerformanceLogging"
|
||||
value = ""
|
||||
isEnabled = "NO">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "SwiftyLineProcessorPerformanceLogging"
|
||||
value = ""
|
||||
isEnabled = "NO">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "SwiftyScannerPerformanceLogging"
|
||||
value = ""
|
||||
isEnabled = "NO">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "SwiftyTokeniserPerformanceLogging"
|
||||
value = ""
|
||||
isEnabled = "NO">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "SwiftyMarkdownPerformanceLogging"
|
||||
value = ""
|
||||
isEnabled = "NO">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
@@ -75,15 +94,6 @@
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F4CE98801C8A921300D735C1"
|
||||
BuildableName = "SwiftyMarkdown.framework"
|
||||
BlueprintName = "SwiftyMarkdown"
|
||||
ReferencedContainer = "container:SwiftyMarkdown.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
@@ -1,19 +0,0 @@
|
||||
//
|
||||
// SwiftyMarkdown.h
|
||||
// SwiftyMarkdown
|
||||
//
|
||||
// Created by Simon Fairbairn on 05/03/2016.
|
||||
// Copyright © 2016 Voyage Travel Apps. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
|
||||
//! Project version number for SwiftyMarkdown.
|
||||
//FOUNDATION_EXPORT double SwiftyMarkdownVersionNumber;
|
||||
|
||||
//! Project version string for SwiftyMarkdown.
|
||||
//FOUNDATION_EXPORT const unsigned char SwiftyMarkdownVersionString[];
|
||||
|
||||
// In this header, you should import all the public headers of your framework using statements like #import <SwiftyMarkdown/PublicHeader.h>
|
||||
|
||||
|
||||
@@ -1,772 +0,0 @@
|
||||
//
|
||||
// SwiftyTokeniser.swift
|
||||
// SwiftyMarkdown
|
||||
//
|
||||
// Created by Simon Fairbairn on 16/12/2019.
|
||||
// Copyright © 2019 Voyage Travel Apps. All rights reserved.
|
||||
//
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
extension OSLog {
|
||||
private static var subsystem = "SwiftyTokeniser"
|
||||
static let tokenising = OSLog(subsystem: subsystem, category: "Tokenising")
|
||||
static let styling = OSLog(subsystem: subsystem, category: "Styling")
|
||||
}
|
||||
|
||||
// Tag definition
|
||||
public protocol CharacterStyling {
|
||||
func isEqualTo( _ other : CharacterStyling ) -> Bool
|
||||
}
|
||||
|
||||
|
||||
public enum SpaceAllowed {
|
||||
case no
|
||||
case bothSides
|
||||
case oneSide
|
||||
case leadingSide
|
||||
case trailingSide
|
||||
}
|
||||
|
||||
public enum Cancel {
|
||||
case none
|
||||
case allRemaining
|
||||
case currentSet
|
||||
}
|
||||
|
||||
public struct CharacterRule : CustomStringConvertible {
|
||||
public let openTag : String
|
||||
public let intermediateTag : String?
|
||||
public let closingTag : String?
|
||||
public let escapeCharacter : Character?
|
||||
public let styles : [Int : [CharacterStyling]]
|
||||
public var maxTags : Int = 1
|
||||
public var spacesAllowed : SpaceAllowed = .oneSide
|
||||
public var cancels : Cancel = .none
|
||||
|
||||
public var description: String {
|
||||
return "Character Rule with Open tag: \(self.openTag) and current styles : \(self.styles) "
|
||||
}
|
||||
|
||||
public init(openTag: String, intermediateTag: String? = nil, closingTag: String? = nil, escapeCharacter: Character? = nil, styles: [Int : [CharacterStyling]] = [:], maxTags : Int = 1, cancels : Cancel = .none) {
|
||||
self.openTag = openTag
|
||||
self.intermediateTag = intermediateTag
|
||||
self.closingTag = closingTag
|
||||
self.escapeCharacter = escapeCharacter
|
||||
self.styles = styles
|
||||
self.maxTags = maxTags
|
||||
self.cancels = cancels
|
||||
}
|
||||
}
|
||||
|
||||
// Token definition
|
||||
public enum TokenType {
|
||||
case repeatingTag
|
||||
case openTag
|
||||
case intermediateTag
|
||||
case closeTag
|
||||
case string
|
||||
case escape
|
||||
case replacement
|
||||
}
|
||||
|
||||
|
||||
|
||||
public struct Token {
|
||||
public let id = UUID().uuidString
|
||||
public let type : TokenType
|
||||
public let inputString : String
|
||||
public fileprivate(set) var metadataString : String? = nil
|
||||
public fileprivate(set) var characterStyles : [CharacterStyling] = []
|
||||
public fileprivate(set) var count : Int = 0
|
||||
public fileprivate(set) var shouldSkip : Bool = false
|
||||
public fileprivate(set) var tokenIndex : Int = -1
|
||||
public fileprivate(set) var isProcessed : Bool = false
|
||||
public fileprivate(set) var isMetadata : Bool = false
|
||||
public var outputString : String {
|
||||
get {
|
||||
switch self.type {
|
||||
case .repeatingTag:
|
||||
if count <= 0 {
|
||||
return ""
|
||||
} else {
|
||||
let range = inputString.startIndex..<inputString.index(inputString.startIndex, offsetBy: self.count)
|
||||
return String(inputString[range])
|
||||
}
|
||||
case .openTag, .closeTag, .intermediateTag:
|
||||
return (self.isProcessed || self.isMetadata) ? "" : inputString
|
||||
case .escape, .string:
|
||||
return (self.isProcessed || self.isMetadata) ? "" : inputString
|
||||
case .replacement:
|
||||
return self.inputString
|
||||
}
|
||||
}
|
||||
}
|
||||
public init( type : TokenType, inputString : String, characterStyles : [CharacterStyling] = []) {
|
||||
self.type = type
|
||||
self.inputString = inputString
|
||||
self.characterStyles = characterStyles
|
||||
}
|
||||
|
||||
func newToken( fromSubstring string: String, isReplacement : Bool) -> Token {
|
||||
var newToken = Token(type: (isReplacement) ? .replacement : .string , inputString: string, characterStyles: self.characterStyles)
|
||||
newToken.metadataString = self.metadataString
|
||||
newToken.isMetadata = self.isMetadata
|
||||
newToken.isProcessed = self.isProcessed
|
||||
return newToken
|
||||
}
|
||||
}
|
||||
|
||||
extension Sequence where Iterator.Element == Token {
|
||||
var oslogDisplay: String {
|
||||
return "[\"\(self.map( { ($0.outputString.isEmpty) ? "\($0.type): \($0.inputString)" : $0.outputString }).joined(separator: "\", \""))\"]"
|
||||
}
|
||||
}
|
||||
|
||||
public class SwiftyTokeniser {
|
||||
let rules : [CharacterRule]
|
||||
var replacements : [String : [Token]] = [:]
|
||||
|
||||
public init( with rules : [CharacterRule] ) {
|
||||
self.rules = rules
|
||||
}
|
||||
|
||||
|
||||
/// This goes through every CharacterRule in order and applies it to the input string, tokenising the string
|
||||
/// if there are any matches.
|
||||
///
|
||||
/// The for loop in the while loop (yeah, I know) is there to separate strings from within tags to
|
||||
/// those outside them.
|
||||
///
|
||||
/// e.g. "A string with a \[link\]\(url\) tag" would have the "link" text tokenised separately.
|
||||
///
|
||||
/// This is to prevent situations like **\[link**\](url) from returing a bold string.
|
||||
///
|
||||
/// - Parameter inputString: A string to have the CharacterRules in `self.rules` applied to
|
||||
public func process( _ inputString : String ) -> [Token] {
|
||||
guard rules.count > 0 else {
|
||||
return [Token(type: .string, inputString: inputString)]
|
||||
}
|
||||
|
||||
var currentTokens : [Token] = []
|
||||
var mutableRules = self.rules
|
||||
|
||||
|
||||
|
||||
while !mutableRules.isEmpty {
|
||||
let nextRule = mutableRules.removeFirst()
|
||||
os_log("------------------------------", log: .tokenising, type: .info)
|
||||
os_log("RULE: %@", log: OSLog.tokenising, type:.info , nextRule.description)
|
||||
|
||||
if currentTokens.isEmpty {
|
||||
// This means it's the first time through
|
||||
currentTokens = self.applyStyles(to: self.scan(inputString, with: nextRule), usingRule: nextRule)
|
||||
continue
|
||||
}
|
||||
|
||||
var outerStringTokens : [Token] = []
|
||||
var innerStringTokens : [Token] = []
|
||||
var isOuter = true
|
||||
for idx in 0..<currentTokens.count {
|
||||
let nextToken = currentTokens[idx]
|
||||
if nextToken.type == .openTag && nextToken.isProcessed {
|
||||
isOuter = false
|
||||
}
|
||||
if nextToken.type == .closeTag {
|
||||
let ref = UUID().uuidString
|
||||
outerStringTokens.append(Token(type: .replacement, inputString: ref))
|
||||
innerStringTokens.append(nextToken)
|
||||
self.replacements[ref] = self.handleReplacementTokens(innerStringTokens, with: nextRule)
|
||||
innerStringTokens.removeAll()
|
||||
isOuter = true
|
||||
continue
|
||||
}
|
||||
(isOuter) ? outerStringTokens.append(nextToken) : innerStringTokens.append(nextToken)
|
||||
}
|
||||
|
||||
currentTokens = self.handleReplacementTokens(outerStringTokens, with: nextRule)
|
||||
|
||||
var finalTokens : [Token] = []
|
||||
for token in currentTokens {
|
||||
guard token.type == .replacement else {
|
||||
finalTokens.append(token)
|
||||
continue
|
||||
}
|
||||
if let hasReplacement = self.replacements[token.inputString] {
|
||||
for var repToken in hasReplacement {
|
||||
guard repToken.type == .string else {
|
||||
finalTokens.append(repToken)
|
||||
continue
|
||||
}
|
||||
for style in token.characterStyles {
|
||||
if !repToken.characterStyles.contains(where: { $0.isEqualTo(style)}) {
|
||||
repToken.characterStyles.append(contentsOf: token.characterStyles)
|
||||
}
|
||||
}
|
||||
|
||||
finalTokens.append(repToken)
|
||||
}
|
||||
}
|
||||
}
|
||||
currentTokens = finalTokens
|
||||
|
||||
|
||||
// Each string could have additional tokens within it, so they have to be scanned as well with the current rule.
|
||||
// The one string token might then be exploded into multiple more tokens
|
||||
}
|
||||
|
||||
os_log("=====RULE PROCESSING COMPLETE=====", log: .tokenising, type: .info)
|
||||
os_log("==================================", log: .tokenising, type: .info)
|
||||
|
||||
return currentTokens
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// In order to reinsert the original replacements into the new string token, the replacements
|
||||
/// need to be searched for in the incoming string one by one.
|
||||
///
|
||||
/// Using the `newToken(fromSubstring:isReplacement:)` function ensures that any metadata and character styles
|
||||
/// are passed over into the newly created tokens.
|
||||
///
|
||||
/// E.g. A string token that has an `outputString` of "This string AAAAA-BBBBB-CCCCC replacements", with
|
||||
/// a characterStyle of `bold` for the entire string, needs to be separated into the following tokens:
|
||||
///
|
||||
/// - `string`: "This string "
|
||||
/// - `replacement`: "AAAAA-BBBBB-CCCCC"
|
||||
/// - `string`: " replacements"
|
||||
///
|
||||
/// Each of these need to have a character style of `bold`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - replacements: An array of `replacement` tokens
|
||||
/// - token: The new `string` token that may contain replacement IDs contained in the `replacements` array
|
||||
func reinsertReplacements(_ replacements : [Token], from stringToken : Token ) -> [Token] {
|
||||
guard !stringToken.outputString.isEmpty && !replacements.isEmpty else {
|
||||
return [stringToken]
|
||||
}
|
||||
var outputTokens : [Token] = []
|
||||
let scanner = Scanner(string: stringToken.outputString)
|
||||
scanner.charactersToBeSkipped = nil
|
||||
var repTokens = replacements
|
||||
while !scanner.isAtEnd {
|
||||
var outputString : String = ""
|
||||
var testString = "\n"
|
||||
if repTokens.count > 0 {
|
||||
testString = repTokens.removeFirst().inputString
|
||||
}
|
||||
|
||||
if #available(iOS 13.0, OSX 10.15, watchOS 6.0, tvOS 13.0, *) {
|
||||
if let nextString = scanner.scanUpToString(testString) {
|
||||
outputString = nextString
|
||||
outputTokens.append(stringToken.newToken(fromSubstring: outputString, isReplacement: false))
|
||||
if let outputToken = scanner.scanString(testString) {
|
||||
outputTokens.append(stringToken.newToken(fromSubstring: outputToken, isReplacement: true))
|
||||
}
|
||||
} else if let outputToken = scanner.scanString(testString) {
|
||||
outputTokens.append(stringToken.newToken(fromSubstring: outputToken, isReplacement: true))
|
||||
}
|
||||
} else {
|
||||
var oldString : NSString? = nil
|
||||
var tokenString : NSString? = nil
|
||||
scanner.scanUpTo(testString, into: &oldString)
|
||||
if let nextString = oldString {
|
||||
outputString = nextString as String
|
||||
outputTokens.append(stringToken.newToken(fromSubstring: outputString, isReplacement: false))
|
||||
scanner.scanString(testString, into: &tokenString)
|
||||
if let outputToken = tokenString as String? {
|
||||
outputTokens.append(stringToken.newToken(fromSubstring: outputToken, isReplacement: true))
|
||||
}
|
||||
} else {
|
||||
scanner.scanString(testString, into: &tokenString)
|
||||
if let outputToken = tokenString as String? {
|
||||
outputTokens.append(stringToken.newToken(fromSubstring: outputToken, isReplacement: true))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return outputTokens
|
||||
}
|
||||
|
||||
|
||||
/// This function is necessary because a previously tokenised string might have
|
||||
///
|
||||
/// Consider a previously tokenised string, where AAAAA-BBBBB-CCCCC represents a replaced \[link\](url) instance.
|
||||
///
|
||||
/// The incoming tokens will look like this:
|
||||
///
|
||||
/// - `string`: "A \*\*Bold"
|
||||
/// - `replacement` : "AAAAA-BBBBB-CCCCC"
|
||||
/// - `string`: " with a trailing string**"
|
||||
///
|
||||
/// However, because the scanner can only tokenise individual strings, passing in the string values
|
||||
/// of these tokens individually and applying the styles will not correctly detect the starting and
|
||||
/// ending `repeatingTag` instances. (e.g. the scanner will see "A \*\*Bold", and then "AAAAA-BBBBB-CCCCC",
|
||||
/// and finally " with a trailing string\*\*")
|
||||
///
|
||||
/// The strings need to be combined, so that they form a single string:
|
||||
/// A \*\*Bold AAAAA-BBBBB-CCCCC with a trailing string\*\*.
|
||||
/// This string is then parsed and tokenised so that it looks like this:
|
||||
///
|
||||
/// - `string`: "A "
|
||||
/// - `repeatingTag`: "\*\*"
|
||||
/// - `string`: "Bold AAAAA-BBBBB-CCCCC with a trailing string"
|
||||
/// - `repeatingTag`: "\*\*"
|
||||
///
|
||||
/// Finally, the replacements from the original incoming token array are searched for and pulled out
|
||||
/// of this new string, so the final result looks like this:
|
||||
///
|
||||
/// - `string`: "A "
|
||||
/// - `repeatingTag`: "\*\*"
|
||||
/// - `string`: "Bold "
|
||||
/// - `replacement`: "AAAAA-BBBBB-CCCCC"
|
||||
/// - `string`: " with a trailing string"
|
||||
/// - `repeatingTag`: "\*\*"
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - tokens: The tokens to be combined, scanned, re-tokenised, and merged
|
||||
/// - rule: The character rule currently being applied
|
||||
func scanReplacementTokens( _ tokens : [Token], with rule : CharacterRule ) -> [Token] {
|
||||
guard tokens.count > 0 else {
|
||||
return []
|
||||
}
|
||||
|
||||
let combinedString = tokens.map({ $0.outputString }).joined()
|
||||
|
||||
let nextTokens = self.scan(combinedString, with: rule)
|
||||
var replacedTokens = self.applyStyles(to: nextTokens, usingRule: rule)
|
||||
|
||||
/// It's necessary here to check to see if the first token (which will always represent the styles
|
||||
/// to be applied from previous scans) has any existing metadata or character styles and apply them
|
||||
/// to *all* the string and replacement tokens found by the new scan.
|
||||
for idx in 0..<replacedTokens.count {
|
||||
guard replacedTokens[idx].type == .string || replacedTokens[idx].type == .replacement else {
|
||||
continue
|
||||
}
|
||||
if tokens.first!.metadataString != nil && replacedTokens[idx].metadataString == nil {
|
||||
replacedTokens[idx].metadataString = tokens.first!.metadataString
|
||||
}
|
||||
replacedTokens[idx].characterStyles.append(contentsOf: tokens.first!.characterStyles)
|
||||
}
|
||||
|
||||
// Swap the original replacement tokens back in
|
||||
let replacements = tokens.filter({ $0.type == .replacement })
|
||||
var outputTokens : [Token] = []
|
||||
for token in replacedTokens {
|
||||
guard token.type == .string else {
|
||||
outputTokens.append(token)
|
||||
continue
|
||||
}
|
||||
outputTokens.append(contentsOf: self.reinsertReplacements(replacements, from: token))
|
||||
}
|
||||
|
||||
return outputTokens
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// This function ensures that only concurrent `string` and `replacement` tokens are processed together.
|
||||
///
|
||||
/// i.e. If there is an existing `repeatingTag` token between two strings, then those strings will be
|
||||
/// processed individually. This prevents incorrect parsing of strings like "\*\*\_Should only be bold\*\*\_"
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - incomingTokens: A group of tokens whose string tokens and replacement tokens should be combined and re-tokenised
|
||||
/// - rule: The current rule being processed
|
||||
func handleReplacementTokens( _ incomingTokens : [Token], with rule : CharacterRule) -> [Token] {
|
||||
|
||||
// Only combine string and replacements that are next to each other.
|
||||
var newTokenSet : [Token] = []
|
||||
var currentTokenSet : [Token] = []
|
||||
for i in 0..<incomingTokens.count {
|
||||
guard incomingTokens[i].type == .string || incomingTokens[i].type == .replacement else {
|
||||
newTokenSet.append(contentsOf: self.scanReplacementTokens(currentTokenSet, with: rule))
|
||||
newTokenSet.append(incomingTokens[i])
|
||||
currentTokenSet.removeAll()
|
||||
continue
|
||||
}
|
||||
guard !incomingTokens[i].isProcessed && !incomingTokens[i].isMetadata && !incomingTokens[i].shouldSkip else {
|
||||
newTokenSet.append(contentsOf: self.scanReplacementTokens(currentTokenSet, with: rule))
|
||||
newTokenSet.append(incomingTokens[i])
|
||||
currentTokenSet.removeAll()
|
||||
continue
|
||||
}
|
||||
currentTokenSet.append(incomingTokens[i])
|
||||
}
|
||||
newTokenSet.append(contentsOf: self.scanReplacementTokens(currentTokenSet, with: rule))
|
||||
|
||||
return newTokenSet
|
||||
}
|
||||
|
||||
|
||||
func handleClosingTagFromOpenTag(withIndex index : Int, in tokens: inout [Token], following rule : CharacterRule ) {
|
||||
|
||||
guard rule.closingTag != nil else {
|
||||
return
|
||||
}
|
||||
guard let closeTokenIdx = tokens.firstIndex(where: { $0.type == .closeTag && !$0.isProcessed }) else {
|
||||
return
|
||||
}
|
||||
|
||||
var metadataIndex = index
|
||||
// If there's an intermediate tag, get the index of that
|
||||
if rule.intermediateTag != nil {
|
||||
guard let nextTokenIdx = tokens.firstIndex(where: { $0.type == .intermediateTag && !$0.isProcessed }) else {
|
||||
return
|
||||
}
|
||||
metadataIndex = nextTokenIdx
|
||||
let styles : [CharacterStyling] = rule.styles[1] ?? []
|
||||
for i in index..<nextTokenIdx {
|
||||
for style in styles {
|
||||
if !tokens[i].characterStyles.contains(where: { $0.isEqualTo(style )}) {
|
||||
tokens[i].characterStyles.append(style)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var metadataString : String = ""
|
||||
for i in metadataIndex..<closeTokenIdx {
|
||||
if tokens[i].type == .string {
|
||||
metadataString.append(tokens[i].outputString)
|
||||
tokens[i].isMetadata = true
|
||||
}
|
||||
}
|
||||
|
||||
for i in index..<metadataIndex {
|
||||
if tokens[i].type == .string {
|
||||
tokens[i].metadataString = metadataString
|
||||
}
|
||||
}
|
||||
|
||||
tokens[closeTokenIdx].isProcessed = true
|
||||
tokens[metadataIndex].isProcessed = true
|
||||
tokens[index].isProcessed = true
|
||||
}
|
||||
|
||||
|
||||
/// This is here to manage how opening tags are matched with closing tags when they're all the same
|
||||
/// character.
|
||||
///
|
||||
/// Of course, because Markdown is about as loose as a spec can be while still being considered any
|
||||
/// kind of spec, the number of times this character repeats causes different effects. Then there
|
||||
/// is the ill-defined way it should work if the number of opening and closing tags are different.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - index: The index of the current token in the loop
|
||||
/// - tokens: An inout variable of the loop tokens of interest
|
||||
/// - rule: The character rule being applied
|
||||
func handleClosingTagFromRepeatingTag(withIndex index : Int, in tokens: inout [Token], following rule : CharacterRule) {
|
||||
let theToken = tokens[index]
|
||||
os_log("Found repeating tag with tag count: %i, tags: %@, current rule open tag: %@", log: .tokenising, type: .info, theToken.count, theToken.inputString, rule.openTag )
|
||||
|
||||
guard theToken.count > 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
let startIdx = index
|
||||
var endIdx : Int? = nil
|
||||
|
||||
let maxCount = (theToken.count > rule.maxTags) ? rule.maxTags : theToken.count
|
||||
// Try to find exact match first
|
||||
if let nextTokenIdx = tokens.firstIndex(where: { $0.inputString.first == theToken.inputString.first && $0.type == theToken.type && $0.count == theToken.count && $0.id != theToken.id && !$0.isProcessed }) {
|
||||
endIdx = nextTokenIdx
|
||||
}
|
||||
|
||||
if endIdx == nil, let nextTokenIdx = tokens.firstIndex(where: { $0.inputString.first == theToken.inputString.first && $0.type == theToken.type && $0.count >= 1 && $0.id != theToken.id && !$0.isProcessed }) {
|
||||
endIdx = nextTokenIdx
|
||||
}
|
||||
guard let existentEnd = endIdx else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let styles : [CharacterStyling] = rule.styles[maxCount] ?? []
|
||||
for i in startIdx..<existentEnd {
|
||||
for style in styles {
|
||||
if !tokens[i].characterStyles.contains(where: { $0.isEqualTo(style )}) {
|
||||
tokens[i].characterStyles.append(style)
|
||||
}
|
||||
}
|
||||
if rule.cancels == .allRemaining {
|
||||
tokens[i].shouldSkip = true
|
||||
}
|
||||
}
|
||||
|
||||
let maxEnd = (tokens[existentEnd].count > rule.maxTags) ? rule.maxTags : tokens[existentEnd].count
|
||||
tokens[index].count = theToken.count - maxEnd
|
||||
tokens[existentEnd].count = tokens[existentEnd].count - maxEnd
|
||||
if maxEnd < rule.maxTags {
|
||||
self.handleClosingTagFromRepeatingTag(withIndex: index, in: &tokens, following: rule)
|
||||
} else {
|
||||
tokens[existentEnd].isProcessed = true
|
||||
tokens[index].isProcessed = true
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
func applyStyles( to tokens : [Token], usingRule rule : CharacterRule ) -> [Token] {
|
||||
var mutableTokens : [Token] = tokens
|
||||
|
||||
os_log("Applying styles to tokens: %@", log: .tokenising, type: .info, tokens.oslogDisplay )
|
||||
for idx in 0..<mutableTokens.count {
|
||||
let token = mutableTokens[idx]
|
||||
switch token.type {
|
||||
case .escape:
|
||||
os_log("Found escape: %@", log: .tokenising, type: .info, token.inputString )
|
||||
case .repeatingTag:
|
||||
self.handleClosingTagFromRepeatingTag(withIndex: idx, in: &mutableTokens, following: rule)
|
||||
case .openTag:
|
||||
let theToken = mutableTokens[idx]
|
||||
os_log("Found repeating tag with tags: %@, current rule open tag: %@", log: .tokenising, type: .info, theToken.inputString, rule.openTag )
|
||||
|
||||
guard rule.closingTag != nil else {
|
||||
|
||||
// If there's an intermediate tag, get the index of that
|
||||
|
||||
// Get the index of the closing tag
|
||||
|
||||
continue
|
||||
}
|
||||
self.handleClosingTagFromOpenTag(withIndex: idx, in: &mutableTokens, following: rule)
|
||||
|
||||
|
||||
case .intermediateTag:
|
||||
let theToken = mutableTokens[idx]
|
||||
os_log("Found intermediate tag with tag count: %i, tags: %@", log: .tokenising, type: .info, theToken.count, theToken.inputString )
|
||||
|
||||
case .closeTag:
|
||||
let theToken = mutableTokens[idx]
|
||||
os_log("Found close tag with tag count: %i, tags: %@", log: .tokenising, type: .info, theToken.count, theToken.inputString )
|
||||
|
||||
case .string:
|
||||
let theToken = mutableTokens[idx]
|
||||
if theToken.isMetadata {
|
||||
os_log("Found Metadata: %@", log: .tokenising, type: .info, theToken.inputString )
|
||||
} else {
|
||||
os_log("Found String: %@", log: .tokenising, type: .info, theToken.inputString )
|
||||
}
|
||||
|
||||
if let hasMetadata = theToken.metadataString {
|
||||
os_log("...with metadata: %@", log: .tokenising, type: .info, hasMetadata )
|
||||
}
|
||||
case .replacement:
|
||||
os_log("Found replacement with ID: %@", log: .tokenising, type: .info, mutableTokens[idx].inputString )
|
||||
}
|
||||
}
|
||||
return mutableTokens
|
||||
}
|
||||
|
||||
|
||||
func scan( _ string : String, with rule : CharacterRule) -> [Token] {
|
||||
let scanner = Scanner(string: string)
|
||||
scanner.charactersToBeSkipped = nil
|
||||
var tokens : [Token] = []
|
||||
var set = CharacterSet(charactersIn: "\(rule.openTag)\(rule.intermediateTag ?? "")\(rule.closingTag ?? "")")
|
||||
if let existentEscape = rule.escapeCharacter {
|
||||
set.insert(charactersIn: String(existentEscape))
|
||||
}
|
||||
|
||||
var openTagFound = false
|
||||
var openingString = ""
|
||||
while !scanner.isAtEnd {
|
||||
|
||||
if #available(iOS 13.0, OSX 10.15, watchOS 6.0, tvOS 13.0, *) {
|
||||
if let start = scanner.scanUpToCharacters(from: set) {
|
||||
openingString.append(start)
|
||||
}
|
||||
} else {
|
||||
var string : NSString?
|
||||
scanner.scanUpToCharacters(from: set, into: &string)
|
||||
if let existentString = string as String? {
|
||||
openingString.append(existentString)
|
||||
}
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
|
||||
let lastChar : String?
|
||||
if #available(iOS 13.0, OSX 10.15, watchOS 6.0, tvOS 13.0, *) {
|
||||
lastChar = ( scanner.currentIndex > string.startIndex ) ? String(string[string.index(before: scanner.currentIndex)..<scanner.currentIndex]) : nil
|
||||
} else {
|
||||
let scanLocation = string.index(string.startIndex, offsetBy: scanner.scanLocation)
|
||||
lastChar = ( scanLocation > string.startIndex ) ? String(string[string.index(before: scanLocation)..<scanLocation]) : nil
|
||||
}
|
||||
let maybeFoundChars : String?
|
||||
if #available(iOS 13.0, OSX 10.15, watchOS 6.0, tvOS 13.0, *) {
|
||||
maybeFoundChars = scanner.scanCharacters(from: set )
|
||||
} else {
|
||||
var string : NSString?
|
||||
scanner.scanCharacters(from: set, into: &string)
|
||||
maybeFoundChars = string as String?
|
||||
}
|
||||
|
||||
let nextChar : String?
|
||||
if #available(iOS 13.0, OSX 10.15, watchOS 6.0,tvOS 13.0, *) {
|
||||
nextChar = (scanner.currentIndex != string.endIndex) ? String(string[scanner.currentIndex]) : nil
|
||||
} else {
|
||||
let scanLocation = string.index(string.startIndex, offsetBy: scanner.scanLocation)
|
||||
nextChar = (scanLocation != string.endIndex) ? String(string[scanLocation]) : nil
|
||||
}
|
||||
|
||||
guard let foundChars = maybeFoundChars else {
|
||||
tokens.append(Token(type: .string, inputString: "\(openingString)"))
|
||||
openingString = ""
|
||||
continue
|
||||
}
|
||||
|
||||
if !validateSpacing(nextCharacter: nextChar, previousCharacter: lastChar, with: rule) {
|
||||
let escapeString = String("\(rule.escapeCharacter ?? Character(""))")
|
||||
var escaped = foundChars.replacingOccurrences(of: "\(escapeString)\(rule.openTag)", with: rule.openTag)
|
||||
if let hasIntermediateTag = rule.intermediateTag {
|
||||
escaped = foundChars.replacingOccurrences(of: "\(escapeString)\(hasIntermediateTag)", with: hasIntermediateTag)
|
||||
}
|
||||
if let existentClosingTag = rule.closingTag {
|
||||
escaped = foundChars.replacingOccurrences(of: "\(escapeString)\(existentClosingTag)", with: existentClosingTag)
|
||||
}
|
||||
|
||||
openingString.append(escaped)
|
||||
continue
|
||||
}
|
||||
|
||||
var cumulativeString = ""
|
||||
var openString = ""
|
||||
var intermediateString = ""
|
||||
var closedString = ""
|
||||
var maybeEscapeNext = false
|
||||
|
||||
|
||||
func addToken( for type : TokenType ) {
|
||||
var inputString : String
|
||||
switch type {
|
||||
case .openTag:
|
||||
inputString = openString
|
||||
case .intermediateTag:
|
||||
inputString = intermediateString
|
||||
case .closeTag:
|
||||
inputString = closedString
|
||||
default:
|
||||
inputString = ""
|
||||
}
|
||||
guard !inputString.isEmpty else {
|
||||
return
|
||||
}
|
||||
if !openingString.isEmpty {
|
||||
tokens.append(Token(type: .string, inputString: "\(openingString)"))
|
||||
openingString = ""
|
||||
}
|
||||
let actualType : TokenType = ( rule.intermediateTag == nil && rule.closingTag == nil ) ? .repeatingTag : type
|
||||
|
||||
var token = Token(type: actualType, inputString: inputString)
|
||||
if rule.closingTag == nil {
|
||||
token.count = inputString.count
|
||||
}
|
||||
|
||||
tokens.append(token)
|
||||
|
||||
switch type {
|
||||
case .openTag:
|
||||
openString = ""
|
||||
case .intermediateTag:
|
||||
intermediateString = ""
|
||||
case .closeTag:
|
||||
closedString = ""
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Here I am going through and adding the characters in the found set to a cumulative string.
|
||||
// If there is an escape character, then the loop stops and any open tags are tokenised.
|
||||
for char in foundChars {
|
||||
cumulativeString.append(char)
|
||||
if maybeEscapeNext {
|
||||
|
||||
var escaped = cumulativeString
|
||||
if String(char) == rule.openTag || String(char) == rule.intermediateTag || String(char) == rule.closingTag {
|
||||
escaped = String(cumulativeString.replacingOccurrences(of: String(rule.escapeCharacter ?? Character("")), with: ""))
|
||||
}
|
||||
|
||||
openingString.append(escaped)
|
||||
cumulativeString = ""
|
||||
maybeEscapeNext = false
|
||||
}
|
||||
if let existentEscape = rule.escapeCharacter {
|
||||
if cumulativeString == String(existentEscape) {
|
||||
maybeEscapeNext = true
|
||||
addToken(for: .openTag)
|
||||
addToken(for: .intermediateTag)
|
||||
addToken(for: .closeTag)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if cumulativeString == rule.openTag {
|
||||
openString.append(char)
|
||||
cumulativeString = ""
|
||||
openTagFound = true
|
||||
} else if cumulativeString == rule.intermediateTag, openTagFound {
|
||||
intermediateString.append(cumulativeString)
|
||||
cumulativeString = ""
|
||||
} else if cumulativeString == rule.closingTag, openTagFound {
|
||||
closedString.append(char)
|
||||
cumulativeString = ""
|
||||
openTagFound = false
|
||||
}
|
||||
}
|
||||
// If we're here, it means that an escape character was found but without a corresponding
|
||||
// tag, which means it might belong to a different rule.
|
||||
// It should be added to the next group of regular characters
|
||||
|
||||
addToken(for: .openTag)
|
||||
addToken(for: .intermediateTag)
|
||||
addToken(for: .closeTag)
|
||||
openingString.append( cumulativeString )
|
||||
}
|
||||
|
||||
if !openingString.isEmpty {
|
||||
tokens.append(Token(type: .string, inputString: "\(openingString)"))
|
||||
}
|
||||
|
||||
return tokens
|
||||
}
|
||||
|
||||
func validateSpacing( nextCharacter : String?, previousCharacter : String?, with rule : CharacterRule ) -> Bool {
|
||||
switch rule.spacesAllowed {
|
||||
case .leadingSide:
|
||||
guard nextCharacter != nil else {
|
||||
return true
|
||||
}
|
||||
if nextCharacter == " " {
|
||||
return false
|
||||
}
|
||||
case .trailingSide:
|
||||
guard previousCharacter != nil else {
|
||||
return true
|
||||
}
|
||||
if previousCharacter == " " {
|
||||
return false
|
||||
}
|
||||
case .no:
|
||||
switch (previousCharacter, nextCharacter) {
|
||||
case (nil, nil), ( " ", _ ), ( _, " " ):
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
|
||||
case .oneSide:
|
||||
switch (previousCharacter, nextCharacter) {
|
||||
case (nil, " " ), (" ", nil), (" ", " " ):
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,542 +0,0 @@
|
||||
//
|
||||
// SwiftyMarkdownCharacterTests.swift
|
||||
// SwiftyMarkdownTests
|
||||
//
|
||||
// Created by Simon Fairbairn on 17/12/2019.
|
||||
// Copyright © 2019 Voyage Travel Apps. All rights reserved.
|
||||
//
|
||||
|
||||
@testable import SwiftyMarkdown
|
||||
import UIKit
|
||||
import XCTest
|
||||
|
||||
class SwiftyMarkdownCharacterTests: XCTestCase {
|
||||
|
||||
func testIsolatedCase() {
|
||||
let challenge = TokenTest(input: "[Link1](http://voyagetravelapps.com/) test, testing another link [Link2](http://voyagetravelapps.com/)", output: "Link1 test, testing another link Link2", tokens: [
|
||||
Token(type: .string, inputString: "Link1", characterStyles: [CharacterStyle.link]),
|
||||
Token(type: .string, inputString: " test, testing another link ", characterStyles: []),
|
||||
Token(type: .string, inputString: "Link2", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
let results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
}
|
||||
|
||||
|
||||
func testThatRegularTraitsAreParsedCorrectly() {
|
||||
|
||||
var challenge = TokenTest(input: "**A bold string**", output: "A bold string", tokens: [
|
||||
Token(type: .string, inputString: "A bold string", characterStyles: [CharacterStyle.bold])
|
||||
])
|
||||
var results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with a **bold** word", output: "A string with a bold word", tokens: [
|
||||
Token(type: .string, inputString: "A string with a ", characterStyles: []),
|
||||
Token(type: .string, inputString: "bold", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: " word", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "`Code (**should** not process internal tags)`", output: "Code (**should** not process internal tags)", tokens: [
|
||||
Token(type: .string, inputString: "Code (**should** not process internal tags) ", characterStyles: [CharacterStyle.code])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with `code` (should not be indented)", output: "A string with code (should not be indented)", tokens : [
|
||||
Token(type: .string, inputString: "A string with ", characterStyles: []),
|
||||
Token(type: .string, inputString: "code", characterStyles: [CharacterStyle.code]),
|
||||
Token(type: .string, inputString: " (should not be indented)", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "*An italicised string*", output: "An italicised string", tokens : [
|
||||
Token(type: .string, inputString: "An italicised string", characterStyles: [CharacterStyle.italic])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with *italicised* text", output: "A string with italicised text", tokens : [
|
||||
Token(type: .string, inputString: "A string with ", characterStyles: []),
|
||||
Token(type: .string, inputString: "italicised", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: " text", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "__A bold string__ with a **mix** **of** bold __styles__", output: "A bold string with a mix of bold styles", tokens : [
|
||||
Token(type: .string, inputString: "A bold string", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: "with a ", characterStyles: []),
|
||||
Token(type: .string, inputString: "mix", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: " ", characterStyles: []),
|
||||
Token(type: .string, inputString: "of", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: " bold ", characterStyles: []),
|
||||
Token(type: .string, inputString: "styles", characterStyles: [CharacterStyle.bold])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "`A code string` with multiple `code` `instances`", output: "A code string with multiple code instances", tokens : [
|
||||
Token(type: .string, inputString: "A code string", characterStyles: [CharacterStyle.code]),
|
||||
Token(type: .string, inputString: " with multiple ", characterStyles: []),
|
||||
Token(type: .string, inputString: "code", characterStyles: [CharacterStyle.code]),
|
||||
Token(type: .string, inputString: " ", characterStyles: []),
|
||||
Token(type: .string, inputString: "instances", characterStyles: [CharacterStyle.code])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "_An italic string_ with a *mix* _of_ italic *styles*", output: "An italic string with a mix of italic styles", tokens : [
|
||||
Token(type: .string, inputString: "An italic string", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: " with a ", characterStyles: []),
|
||||
Token(type: .string, inputString: "mix", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: " ", characterStyles: []),
|
||||
Token(type: .string, inputString: "of", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: " italic ", characterStyles: []),
|
||||
Token(type: .string, inputString: "styles", characterStyles: [CharacterStyle.italic])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "_An italic string_, **follwed by a bold one**, `with some code`, \\*\\*and some\\*\\* \\_escaped\\_ \\`characters\\`, `ending` *with* __more__ variety.", output: "An italic string, follwed by a bold one, with some code, **and some** _escaped_ `characters`, ending with more variety.", tokens : [
|
||||
Token(type: .string, inputString: "An italic string", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: ", ", characterStyles: []),
|
||||
Token(type: .string, inputString: "followed by a bold one", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: ", ", characterStyles: []),
|
||||
Token(type: .string, inputString: "with some code", characterStyles: [CharacterStyle.code]),
|
||||
Token(type: .string, inputString: ", **and some** _escaped_ `characters`, ", characterStyles: []),
|
||||
Token(type: .string, inputString: "ending", characterStyles: [CharacterStyle.code]),
|
||||
Token(type: .string, inputString: " ", characterStyles: []),
|
||||
Token(type: .string, inputString: "with", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: " ", characterStyles: []),
|
||||
Token(type: .string, inputString: "more", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: " variety.", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
}
|
||||
|
||||
func testThatExtraCharactersAreHandles() {
|
||||
var challenge = TokenTest(input: "***A bold italic string***", output: "A bold italic string", tokens: [
|
||||
Token(type: .string, inputString: "A bold italic string", characterStyles: [CharacterStyle.bold, CharacterStyle.italic])
|
||||
])
|
||||
var results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with a ****bold italic**** word", output: "A string with a *bold italic* word", tokens: [
|
||||
Token(type: .string, inputString: "A string with a ", characterStyles: []),
|
||||
Token(type: .string, inputString: "*bold italic*", characterStyles: [CharacterStyle.bold, CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: " word", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with a ****bold italic*** word", output: "A string with a *bold italic word", tokens: [
|
||||
Token(type: .string, inputString: "A string with a ", characterStyles: []),
|
||||
Token(type: .string, inputString: "*bold italic", characterStyles: [CharacterStyle.bold, CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: " word", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with a ***bold** italic* word", output: "A string with a bold italic word", tokens: [
|
||||
Token(type: .string, inputString: "A string with a ", characterStyles: []),
|
||||
Token(type: .string, inputString: "bold", characterStyles: [CharacterStyle.bold, CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: " italic", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: " word", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with a **bold*italic*bold** word", output: "A string with a bolditalicbold word", tokens: [
|
||||
Token(type: .string, inputString: "A string with a ", characterStyles: []),
|
||||
Token(type: .string, inputString: "bold", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: "italic", characterStyles: [CharacterStyle.bold, CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: "bold", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: " word", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
}
|
||||
|
||||
|
||||
// The new version of SwiftyMarkdown is a lot more strict than the old version, although this may change in future
|
||||
func offtestThatMarkdownMistakesAreHandledAppropriately() {
|
||||
let mismatchedBoldCharactersAtStart = "**This should be bold*"
|
||||
let mismatchedBoldCharactersWithin = "A string *that should be italic**"
|
||||
|
||||
var md = SwiftyMarkdown(string: mismatchedBoldCharactersAtStart)
|
||||
XCTAssertEqual(md.attributedString().string, "This should be bold")
|
||||
|
||||
md = SwiftyMarkdown(string: mismatchedBoldCharactersWithin)
|
||||
XCTAssertEqual(md.attributedString().string, "A string that should be italic")
|
||||
|
||||
}
|
||||
|
||||
func testThatEscapedCharactersAreEscapedCorrectly() {
|
||||
var challenge = TokenTest(input: "\\*\\*A normal string\\*\\*", output: "**A normal string**", tokens: [
|
||||
Token(type: .string, inputString: "**A normal string**", characterStyles: [])
|
||||
])
|
||||
var results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with double \\*\\*escaped\\*\\* asterisks", output: "A string with double **escaped** asterisks", tokens: [
|
||||
Token(type: .string, inputString: "A string with double **escaped** asterisks", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "\\_A normal string\\_", output: "_A normal string_", tokens: [
|
||||
Token(type: .string, inputString: "_A normal string_", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with \\_escaped\\_ underscores", output: "A string with _escaped_ underscores", tokens: [
|
||||
Token(type: .string, inputString: "A string with _escaped_ underscores", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "\\`A normal string\\`", output: "`A normal string`", tokens: [
|
||||
Token(type: .string, inputString: "`A normal string`", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with \\`escaped\\` backticks", output: "A string with `escaped` backticks", tokens: [
|
||||
Token(type: .string, inputString: "A string with `escaped` backticks", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "\\**One escaped, one not at either end\\**", output: "*One escaped, one not at either end*", tokens: [
|
||||
Token(type: .string, inputString: "*", characterStyles: []),
|
||||
Token(type: .string, inputString: "One escaped, one not at either end*", characterStyles: [CharacterStyle.italic]),
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with one \\**escaped\\** asterisk, one not at either end", output: "A string with one *escaped* asterisk, one not at either end", tokens: [
|
||||
Token(type: .string, inputString: "A string with one *", characterStyles: []),
|
||||
Token(type: .string, inputString: "escaped*", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: " asterisk, one not at either end", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
}
|
||||
|
||||
func offtestAdvancedEscaping() {
|
||||
|
||||
var challenge = TokenTest(input: "\\***A normal string*\\**", output: "**A normal string*", tokens: [
|
||||
Token(type: .string, inputString: "**", characterStyles: []),
|
||||
Token(type: .string, inputString: "A normal string", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: "**", characterStyles: [])
|
||||
])
|
||||
var results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with randomly *\\**escaped**\\* asterisks", output: "A string with randomly **escaped** asterisks", tokens: [
|
||||
Token(type: .string, inputString: "A string with randomly **", characterStyles: []),
|
||||
Token(type: .string, inputString: "escaped", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: "** asterisks", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
}
|
||||
|
||||
func testThatAsterisksAndUnderscoresNotAttachedToWordsAreNotRemoved() {
|
||||
let asteriskSpace = """
|
||||
An asterisk followed by a space: *
|
||||
Line break
|
||||
"""
|
||||
let backtickSpace = "A backtick followed by a space: `"
|
||||
let underscoreSpace = "An underscore followed by a space: _"
|
||||
|
||||
let asteriskFullStop = "Two asterisks followed by a full stop: **."
|
||||
let backtickFullStop = "Two backticks followed by a full stop: ``."
|
||||
let underscoreFullStop = "Two underscores followed by a full stop: __."
|
||||
|
||||
let asteriskComma = "An asterisk followed by a full stop: *, *"
|
||||
let backtickComma = "A backtick followed by a space: `, `"
|
||||
let underscoreComma = "An underscore followed by a space: _, _"
|
||||
|
||||
let asteriskWithBold = "A **bold** word followed by an asterisk * "
|
||||
let backtickWithCode = "A `code` word followed by a backtick ` "
|
||||
let underscoreWithItalic = "An _italic_ word followed by an underscore _ "
|
||||
|
||||
var md = SwiftyMarkdown(string: asteriskSpace)
|
||||
XCTAssertEqual(md.attributedString().string, asteriskSpace)
|
||||
|
||||
md = SwiftyMarkdown(string: backtickSpace)
|
||||
XCTAssertEqual(md.attributedString().string, backtickSpace)
|
||||
|
||||
md = SwiftyMarkdown(string: underscoreSpace)
|
||||
XCTAssertEqual(md.attributedString().string, underscoreSpace)
|
||||
|
||||
md = SwiftyMarkdown(string: asteriskFullStop)
|
||||
XCTAssertEqual(md.attributedString().string, asteriskFullStop)
|
||||
|
||||
md = SwiftyMarkdown(string: backtickFullStop)
|
||||
XCTAssertEqual(md.attributedString().string, backtickFullStop)
|
||||
|
||||
md = SwiftyMarkdown(string: underscoreFullStop)
|
||||
XCTAssertEqual(md.attributedString().string, underscoreFullStop)
|
||||
|
||||
md = SwiftyMarkdown(string: asteriskComma)
|
||||
XCTAssertEqual(md.attributedString().string, asteriskComma)
|
||||
|
||||
md = SwiftyMarkdown(string: backtickComma)
|
||||
XCTAssertEqual(md.attributedString().string, backtickComma)
|
||||
|
||||
md = SwiftyMarkdown(string: underscoreComma)
|
||||
XCTAssertEqual(md.attributedString().string, underscoreComma)
|
||||
|
||||
md = SwiftyMarkdown(string: asteriskWithBold)
|
||||
XCTAssertEqual(md.attributedString().string, "A bold word followed by an asterisk *")
|
||||
|
||||
md = SwiftyMarkdown(string: backtickWithCode)
|
||||
XCTAssertEqual(md.attributedString().string, "A code word followed by a backtick `")
|
||||
|
||||
md = SwiftyMarkdown(string: underscoreWithItalic)
|
||||
XCTAssertEqual(md.attributedString().string, "An italic word followed by an underscore _")
|
||||
|
||||
}
|
||||
|
||||
|
||||
func testForLinks() {
|
||||
|
||||
var challenge = TokenTest(input: "[Link at start](http://voyagetravelapps.com/)", output: "Link at start", tokens: [
|
||||
Token(type: .string, inputString: "Link at start", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
var results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
if let existentOpen = results.tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.link) ?? false) }).first {
|
||||
XCTAssertEqual(existentOpen.metadataString, "http://voyagetravelapps.com/")
|
||||
} else {
|
||||
XCTFail("Failed to find an open link tag")
|
||||
}
|
||||
|
||||
|
||||
challenge = TokenTest(input: "A [Link](http://voyagetravelapps.com/)", output: "A Link", tokens: [
|
||||
Token(type: .string, inputString: "A ", characterStyles: []),
|
||||
Token(type: .string, inputString: "Link", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
|
||||
challenge = TokenTest(input: "[Link 1](http://voyagetravelapps.com/), [Link 2](https://www.neverendingvoyage.com/)", output: "Link 1, Link 2", tokens: [
|
||||
Token(type: .string, inputString: "Link 1", characterStyles: [CharacterStyle.link]),
|
||||
Token(type: .string, inputString: ", ", characterStyles: []),
|
||||
Token(type: .string, inputString: "Link 2", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
var links = results.tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.link) ?? false) })
|
||||
XCTAssertEqual(links.count, 2)
|
||||
XCTAssertEqual(links[0].metadataString, "http://voyagetravelapps.com/")
|
||||
XCTAssertEqual(links[1].metadataString, "https://www.neverendingvoyage.com/")
|
||||
|
||||
challenge = TokenTest(input: "Email us at [simon@voyagetravelapps.com](mailto:simon@voyagetravelapps.com) Twitter [@VoyageTravelApp](twitter://user?screen_name=VoyageTravelApp)", output: "Email us at simon@voyagetravelapps.com Twitter @VoyageTravelApp", tokens: [
|
||||
Token(type: .string, inputString: "Email us at ", characterStyles: []),
|
||||
Token(type: .string, inputString: "simon@voyagetravelapps.com", characterStyles: [CharacterStyle.link]),
|
||||
Token(type: .string, inputString: " Twitter", characterStyles: []),
|
||||
Token(type: .string, inputString: "@VoyageTravelApp", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
links = results.tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.link) ?? false) })
|
||||
XCTAssertEqual(links.count, 2)
|
||||
XCTAssertEqual(links[0].metadataString, "mailto:simon@voyagetravelapps.com")
|
||||
XCTAssertEqual(links[1].metadataString, "twitter://user?screen_name=VoyageTravelApp")
|
||||
|
||||
challenge = TokenTest(input: "[Link with missing square(http://voyagetravelapps.com/)", output: "[Link with missing square(http://voyagetravelapps.com/)", tokens: [
|
||||
Token(type: .string, inputString: "Link with missing square(http://voyagetravelapps.com/)", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A [Link(http://voyagetravelapps.com/)", output: "A [Link(http://voyagetravelapps.com/)", tokens: [
|
||||
Token(type: .string, inputString: "A ", characterStyles: []),
|
||||
Token(type: .string, inputString: "[Link(http://voyagetravelapps.com/)", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
|
||||
challenge = TokenTest(input: "[Link with missing parenthesis](http://voyagetravelapps.com/", output: "[Link with missing parenthesis](http://voyagetravelapps.com/", tokens: [
|
||||
Token(type: .string, inputString: "[Link with missing parenthesis](", characterStyles: []),
|
||||
Token(type: .string, inputString: "http://voyagetravelapps.com/", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A [Link](http://voyagetravelapps.com/", output: "A [Link](http://voyagetravelapps.com/", tokens: [
|
||||
Token(type: .string, inputString: "A ", characterStyles: []),
|
||||
Token(type: .string, inputString: "[Link](", characterStyles: []),
|
||||
Token(type: .string, inputString: "http://voyagetravelapps.com/", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
}
|
||||
|
||||
func testLinksWithOtherStyles() {
|
||||
var challenge = TokenTest(input: "A **Bold [Link](http://voyagetravelapps.com/)**", output: "A Bold Link", tokens: [
|
||||
Token(type: .string, inputString: "A ", characterStyles: []),
|
||||
Token(type: .string, inputString: "Bold ", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: "Link", characterStyles: [CharacterStyle.link, CharacterStyle.bold])
|
||||
])
|
||||
var results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
// XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
var links = results.tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.link) ?? false) })
|
||||
XCTAssertEqual(links.count, 1)
|
||||
if links.count == 1 {
|
||||
XCTAssertEqual(links[0].metadataString, "http://voyagetravelapps.com/")
|
||||
} else {
|
||||
XCTFail("Incorrect link count. Expecting 1, found \(links.count)")
|
||||
}
|
||||
|
||||
|
||||
challenge = TokenTest(input: "A Bold [**Link**](http://voyagetravelapps.com/)", output: "A Bold Link", tokens: [
|
||||
Token(type: .string, inputString: "A Bold ", characterStyles: []),
|
||||
Token(type: .string, inputString: "Link", characterStyles: [CharacterStyle.bold, CharacterStyle.link])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
links = results.tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.link) ?? false) })
|
||||
XCTAssertEqual(links.count, 1)
|
||||
XCTAssertEqual(links[0].metadataString, "http://voyagetravelapps.com/")
|
||||
}
|
||||
|
||||
func testForImages() {
|
||||
let challenge = TokenTest(input: "An ", output: "An Image", tokens: [
|
||||
Token(type: .string, inputString: "An Image", characterStyles: []),
|
||||
Token(type: .string, inputString: "", characterStyles: [CharacterStyle.image])
|
||||
])
|
||||
let results = self.attempt(challenge)
|
||||
XCTAssertEqual(challenge.tokens.count, results.stringTokens.count)
|
||||
XCTAssertEqual(results.tokens.map({ $0.outputString }).joined(), challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
let links = results.tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.image) ?? false) })
|
||||
XCTAssertEqual(links.count, 1)
|
||||
XCTAssertEqual(links[0].metadataString, "imageName")
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
//
|
||||
// XCTest+SwiftyMarkdown.swift
|
||||
// SwiftyMarkdownTests
|
||||
//
|
||||
// Created by Simon Fairbairn on 17/12/2019.
|
||||
// Copyright © 2019 Voyage Travel Apps. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import SwiftyMarkdown
|
||||
|
||||
extension XCTestCase {
|
||||
func attempt( _ challenge : TokenTest ) -> (tokens : [Token], stringTokens: [Token], attributedString : NSAttributedString, foundStyles : [[CharacterStyle]], expectedStyles : [[CharacterStyle]] ) {
|
||||
let md = SwiftyMarkdown(string: challenge.input)
|
||||
let tokeniser = SwiftyTokeniser(with: SwiftyMarkdown.characterRules)
|
||||
let tokens = tokeniser.process(challenge.input)
|
||||
let stringTokens = tokens.filter({ $0.type == .string && !$0.isMetadata })
|
||||
|
||||
let existentTokenStyles = stringTokens.compactMap({ $0.characterStyles as? [CharacterStyle] })
|
||||
let expectedStyles = challenge.tokens.compactMap({ $0.characterStyles as? [CharacterStyle] })
|
||||
|
||||
return (tokens, stringTokens, md.attributedString(), existentTokenStyles, expectedStyles)
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
# SwiftyMarkdown 1.0
|
||||
|
||||
SwiftyMarkdown converts Markdown files and strings into `NSAttributedString`s using sensible defaults and a Swift-style syntax. It uses dynamic type to set the font size correctly with whatever font you'd like to use.
|
||||
|
||||
## Fully Rebuilt For 2020!
|
||||
|
||||
SwiftyMarkdown now features a more robust and reliable rules-based line processing and tokenisation engine. It has added support for images stored in the bundle (``), codeblocks, blockquotes, and unordered lists!
|
||||
|
||||
Line-level attributes can now have a paragraph alignment applied to them (e.g. `h2.aligment = .center`), and links can be underlined by setting underlineLinks to `true`.
|
||||
|
||||
It also uses the system color `.label` as the default font color on iOS 13 and above for Dark Mode support out of the box.
|
||||
|
||||
## Installation
|
||||
|
||||
### CocoaPods:
|
||||
|
||||
`pod 'SwiftyMarkdown'`
|
||||
|
||||
### SPM:
|
||||
|
||||
In Xcode, `File -> Swift Packages -> Add Package Dependency` and add the GitHub URL.
|
||||
@@ -0,0 +1,7 @@
|
||||
import XCTest
|
||||
|
||||
import AppLibrarianTests
|
||||
|
||||
var tests = [XCTestCaseEntry]()
|
||||
tests += AppLibrarianTests.allTests()
|
||||
XCTMain(tests)
|
||||
@@ -0,0 +1,806 @@
|
||||
//
|
||||
// SwiftyMarkdownCharacterTests.swift
|
||||
// SwiftyMarkdownTests
|
||||
//
|
||||
// Created by Simon Fairbairn on 17/12/2019.
|
||||
// Copyright © 2019 Voyage Travel Apps. All rights reserved.
|
||||
//
|
||||
|
||||
@testable import SwiftyMarkdown
|
||||
import XCTest
|
||||
|
||||
class SwiftyMarkdownStylingTests: SwiftyMarkdownCharacterTests {
|
||||
|
||||
func off_testIsolatedCase() {
|
||||
|
||||
challenge = TokenTest(input: "*\\***\\****b*\\***\\****\\", output: "***b***\\", tokens : [
|
||||
Token(type: .string, inputString: "*", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: "*b**", characterStyles: [CharacterStyle.bold, CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: "\\", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
return
|
||||
|
||||
challenge = TokenTest(input: """
|
||||
An asterisk: *
|
||||
Line break
|
||||
""", output: """
|
||||
An asterisk: *
|
||||
Line break
|
||||
""", tokens: [
|
||||
Token(type: .string, inputString: "An asterisk: *", characterStyles: []),
|
||||
Token(type: .string, inputString: "Line break", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count )
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
return
|
||||
|
||||
challenge = TokenTest(input: "A [referenced link][link]\n[link]: https://www.neverendingvoyage.com/", output: "A referenced link", tokens: [
|
||||
Token(type: .string, inputString: "A ", characterStyles: []),
|
||||
Token(type: .string, inputString: "referenced link", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
if results.links.count == 1 {
|
||||
XCTAssertEqual(results.links[0].metadataStrings.first, "https://www.neverendingvoyage.com/")
|
||||
} else {
|
||||
XCTFail("Incorrect link count. Expecting 1, found \(results.links.count)")
|
||||
}
|
||||
|
||||
|
||||
|
||||
challenge = TokenTest(input: "A [referenced link][link]\n[notLink]: https://www.neverendingvoyage.com/", output: "A [referenced link][link]", tokens: [
|
||||
Token(type: .string, inputString: "A [referenced link][link]", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge, rules: [.links, .images, .referencedLinks])
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
XCTAssertEqual(results.links.count, 0)
|
||||
|
||||
}
|
||||
|
||||
func testThatBoldTraitsAreRecognised() {
|
||||
challenge = TokenTest(input: "**A bold string**", output: "A bold string", tokens: [
|
||||
Token(type: .string, inputString: "A bold string", characterStyles: [CharacterStyle.bold])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with a **bold** word", output: "A string with a bold word", tokens: [
|
||||
Token(type: .string, inputString: "A string with a ", characterStyles: []),
|
||||
Token(type: .string, inputString: "bold", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: " word", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "\\*\\*A normal string\\*\\*", output: "**A normal string**", tokens: [
|
||||
Token(type: .string, inputString: "**A normal string**", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "\\\\*\\*A normal \\\\ string\\*\\*", output: "\\**A normal \\\\ string**", tokens: [
|
||||
Token(type: .string, inputString: "\\**A normal \\\\ string**", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with double \\*\\*escaped\\*\\* asterisks", output: "A string with double **escaped** asterisks", tokens: [
|
||||
Token(type: .string, inputString: "A string with double **escaped** asterisks", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "\\**One escaped, one not at either end\\**", output: "*One escaped, one not at either end*", tokens: [
|
||||
Token(type: .string, inputString: "*", characterStyles: []),
|
||||
Token(type: .string, inputString: "One escaped, one not at either end*", characterStyles: [CharacterStyle.italic]),
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with one \\**escaped\\** asterisk, one not at either end", output: "A string with one *escaped* asterisk, one not at either end", tokens: [
|
||||
Token(type: .string, inputString: "A string with one *", characterStyles: []),
|
||||
Token(type: .string, inputString: "escaped*", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: " asterisk, one not at either end", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
}
|
||||
|
||||
func testThatCodeTraitsAreRecognised() {
|
||||
challenge = TokenTest(input: "`Code (**should** not process internal tags)`", output: "Code (**should** not process internal tags)", tokens: [
|
||||
Token(type: .string, inputString: "Code (**should** not process internal tags)", characterStyles: [CharacterStyle.code])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with `code` (should not be indented)", output: "A string with code (should not be indented)", tokens : [
|
||||
Token(type: .string, inputString: "A string with ", characterStyles: []),
|
||||
Token(type: .string, inputString: "code", characterStyles: [CharacterStyle.code]),
|
||||
Token(type: .string, inputString: " (should not be indented)", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "`A code string` with multiple `code` `instances`", output: "A code string with multiple code instances", tokens : [
|
||||
Token(type: .string, inputString: "A code string", characterStyles: [CharacterStyle.code]),
|
||||
Token(type: .string, inputString: " with multiple ", characterStyles: []),
|
||||
Token(type: .string, inputString: "code", characterStyles: [CharacterStyle.code]),
|
||||
Token(type: .string, inputString: " ", characterStyles: []),
|
||||
Token(type: .string, inputString: "instances", characterStyles: [CharacterStyle.code])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "\\`A normal string\\`", output: "`A normal string`", tokens: [
|
||||
Token(type: .string, inputString: "`A normal string`", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with \\`escaped\\` backticks", output: "A string with `escaped` backticks", tokens: [
|
||||
Token(type: .string, inputString: "A string with `escaped` backticks", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A lonely backtick: `", output: "A lonely backtick: `", tokens: [
|
||||
Token(type: .string, inputString: "A lonely backtick: `", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "Two backticks followed by a full stop ``.", output: "Two backticks followed by a full stop ``.", tokens: [
|
||||
Token(type: .string, inputString: "Two backticks followed by a full stop ``.", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
}
|
||||
|
||||
func testThatItalicTraitsAreParsedCorrectly() {
|
||||
|
||||
challenge = TokenTest(input: "*An italicised string*", output: "An italicised string", tokens : [
|
||||
Token(type: .string, inputString: "An italicised string", characterStyles: [CharacterStyle.italic])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with *italicised* text", output: "A string with italicised text", tokens : [
|
||||
Token(type: .string, inputString: "A string with ", characterStyles: []),
|
||||
Token(type: .string, inputString: "italicised", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: " text", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
|
||||
challenge = TokenTest(input: "_An italic string_ with a *mix* _of_ italic *styles*", output: "An italic string with a mix of italic styles", tokens : [
|
||||
Token(type: .string, inputString: "An italic string", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: " with a ", characterStyles: []),
|
||||
Token(type: .string, inputString: "mix", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: " ", characterStyles: []),
|
||||
Token(type: .string, inputString: "of", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: " italic ", characterStyles: []),
|
||||
Token(type: .string, inputString: "styles", characterStyles: [CharacterStyle.italic])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
|
||||
challenge = TokenTest(input: "\\_A normal string\\_", output: "_A normal string_", tokens: [
|
||||
Token(type: .string, inputString: "_A normal string_", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with \\_escaped\\_ underscores", output: "A string with _escaped_ underscores", tokens: [
|
||||
Token(type: .string, inputString: "A string with _escaped_ underscores", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: """
|
||||
An asterisk: *
|
||||
Line break
|
||||
""", output: """
|
||||
An asterisk: *
|
||||
Line break
|
||||
""", tokens: [
|
||||
Token(type: .string, inputString: "An asterisk: *", characterStyles: []),
|
||||
Token(type: .string, inputString: "Line break", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count )
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
}
|
||||
|
||||
func testThatStrikethroughTraitsAreRecognised() {
|
||||
challenge = TokenTest(input: "~~An~~A crossed-out string", output: "AnA crossed-out string", tokens: [
|
||||
Token(type: .string, inputString: "An", characterStyles: [CharacterStyle.strikethrough]),
|
||||
Token(type: .string, inputString: "A crossed-out string", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
|
||||
challenge = TokenTest(input: "A **Bold** string and a ~~removed~~crossed-out string", output: "A Bold string and a removedcrossed-out string", tokens: [
|
||||
Token(type: .string, inputString: "A ", characterStyles: []),
|
||||
Token(type: .string, inputString: "Bold", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: " string and a ", characterStyles: []),
|
||||
Token(type: .string, inputString: "removed", characterStyles: [CharacterStyle.strikethrough]),
|
||||
Token(type: .string, inputString: "crossed-out string", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
|
||||
challenge = TokenTest(input: "\\~\\~removed\\~\\~crossed-out string. ~This should be ignored~", output: "~~removed~~crossed-out string. ~This should be ignored~", tokens: [
|
||||
Token(type: .string, inputString: "~~removed~~crossed-out string. ~This should be ignored~", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
|
||||
}
|
||||
|
||||
func testThatMixedTraitsAreRecognised() {
|
||||
|
||||
challenge = TokenTest(input: "__A bold string__ with a **mix** **of** bold __styles__", output: "A bold string with a mix of bold styles", tokens : [
|
||||
Token(type: .string, inputString: "A bold string", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: " with a ", characterStyles: []),
|
||||
Token(type: .string, inputString: "mix", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: " ", characterStyles: []),
|
||||
Token(type: .string, inputString: "of", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: " bold ", characterStyles: []),
|
||||
Token(type: .string, inputString: "styles", characterStyles: [CharacterStyle.bold])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "_An italic string_, **followed by a bold one**, `with some code`, \\*\\*and some\\*\\* \\_escaped\\_ \\`characters\\`, `ending` *with* __more__ variety.", output: "An italic string, followed by a bold one, with some code, **and some** _escaped_ `characters`, ending with more variety.", tokens : [
|
||||
Token(type: .string, inputString: "An italic string", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: ", ", characterStyles: []),
|
||||
Token(type: .string, inputString: "followed by a bold one", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: ", ", characterStyles: []),
|
||||
Token(type: .string, inputString: "with some code", characterStyles: [CharacterStyle.code]),
|
||||
Token(type: .string, inputString: ", **and some** _escaped_ `characters`, ", characterStyles: []),
|
||||
Token(type: .string, inputString: "ending", characterStyles: [CharacterStyle.code]),
|
||||
Token(type: .string, inputString: " ", characterStyles: []),
|
||||
Token(type: .string, inputString: "with", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: " ", characterStyles: []),
|
||||
Token(type: .string, inputString: "more", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: " variety.", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
}
|
||||
|
||||
func testForExtremeEscapeCombinations() {
|
||||
|
||||
challenge = TokenTest(input: "\\****b\\****", output: "*b*", tokens : [
|
||||
Token(type: .string, inputString: "*", characterStyles: []),
|
||||
Token(type: .string, inputString: "b*", characterStyles: [CharacterStyle.bold, CharacterStyle.italic])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "**\\**b*\\***", output: "*b*", tokens : [
|
||||
Token(type: .string, inputString: "*", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: "b", characterStyles: [CharacterStyle.italic, CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: "*", characterStyles: [CharacterStyle.bold]),
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
// challenge = TokenTest(input: "Before *\\***\\****A bold string*\\***\\****\\ After", output: "Before ***A bold string***\\ After", tokens : [
|
||||
// Token(type: .string, inputString: "Before ", characterStyles: []),
|
||||
// Token(type: .string, inputString: "*", characterStyles: [CharacterStyle.italic]),
|
||||
// Token(type: .string, inputString: "**", characterStyles: [CharacterStyle.bold]),
|
||||
// Token(type: .string, inputString: "A bold string**", characterStyles: [CharacterStyle.bold, CharacterStyle.italic]),
|
||||
// Token(type: .string, inputString: "\\ After", characterStyles: [])
|
||||
// ])
|
||||
// results = self.attempt(challenge)
|
||||
// if results.stringTokens.count == challenge.tokens.count {
|
||||
// for (idx, token) in results.stringTokens.enumerated() {
|
||||
// XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
// XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
// }
|
||||
// } else {
|
||||
// XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
// }
|
||||
// XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
// XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
}
|
||||
|
||||
func testThatExtraCharactersAreHandles() {
|
||||
challenge = TokenTest(input: "***A bold italic string***", output: "A bold italic string", tokens: [
|
||||
Token(type: .string, inputString: "A bold italic string", characterStyles: [CharacterStyle.bold, CharacterStyle.italic])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with a ****bold**** word", output: "A string with a bold word", tokens: [
|
||||
Token(type: .string, inputString: "A string with a ", characterStyles: []),
|
||||
Token(type: .string, inputString: "bold", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: " word", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with a ****bold italic*** word", output: "A string with a *bold italic word", tokens: [
|
||||
Token(type: .string, inputString: "A string with a ", characterStyles: []),
|
||||
Token(type: .string, inputString: "*bold italic", characterStyles: [CharacterStyle.bold, CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: " word", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with a ***bold** italic* word", output: "A string with a bold italic word", tokens: [
|
||||
Token(type: .string, inputString: "A string with a ", characterStyles: []),
|
||||
Token(type: .string, inputString: "bold", characterStyles: [CharacterStyle.bold, CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: " italic", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: " word", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with a **bold*italic*bold** word", output: "A string with a bolditalicbold word", tokens: [
|
||||
Token(type: .string, inputString: "A string with a ", characterStyles: []),
|
||||
Token(type: .string, inputString: "bold", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: "italic", characterStyles: [CharacterStyle.italic, CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: "bold", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: " word", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with ```code`", output: "A string with ```code`", tokens : [
|
||||
Token(type: .string, inputString: "A string with ```code`", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with ```code```", output: "A string with code", tokens : [
|
||||
Token(type: .string, inputString: "A string with ", characterStyles: []),
|
||||
Token(type: .string, inputString: "code", characterStyles: [CharacterStyle.code])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
}
|
||||
|
||||
|
||||
// The new version of SwiftyMarkdown is a lot more strict than the old version, although this may change in future
|
||||
func offtestThatMarkdownMistakesAreHandledAppropriately() {
|
||||
let mismatchedBoldCharactersAtStart = "**This should be bold*"
|
||||
let mismatchedBoldCharactersWithin = "A string *that should be italic**"
|
||||
|
||||
var md = SwiftyMarkdown(string: mismatchedBoldCharactersAtStart)
|
||||
XCTAssertEqual(md.attributedString().string, "This should be bold")
|
||||
|
||||
md = SwiftyMarkdown(string: mismatchedBoldCharactersWithin)
|
||||
XCTAssertEqual(md.attributedString().string, "A string that should be italic")
|
||||
|
||||
}
|
||||
|
||||
func offtestAdvancedEscaping() {
|
||||
|
||||
challenge = TokenTest(input: "\\***A normal string*\\**", output: "**A normal string*", tokens: [
|
||||
Token(type: .string, inputString: "**", characterStyles: []),
|
||||
Token(type: .string, inputString: "A normal string", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: "**", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A string with randomly *\\**escaped**\\* asterisks", output: "A string with randomly **escaped** asterisks", tokens: [
|
||||
Token(type: .string, inputString: "A string with randomly **", characterStyles: []),
|
||||
Token(type: .string, inputString: "escaped", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: "** asterisks", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
}
|
||||
|
||||
func testThatAsterisksAndUnderscoresNotAttachedToWordsAreNotRemoved() {
|
||||
|
||||
let asteriskFullStop = "Two asterisks followed by a full stop: **."
|
||||
let asteriskWithBold = "A **bold** word followed by an asterisk * "
|
||||
let underscoreFullStop = "Two underscores followed by a full stop: __."
|
||||
let asteriskComma = "An asterisk followed by a full stop: *, *"
|
||||
|
||||
let backtickSpace = "A backtick followed by a space: `"
|
||||
|
||||
let underscoreSpace = "An underscore followed by a space: _"
|
||||
|
||||
let backtickComma = "A backtick followed by a space: `, `"
|
||||
let underscoreComma = "An underscore followed by a space: _, _"
|
||||
|
||||
let backtickWithCode = "A `code` word followed by a backtick ` "
|
||||
let underscoreWithItalic = "An _italic_ word followed by an underscore _ "
|
||||
|
||||
var md = SwiftyMarkdown(string: backtickSpace)
|
||||
SwiftyMarkdown.characterRules = self.defaultRules
|
||||
XCTAssertEqual(md.attributedString().string, backtickSpace)
|
||||
|
||||
md = SwiftyMarkdown(string: underscoreSpace)
|
||||
XCTAssertEqual(md.attributedString().string, underscoreSpace)
|
||||
|
||||
md = SwiftyMarkdown(string: asteriskFullStop)
|
||||
XCTAssertEqual(md.attributedString().string, asteriskFullStop)
|
||||
|
||||
md = SwiftyMarkdown(string: underscoreFullStop)
|
||||
XCTAssertEqual(md.attributedString().string, underscoreFullStop)
|
||||
|
||||
md = SwiftyMarkdown(string: asteriskComma)
|
||||
XCTAssertEqual(md.attributedString().string, asteriskComma)
|
||||
|
||||
md = SwiftyMarkdown(string: backtickComma)
|
||||
XCTAssertEqual(md.attributedString().string, backtickComma)
|
||||
|
||||
md = SwiftyMarkdown(string: underscoreComma)
|
||||
XCTAssertEqual(md.attributedString().string, underscoreComma)
|
||||
|
||||
md = SwiftyMarkdown(string: asteriskWithBold)
|
||||
XCTAssertEqual(md.attributedString().string, "A bold word followed by an asterisk *")
|
||||
|
||||
md = SwiftyMarkdown(string: backtickWithCode)
|
||||
XCTAssertEqual(md.attributedString().string, "A code word followed by a backtick `")
|
||||
|
||||
md = SwiftyMarkdown(string: underscoreWithItalic)
|
||||
XCTAssertEqual(md.attributedString().string, "An italic word followed by an underscore _")
|
||||
|
||||
}
|
||||
|
||||
func testReportedCrashingStrings() {
|
||||
challenge = TokenTest(input: "[**\\!bang**](https://duckduckgo.com/bang)", output: "\\!bang", tokens: [
|
||||
Token(type: .string, inputString: "\\!bang", characterStyles: [CharacterStyle.link, CharacterStyle.bold])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+64
-5
@@ -114,6 +114,63 @@ class SwiftyMarkdownTests: XCTestCase {
|
||||
XCTAssertEqual(md.attributedString().string, h2StringWithCode.expectedOutput)
|
||||
}
|
||||
|
||||
func testThatUnorderedListsAreHandledCorrectly() {
|
||||
let dashBullets = StringTest(input: "An Unordered List\n- Item 1\n\t- Indented\n- Item 2", expectedOutput: "An Unordered List\n-\tItem 1\n\t-\tIndented\n-\tItem 2")
|
||||
var md = SwiftyMarkdown(string: dashBullets.input)
|
||||
md.bullet = "-"
|
||||
XCTAssertEqual(md.attributedString().string, dashBullets.expectedOutput)
|
||||
|
||||
let starBullets = StringTest(input: "An Unordered List\n* Item 1\n\t* Indented\n* Item 2", expectedOutput: "An Unordered List\n-\tItem 1\n\t-\tIndented\n-\tItem 2")
|
||||
md = SwiftyMarkdown(string: starBullets.input)
|
||||
md.bullet = "-"
|
||||
XCTAssertEqual(md.attributedString().string, starBullets.expectedOutput)
|
||||
|
||||
}
|
||||
|
||||
func testThatOrderedListsAreHandled() {
|
||||
let dashBullets = StringTest(input: "An Ordered List\n1. Item 1\n\t1. Indented\n1. Item 2", expectedOutput: "An Ordered List\n1.\tItem 1\n\t1.\tIndented\n2.\tItem 2")
|
||||
var md = SwiftyMarkdown(string: dashBullets.input)
|
||||
XCTAssertEqual(md.attributedString().string, dashBullets.expectedOutput)
|
||||
|
||||
let moreComplicatedList = StringTest(input: """
|
||||
A long ordered list:
|
||||
|
||||
1. Item 1
|
||||
1. Item 2
|
||||
1. First Indent 1
|
||||
1. First Indent 2
|
||||
1. Second Indent 1
|
||||
1. First Indent 3
|
||||
1. Second Indent 2
|
||||
1. Item 3
|
||||
|
||||
A break
|
||||
|
||||
1. Item 1
|
||||
1. Item 2
|
||||
""", expectedOutput: """
|
||||
A long ordered list:
|
||||
|
||||
1. Item 1
|
||||
2. Item 2
|
||||
1. First Indent 1
|
||||
2. First Indent 2
|
||||
1. Second Indent 1
|
||||
3. First Indent 3
|
||||
1. Second Indent 2
|
||||
3. Item 3
|
||||
|
||||
A break
|
||||
|
||||
1. Item 1
|
||||
2. Item 2
|
||||
""")
|
||||
md = SwiftyMarkdown(string: moreComplicatedList.input)
|
||||
XCTAssertEqual(md.attributedString().string, moreComplicatedList.expectedOutput)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
@@ -132,11 +189,13 @@ class SwiftyMarkdownTests: XCTestCase {
|
||||
|
||||
}
|
||||
|
||||
func testReportedCrashingStrings() {
|
||||
let text = "[**\\!bang**](https://duckduckgo.com/bang) "
|
||||
let expected = "\\!bang"
|
||||
let output = SwiftyMarkdown(string: text).attributedString().string
|
||||
XCTAssertEqual(output, expected)
|
||||
|
||||
|
||||
func testThatYAMLMetadataIsRemoved() {
|
||||
let yaml = StringTest(input: "---\nlayout: page\ntitle: \"Trail Wallet FAQ\"\ndate: 2015-04-22 10:59\ncomments: true\nsharing: true\nliking: false\nfooter: true\nsidebar: false\n---\n# Finally some Markdown!\n\nWith A Heading\n---", expectedOutput: "Finally some Markdown!\n\nWith A Heading")
|
||||
let md = SwiftyMarkdown(string: yaml.input)
|
||||
XCTAssertEqual(md.attributedString().string, yaml.expectedOutput)
|
||||
XCTAssertEqual(md.frontMatterAttributes.count, 8)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,734 @@
|
||||
//
|
||||
// SwiftyMarkdownCharacterTests.swift
|
||||
// SwiftyMarkdownTests
|
||||
//
|
||||
// Created by Simon Fairbairn on 17/12/2019.
|
||||
// Copyright © 2019 Voyage Travel Apps. All rights reserved.
|
||||
//
|
||||
|
||||
@testable import SwiftyMarkdown
|
||||
import XCTest
|
||||
|
||||
class SwiftyMarkdownLinkTests: SwiftyMarkdownCharacterTests {
|
||||
|
||||
func testSingleLinkPositions() {
|
||||
challenge = TokenTest(input: "[a](b)", output: "a", tokens: [
|
||||
Token(type: .string, inputString: "a", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
results = self.attempt(challenge, rules: [.links])
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
if let existentOpen = results.tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.link) ?? false) }).first {
|
||||
XCTAssertEqual(existentOpen.metadataStrings.first, "b")
|
||||
} else {
|
||||
XCTFail("Failed to find an open link tag")
|
||||
}
|
||||
|
||||
challenge = TokenTest(input: "[Link at](http://voyagetravelapps.com/) start", output: "Link at start", tokens: [
|
||||
Token(type: .string, inputString: "Link at", characterStyles: [CharacterStyle.link]),
|
||||
Token(type: .string, inputString: " start")
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
if let existentOpen = results.tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.link) ?? false) }).first {
|
||||
XCTAssertEqual(existentOpen.metadataStrings.first, "http://voyagetravelapps.com/")
|
||||
} else {
|
||||
XCTFail("Failed to find an open link tag")
|
||||
}
|
||||
|
||||
challenge = TokenTest(input: "A [link at end](http://voyagetravelapps.com/)", output: "A link at end", tokens: [
|
||||
Token(type: .string, inputString: "A ", characterStyles: []),
|
||||
Token(type: .string, inputString: "link at end", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A [link in the](http://voyagetravelapps.com/) middle", output: "A link in the middle", tokens: [
|
||||
Token(type: .string, inputString: "A ", characterStyles: []),
|
||||
Token(type: .string, inputString: "link in the", characterStyles: [CharacterStyle.link]),
|
||||
Token(type: .string, inputString: " middle", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
}
|
||||
|
||||
func testEscapedLinks() {
|
||||
challenge = TokenTest(input: "\\[a](b)", output: "[a](b)", tokens: [
|
||||
Token(type: .string, inputString: "[a](b)", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge, rules: [.images, .referencedLinks, .links])
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "", output: "!a", tokens: [
|
||||
Token(type: .string, inputString: "!", characterStyles: []),
|
||||
Token(type: .string, inputString: "a", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
results = self.attempt(challenge, rules: [.links])
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
}
|
||||
|
||||
func testMultipleLinkPositions() {
|
||||
|
||||
challenge = TokenTest(input: "[Link 1](http://voyagetravelapps.com/)[Link 2](https://www.neverendingvoyage.com/)", output: "Link 1Link 2", tokens: [
|
||||
Token(type: .string, inputString: "Link 1", characterStyles: [CharacterStyle.link]),
|
||||
Token(type: .string, inputString: "Link 2", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
var links = results.tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.link) ?? false) })
|
||||
if links.count == 2 {
|
||||
XCTAssertEqual(links[0].metadataStrings.first, "http://voyagetravelapps.com/")
|
||||
XCTAssertEqual(links[1].metadataStrings.first, "https://www.neverendingvoyage.com/")
|
||||
} else {
|
||||
XCTFail("Incorrect number of links found. Expecting 2, found \(links.count)")
|
||||
}
|
||||
|
||||
challenge = TokenTest(input: "[Link 1](http://voyagetravelapps.com/), [Link 2](https://www.neverendingvoyage.com/)", output: "Link 1, Link 2", tokens: [
|
||||
Token(type: .string, inputString: "Link 1", characterStyles: [CharacterStyle.link]),
|
||||
Token(type: .string, inputString: ", ", characterStyles: []),
|
||||
Token(type: .string, inputString: "Link 2", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
links = results.tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.link) ?? false) })
|
||||
if links.count == 2 {
|
||||
XCTAssertEqual(links[0].metadataStrings.first, "http://voyagetravelapps.com/")
|
||||
XCTAssertEqual(links[1].metadataStrings.first, "https://www.neverendingvoyage.com/")
|
||||
} else {
|
||||
XCTFail("Incorrect number of links found. Expecting 2, found \(links.count)")
|
||||
}
|
||||
|
||||
challenge = TokenTest(input: "String at start [Link 1](http://voyagetravelapps.com/), [Link 2](https://www.neverendingvoyage.com/)", output: "String at start Link 1, Link 2", tokens: [
|
||||
Token(type: .string, inputString: "String at start ", characterStyles: []),
|
||||
Token(type: .string, inputString: "Link 1", characterStyles: [CharacterStyle.link]),
|
||||
Token(type: .string, inputString: ", ", characterStyles: []),
|
||||
Token(type: .string, inputString: "Link 2", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
links = results.tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.link) ?? false) })
|
||||
if links.count == 2 {
|
||||
XCTAssertEqual(links[0].metadataStrings.first, "http://voyagetravelapps.com/")
|
||||
XCTAssertEqual(links[1].metadataStrings.first, "https://www.neverendingvoyage.com/")
|
||||
} else {
|
||||
XCTFail("Incorrect number of links found. Expecting 2, found \(links.count)")
|
||||
}
|
||||
|
||||
challenge = TokenTest(input: "String at start [Link 1](http://voyagetravelapps.com/)[Link 2](https://www.neverendingvoyage.com/)", output: "String at start Link 1Link 2", tokens: [
|
||||
Token(type: .string, inputString: "String at start ", characterStyles: []),
|
||||
Token(type: .string, inputString: "Link 1", characterStyles: [CharacterStyle.link]),
|
||||
Token(type: .string, inputString: "Link 2", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
links = results.tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.link) ?? false) })
|
||||
if links.count == 2 {
|
||||
XCTAssertEqual(links[0].metadataStrings.first, "http://voyagetravelapps.com/")
|
||||
XCTAssertEqual(links[1].metadataStrings.first, "https://www.neverendingvoyage.com/")
|
||||
} else {
|
||||
XCTFail("Incorrect number of links found. Expecting 2, found \(links.count)")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
func testForAlternativeURLs() {
|
||||
|
||||
|
||||
challenge = TokenTest(input: "Email us at [simon@voyagetravelapps.com](mailto:simon@voyagetravelapps.com) Twitter [@VoyageTravelApp](twitter://user?screen_name=VoyageTravelApp)", output: "Email us at simon@voyagetravelapps.com Twitter @VoyageTravelApp", tokens: [
|
||||
Token(type: .string, inputString: "Email us at ", characterStyles: []),
|
||||
Token(type: .string, inputString: "simon@voyagetravelapps.com", characterStyles: [CharacterStyle.link]),
|
||||
Token(type: .string, inputString: " Twitter ", characterStyles: []),
|
||||
Token(type: .string, inputString: "@VoyageTravelApp", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
let links = results.tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.link) ?? false) })
|
||||
if links.count == 2 {
|
||||
XCTAssertEqual(links[0].metadataStrings.first, "mailto:simon@voyagetravelapps.com")
|
||||
XCTAssertEqual(links[1].metadataStrings.first, "twitter://user?screen_name=VoyageTravelApp")
|
||||
} else {
|
||||
XCTFail("Incorrect number of links found. Expecting 2, found \(links.count)")
|
||||
}
|
||||
}
|
||||
|
||||
func testForLinksMixedWithTokenCharacters() {
|
||||
|
||||
challenge = TokenTest(input: "Link ([Surrounded by parentheses](https://www.neverendingvoyage.com/))", output: "Link (Surrounded by parentheses)", tokens: [
|
||||
Token(type: .string, inputString: "Link (", characterStyles: []),
|
||||
Token(type: .string, inputString: "Surrounded by parentheses", characterStyles: [CharacterStyle.link]),
|
||||
Token(type: .string, inputString: ")", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
var links = results.tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.link) ?? false) })
|
||||
if links.count == 1 {
|
||||
XCTAssertEqual(links[0].metadataStrings.first, "https://www.neverendingvoyage.com/")
|
||||
} else {
|
||||
XCTFail("Incorrect number of links found. Expecting 2, found \(links.count)")
|
||||
}
|
||||
|
||||
challenge = TokenTest(input: "[[Surrounded by square brackets](https://www.neverendingvoyage.com/)]", output: "[Surrounded by square brackets]", tokens: [
|
||||
Token(type: .string, inputString: "[", characterStyles: []),
|
||||
Token(type: .string, inputString: "Surrounded by square brackets", characterStyles: [CharacterStyle.link]),
|
||||
Token(type: .string, inputString: "]", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
links = results.tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.link) ?? false) })
|
||||
if links.count == 1 {
|
||||
XCTAssertEqual(links[0].metadataStrings.first, "https://www.neverendingvoyage.com/")
|
||||
} else {
|
||||
XCTFail("Incorrect number of links found. Expecting 2, found \(links.count)")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func testMalformedLinks() {
|
||||
|
||||
challenge = TokenTest(input: "[Link with missing parenthesis](http://voyagetravelapps.com/", output: "[Link with missing parenthesis](http://voyagetravelapps.com/", tokens: [
|
||||
Token(type: .string, inputString: "[Link with missing parenthesis](http://voyagetravelapps.com/", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count )
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "A [Link](http://voyagetravelapps.com/", output: "A [Link](http://voyagetravelapps.com/", tokens: [
|
||||
Token(type: .string, inputString: "A [Link](http://voyagetravelapps.com/", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "[A link](((url)", output: "[A link](((url)", tokens: [
|
||||
Token(type: .string, inputString: "[A link](((url)", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge, rules: [.images, .links])
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
XCTAssertEqual(results.links.count, 0)
|
||||
|
||||
challenge = TokenTest(input: "[[a](((b)](c)", output: "[a](((b)", tokens: [
|
||||
Token(type: .string, inputString: "[a](((b)", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
results = self.attempt(challenge, rules: [.images, .links])
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
XCTAssertEqual(results.links.count, 1)
|
||||
if results.links.count == 1 {
|
||||
XCTAssertEqual(results.links[0].metadataStrings.first, "c")
|
||||
} else {
|
||||
XCTFail("Incorrect link count. Expecting 1, found \(results.links.count)")
|
||||
}
|
||||
|
||||
|
||||
|
||||
challenge = TokenTest(input: "[Link with missing square(http://voyagetravelapps.com/)", output: "[Link with missing square(http://voyagetravelapps.com/)", tokens: [
|
||||
Token(type: .string, inputString: "[Link with missing square(http://voyagetravelapps.com/)", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
challenge = TokenTest(input: "[Link with [second opening](http://voyagetravelapps.com/)", output: "[Link with second opening", tokens: [
|
||||
Token(type: .string, inputString: "[Link with ", characterStyles: []),
|
||||
Token(type: .string, inputString: "second opening", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
XCTAssertEqual(results.links.count, 1)
|
||||
if results.links.count == 1 {
|
||||
XCTAssertEqual(results.links[0].metadataStrings.first, "http://voyagetravelapps.com/")
|
||||
} else {
|
||||
XCTFail("Incorrect link count. Expecting 1, found \(results.links.count)")
|
||||
}
|
||||
|
||||
challenge = TokenTest(input: "A [Link(http://voyagetravelapps.com/)", output: "A [Link(http://voyagetravelapps.com/)", tokens: [
|
||||
Token(type: .string, inputString: "A [Link(http://voyagetravelapps.com/)", characterStyles: [])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
|
||||
}
|
||||
|
||||
func testMalformedLinksWithValidLinks() {
|
||||
|
||||
challenge = TokenTest(input: "[Link with missing parenthesis](http://voyagetravelapps.com/ followed by a [valid link](http://voyagetravelapps.com/)", output: "[Link with missing parenthesis](http://voyagetravelapps.com/ followed by a valid link", tokens: [
|
||||
Token(type: .string, inputString: "[Link with missing parenthesis](http://voyagetravelapps.com/ followed by a ", characterStyles: []),
|
||||
Token(type: .string, inputString: "valid link", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count )
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
XCTAssertEqual(results.links.count, 1)
|
||||
if results.links.count == 1 {
|
||||
XCTAssertEqual(results.links[0].metadataStrings.first, "http://voyagetravelapps.com/")
|
||||
} else {
|
||||
XCTFail("Incorrect link count. Expecting 1, found \(results.links.count)")
|
||||
}
|
||||
|
||||
challenge = TokenTest(input: "A [Link](http://voyagetravelapps.com/ followed by a [valid link](http://voyagetravelapps.com/)", output: "A [Link](http://voyagetravelapps.com/ followed by a valid link", tokens: [
|
||||
Token(type: .string, inputString: "A [Link](http://voyagetravelapps.com/ followed by a ", characterStyles: []),
|
||||
Token(type: .string, inputString: "valid link", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
XCTAssertEqual(results.links.count, 1)
|
||||
if results.links.count == 1 {
|
||||
XCTAssertEqual(results.links[0].metadataStrings.first, "http://voyagetravelapps.com/")
|
||||
} else {
|
||||
XCTFail("Incorrect link count. Expecting 1, found \(results.links.count)")
|
||||
}
|
||||
|
||||
challenge = TokenTest(input: "[Link with missing square(http://voyagetravelapps.com/) followed by a [valid link](http://voyagetravelapps.com/)", output: "[Link with missing square(http://voyagetravelapps.com/) followed by a valid link", tokens: [
|
||||
Token(type: .string, inputString: "[Link with missing square(http://voyagetravelapps.com/) followed by a ", characterStyles: []),
|
||||
Token(type: .string, inputString: "valid link", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
XCTAssertEqual(results.links.count, 1)
|
||||
if results.links.count == 1 {
|
||||
XCTAssertEqual(results.links[0].metadataStrings.first, "http://voyagetravelapps.com/")
|
||||
} else {
|
||||
XCTFail("Incorrect link count. Expecting 1, found \(results.links.count)")
|
||||
}
|
||||
|
||||
challenge = TokenTest(input: "A [Link(http://voyagetravelapps.com/) followed by a [valid link](http://voyagetravelapps.com/)", output: "A [Link(http://voyagetravelapps.com/) followed by a valid link", tokens: [
|
||||
Token(type: .string, inputString: "A [Link(http://voyagetravelapps.com/) followed by a ", characterStyles: []),
|
||||
Token(type: .string, inputString: "valid link", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
|
||||
|
||||
}
|
||||
|
||||
func testLinksWithOtherStyles() {
|
||||
challenge = TokenTest(input: "A **Bold [Link](http://voyagetravelapps.com/)**", output: "A Bold Link", tokens: [
|
||||
Token(type: .string, inputString: "A ", characterStyles: []),
|
||||
Token(type: .string, inputString: "Bold ", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: "Link", characterStyles: [CharacterStyle.link, CharacterStyle.bold])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.links.count, 1)
|
||||
if results.links.count == 1 {
|
||||
XCTAssertEqual(results.links[0].metadataStrings.first, "http://voyagetravelapps.com/")
|
||||
} else {
|
||||
XCTFail("Incorrect link count. Expecting 1, found \(results.links.count)")
|
||||
}
|
||||
|
||||
challenge = TokenTest(input: "A Bold [**Link**](http://voyagetravelapps.com/)", output: "A Bold Link", tokens: [
|
||||
Token(type: .string, inputString: "A Bold ", characterStyles: []),
|
||||
Token(type: .string, inputString: "Link", characterStyles: [ CharacterStyle.link, CharacterStyle.bold])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
XCTAssertEqual(results.links.count, 1)
|
||||
if results.links.count == 1 {
|
||||
XCTAssertEqual(results.links[0].metadataStrings.first, "http://voyagetravelapps.com/")
|
||||
} else {
|
||||
XCTFail("Incorrect link count. Expecting 1, found \(results.links.count)")
|
||||
}
|
||||
|
||||
challenge = TokenTest(input: "[Link1](http://voyagetravelapps.com/) **bold** [Link2](http://voyagetravelapps.com/)", output: "Link1 bold Link2", tokens: [
|
||||
Token(type: .string, inputString: "Link1", characterStyles: [CharacterStyle.link]),
|
||||
Token(type: .string, inputString: " ", characterStyles: []),
|
||||
Token(type: .string, inputString: "bold", characterStyles: [CharacterStyle.bold]),
|
||||
Token(type: .string, inputString: " ", characterStyles: []),
|
||||
Token(type: .string, inputString: "Link2", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
}
|
||||
|
||||
func testForImages() {
|
||||
challenge = TokenTest(input: "An ", output: "An ", tokens: [
|
||||
Token(type: .string, inputString: "An ", characterStyles: []),
|
||||
Token(type: .string, inputString: "Image", characterStyles: [CharacterStyle.image])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
if results.images.count == 1 {
|
||||
XCTAssertEqual(results.images[0].metadataStrings.first, "imageName")
|
||||
} else {
|
||||
XCTFail("Incorrect link count. Expecting 1, found \(results.images.count)")
|
||||
}
|
||||
|
||||
challenge = TokenTest(input: "An [](https://www.neverendingvoyage.com/)", output: "An ", tokens: [
|
||||
Token(type: .string, inputString: "An ", characterStyles: []),
|
||||
Token(type: .string, inputString: "Image", characterStyles: [CharacterStyle.image, CharacterStyle.link])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
if results.images.count == 1 {
|
||||
XCTAssertEqual(results.images[0].metadataStrings.first, "imageName")
|
||||
} else {
|
||||
XCTFail("Incorrect link count. Expecting 1, found \(results.images.count)")
|
||||
}
|
||||
if results.links.count == 1 {
|
||||
XCTAssertEqual(results.links[0].metadataStrings.last, "https://www.neverendingvoyage.com/")
|
||||
} else {
|
||||
XCTFail("Incorrect link count. Expecting 1, found \(results.links.count)")
|
||||
}
|
||||
}
|
||||
|
||||
func testForReferencedImages() {
|
||||
challenge = TokenTest(input: "A ![referenced image][image]\n[image]: imageName", output: "A ", tokens: [
|
||||
Token(type: .string, inputString: "A ", characterStyles: []),
|
||||
Token(type: .string, inputString: "referenced image", characterStyles: [CharacterStyle.image])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
if results.images.count == 1 {
|
||||
XCTAssertEqual(results.images[0].metadataStrings.first, "imageName")
|
||||
} else {
|
||||
XCTFail("Incorrect link count. Expecting 1, found \(results.links.count)")
|
||||
}
|
||||
}
|
||||
|
||||
func testForReferencedLinks() {
|
||||
challenge = TokenTest(input: "A [referenced link][link]\n[link]: https://www.neverendingvoyage.com/", output: "A referenced link", tokens: [
|
||||
Token(type: .string, inputString: "A ", characterStyles: []),
|
||||
Token(type: .string, inputString: "referenced link", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
XCTAssertEqual(results.attributedString.string, challenge.output)
|
||||
if results.links.count == 1 {
|
||||
XCTAssertEqual(results.links[0].metadataStrings.first, "https://www.neverendingvoyage.com/")
|
||||
} else {
|
||||
XCTFail("Incorrect link count. Expecting 1, found \(results.links.count)")
|
||||
}
|
||||
|
||||
challenge = TokenTest(input: "A [referenced link][link]\n [link]: https://www.neverendingvoyage.com/", output: "A referenced link", tokens: [
|
||||
Token(type: .string, inputString: "A ", characterStyles: []),
|
||||
Token(type: .string, inputString: "referenced link", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
if results.links.count == 1 {
|
||||
XCTAssertEqual(results.links[0].metadataStrings.first, "https://www.neverendingvoyage.com/")
|
||||
} else {
|
||||
XCTFail("Incorrect link count. Expecting 1, found \(results.links.count)")
|
||||
}
|
||||
|
||||
challenge = TokenTest(input: "An *\\*italic\\** [referenced link][a]\n[a]: link", output: "An *italic* referenced link", tokens: [
|
||||
Token(type: .string, inputString: "An ", characterStyles: []),
|
||||
Token(type: .string, inputString: "*italic*", characterStyles: [CharacterStyle.italic]),
|
||||
Token(type: .string, inputString: " ", characterStyles: []),
|
||||
Token(type: .string, inputString: "referenced link", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
results = self.attempt(challenge, rules: [.asterisks, .links, .referencedLinks])
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
if results.links.count == 1 {
|
||||
XCTAssertEqual(results.links[0].metadataStrings.first, "link")
|
||||
} else {
|
||||
XCTFail("Incorrect link count. Expecting 1, found \(results.links.count)")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func testForMixedLinkStyles() {
|
||||
challenge = TokenTest(input: "A [referenced link][link] and a [regular link](http://voyagetravelapps.com/)\n[link]: https://www.neverendingvoyage.com/", output: "A referenced link and a regular link", tokens: [
|
||||
Token(type: .string, inputString: "A ", characterStyles: []),
|
||||
Token(type: .string, inputString: "referenced link", characterStyles: [CharacterStyle.link]),
|
||||
Token(type: .string, inputString: " and a ", characterStyles: []),
|
||||
Token(type: .string, inputString: "regular link", characterStyles: [CharacterStyle.link])
|
||||
])
|
||||
results = self.attempt(challenge)
|
||||
if results.stringTokens.count == challenge.tokens.count {
|
||||
for (idx, token) in results.stringTokens.enumerated() {
|
||||
XCTAssertEqual(token.inputString, challenge.tokens[idx].inputString)
|
||||
XCTAssertEqual(token.characterStyles as? [CharacterStyle], challenge.tokens[idx].characterStyles as? [CharacterStyle])
|
||||
}
|
||||
} else {
|
||||
XCTAssertEqual(results.stringTokens.count, challenge.tokens.count)
|
||||
}
|
||||
XCTAssertEqual(results.foundStyles, results.expectedStyles)
|
||||
if results.links.count == 2 {
|
||||
XCTAssertEqual(results.links[0].metadataStrings.first, "https://www.neverendingvoyage.com/")
|
||||
XCTAssertEqual(results.links[1].metadataStrings.first, "http://voyagetravelapps.com/")
|
||||
} else {
|
||||
XCTFail("Incorrect link count. Expecting 1, found \(results.links.count)")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+8
-7
@@ -13,13 +13,14 @@ class SwiftyMarkdownPerformanceTests: XCTestCase {
|
||||
|
||||
func testThatFilesAreProcessedQuickly() {
|
||||
|
||||
guard let url = Bundle(for: SwiftyMarkdownPerformanceTests.self).url(forResource: "test", withExtension: "md") else {
|
||||
XCTFail("Failed to load test.md in test bundle")
|
||||
return
|
||||
}
|
||||
let url = self.resourceURL(for: "test.md")
|
||||
|
||||
measure {
|
||||
let md = SwiftyMarkdown(url: url)
|
||||
_ = md?.attributedString()
|
||||
guard let md = SwiftyMarkdown(url: url) else {
|
||||
XCTFail("Failed to load file")
|
||||
return
|
||||
}
|
||||
_ = md.attributedString()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,7 +33,7 @@ class SwiftyMarkdownPerformanceTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
func testThatVeryLongStringsAreProcessedQuickly() {
|
||||
func testThatVeryLongStringsAreProcessedQuickly() {
|
||||
let string = "SwiftyMarkdown converts Markdown files and strings into `NSAttributedString`s using sensible defaults and a *Swift*-style syntax. It uses **dynamic type** to set the font size correctly with [whatever](https://www.neverendingvoyage.com/) font you'd like to use. SwiftyMarkdown converts Markdown files and strings into `NSAttributedString`s using sensible defaults and a *Swift*-style syntax. It uses **dynamic type** to set the font size correctly with [whatever](https://www.neverendingvoyage.com/) font you'd like to use. SwiftyMarkdown converts Markdown files and strings into `NSAttributedString`s using sensible defaults and a *Swift*-style syntax. It uses **dynamic type** to set the font size correctly with [whatever](https://www.neverendingvoyage.com/) font you'd like to use. SwiftyMarkdown converts Markdown files and strings into `NSAttributedString`s using sensible defaults and a *Swift*-style syntax. It uses **dynamic type** to set the font size correctly with [whatever](https://www.neverendingvoyage.com/) font you'd like to use. SwiftyMarkdown converts Markdown files and strings into `NSAttributedString`s using sensible defaults and a *Swift*-style syntax. It uses **dynamic type** to set the font size correctly with [whatever](https://www.neverendingvoyage.com/) font you'd like to use. SwiftyMarkdown converts Markdown files and strings into `NSAttributedString`s using sensible defaults and a *Swift*-style syntax. It uses **dynamic type** to set the font size correctly with [whatever](https://www.neverendingvoyage.com/) font you'd like to use."
|
||||
let md = SwiftyMarkdown(string: string)
|
||||
measure {
|
||||
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// XCTest+SwiftyMarkdown.swift
|
||||
// SwiftyMarkdownTests
|
||||
//
|
||||
// Created by Simon Fairbairn on 17/12/2019.
|
||||
// Copyright © 2019 Voyage Travel Apps. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import SwiftyMarkdown
|
||||
|
||||
|
||||
struct ChallengeReturn {
|
||||
let tokens : [Token]
|
||||
let stringTokens : [Token]
|
||||
let links : [Token]
|
||||
let images : [Token]
|
||||
let attributedString : NSAttributedString
|
||||
let foundStyles : [[CharacterStyle]]
|
||||
let expectedStyles : [[CharacterStyle]]
|
||||
}
|
||||
|
||||
enum Rule {
|
||||
case asterisks
|
||||
case backticks
|
||||
case underscores
|
||||
case images
|
||||
case links
|
||||
case referencedLinks
|
||||
case referencedImages
|
||||
case tildes
|
||||
|
||||
func asCharacterRule() -> CharacterRule {
|
||||
switch self {
|
||||
case .images:
|
||||
return SwiftyMarkdown.characterRules.filter({ $0.primaryTag.tag == "![" && !$0.metadataLookup }).first!
|
||||
case .links:
|
||||
return SwiftyMarkdown.characterRules.filter({ $0.primaryTag.tag == "[" && !$0.metadataLookup }).first!
|
||||
case .backticks:
|
||||
return SwiftyMarkdown.characterRules.filter({ $0.primaryTag.tag == "`" }).first!
|
||||
case .tildes:
|
||||
return SwiftyMarkdown.characterRules.filter({ $0.primaryTag.tag == "~" }).first!
|
||||
case .asterisks:
|
||||
return SwiftyMarkdown.characterRules.filter({ $0.primaryTag.tag == "*" }).first!
|
||||
case .underscores:
|
||||
return SwiftyMarkdown.characterRules.filter({ $0.primaryTag.tag == "_" }).first!
|
||||
case .referencedLinks:
|
||||
return SwiftyMarkdown.characterRules.filter({ $0.primaryTag.tag == "[" && $0.metadataLookup }).first!
|
||||
case .referencedImages:
|
||||
return SwiftyMarkdown.characterRules.filter({ $0.primaryTag.tag == "![" && $0.metadataLookup }).first!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SwiftyMarkdownCharacterTests : XCTestCase {
|
||||
let defaultRules = SwiftyMarkdown.characterRules
|
||||
|
||||
var challenge : TokenTest!
|
||||
var results : ChallengeReturn!
|
||||
|
||||
func attempt( _ challenge : TokenTest, rules : [Rule]? = nil ) -> ChallengeReturn {
|
||||
if let validRules = rules {
|
||||
SwiftyMarkdown.characterRules = validRules.map({ $0.asCharacterRule() })
|
||||
} else {
|
||||
SwiftyMarkdown.characterRules = self.defaultRules
|
||||
}
|
||||
|
||||
let md = SwiftyMarkdown(string: challenge.input)
|
||||
md.applyAttachments = false
|
||||
let attributedString = md.attributedString()
|
||||
let tokens : [Token] = md.previouslyFoundTokens
|
||||
let stringTokens = tokens.filter({ $0.type == .string && !$0.isMetadata })
|
||||
|
||||
let existentTokenStyles = stringTokens.compactMap({ $0.characterStyles as? [CharacterStyle] })
|
||||
let expectedStyles = challenge.tokens.compactMap({ $0.characterStyles as? [CharacterStyle] })
|
||||
|
||||
let linkTokens = tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.link) ?? false) })
|
||||
let imageTokens = tokens.filter({ $0.type == .string && (($0.characterStyles as? [CharacterStyle])?.contains(.image) ?? false) })
|
||||
|
||||
return ChallengeReturn(tokens: tokens, stringTokens: stringTokens, links : linkTokens, images: imageTokens, attributedString: attributedString, foundStyles: existentTokenStyles, expectedStyles : expectedStyles)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension XCTestCase {
|
||||
|
||||
func resourceURL(for filename : String ) -> URL {
|
||||
let thisSourceFile = URL(fileURLWithPath: #file)
|
||||
let thisDirectory = thisSourceFile.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent()
|
||||
return thisDirectory.appendingPathComponent("Resources", isDirectory: true).appendingPathComponent(filename)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -20,4 +20,4 @@ $scheme = "SwiftyMarkdown"
|
||||
$spec = "SwiftyMarkdown.podspec"
|
||||
$project = './SwiftyMarkdown.xcodeproj'
|
||||
|
||||
import "../../Fastlane/FastfilePods"
|
||||
import "/Users/simon/Developer/Fastlane/FastfilePods"
|
||||
+35
-20
@@ -1,25 +1,26 @@
|
||||
fastlane documentation
|
||||
================
|
||||
----
|
||||
|
||||
# Installation
|
||||
|
||||
Make sure you have the latest version of the Xcode command line tools installed:
|
||||
|
||||
```
|
||||
```sh
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
Install _fastlane_ using
|
||||
```
|
||||
[sudo] gem install fastlane -NV
|
||||
```
|
||||
or alternatively using `brew cask install fastlane`
|
||||
For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
|
||||
|
||||
# Available Actions
|
||||
|
||||
## iOS
|
||||
|
||||
### ios patch
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios patch
|
||||
```
|
||||
fastlane ios patch
|
||||
```
|
||||
|
||||
This does the following:
|
||||
|
||||
|
||||
@@ -29,10 +30,13 @@ This does the following:
|
||||
- Ensures Cocoapods compatibility
|
||||
|
||||
- Bumps the patch version
|
||||
|
||||
### ios minor
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios minor
|
||||
```
|
||||
fastlane ios minor
|
||||
```
|
||||
|
||||
This does the following:
|
||||
|
||||
|
||||
@@ -42,10 +46,13 @@ This does the following:
|
||||
- Ensures Cocoapods compatibility
|
||||
|
||||
- Bumps the minor version
|
||||
|
||||
### ios major
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios major
|
||||
```
|
||||
fastlane ios major
|
||||
```
|
||||
|
||||
This does the following:
|
||||
|
||||
|
||||
@@ -55,19 +62,27 @@ This does the following:
|
||||
- Ensures Cocoapods compatibility
|
||||
|
||||
- Bumps the major version
|
||||
|
||||
### ios test
|
||||
```
|
||||
fastlane ios test
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios test
|
||||
```
|
||||
|
||||
|
||||
|
||||
### ios submit_pod
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios submit_pod
|
||||
```
|
||||
fastlane ios submit_pod
|
||||
```
|
||||
|
||||
Push the repo to remote and submits the Pod to the given spec repository. Do this after running update to run tests, bump versions, and commit changes.
|
||||
|
||||
----
|
||||
|
||||
This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
|
||||
More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
|
||||
The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
||||
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
|
||||
|
||||
More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
|
||||
|
||||
The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
||||
|
||||
Reference in New Issue
Block a user