diff --git a/Evergreen.xcodeproj/project.pbxproj b/Evergreen.xcodeproj/project.pbxproj index c719c6624..8b6611303 100644 --- a/Evergreen.xcodeproj/project.pbxproj +++ b/Evergreen.xcodeproj/project.pbxproj @@ -161,6 +161,7 @@ D5558FD32002245C0066386B /* ScriptingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5558FD22002245C0066386B /* ScriptingTests.swift */; }; D5558FD5200225680066386B /* NSAppleEventDescriptor+UserRecordFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5558FD4200225680066386B /* NSAppleEventDescriptor+UserRecordFields.swift */; }; D5558FD9200228D30066386B /* AppleEventUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5558FD7200228B80066386B /* AppleEventUtils.swift */; }; + D57BE6E0204CD35F00D11AAC /* NSScriptCommand+Evergreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57BE6DF204CD35F00D11AAC /* NSScriptCommand+Evergreen.swift */; }; D5907CA0200232A1005947E5 /* testGenericScript.applescript in Sources */ = {isa = PBXBuildFile; fileRef = D5907C9D20023249005947E5 /* testGenericScript.applescript */; }; D5907CA1200232A1005947E5 /* testGetURL.applescript in Sources */ = {isa = PBXBuildFile; fileRef = D5558FD1200223F60066386B /* testGetURL.applescript */; }; D5907CA2200232AD005947E5 /* testGenericScript.applescript in CopyFiles */ = {isa = PBXBuildFile; fileRef = D5907C9D20023249005947E5 /* testGenericScript.applescript */; }; @@ -174,6 +175,8 @@ D5A2679D201313A200A8D3C0 /* testNameOfAuthors.applescript in CopyFiles */ = {isa = PBXBuildFile; fileRef = D5A2679B201312F900A8D3C0 /* testNameOfAuthors.applescript */; }; D5A267C120131B8300A8D3C0 /* testFeedOPML.applescript in Sources */ = {isa = PBXBuildFile; fileRef = D5A267B220131B8300A8D3C0 /* testFeedOPML.applescript */; }; D5A267C220131BA000A8D3C0 /* testFeedOPML.applescript in CopyFiles */ = {isa = PBXBuildFile; fileRef = D5A267B220131B8300A8D3C0 /* testFeedOPML.applescript */; }; + D5D07B18204B42050093F739 /* testIterativeCreateAndDeleteFeed.applescript in Sources */ = {isa = PBXBuildFile; fileRef = D5D07B09204B42050093F739 /* testIterativeCreateAndDeleteFeed.applescript */; }; + D5D07B19204B423C0093F739 /* testIterativeCreateAndDeleteFeed.applescript in CopyFiles */ = {isa = PBXBuildFile; fileRef = D5D07B09204B42050093F739 /* testIterativeCreateAndDeleteFeed.applescript */; }; D5D1751220020B980047B29D /* Evergreen.sdef in Resources */ = {isa = PBXBuildFile; fileRef = D5D175012002039D0047B29D /* Evergreen.sdef */; }; D5E4CC54202C1361009B4FFC /* AppDelegate+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5E4CC53202C1361009B4FFC /* AppDelegate+Scriptability.swift */; }; D5E4CC64202C1AC1009B4FFC /* MainWindowController+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5E4CC63202C1AC1009B4FFC /* MainWindowController+Scriptability.swift */; }; @@ -480,6 +483,7 @@ dstPath = TestScripts; dstSubfolderSpec = 7; files = ( + D5D07B19204B423C0093F739 /* testIterativeCreateAndDeleteFeed.applescript in CopyFiles */, D5E4CCDE20303A66009B4FFC /* testCurrentArticleIsNil.applescript in CopyFiles */, D5E4CCDF20303A66009B4FFC /* testURLsOfCurrentArticle.applescript in CopyFiles */, D5E4CCDD20303A59009B4FFC /* establishMainWindowStartingState.applescript in CopyFiles */, @@ -660,6 +664,7 @@ D5558FD22002245C0066386B /* ScriptingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScriptingTests.swift; path = EvergreenTests/ScriptingTests/ScriptingTests.swift; sourceTree = SOURCE_ROOT; }; D5558FD4200225680066386B /* NSAppleEventDescriptor+UserRecordFields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "NSAppleEventDescriptor+UserRecordFields.swift"; path = "AppleEvents/NSAppleEventDescriptor+UserRecordFields.swift"; sourceTree = SOURCE_ROOT; }; D5558FD7200228B80066386B /* AppleEventUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleEventUtils.swift; sourceTree = ""; }; + D57BE6DF204CD35F00D11AAC /* NSScriptCommand+Evergreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSScriptCommand+Evergreen.swift"; sourceTree = ""; }; D5907C9D20023249005947E5 /* testGenericScript.applescript */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.applescript; path = testGenericScript.applescript; sourceTree = ""; }; D5907CDC2002F0BE005947E5 /* Evergreen_project_release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Evergreen_project_release.xcconfig; sourceTree = ""; }; D5907CDD2002F0BE005947E5 /* Evergreen_project_debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Evergreen_project_debug.xcconfig; sourceTree = ""; }; @@ -672,6 +677,7 @@ D5A2678B20130ECF00A8D3C0 /* Author+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Author+Scriptability.swift"; sourceTree = ""; }; D5A2679B201312F900A8D3C0 /* testNameOfAuthors.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = testNameOfAuthors.applescript; sourceTree = ""; }; D5A267B220131B8300A8D3C0 /* testFeedOPML.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = testFeedOPML.applescript; sourceTree = ""; }; + D5D07B09204B42050093F739 /* testIterativeCreateAndDeleteFeed.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = testIterativeCreateAndDeleteFeed.applescript; sourceTree = ""; }; D5D175012002039D0047B29D /* Evergreen.sdef */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = Evergreen.sdef; path = ../Resources/Evergreen.sdef; sourceTree = ""; }; D5E4CC53202C1361009B4FFC /* AppDelegate+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Scriptability.swift"; sourceTree = ""; }; D5E4CC63202C1AC1009B4FFC /* MainWindowController+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainWindowController+Scriptability.swift"; sourceTree = ""; }; @@ -1325,6 +1331,7 @@ D5A2679B201312F900A8D3C0 /* testNameOfAuthors.applescript */, D5F4EDD720075C1300B9E363 /* testNameOfEveryFolder.applescript */, D55373A02018797B006D8857 /* testTitleOfArticlesWhose.applescript */, + D5D07B09204B42050093F739 /* testIterativeCreateAndDeleteFeed.applescript */, D5E4CCD12030260E009B4FFC /* testCurrentArticleIsNil.applescript */, D5E4CCCF203025FF009B4FFC /* testURLsOfCurrentArticle.applescript */, D5E4CCB220300024009B4FFC /* uiScriptingTestSetup.applescript */, @@ -1366,6 +1373,7 @@ D5907D7E2004AC00005947E5 /* NSApplication+Scriptability.swift */, D5907DB12004BB37005947E5 /* ScriptingObjectContainer.swift */, D5F4EDB4200744A700B9E363 /* ScriptingObject.swift */, + D57BE6DF204CD35F00D11AAC /* NSScriptCommand+Evergreen.swift */, ); name = Scriptability; path = Evergreen/Scriptability; @@ -1876,6 +1884,7 @@ 849A97641ED9EB96007D329B /* SidebarOutlineView.swift in Sources */, D5A2678C20130ECF00A8D3C0 /* Author+Scriptability.swift in Sources */, 84F2D5371FC22FCC00998D64 /* PseudoFeed.swift in Sources */, + D57BE6E0204CD35F00D11AAC /* NSScriptCommand+Evergreen.swift in Sources */, D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */, 845EE7C11FC2488C00854A1F /* SmartFeed.swift in Sources */, 84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */, @@ -1971,6 +1980,7 @@ D5558FD9200228D30066386B /* AppleEventUtils.swift in Sources */, D55373AF2018797C006D8857 /* testTitleOfArticlesWhose.applescript in Sources */, D5907CA1200232A1005947E5 /* testGetURL.applescript in Sources */, + D5D07B18204B42050093F739 /* testIterativeCreateAndDeleteFeed.applescript in Sources */, D5A267C120131B8300A8D3C0 /* testFeedOPML.applescript in Sources */, D5A2679C201312F900A8D3C0 /* testNameOfAuthors.applescript in Sources */, 849C64761ED37A5D003D8FC0 /* EvergreenTests.swift in Sources */, diff --git a/Evergreen/Resources/Evergreen.sdef b/Evergreen/Resources/Evergreen.sdef index 2cedc5478..968ee7594 100644 --- a/Evergreen/Resources/Evergreen.sdef +++ b/Evergreen/Resources/Evergreen.sdef @@ -7,12 +7,34 @@ + + + + + + + + + + + + + + + + + + + + + + @@ -140,6 +162,9 @@ + + + diff --git a/Evergreen/Scriptability/Account+Scriptability.swift b/Evergreen/Scriptability/Account+Scriptability.swift index 4530dfea8..61302f737 100644 --- a/Evergreen/Scriptability/Account+Scriptability.swift +++ b/Evergreen/Scriptability/Account+Scriptability.swift @@ -9,6 +9,7 @@ import AppKit import Account import Data +import RSCore @objc(ScriptableAccount) class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer { @@ -45,6 +46,23 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta var scriptingClassDescription: NSScriptClassDescription { return self.classDescription as! NSScriptClassDescription } + + func deleteElement(_ element:ScriptingObject) { + if let scriptableFolder = element as? ScriptableFolder { + BatchUpdate.shared.perform { + account.deleteFolder(scriptableFolder.folder) + } + } else if let scriptableFeed = element as? ScriptableFeed { + BatchUpdate.shared.perform { + account.deleteFeed(scriptableFeed.feed) + } + } + } + + @objc(isLocationRequiredToCreateForKey:) + func isLocationRequiredToCreate(forKey key:String) -> Bool { + return false; + } // MARK: --- Scriptable elements --- @@ -61,6 +79,12 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta return ScriptableFeed(feed, container:self) } + @objc(valueInFeedsWithName:) + func valueInFeeds(withName name:String) -> ScriptableFeed? { + let feeds = account.children.compactMap { $0 as? Feed } + guard let feed = feeds.first(where:{$0.name == name}) else { return nil } + return ScriptableFeed(feed, container:self) + } @objc(folders) var folders:NSArray { @@ -74,8 +98,7 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta let folders = account.children.compactMap { $0 as? Folder } guard let folder = folders.first(where:{$0.folderID == folderId}) else { return nil } return ScriptableFolder(folder, container:self) - } - + } // MARK: --- Scriptable properties --- diff --git a/Evergreen/Scriptability/AppDelegate+Scriptability.swift b/Evergreen/Scriptability/AppDelegate+Scriptability.swift index 47e3bf5e1..7b9bd1011 100644 --- a/Evergreen/Scriptability/AppDelegate+Scriptability.swift +++ b/Evergreen/Scriptability/AppDelegate+Scriptability.swift @@ -3,7 +3,7 @@ // Evergreen // // Created by Olof Hellman on 2/7/18. -// Copyright © 2018 Ranchero Software. All rights reserved. +// Copyright © 2018 Olof Hellman. All rights reserved. // /* @@ -56,8 +56,83 @@ extension AppDelegate : AppDelegateAppleEvents { } } -class EvergreenExistsCommand : NSExistsCommand { +class EvergreenCreateElementCommand : NSCreateCommand { + override func performDefaultImplementation() -> Any? { + let classDescription = self.createClassDescription + if (classDescription.className == "feed") { + return ScriptableFeed.handleCreateElement(command:self) + } else if (classDescription.className == "folder") { + return ScriptableFolder.handleCreateElement(command:self) + } + return nil + } +} + +/* + NSDeleteCommand is kind of an oddball AppleScript command in that the command dispatch + goes to the container of the object(s) to be deleted, and the container needs to + figure out what to delete. In the code below, 'receivers' is the container object(s) + and keySpecifier is the thing to delete, relative to the container(s). Because there + is ambiguity about whether specifiers are lists or single objects, the code switches + based on which it is. +*/ +class EvergreenDeleteCommand : NSDeleteCommand { + + /* + delete(objectToDelete:, from container:) + At this point in handling the command, we know what the container is. + Here the code unravels the case of objectToDelete being a list or a single object, + ultimately calling container.deleteElement(element) for each element to delete + */ + func delete(objectToDelete:Any, from container:ScriptingObjectContainer) { + if let objectList = objectToDelete as? [Any] { + for nthObject in objectList { + self.delete(objectToDelete:nthObject, from:container) + } + } else if let element = objectToDelete as? ScriptingObject { + container.deleteElement(element) + } + } + /* + delete(specifier:, from container:) + At this point in handling the command, the container could be a list or a single object, + and what to delete is still an unresolved NSScriptObjectSpecifier. + Here the code unravels the case of container being a list or a single object. Once the + container(s) is known, it is possible to resolve the keySpecifier based on that container. + After resolving, we call delete(objectToDelete:, from container:) with the container and + the resolved objects + */ + func delete(specifier:NSScriptObjectSpecifier, from container:Any) { + if let containerList = container as? [Any] { + for nthObject in containerList { + self.delete(specifier:specifier, from:nthObject) + } + } else if let container = container as? ScriptingObjectContainer { + if let resolvedObjects = specifier.objectsByEvaluating(withContainers:container) { + self.delete(objectToDelete:resolvedObjects, from:container) + } + } + } + + /* + performDefaultImplementation() + This is where handling the delete event starts. receiversSpecifier should be the container(s) of + the item to be deleted. keySpecifier is the thing in that container(s) to be deleted + The first step is to resolve the receiversSpecifier and then call delete(specifier:, from container:) + */ + override func performDefaultImplementation() -> Any? { + if let receiversSpecifier = self.receiversSpecifier { + if let receiverObjects = receiversSpecifier.objectsByEvaluatingSpecifier { + self.delete(specifier:self.keySpecifier, from:receiverObjects) + } + } + return nil + } +} + +class EvergreenExistsCommand : NSExistsCommand { + // cocoa default behavior doesn't work here, because of cases where we define an object's property // to be another object type. e.g., 'permalink of the current article' parses as // of of @@ -71,8 +146,6 @@ class EvergreenExistsCommand : NSExistsCommand { override func performDefaultImplementation() -> Any? { guard let result = super.performDefaultImplementation() else { return NSNumber(booleanLiteral:false) } return result - // return NSNumber(booleanLiteral:true) - // scriptingContainer.handleDoObjectsExist(command:self) } } diff --git a/Evergreen/Scriptability/Article+Scriptability.swift b/Evergreen/Scriptability/Article+Scriptability.swift index 18e3d856a..fe15287a7 100644 --- a/Evergreen/Scriptability/Article+Scriptability.swift +++ b/Evergreen/Scriptability/Article+Scriptability.swift @@ -47,6 +47,10 @@ class ScriptableArticle: NSObject, UniqueIdScriptingObject, ScriptingObjectConta var scriptingClassDescription: NSScriptClassDescription { return self.classDescription as! NSScriptClassDescription } + + func deleteElement(_ element:ScriptingObject) { + print ("delete event not handled") + } // MARK: --- Scriptable properties --- diff --git a/Evergreen/Scriptability/Feed+Scriptability.swift b/Evergreen/Scriptability/Feed+Scriptability.swift index 8a5ddad8b..5caec6bfd 100644 --- a/Evergreen/Scriptability/Feed+Scriptability.swift +++ b/Evergreen/Scriptability/Feed+Scriptability.swift @@ -7,6 +7,7 @@ // import Foundation +import RSParser import Account import Data @@ -27,6 +28,11 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine return (scriptObjectSpecifier) } + @objc(scriptingSpecifierDescriptor) + func scriptingSpecifierDescriptor() -> NSScriptObjectSpecifier { + return (self.objectSpecifier ?? NSScriptObjectSpecifier() ) + } + // MARK: --- ScriptingObject protocol --- var scriptingKey: String { @@ -47,9 +53,89 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine var scriptingClassDescription: NSScriptClassDescription { return self.classDescription as! NSScriptClassDescription } + + func deleteElement(_ element:ScriptingObject) { + } + + // MARK: --- handle NSCreateCommand --- + + class func parsedFeedForURL(_ urlString:String, _ completionHandler: @escaping (_ parsedFeed: ParsedFeed?) -> Void) { + guard let url = URL(string: urlString) else { + completionHandler(nil) + return + } + InitialFeedDownloader.download(url) { (parsedFeed) in + completionHandler(parsedFeed) + } + } + + class func urlForNewFeed(arguments:[String:Any]) -> String? { + var url:String? + if let withDataParam = arguments["ObjectData"] { + if let objectDataDescriptor = withDataParam as? NSAppleEventDescriptor { + url = objectDataDescriptor.stringValue + } + } else if let withPropsParam = arguments["ObjectProperties"] as? [String:Any] { + url = withPropsParam["url"] as? String + } + return url + } + + class func scriptableFeed(_ feed:Feed, account:Account, folder:Folder?) -> ScriptableFeed { + let scriptableAccount = ScriptableAccount(account) + if let folder = folder { + let scriptableFolder = ScriptableFolder(folder, container:scriptableAccount) + return ScriptableFeed(feed, container:scriptableFolder) + } else { + return ScriptableFeed(feed, container:scriptableAccount) + } + } + + class func handleCreateElement(command:NSCreateCommand) -> Any? { + guard command.isCreateCommand(forClass:"Feed") else { return nil } + guard let arguments = command.arguments else {return nil} + let titleFromArgs = command.property(forKey:"name") as? String + let (account, folder) = command.accountAndFolderForNewChild() + guard let url = self.urlForNewFeed(arguments:arguments) else {return nil} + + if let existingFeed = account.existingFeed(withURL:url) { + return self.scriptableFeed(existingFeed, account:account, folder:folder) + } + + // at this point, we need to download the feed and parse it. + // RS Parser does the callback for the download on the main thread (which it probably shouldn't?) + // because we can't wait here (on the main thread, maybe) for the callback, we have to return from this function + // Generally, returning from an AppleEvent handler function means that handling the appleEvent is over, + // but we don't yet have the result of the event yet, so we prevent the AppleEvent from returning by calling + // suspendExecution(). When we get the callback, we can supply the event result and call resumeExecution() + command.suspendExecution() + + self.parsedFeedForURL(url, { (parsedFeedOptional) in + if let parsedFeed = parsedFeedOptional { + let titleFromFeed = parsedFeed.title + + guard let feed = account.createFeed(with: titleFromFeed, editedName: titleFromArgs, url: url) else { + command.resumeExecution(withResult:nil) + return + } + account.update(feed, with:parsedFeed, {}) + + // add the feed, putting it in a folder if needed + if account.addFeed(feed, to:folder) { + NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed]) + } + + let scriptableFeed = self.scriptableFeed(feed, account:account, folder:folder) + command.resumeExecution(withResult:scriptableFeed.objectSpecifier) + } else { + command.resumeExecution(withResult:nil) + } + }) + return nil + } // MARK: --- Scriptable properties --- - + @objc(url) var url:String { return self.feed.url diff --git a/Evergreen/Scriptability/Folder+Scriptability.swift b/Evergreen/Scriptability/Folder+Scriptability.swift index 2cab418d7..ad6039bad 100644 --- a/Evergreen/Scriptability/Folder+Scriptability.swift +++ b/Evergreen/Scriptability/Folder+Scriptability.swift @@ -8,9 +8,11 @@ import Foundation import Account +import Data +import RSCore @objc(ScriptableFolder) -class ScriptableFolder: NSObject, UniqueIdScriptingObject { +class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer { let folder:Folder let container:ScriptingObjectContainer @@ -41,6 +43,58 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject { return folder.folderID } + // MARK: --- ScriptingObjectContainer protocol --- + + var scriptingClassDescription: NSScriptClassDescription { + return self.classDescription as! NSScriptClassDescription + } + + func deleteElement(_ element:ScriptingObject) { + if let scriptableFolder = element as? ScriptableFolder { + BatchUpdate.shared.perform { + folder.deleteFolder(scriptableFolder.folder) + } + } else if let scriptableFeed = element as? ScriptableFeed { + BatchUpdate.shared.perform { + folder.deleteFeed(scriptableFeed.feed) + } + } + } + + // MARK: --- handle NSCreateCommand --- + /* + handle an AppleScript like + make new folder in account X with properties {name:"new folder name"} + or + tell account X to make new folder at end with properties {name:"new folder name"} + */ + class func handleCreateElement(command:NSCreateCommand) -> Any? { + guard command.isCreateCommand(forClass:"fold") else { return nil } + let name = command.property(forKey:"name") as? String ?? "" + + // some combination of the tell target and the location specifier ("in" or "at") + // identifies where the new folder should be created + let (account, folder) = command.accountAndFolderForNewChild() + guard folder == nil else { + print("support for folders within folders is NYI"); + return nil + } + let scriptableAccount = ScriptableAccount(account) + if let newFolder = account.ensureFolder(with:name) { + let scriptableFolder = ScriptableFolder(newFolder, container:scriptableAccount) + return(scriptableFolder.objectSpecifier) + } + return nil + } + + // MARK: --- Scriptable elements --- + + @objc(feeds) + var feeds:NSArray { + let feeds = folder.children.compactMap { $0 as? Feed } + return feeds.map { ScriptableFeed($0, container:self) } as NSArray + } + // MARK: --- Scriptable properties --- @objc(uniqueId) diff --git a/Evergreen/Scriptability/MainWindowController+Scriptability.swift b/Evergreen/Scriptability/MainWindowController+Scriptability.swift index 71cdb1cd7..02664e2fb 100644 --- a/Evergreen/Scriptability/MainWindowController+Scriptability.swift +++ b/Evergreen/Scriptability/MainWindowController+Scriptability.swift @@ -3,7 +3,7 @@ // Evergreen // // Created by Olof Hellman on 2/7/18. -// Copyright © 2018 Ranchero Software. All rights reserved. +// Copyright © 2018 Olof Hellman. All rights reserved. // import Foundation diff --git a/Evergreen/Scriptability/NSApplication+Scriptability.swift b/Evergreen/Scriptability/NSApplication+Scriptability.swift index 47a298519..fbe52ea33 100644 --- a/Evergreen/Scriptability/NSApplication+Scriptability.swift +++ b/Evergreen/Scriptability/NSApplication+Scriptability.swift @@ -11,11 +11,17 @@ import Account import Data extension NSApplication : ScriptingObjectContainer { - + + // MARK: --- ScriptingObjectContainer protocol --- + var scriptingClassDescription: NSScriptClassDescription { return NSApplication.shared.classDescription as! NSScriptClassDescription } - + + func deleteElement(_ element:ScriptingObject) { + print ("delete event not handled") + } + var scriptingKey: String { return "application" } diff --git a/Evergreen/Scriptability/NSScriptCommand+Evergreen.swift b/Evergreen/Scriptability/NSScriptCommand+Evergreen.swift new file mode 100644 index 000000000..19ab580a1 --- /dev/null +++ b/Evergreen/Scriptability/NSScriptCommand+Evergreen.swift @@ -0,0 +1,63 @@ +// +// NSScriptCommand+Evergreen.swift +// Evergreen +// +// Created by Olof Hellman on 3/4/18. +// Copyright © 2018 Olof Hellman. All rights reserved. +// + +import Foundation +import Account + +extension NSScriptCommand { + func property(forKey key:String) -> Any? { + if let evaluatedArguments = self.evaluatedArguments { + if let props = evaluatedArguments["KeyDictionary"] as? [String: Any] { + return props[key] + } + } + return nil + } + + func isCreateCommand(forClass whatClass:String) -> Bool { + guard let arguments = self.arguments else {return false} + guard let newObjectClass = arguments["ObjectClass"] as? Int else {return false} + guard (newObjectClass.FourCharCode() == whatClass.FourCharCode()) else {return false} + return true + } + + func accountAndFolderForNewChild() -> (Account, Folder?) { + let appleEvent = self.appleEvent + var account = AccountManager.shared.localAccount + var folder:Folder? = nil + if let appleEvent = appleEvent { + var descriptorToConsider:NSAppleEventDescriptor? + if let insertionLocationDescriptor = appleEvent.paramDescriptor(forKeyword:keyAEInsertHere) { + print("insertionLocation : \(insertionLocationDescriptor)") + // insertion location can be a typeObjectSpecifier, e.g. 'in account "Acct"' + // or a typeInsertionLocation, e.g. 'at end of folder " + if (insertionLocationDescriptor.descriptorType == "insl".FourCharCode()) { + descriptorToConsider = insertionLocationDescriptor.forKeyword("kobj".FourCharCode()) + } else if ( insertionLocationDescriptor.descriptorType == "obj ".FourCharCode()) { + descriptorToConsider = insertionLocationDescriptor + } + } else if let subjectDescriptor = appleEvent.attributeDescriptor(forKeyword:"subj".FourCharCode()) { + descriptorToConsider = subjectDescriptor + } + + if let descriptorToConsider = descriptorToConsider { + guard let newContainerSpecifier = NSScriptObjectSpecifier(descriptor:descriptorToConsider) else {return (account, folder)} + let newContainer = newContainerSpecifier.objectsByEvaluatingSpecifier + if let scriptableAccount = newContainer as? ScriptableAccount { + account = scriptableAccount.account + } else if let scriptableFolder = newContainer as? ScriptableFolder { + if let folderAccount = scriptableFolder.folder.account { + folder = scriptableFolder.folder + account = folderAccount + } + } + } + } + return (account, folder) + } +} diff --git a/Evergreen/Scriptability/ScriptingObjectContainer.swift b/Evergreen/Scriptability/ScriptingObjectContainer.swift index e04979d27..8bfe695d7 100644 --- a/Evergreen/Scriptability/ScriptingObjectContainer.swift +++ b/Evergreen/Scriptability/ScriptingObjectContainer.swift @@ -7,9 +7,11 @@ // import AppKit +import Account protocol ScriptingObjectContainer: ScriptingObject { var scriptingClassDescription:NSScriptClassDescription { get } + func deleteElement(_ element:ScriptingObject) } extension ScriptingObjectContainer { diff --git a/EvergreenTests/ScriptingTests/ScriptingTests.swift b/EvergreenTests/ScriptingTests/ScriptingTests.swift index 8463dbcc0..29574df34 100644 --- a/EvergreenTests/ScriptingTests/ScriptingTests.swift +++ b/EvergreenTests/ScriptingTests/ScriptingTests.swift @@ -56,6 +56,10 @@ class ScriptingTests: AppleScriptXCTestCase { _ = doIndividualScript(filename: "testTitleOfArticlesWhose") } + func testIterativeCreateAndDeleteScript() { + _ = doIndividualScriptWithExpectation(filename: "testIterativeCreateAndDeleteFeed") + } + func doIndividualScriptWithExpectation(filename:String) { let queue = DispatchQueue(label:"testQueue") let scriptExpectation = self.expectation(description: filename+"expectation") diff --git a/EvergreenTests/ScriptingTests/scripts/testIterativeCreateAndDeleteFeed.applescript b/EvergreenTests/ScriptingTests/scripts/testIterativeCreateAndDeleteFeed.applescript new file mode 100644 index 000000000..6ebb35eec --- /dev/null +++ b/EvergreenTests/ScriptingTests/scripts/testIterativeCreateAndDeleteFeed.applescript @@ -0,0 +1,27 @@ + +try + tell application "Evergreen" + tell account id "OnMyMac" + repeat 3 times + set newFeed to make new feed with data "https://boingboing.net/feed" + delete newFeed + end repeat + + set newFolder to make new folder with properties {name:"XCTest folder"} + repeat 3 times + set newFeed to make new feed in newFolder with data "https://boingboing.net/feed" + delete newFeed + end repeat + delete newFolder + + end tell + end tell + + + set test_result to true + set script_result to "Success" +on error message + return {test_result:false, script_result:message} +end try + +return {test_result:test_result, script_result:script_result} diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index 81c38f919..aaf252c50 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -258,6 +258,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, let feed = Feed(accountID: accountID, url: url, feedID: url) feed.name = name feed.editedName = editedName + return feed }