Compare commits
73 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 |
@@ -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.2</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
|
||||
+22
-7
@@ -14,22 +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 so you can go completely wild with styling! Wow! Such styles! Much fun!
|
||||
|
||||
**List**
|
||||
**Lists**
|
||||
|
||||
- And
|
||||
- It Supports
|
||||
- Unordered
|
||||
- Lists
|
||||
- Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
|
||||
|
||||
|
||||
- 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
|
||||
|
||||
|
||||
|
||||
+174
-109
@@ -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.72.0)
|
||||
faraday (0.17.3)
|
||||
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.140.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.29.2, < 0.37.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,104 +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.36.4)
|
||||
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.9)
|
||||
httpclient (>= 2.8.1, < 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.12)
|
||||
google-cloud-core (1.5.0)
|
||||
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-errors (~> 1.0)
|
||||
google-cloud-env (1.3.0)
|
||||
faraday (~> 0.11)
|
||||
google-cloud-errors (1.0.0)
|
||||
google-cloud-storage (1.25.1)
|
||||
addressable (~> 2.5)
|
||||
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.33)
|
||||
google-cloud-core (~> 1.2)
|
||||
googleauth (~> 0.9)
|
||||
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 (0.10.0)
|
||||
faraday (~> 0.12)
|
||||
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.12)
|
||||
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)
|
||||
mini_magick (4.10.1)
|
||||
mini_mime (1.0.2)
|
||||
minitest (5.14.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)
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.7.0)
|
||||
tty-screen (0.8.1)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
tzinfo (1.2.6)
|
||||
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.1)
|
||||
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
|
||||
@@ -216,4 +281,4 @@ DEPENDENCIES
|
||||
fastlane
|
||||
|
||||
BUNDLED WITH
|
||||
2.1.4
|
||||
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,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
|
||||
|
||||
}
|
||||
}
|
||||
+78
-5
@@ -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 }
|
||||
@@ -41,6 +47,12 @@ public enum ChangeApplication {
|
||||
case untilClose
|
||||
}
|
||||
|
||||
public struct FrontMatterRule {
|
||||
let openTag : String
|
||||
let closeTag : String
|
||||
let keyValueSeparator : Character
|
||||
}
|
||||
|
||||
public struct LineRule {
|
||||
let token : String
|
||||
let removeFrom : Remove
|
||||
@@ -59,14 +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 {
|
||||
@@ -106,6 +125,10 @@ public class SwiftyLineProcessor {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !text.contains(element.token) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch element.removeFrom {
|
||||
case .leading:
|
||||
output = findLeadingLineElement(element, in: output)
|
||||
@@ -146,15 +169,63 @@ public class SwiftyLineProcessor {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
guard let input = processLineLevelAttributes(String(heading)) else {
|
||||
continue
|
||||
}
|
||||
@@ -167,6 +238,8 @@ public class SwiftyLineProcessor {
|
||||
continue
|
||||
}
|
||||
foundAttributes.append(input)
|
||||
|
||||
self.perfomanceLog.tag(with: "(line completed: \(heading)")
|
||||
}
|
||||
return foundAttributes
|
||||
}
|
||||
+8
-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
|
||||
@@ -160,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
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -133,10 +133,12 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
+219
-54
@@ -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
|
||||
}
|
||||
@@ -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,20 +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),
|
||||
@@ -141,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
|
||||
@@ -176,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()
|
||||
@@ -187,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
|
||||
@@ -209,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()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,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)
|
||||
@@ -262,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)
|
||||
@@ -278,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) {
|
||||
@@ -293,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
|
||||
|
||||
@@ -309,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
|
||||
}
|
||||
|
||||
@@ -347,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:
|
||||
@@ -362,7 +493,6 @@ extension SwiftyMarkdown {
|
||||
lineProperties = self.h5
|
||||
case .h6:
|
||||
lineProperties = self.h6
|
||||
|
||||
case .codeblock:
|
||||
lineProperties = body
|
||||
let paragraphStyle = NSMutableParagraphStyle()
|
||||
@@ -374,34 +504,59 @@ extension SwiftyMarkdown {
|
||||
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
|
||||
|
||||
let paragraphStyle = NSMutableParagraphStyle()
|
||||
let nonOptions = [NSTextTab.OptionKey: Any]()
|
||||
paragraphStyle.tabStops = [
|
||||
NSTextTab(textAlignment: .left, location: 20, options: nonOptions)]
|
||||
paragraphStyle.defaultTabInterval = 20
|
||||
paragraphStyle.headIndent = 20
|
||||
|
||||
attributes[.paragraphStyle] = paragraphStyle
|
||||
finalTokens.insert(Token(type: .string, inputString: "\(self.bullet)\t"), 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
|
||||
}
|
||||
@@ -414,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.2"
|
||||
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.2</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,151 +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>"; };
|
||||
F4FEF07423E13365007219EF /* metadataTest.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = metadataTest.md; 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 */,
|
||||
F4FEF07423E13365007219EF /* metadataTest.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 = (
|
||||
);
|
||||
@@ -158,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,784 +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] {
|
||||
os_log("Found replacement for %@", log: .tokenising, type: .info, 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
|
||||
|
||||
// Remove any replacements that don't appear in the incoming string
|
||||
var repTokens = replacements.filter({ stringToken.outputString.contains($0.inputString) })
|
||||
|
||||
var testString = "\n"
|
||||
while !scanner.isAtEnd {
|
||||
var outputString : String = ""
|
||||
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 {
|
||||
if let scanLocation = string.index(string.startIndex, offsetBy: scanner.scanLocation, limitedBy: string.endIndex) {
|
||||
lastChar = ( scanLocation > string.startIndex ) ? String(string[string.index(before: scanLocation)..<scanLocation]) : nil
|
||||
} else {
|
||||
lastChar = 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 {
|
||||
if let scanLocation = string.index(string.startIndex, offsetBy: scanner.scanLocation, limitedBy: string.endIndex) {
|
||||
nextChar = (scanLocation != string.endIndex) ? String(string[scanLocation]) : nil
|
||||
} else {
|
||||
nextChar = 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,557 +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/) **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])
|
||||
])
|
||||
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)
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+62
-9
@@ -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,17 +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!", expectedOutput: "Finally some Markdown!")
|
||||
// let md = SwiftyMarkdown(string: yaml.input)
|
||||
// XCTAssertEqual(md.attributedString().string, yaml.expectedOutput)
|
||||
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