Support for creating and deleting feeds in accounts and folders, and
for creating and deleting folders in accounts
This commit is contained in:
parent
71005b56af
commit
c48c54b468
|
@ -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 */; };
|
||||
|
@ -502,6 +505,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 */,
|
||||
|
@ -681,6 +685,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>"; };
|
||||
|
@ -693,6 +698,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>"; };
|
||||
|
@ -1355,6 +1361,7 @@
|
|||
D5A2679B201312F900A8D3C0 /* testNameOfAuthors.applescript */,
|
||||
D5F4EDD720075C1300B9E363 /* testNameOfEveryFolder.applescript */,
|
||||
D55373A02018797B006D8857 /* testTitleOfArticlesWhose.applescript */,
|
||||
D5D07B09204B42050093F739 /* testIterativeCreateAndDeleteFeed.applescript */,
|
||||
D5E4CCD12030260E009B4FFC /* testCurrentArticleIsNil.applescript */,
|
||||
D5E4CCCF203025FF009B4FFC /* testURLsOfCurrentArticle.applescript */,
|
||||
D5E4CCB220300024009B4FFC /* uiScriptingTestSetup.applescript */,
|
||||
|
@ -1396,6 +1403,7 @@
|
|||
D5907D7E2004AC00005947E5 /* NSApplication+Scriptability.swift */,
|
||||
D5907DB12004BB37005947E5 /* ScriptingObjectContainer.swift */,
|
||||
D5F4EDB4200744A700B9E363 /* ScriptingObject.swift */,
|
||||
D57BE6DF204CD35F00D11AAC /* NSScriptCommand+Evergreen.swift */,
|
||||
);
|
||||
name = Scriptability;
|
||||
path = Evergreen/Scriptability;
|
||||
|
@ -1535,12 +1543,12 @@
|
|||
};
|
||||
849C645F1ED37A5D003D8FC0 = {
|
||||
CreatedOnToolsVersion = 8.2.1;
|
||||
DevelopmentTeam = M8L2WTLA8W;
|
||||
ProvisioningStyle = Manual;
|
||||
DevelopmentTeam = 6V7D786XTL;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
849C64701ED37A5D003D8FC0 = {
|
||||
CreatedOnToolsVersion = 8.2.1;
|
||||
DevelopmentTeam = 9C84TZ7Q6Z;
|
||||
DevelopmentTeam = 6V7D786XTL;
|
||||
ProvisioningStyle = Automatic;
|
||||
TestTargetID = 849C645F1ED37A5D003D8FC0;
|
||||
};
|
||||
|
@ -1925,6 +1933,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 */,
|
||||
|
@ -2018,6 +2027,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 */,
|
||||
|
|
|
@ -7,6 +7,11 @@
|
|||
|
||||
<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."/>
|
||||
|
@ -15,7 +20,6 @@
|
|||
|
||||
<command name="make" code="corecrel" description="Create a new object.">
|
||||
<cocoa class="Evergreen.EvergreenCreateElementCommand"/>
|
||||
<access-group identifier="*"/>
|
||||
<parameter name="new" code="kocl" type="type" description="The class of the new object.">
|
||||
<cocoa key="ObjectClass"/>
|
||||
</parameter>
|
||||
|
@ -158,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">
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import AppKit
|
||||
import Account
|
||||
import Data
|
||||
import RSCore
|
||||
|
||||
@objc(ScriptableAccount)
|
||||
class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer {
|
||||
|
@ -46,6 +47,18 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
|||
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;
|
||||
|
@ -66,6 +79,13 @@ 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 {
|
||||
let folders = account.children.compactMap { $0 as? Folder }
|
||||
|
|
|
@ -61,6 +61,71 @@ class EvergreenCreateElementCommand : NSCreateCommand {
|
|||
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
|
||||
}
|
||||
|
@ -81,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,6 +48,10 @@ class ScriptableArticle: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
|||
return self.classDescription as! NSScriptClassDescription
|
||||
}
|
||||
|
||||
func deleteElement(_ element:ScriptingObject) {
|
||||
print ("delete event not handled")
|
||||
}
|
||||
|
||||
// MARK: --- Scriptable properties ---
|
||||
|
||||
@objc(url)
|
||||
|
|
|
@ -54,8 +54,10 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
|
|||
return self.classDescription as! NSScriptClassDescription
|
||||
}
|
||||
|
||||
func deleteElement(_ element:ScriptingObject) {
|
||||
}
|
||||
|
||||
// MARK: --- Create Element Handlers ---
|
||||
// MARK: --- handle NSCreateCommand ---
|
||||
|
||||
class func parsedFeedForURL(_ urlString:String, _ completionHandler: @escaping (_ parsedFeed: ParsedFeed?) -> Void) {
|
||||
guard let url = URL(string: urlString) else {
|
||||
|
@ -79,90 +81,38 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
|
|||
return url
|
||||
}
|
||||
|
||||
class func accountAndFolderForNewFeed(appleEvent:NSAppleEventDescriptor?) -> (Account, Folder?) {
|
||||
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
|
||||
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)
|
||||
}
|
||||
} 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
|
||||
}
|
||||
}
|
||||
}
|
||||
print("found account : \(account)")
|
||||
print("found folder : \(folder)")
|
||||
}
|
||||
return (account, folder)
|
||||
}
|
||||
|
||||
class func handleCreateElement(command:NSCreateCommand) -> Any? {
|
||||
let appleEventManager = NSAppleEventManager.shared()
|
||||
if let receivers = command.receiversSpecifier {
|
||||
print("receivers : \(receivers)")
|
||||
}
|
||||
if let evaluatedReceivers = command.evaluatedReceivers {
|
||||
print("evaluatedReceivers : \(evaluatedReceivers)")
|
||||
}
|
||||
if let evaluatedArguments = command.evaluatedArguments {
|
||||
print("evaluatedArguments : \(evaluatedArguments)")
|
||||
}
|
||||
if let directObject = command.directParameter {
|
||||
print("directObject : \(directObject)")
|
||||
}
|
||||
if let appleEvent = command.appleEvent { // keyDirectObject
|
||||
print("appleEvent : \(appleEvent)")
|
||||
if let subjectDescriptor = appleEvent.attributeDescriptor(forKeyword:"subj".FourCharCode()) {
|
||||
print("subjectDescriptor : \(subjectDescriptor)")
|
||||
let subjectObjectSpecifier = NSScriptObjectSpecifier(descriptor:subjectDescriptor)
|
||||
let subjects = subjectObjectSpecifier?.objectsByEvaluatingSpecifier
|
||||
print("resolvedSubjects : \(subjects)")
|
||||
}
|
||||
}
|
||||
let commandDescription = command.commandDescription
|
||||
print("commandDescription : \(commandDescription)")
|
||||
|
||||
let (account, folder) = self.accountAndFolderForNewFeed(appleEvent:command.appleEvent)
|
||||
let scriptableAccount = ScriptableAccount(account)
|
||||
guard command.isCreateCommand(forClass:"Feed") else { return nil }
|
||||
guard let arguments = command.arguments else {return nil}
|
||||
guard let newObjectClass = arguments["ObjectClass"] as? Int else {return nil}
|
||||
guard (newObjectClass.FourCharCode() == "Feed".FourCharCode()) 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 ScriptableFeed(existingFeed, container:scriptableAccount)
|
||||
return self.scriptableFeed(existingFeed, account:account, folder:folder)
|
||||
}
|
||||
|
||||
// at this point, we have to download the feed and parse it.
|
||||
// RS Parser does the callback for the download on the main thread
|
||||
// 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, the means handling the appleEvent is over, but to prevent the apple event from returning
|
||||
// we call suspendExecution here. When we get the callback, we can resume execution
|
||||
// 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
|
||||
let titleFromArgs = arguments["name"] as? String
|
||||
|
||||
guard let feed = account.createFeed(with: titleFromFeed, editedName: titleFromArgs, url: url) else {
|
||||
command.resumeExecution(withResult:nil)
|
||||
|
@ -175,9 +125,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
|
|||
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed])
|
||||
}
|
||||
|
||||
let resolvedKeyDictionary = command.resolvedKeyDictionary
|
||||
print("resolvedKeyDictionary : \(resolvedKeyDictionary)")
|
||||
let scriptableFeed = ScriptableFeed(feed, container:ScriptableAccount(account))
|
||||
let scriptableFeed = self.scriptableFeed(feed, account:account, folder:folder)
|
||||
command.resumeExecution(withResult:scriptableFeed.objectSpecifier)
|
||||
} else {
|
||||
command.resumeExecution(withResult:nil)
|
||||
|
@ -186,9 +134,6 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
|
|||
return nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// MARK: --- Scriptable properties ---
|
||||
|
||||
@objc(url)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -12,10 +12,16 @@ 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"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
//
|
||||
// NSScriptCommand+Evergreen.swift
|
||||
// Evergreen
|
||||
//
|
||||
// Created by Olof Hellman on 3/4/18.
|
||||
// Copyright © 2018 Ranchero Software. 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)
|
||||
}
|
||||
}
|
|
@ -7,9 +7,11 @@
|
|||
//
|
||||
|
||||
import AppKit
|
||||
import Account
|
||||
|
||||
protocol ScriptingObjectContainer: ScriptingObject {
|
||||
var scriptingClassDescription:NSScriptClassDescription { get }
|
||||
func deleteElement(_ element:ScriptingObject)
|
||||
}
|
||||
|
||||
extension ScriptingObjectContainer {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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}
|
Loading…
Reference in New Issue