Merge branch 'master' of https://github.com/brentsimmons/Evergreen
This commit is contained in:
commit
b766d4bcc7
|
@ -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 */,
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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 ---
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 ---
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue