Read and write project files
This commit is contained in:
parent
5fe686b133
commit
2ece54f8e5
|
@ -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,6 +61,21 @@ 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 {
|
||||||
|
@ -62,13 +83,22 @@ class ASFileGroup : ASFileNode {
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -91,31 +131,74 @@ 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
|
||||||
|
|
|
@ -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?
|
||||||
|
|
Loading…
Reference in New Issue
Block a user