Merge pull request #357 from olofhellman/master

AppleScript support for make/delete of feeds and folders
This commit is contained in:
Brent Simmons 2018-03-04 20:16:30 -08:00 committed by GitHub
commit 8720c19209
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 389 additions and 11 deletions

View File

@ -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 = "<group>"; };
D57BE6DF204CD35F00D11AAC /* NSScriptCommand+Evergreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSScriptCommand+Evergreen.swift"; sourceTree = "<group>"; };
D5907C9D20023249005947E5 /* testGenericScript.applescript */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.applescript; path = testGenericScript.applescript; sourceTree = "<group>"; };
D5907CDC2002F0BE005947E5 /* Evergreen_project_release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Evergreen_project_release.xcconfig; sourceTree = "<group>"; };
D5907CDD2002F0BE005947E5 /* Evergreen_project_debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Evergreen_project_debug.xcconfig; sourceTree = "<group>"; };
@ -672,6 +677,7 @@
D5A2678B20130ECF00A8D3C0 /* Author+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Author+Scriptability.swift"; sourceTree = "<group>"; };
D5A2679B201312F900A8D3C0 /* testNameOfAuthors.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = testNameOfAuthors.applescript; sourceTree = "<group>"; };
D5A267B220131B8300A8D3C0 /* testFeedOPML.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = testFeedOPML.applescript; sourceTree = "<group>"; };
D5D07B09204B42050093F739 /* testIterativeCreateAndDeleteFeed.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = testIterativeCreateAndDeleteFeed.applescript; sourceTree = "<group>"; };
D5D175012002039D0047B29D /* Evergreen.sdef */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = Evergreen.sdef; path = ../Resources/Evergreen.sdef; sourceTree = "<group>"; };
D5E4CC53202C1361009B4FFC /* AppDelegate+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Scriptability.swift"; sourceTree = "<group>"; };
D5E4CC63202C1AC1009B4FFC /* MainWindowController+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainWindowController+Scriptability.swift"; sourceTree = "<group>"; };
@ -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 */,

View File

@ -7,12 +7,34 @@
<suite name="Standard Suite" code="core" description="Subset of the Standard Suite.">
<access-group identifier="com.ranchero.Evergreen" access="rw"/>
<command name="delete" code="coredelo" description="Delete an object.">
<cocoa class="Evergreen.EvergreenDeleteCommand"/>
<direct-parameter type="specifier" description="The object(s) to delete."/>
</command>
<command name="exists" code="coredoex" description="Verify that an object exists.">
<cocoa class="Evergreen.EvergreenExistsCommand"/>
<direct-parameter type="any" requires-access="r" description="The object(s) to check."/>
<result type="boolean" description="Does the object(s) exist?"/>
</command>
<command name="make" code="corecrel" description="Create a new object.">
<cocoa class="Evergreen.EvergreenCreateElementCommand"/>
<parameter name="new" code="kocl" type="type" description="The class of the new object.">
<cocoa key="ObjectClass"/>
</parameter>
<parameter name="at" code="insh" type="location specifier" optional="yes" description="The location at which to insert the object.">
<cocoa key="Location"/>
</parameter>
<parameter name="with data" code="data" type="any" optional="yes" description="The initial contents of the object.">
<cocoa key="ObjectData"/>
</parameter>
<parameter name="with properties" code="prdt" type="record" optional="yes" description="The initial values for properties of the object.">
<cocoa key="KeyDictionary"/>
</parameter>
<result type="specifier" description="The new object."/>
</command>
<class name="application" code="capp" description="The application's top-level scripting object.">
<cocoa class="NSApplication"/>
<property name="name" code="pnam" type="text" access="r" description="The name of the application."/>
@ -140,6 +162,9 @@
<property name="opml representation" code="OPML" type="text" access="r" description="OPML representation for the folder">
<cocoa key="opmlRepresentation"/>
</property>
<element type="feed">
<cocoa key="feeds"/>
</element>
</class>
<class name="article" code="Arcl" plural="articles" description="An article in a feed">

View File

@ -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 ---

View File

@ -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
// <property> of <property> of <top level object>
@ -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)
}
}

View File

@ -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 ---

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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"
}

View File

@ -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)
}
}

View File

@ -7,9 +7,11 @@
//
import AppKit
import Account
protocol ScriptingObjectContainer: ScriptingObject {
var scriptingClassDescription:NSScriptClassDescription { get }
func deleteElement(_ element:ScriptingObject)
}
extension ScriptingObjectContainer {

View File

@ -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")

View File

@ -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}

View File

@ -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
}