Initial import

This commit is contained in:
Donald Burr 2015-08-13 19:36:12 -07:00
parent ea6a546f97
commit e95bc01469
9 changed files with 581 additions and 0 deletions

3
.gitignore vendored
View file

@ -31,3 +31,6 @@ DerivedData
# Carthage/Checkouts # Carthage/Checkouts
Carthage/Build Carthage/Build
# DS_Store
.DS_Store

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "CommandLine"]
path = CommandLine
url = https://github.com/jatoben/CommandLine.git

1
CommandLine Submodule

@ -0,0 +1 @@
Subproject commit 7a8323d3a037c0a0d82c6cf734b46d98da10498a

60
README.md Normal file
View file

@ -0,0 +1,60 @@
opml2html
=========
### What is this? (aka the Problem to be Solved)
O HAI
Need stuff here
[NosillaCast][NC]
[Omni Group][OMNI]
[OmniOutliner][OO]
### Usage
```
Usage: opml2html [options]
-f, --input-file:
Path to the input file. (Required)
-o, --output-file:
Path to the output file. (Optional, default = <filename without extension>.html)
-l, --full-mode:
Create full HTML output (including <head>, etc.)
-h, --help:
Prints a help message.
```
### License
This software is licensed under the terms of the [MIT License][MIT].
Copyright (c) 2015 Donald Burr <[dburr@DonaldBurr.com][EMAIL]>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
### Credits
Uses Ben Gollmer's Swift [CommandLine][CL] library.
[NC]: http://podfeet.com/ "NosillaCast"
[OMNI]: https://www.omnigroup.com "The Omni Group"
[OO]: https://www.omnigroup.com/omnioutliner "OmniOutliner"
[EMAIL]: mailto:dburr@DonaldBurr.com?subject=opml2html "Email"
[MIT]: http://opensource.org/licenses/MIT "MIT License"
[CL]: https://github.com/jatoben/CommandLine "CommandLine"

BIN
bin/opml2html Executable file

Binary file not shown.

View file

@ -0,0 +1,260 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
EA273E611B7D0E810083B1C5 /* CommandLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA273E5E1B7D0E810083B1C5 /* CommandLine.swift */; };
EA273E621B7D0E810083B1C5 /* Option.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA273E5F1B7D0E810083B1C5 /* Option.swift */; };
EA273E631B7D0E810083B1C5 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA273E601B7D0E810083B1C5 /* StringExtensions.swift */; };
EA37B3E91B7C6A970025B496 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA37B3E81B7C6A970025B496 /* main.swift */; };
EA37B3F01B7C6D7C0025B496 /* ParseEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA37B3EF1B7C6D7C0025B496 /* ParseEngine.swift */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
EA37B3E31B7C6A970025B496 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
EA273E5E1B7D0E810083B1C5 /* CommandLine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CommandLine.swift; path = ../CommandLine/CommandLine/CommandLine.swift; sourceTree = "<group>"; };
EA273E5F1B7D0E810083B1C5 /* Option.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Option.swift; path = ../CommandLine/CommandLine/Option.swift; sourceTree = "<group>"; };
EA273E601B7D0E810083B1C5 /* StringExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StringExtensions.swift; path = ../CommandLine/CommandLine/StringExtensions.swift; sourceTree = "<group>"; };
EA37B3E51B7C6A970025B496 /* opml2html */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = opml2html; sourceTree = BUILT_PRODUCTS_DIR; };
EA37B3E81B7C6A970025B496 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
EA37B3EF1B7C6D7C0025B496 /* ParseEngine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParseEngine.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
EA37B3E21B7C6A970025B496 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
EA37B3DC1B7C6A970025B496 = {
isa = PBXGroup;
children = (
EA37B3E71B7C6A970025B496 /* opml2html */,
EA37B3E61B7C6A970025B496 /* Products */,
);
sourceTree = "<group>";
};
EA37B3E61B7C6A970025B496 /* Products */ = {
isa = PBXGroup;
children = (
EA37B3E51B7C6A970025B496 /* opml2html */,
);
name = Products;
sourceTree = "<group>";
};
EA37B3E71B7C6A970025B496 /* opml2html */ = {
isa = PBXGroup;
children = (
EA37B3E81B7C6A970025B496 /* main.swift */,
EA37B3EF1B7C6D7C0025B496 /* ParseEngine.swift */,
EA273E5E1B7D0E810083B1C5 /* CommandLine.swift */,
EA273E5F1B7D0E810083B1C5 /* Option.swift */,
EA273E601B7D0E810083B1C5 /* StringExtensions.swift */,
);
path = opml2html;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
EA37B3E41B7C6A970025B496 /* opml2html */ = {
isa = PBXNativeTarget;
buildConfigurationList = EA37B3EC1B7C6A970025B496 /* Build configuration list for PBXNativeTarget "opml2html" */;
buildPhases = (
EA37B3E11B7C6A970025B496 /* Sources */,
EA37B3E21B7C6A970025B496 /* Frameworks */,
EA37B3E31B7C6A970025B496 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = opml2html;
productName = opml2html;
productReference = EA37B3E51B7C6A970025B496 /* opml2html */;
productType = "com.apple.product-type.tool";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
EA37B3DD1B7C6A970025B496 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0640;
ORGANIZATIONNAME = "Donald Burr";
TargetAttributes = {
EA37B3E41B7C6A970025B496 = {
CreatedOnToolsVersion = 6.4;
};
};
};
buildConfigurationList = EA37B3E01B7C6A970025B496 /* Build configuration list for PBXProject "opml2html" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = EA37B3DC1B7C6A970025B496;
productRefGroup = EA37B3E61B7C6A970025B496 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
EA37B3E41B7C6A970025B496 /* opml2html */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
EA37B3E11B7C6A970025B496 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
EA273E621B7D0E810083B1C5 /* Option.swift in Sources */,
EA273E611B7D0E810083B1C5 /* CommandLine.swift in Sources */,
EA273E631B7D0E810083B1C5 /* StringExtensions.swift in Sources */,
EA37B3E91B7C6A970025B496 /* main.swift in Sources */,
EA37B3F01B7C6D7C0025B496 /* ParseEngine.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
EA37B3EA1B7C6A970025B496 /* 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_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
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;
MACOSX_DEPLOYMENT_TARGET = 10.10;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
EA37B3EB1B7C6A970025B496 /* 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_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
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;
MACOSX_DEPLOYMENT_TARGET = 10.10;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
};
name = Release;
};
EA37B3ED1B7C6A970025B496 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
EA37B3EE1B7C6A970025B496 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
EA37B3E01B7C6A970025B496 /* Build configuration list for PBXProject "opml2html" */ = {
isa = XCConfigurationList;
buildConfigurations = (
EA37B3EA1B7C6A970025B496 /* Debug */,
EA37B3EB1B7C6A970025B496 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
EA37B3EC1B7C6A970025B496 /* Build configuration list for PBXNativeTarget "opml2html" */ = {
isa = XCConfigurationList;
buildConfigurations = (
EA37B3ED1B7C6A970025B496 /* Debug */,
EA37B3EE1B7C6A970025B496 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = EA37B3DD1B7C6A970025B496 /* Project object */;
}

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:opml2html.xcodeproj">
</FileRef>
</Workspace>

156
opml2html/ParseEngine.swift Normal file
View file

@ -0,0 +1,156 @@
//
// ParseEngine.swift
// opml2html
//
// Created by Donald Burr on 8/12/15.
// Copyright (c) 2015 Donald Burr. All rights reserved.
//
import Foundation
protocol ParseEngineDelegate {
func parseDidSucceed(output: String)
func parseDidFail(error: NSError)
}
class ParseEngine : NSObject, NSXMLParserDelegate {
var parser: NSXMLParser?
var currentElementText: String = ""
var level: Int = 0
var fullOutput: Bool = false
var parseOutput: String = ""
var delegate: ParseEngineDelegate?
var validChars: NSMutableCharacterSet
override init() {
self.validChars = NSMutableCharacterSet(charactersInString: " ")
self.validChars.formUnionWithCharacterSet(NSCharacterSet.URLPathAllowedCharacterSet())
}
init(delegate: ParseEngineDelegate) {
self.delegate = delegate
self.validChars = NSMutableCharacterSet(charactersInString: " ")
self.validChars.formUnionWithCharacterSet(NSCharacterSet.URLPathAllowedCharacterSet())
}
private func setup() {
}
func parse(#path: String, fullOutput: Bool) {
self.fullOutput = fullOutput
if !NSFileManager.defaultManager().fileExistsAtPath(path) {
if let delegate = delegate {
let userInfo: [String:String] =
[
NSLocalizedDescriptionKey: "Operation was unsuccessful.",
NSLocalizedFailureReasonErrorKey: "File does not exist.",
NSLocalizedRecoverySuggestionErrorKey: "Check the filename."
]
let error = NSError(domain: "ParseEngineErrorDomain", code: 1, userInfo: userInfo)
delegate.parseDidFail(error)
}
return
}
let fileUrl = NSURL(fileURLWithPath: path)
parser = NSXMLParser(contentsOfURL: fileUrl)
if let p = parser {
p.delegate = self
if !p.parse() {
if let delegate = delegate {
let userInfo: [String:String] =
[
NSLocalizedDescriptionKey: "Operation was unsuccessful.",
NSLocalizedFailureReasonErrorKey: "Parser failed to parse file.",
NSLocalizedRecoverySuggestionErrorKey: "Check to make sure that the OPML file syntax is correct."
]
let error = NSError(domain: "ParseEngineErrorDomain", code: 2, userInfo: userInfo)
delegate.parseDidFail(error)
}
return
}
} else {
if let delegate = delegate {
let userInfo: [String:String] =
[
NSLocalizedDescriptionKey: "Operation was unsuccessful.",
NSLocalizedFailureReasonErrorKey: "Parser failed to initialize.",
NSLocalizedRecoverySuggestionErrorKey: "Check to make sure that the OPML file syntax is correct."
]
let error = NSError(domain: "ParseEngineErrorDomain", code: 2, userInfo: userInfo)
delegate.parseDidFail(error)
}
return
}
}
func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [NSObject : AnyObject]) {
if (elementName == "outline") {
let outlineText: String = attributeDict["text"] as! String
let saneText = saneify(outlineText)
addParsedText("<UL>\n")
addParsedText("<LI>\(saneText)</LI>\n")
level++
}
}
func parser(parser: NSXMLParser, foundCharacters string: String?) {
if let string = string {
currentElementText += string.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
}
}
func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
let saneText = saneify(currentElementText)
if (elementName == "title") {
if (fullOutput) {
addParsedText("<HTML>\n<HEAD>\n<TITLE>\(saneText)</TITLE>\n</HEAD>\n<BODY>\n")
}
addParsedText("<H1>\(saneText)</H1>\n")
currentElementText = ""
} else if (elementName == "outline") {
addParsedText("</UL>\n")
level--;
if (level == 0 && fullOutput) {
addParsedText("</BODY>\n</HTML>\n")
}
}
}
func parserDidEndDocument(parser: NSXMLParser) {
if let delegate = delegate {
delegate.parseDidSucceed(parseOutput)
}
}
func parser(parser: NSXMLParser, parseErrorOccurred parseError: NSError) {
if let delegate = delegate {
let userInfo: [String:String] =
[
NSLocalizedDescriptionKey: "Parse Failed.",
NSLocalizedFailureReasonErrorKey: "Parse Error #\(parseError.code): \(parseError.localizedDescription)",
NSLocalizedRecoverySuggestionErrorKey: "Check the validity of your XML."
]
let error = NSError(domain: "ParseEngineErrorDomain", code: 3, userInfo: userInfo)
delegate.parseDidFail(error)
}
}
private func addParsedText(text: String) {
parseOutput += text
}
private func saneify(string: String) -> String {
// let escapedText = currentElementText.stringByAddingPercentEncodingWithAllowedCharacters(self.validChars)!
// let escapedText = currentElementText.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
let string1 = string.stringByReplacingOccurrencesOfString("", withString: "'", options: NSStringCompareOptions.LiteralSearch, range: nil)
if (string1.rangeOfString("http") != nil) {
return "<A HREF=\"\(string1)\">\(string1)</A>"
} else {
return string1
}
}
}

91
opml2html/main.swift Normal file
View file

@ -0,0 +1,91 @@
//
// main.swift
// opml2html
//
// Created by Donald Burr on 8/12/15.
// Copyright (c) 2015 Donald Burr. All rights reserved.
//
import Foundation
var full: Bool = false
var fileName: String?
let cli = CommandLine()
class Handler: NSObject, ParseEngineDelegate {
var fileName: String?
let parseEngine = ParseEngine()
func process(#inFile: String, outFile: String, fullOutput: Bool) {
parseEngine.delegate = self
println("Operating on file: \(inFile)")
print("Saving to file: \(outFile)")
if (fullOutput) {
println(" (full mode)")
} else {
println()
}
self.fileName = outFile
parseEngine.parse(path: inFile, fullOutput: fullOutput)
}
func parseDidSucceed(output: String) {
if let fileName = fileName {
output.writeToFile(fileName, atomically: true, encoding: NSUTF8StringEncoding, error: nil)
} else {
println(output)
}
}
func parseDidFail(error: NSError) {
println("Parse Failed: \(error)")
}
}
let handler = Handler()
let filePath = StringOption(shortFlag: "f", longFlag: "input-file", required: true,
helpMessage: "Path to the input file. (Required)")
let outputFilePath = StringOption(shortFlag: "o", longFlag: "output-file", required: false,
helpMessage: "Path to the output file. (Optional, default = <filename without extension>.html)")
let fullMode = BoolOption(shortFlag: "l", longFlag: "full-mode",
helpMessage: "Create full HTML output (including <head>, etc.)")
let help = BoolOption(shortFlag: "h", longFlag: "help",
helpMessage: "Prints a help message.")
cli.addOptions(filePath, outputFilePath, fullMode, help)
let (success, error) = cli.parse()
if !success {
println(error!)
cli.printUsage()
exit(EX_USAGE)
}
full = fullMode.value
if let fn = filePath.value {
if let saveFile = outputFilePath.value {
handler.process(inFile: fn, outFile: saveFile, fullOutput: full)
} else {
let saveFile = fn.stringByDeletingPathExtension.stringByAppendingPathExtension("html")!
handler.process(inFile: fn, outFile: saveFile, fullOutput: full)
}
} else {
println("Error: no file name specified")
exit(1)
}
/*
for argument in Process.arguments {
switch argument {
case "--full":
println("Full mode enabled")
full = true
default:
println("an argument: \(argument)")
}
}
*/