Read and write project files

This commit is contained in:
Matthias Neeracher 2014-11-17 02:29:55 +01:00 committed by Matthias Neeracher
parent 5fe686b133
commit 2ece54f8e5
2 changed files with 126 additions and 20 deletions

View File

@ -48,6 +48,12 @@ enum ASFileType : String {
} }
} }
private let kTypeKey = "Type"
private let kNodeTypeProject = "Project"
private let kNodeTypeGroup = "Group"
private let kNodeTypeFile = "File"
private let kNameKey = "Name"
class ASFileNode { class ASFileNode {
func nodeName() -> String { func nodeName() -> String {
return "" return ""
@ -55,20 +61,44 @@ class ASFileNode {
func apply(closure:(ASFileNode)->()) { func apply(closure:(ASFileNode)->()) {
closure(self) closure(self)
} }
func propertyList(rootPath: NSString) -> AnyObject {
return ""
}
class func readPropertyList(prop: NSDictionary, rootURL: NSURL) -> ASFileNode {
switch prop[kTypeKey] as String {
case kNodeTypeProject:
return ASProject(prop, withRootURL:rootURL)
case kNodeTypeGroup:
return ASFileGroup(prop, withRootURL:rootURL)
case kNodeTypeFile:
return ASFileItem(prop, withRootURL:rootURL)
default:
assertionFailure("Undefined item type in file hierarchy")
}
}
} }
class ASFileGroup : ASFileNode { class ASFileGroup : ASFileNode {
var name : String var name : String
var children : [ASFileNode] var children : [ASFileNode]
var expanded : Bool var expanded : Bool
init(name: String) { private let kChildrenKey = "Children"
private let kExpandedKey = "Expanded"
private var kNodeType : String { return kNodeTypeGroup }
init(name: String = "") {
self.name = name self.name = name
self.children = [] self.children = []
self.expanded = true self.expanded = true
} }
convenience override init() { init(_ prop: NSDictionary, withRootURL rootURL: NSURL) {
self.init(name: "") name = prop[kNameKey] as String
expanded = prop[kExpandedKey] as Bool
children = []
for child in (prop[kChildrenKey] as NSArray) {
children.append(ASFileNode.readPropertyList(child as NSDictionary, rootURL: rootURL))
}
} }
override func nodeName() -> String { override func nodeName() -> String {
return (expanded ? "📂" : "📁")+" "+name return (expanded ? "📂" : "📁")+" "+name
@ -79,9 +109,19 @@ class ASFileGroup : ASFileNode {
child.apply(closure) child.apply(closure)
} }
} }
func childrenPropertyList(rootPath: NSString) -> [AnyObject] {
return children.map() { (node) in node.propertyList(rootPath) }
}
override func propertyList(rootPath: NSString) -> AnyObject {
return [kTypeKey: kNodeType, kNameKey: name, kExpandedKey: expanded,
kChildrenKey: childrenPropertyList(rootPath)]
}
} }
class ASProject : ASFileGroup { class ASProject : ASFileGroup {
override private var kNodeType : String { return kNodeTypeProject }
override func nodeName() -> String { override func nodeName() -> String {
return "📘 "+name return "📘 "+name
} }
@ -90,32 +130,75 @@ class ASProject : ASFileGroup {
class ASFileItem : ASFileNode { class ASFileItem : ASFileNode {
var url : NSURL var url : NSURL
var type : ASFileType var type : ASFileType
private let kPathKey = "Path"
private let kKindKey = "Kind"
init(url: NSURL, type: ASFileType) { init(url: NSURL, type: ASFileType) {
self.url = url self.url = url
self.type = type self.type = type
} }
init(_ prop: NSDictionary, withRootURL rootURL: NSURL) {
type = ASFileType(rawValue: prop[kKindKey] as String)!
url = NSURL(string: prop[kPathKey] as NSString, relativeToURL: rootURL)!.standardizedURL!
}
override func nodeName() -> String { override func nodeName() -> String {
return "📄 "+url.lastPathComponent return "📄 "+url.lastPathComponent
} }
func relativePath(relativeTo: String) -> String {
let path = url.path!
let relComp = relativeTo.componentsSeparatedByString("/") as [String]
let pathComp = path.componentsSeparatedByString("/") as [String]
let relCount = relComp.count
let pathCount = pathComp.count
var matchComp = 0
while (matchComp < relCount && matchComp < pathCount) {
if pathComp[matchComp] == relComp[matchComp] {
++matchComp
} else {
break
}
}
if matchComp==1 {
return path
}
let resComp = Array(count: relCount-matchComp, repeatedValue: "..")+pathComp[matchComp..<pathCount]
return "/".join(resComp)
}
override func propertyList(rootPath: NSString) -> AnyObject {
return [kTypeKey: kNodeTypeFile, kKindKey: type.rawValue, kPathKey: relativePath(rootPath)]
}
} }
class ASFileTree : NSObject, NSOutlineViewDataSource { class ASFileTree : NSObject, NSOutlineViewDataSource {
let root = ASProject() var root = ASProject()
var dir = NSURL()
func addFileURL(url: NSURL, omitUnknown: Bool = true) { func addFileURL(url: NSURL, omitUnknown: Bool = true) {
let type = ASFileType.guessForURL(url) let type = ASFileType.guessForURL(url)
if !omitUnknown || type != .Unknown { if !omitUnknown || type != .Unknown {
root.children.append(ASFileItem(url: url, type: type)) root.children.append(ASFileItem(url: url.standardizedURL!, type: type))
} }
} }
func setProjectURL(url: NSURL) { func setProjectURL(url: NSURL) {
root.name = url.lastPathComponent.stringByDeletingPathExtension root.name = url.lastPathComponent.stringByDeletingPathExtension
dir = url.URLByDeletingLastPathComponent!.standardizedURL!
} }
func apply(closure: (ASFileNode) -> ()) { func apply(closure: (ASFileNode) -> ()) {
root.apply(closure) root.apply(closure)
} }
func propertyList() -> AnyObject {
return root.propertyList(dir.path!)
}
func readPropertyList(prop: NSDictionary) {
root = ASFileNode.readPropertyList(prop, rootURL:dir) as ASProject
}
// MARK: Outline Data Source
func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int { func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {
if item == nil { if item == nil {
return 1 return 1

View File

@ -14,6 +14,8 @@ class ASProjDoc: NSDocument, NSOutlineViewDelegate {
let files : ASFileTree = ASFileTree() let files : ASFileTree = ASFileTree()
var mainEditor : ASFileNode? var mainEditor : ASFileNode?
// MARK: Initialization / Finalization
override init() { override init() {
super.init() super.init()
// Add your subclass-specific initialization here. // Add your subclass-specific initialization here.
@ -22,12 +24,6 @@ class ASProjDoc: NSDocument, NSOutlineViewDelegate {
saveCurEditor() saveCurEditor()
} }
func saveCurEditor() {
if let file = (mainEditor as? ASFileItem) {
editor.string().writeToURL(file.url, atomically: true, encoding: NSUTF8StringEncoding, error: nil)
}
}
override func windowControllerDidLoadNib(aController: NSWindowController) { override func windowControllerDidLoadNib(aController: NSWindowController) {
super.windowControllerDidLoadNib(aController) super.windowControllerDidLoadNib(aController)
outline.setDataSource(files) outline.setDataSource(files)
@ -50,9 +46,21 @@ class ASProjDoc: NSDocument, NSOutlineViewDelegate {
return "ASProjDoc" return "ASProjDoc"
} }
// MARK: Load / Save
func saveCurEditor() {
if let file = (mainEditor as? ASFileItem) {
editor.string().writeToURL(file.url, atomically: true, encoding: NSUTF8StringEncoding, error: nil)
}
}
let kVersionKey = "Version"
let kCurVersion = 1.0
let kFilesKey = "Files"
override func dataOfType(typeName: String, error outError: NSErrorPointer) -> NSData? { override func dataOfType(typeName: String, error outError: NSErrorPointer) -> NSData? {
outError.memory = NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil) let data = [kVersionKey: kCurVersion, kFilesKey: files.propertyList()]
return nil return NSPropertyListSerialization.dataFromPropertyList(data, format: .XMLFormat_v1_0, errorDescription: nil)
} }
func importProject(url: NSURL, error outError: NSErrorPointer) -> Bool { func importProject(url: NSURL, error outError: NSErrorPointer) -> Bool {
@ -74,16 +82,31 @@ class ASProjDoc: NSDocument, NSOutlineViewDelegate {
var success : Bool = false var success : Bool = false
if typeName == "Arduino Source File" { if typeName == "Arduino Source File" {
let projectURL = url.URLByDeletingPathExtension!.URLByAppendingPathExtension("avrsackproj") let projectURL = url.URLByDeletingPathExtension!.URLByAppendingPathExtension("avrsackproj")
fileURL = projectURL
success = importProject(url.URLByDeletingLastPathComponent!, error: outError) success = importProject(url.URLByDeletingLastPathComponent!, error: outError)
if success {
files.setProjectURL(fileURL!)
fileURL = projectURL
success = writeToURL(projectURL, ofType: "Project", forSaveOperation: .SaveAsOperation, originalContentsURL: nil, error: outError)
}
} else { } else {
success = true success = super.readFromURL(url, ofType: typeName, error: outError)
}
if success {
files.setProjectURL(fileURL!)
} }
return success return success
} }
override func readFromData(data: NSData, ofType typeName: String, error outError: NSErrorPointer) -> Bool {
if typeName != "Project" {
return false
}
files.setProjectURL(fileURL!)
let projectData : NSDictionary = NSPropertyListSerialization.propertyListFromData(data, mutabilityOption: .Immutable, format: nil, errorDescription: nil) as NSDictionary
let projectVersion = projectData[kVersionKey] as Double
assert(projectVersion <= floor(kCurVersion+1.0), "Project version too new for this app")
files.readPropertyList(projectData[kFilesKey] as NSDictionary)
return true
}
// MARK: Outline View Delegate
func outlineViewSelectionDidChange(notification: NSNotification) { func outlineViewSelectionDidChange(notification: NSNotification) {
let selection = outline.itemAtRow(outline.selectedRow) as ASFileNode? let selection = outline.itemAtRow(outline.selectedRow) as ASFileNode?