Merge pull request #308 from olofhellman/master

Scripting support for articles and article properties
This commit is contained in:
Brent Simmons 2018-01-24 21:08:45 -08:00 committed by GitHub
commit 9d5f7570eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 228 additions and 2 deletions

View File

@ -133,6 +133,9 @@
84FB9A2F1EDCD6C4003D53B9 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; };
84FB9A301EDCD6C4003D53B9 /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; };
D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D553737C20186C1F006D8857 /* Article+Scriptability.swift */; };
D55373AF2018797C006D8857 /* testTitleOfArticlesWhose.applescript in Sources */ = {isa = PBXBuildFile; fileRef = D55373A02018797B006D8857 /* testTitleOfArticlesWhose.applescript */; };
D55373B020187A05006D8857 /* testTitleOfArticlesWhose.applescript in CopyFiles */ = {isa = PBXBuildFile; fileRef = D55373A02018797B006D8857 /* testTitleOfArticlesWhose.applescript */; };
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 */; };
@ -454,6 +457,7 @@
D5A2679D201313A200A8D3C0 /* testNameOfAuthors.applescript in CopyFiles */,
D5F4EDE920075C6700B9E363 /* testNameAndUrlOfEveryFeed.applescript in CopyFiles */,
D5F4EDEA20075C6700B9E363 /* testNameOfEveryFolder.applescript in CopyFiles */,
D55373B020187A05006D8857 /* testTitleOfArticlesWhose.applescript in CopyFiles */,
D5907CA2200232AD005947E5 /* testGenericScript.applescript in CopyFiles */,
D5907CA3200232AF005947E5 /* testGetURL.applescript in CopyFiles */,
);
@ -585,6 +589,8 @@
84F2D5391FC2308B00998D64 /* UnreadFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadFeed.swift; sourceTree = "<group>"; };
84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Frameworks/Vendor/Sparkle.framework; sourceTree = SOURCE_ROOT; };
84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconURLFinder.swift; sourceTree = "<group>"; };
D553737C20186C1F006D8857 /* Article+Scriptability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Article+Scriptability.swift"; sourceTree = "<group>"; };
D55373A02018797B006D8857 /* testTitleOfArticlesWhose.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = testTitleOfArticlesWhose.applescript; sourceTree = "<group>"; };
D5558FD1200223F60066386B /* testGetURL.applescript */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.applescript; path = testGetURL.applescript; sourceTree = "<group>"; };
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; };
@ -1157,6 +1163,7 @@
D5F4EDE720075C1800B9E363 /* testNameAndUrlOfEveryFeed.applescript */,
D5A2679B201312F900A8D3C0 /* testNameOfAuthors.applescript */,
D5F4EDD720075C1300B9E363 /* testNameOfEveryFolder.applescript */,
D55373A02018797B006D8857 /* testTitleOfArticlesWhose.applescript */,
);
path = scripts;
sourceTree = "<group>";
@ -1186,6 +1193,7 @@
isa = PBXGroup;
children = (
D5907D962004B7EB005947E5 /* Account+Scriptability.swift */,
D553737C20186C1F006D8857 /* Article+Scriptability.swift */,
D5A2678B20130ECF00A8D3C0 /* Author+Scriptability.swift */,
D5F4EDB620074D6500B9E363 /* Feed+Scriptability.swift */,
D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */,
@ -1596,6 +1604,7 @@
849A97641ED9EB96007D329B /* SidebarOutlineView.swift in Sources */,
D5A2678C20130ECF00A8D3C0 /* Author+Scriptability.swift in Sources */,
84F2D5371FC22FCC00998D64 /* PseudoFeed.swift in Sources */,
D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */,
845EE7C11FC2488C00854A1F /* SmartFeed.swift in Sources */,
84702AA41FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift in Sources */,
D5907D7F2004AC00005947E5 /* NSApplication+Scriptability.swift in Sources */,
@ -1672,6 +1681,7 @@
files = (
D5558FD5200225680066386B /* NSAppleEventDescriptor+UserRecordFields.swift in Sources */,
D5558FD9200228D30066386B /* AppleEventUtils.swift in Sources */,
D55373AF2018797C006D8857 /* testTitleOfArticlesWhose.applescript in Sources */,
D5907CA1200232A1005947E5 /* testGetURL.applescript in Sources */,
D5A267C120131B8300A8D3C0 /* testFeedOPML.applescript in Sources */,
D5A2679C201312F900A8D3C0 /* testNameOfAuthors.applescript in Sources */,

View File

@ -24,6 +24,9 @@
<element type="account">
<cocoa key="accounts"/>
</element>
<element type="feed">
<cocoa key="feeds"/>
</element>
</class>
<enumeration name="account type" code="enum">
@ -89,6 +92,9 @@
<element type="author">
<cocoa key="authors"/>
</element>
<element type="article">
<cocoa key="articles"/>
</element>
</class>
<class name="author" code="Athr" plural="authors" description="A feed author">
@ -122,6 +128,52 @@
</property>
</class>
<class name="article" code="Arcl" plural="articles" description="An article in a feed">
<cocoa class="ScriptableArticle"/>
<property name="id" code="id " type="text" access="r" description="The unique id of the article as set by the feed">
<cocoa key="uniqueId"/>
</property>
<property name="title" code="titl" type="text" access="r" description="The article title">
<cocoa key="title"/>
</property>
<property name="url" code="URL " type="text" access="r" description="url for the article">
<cocoa key="url"/>
</property>
<property name="contents" code="Cnts" type="text" access="r" description="text of the article">
<cocoa key="contents"/>
</property>
<property name="html" code="HTML" type="text" access="r" description="html of the article">
<cocoa key="html"/>
</property>
<property name="summary" code="Smry" type="text" access="r" description="a summary of the article">
<cocoa key="summary"/>
</property>
<property name="published date" code="PDat" type="date" access="r" description="date the article was published">
<cocoa key="datePublished"/>
</property>
<property name="arrived date" code="ADat" type="date" access="r" description="date the article was seen by Evergreen">
<cocoa key="dateArrived"/>
</property>
<property name="modified date" code="MDat" type="date" access="r" description="date the article was last modified">
<cocoa key="dateModified"/>
</property>
<property name="read" code="Read" type="boolean" access="r" description="has the article been read">
<cocoa key="read"/>
</property>
<property name="starred" code="Star" type="boolean" access="r" description="has the article been marked with a star">
<cocoa key="starred"/>
</property>
<property name="deleted" code="Delt" type="boolean" access="r" description="has the article been deleted by the user">
<cocoa key="deleted"/>
</property>
<property name="image url" code="IURL" type="text" access="r" description="an image url for the article">
<cocoa key="imageURL"/>
</property>
<element type="author">
<cocoa key="authors"/>
</element>
</class>
</suite>
<suite name="Internet Suite" code="GURL"

View File

@ -0,0 +1,124 @@
//
// Article+Scriptability.swift
// Evergreen
//
// Created by Olof Hellman on 1/23/18.
// Copyright © 2018 Olof Hellman. All rights reserved.
//
import Foundation
import Account
import Data
@objc(ScriptableArticle)
class ScriptableArticle: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer {
let article:Article
let container:ScriptingObjectContainer
init (_ article:Article, container:ScriptingObjectContainer) {
self.article = article
self.container = container
}
@objc(objectSpecifier)
override var objectSpecifier: NSScriptObjectSpecifier? {
let scriptObjectSpecifier = self.container.makeFormUniqueIDScriptObjectSpecifier(forObject:self)
return (scriptObjectSpecifier)
}
// MARK: --- ScriptingObject protocol ---
var scriptingKey: String {
return "articles"
}
// MARK: --- UniqueIdScriptingObject protocol ---
// articles have id in the Evergreen database and id in the feed
// article.uniqueID here is the feed unique id
var scriptingUniqueId:Any {
return article.uniqueID
}
// MARK: --- ScriptingObjectContainer protocol ---
var scriptingClassDescription: NSScriptClassDescription {
return self.classDescription as! NSScriptClassDescription
}
// MARK: --- Scriptable properties ---
@objc(url)
var url:String {
return article.url ?? ""
}
@objc(uniqueId)
var uniqueId:String {
return article.uniqueID
}
@objc(title)
var title:String {
return article.title ?? ""
}
@objc(contents)
var contents:String {
return article.contentText ?? ""
}
@objc(html)
var html:String {
return article.contentHTML ?? ""
}
@objc(summary)
var summary:String {
return article.summary ?? ""
}
@objc(datePublished)
var datePublished:Date? {
return article.datePublished
}
@objc(dateModified)
var dateModified:Date? {
return article.dateModified
}
@objc(dateArrived)
var dateArrived:Date {
return article.status.dateArrived
}
@objc(read)
var read:Bool {
return article.status.boolStatus(forKey:.read)
}
@objc(starred)
var starred:Bool {
return article.status.boolStatus(forKey:.starred)
}
@objc(deleted)
var deleted:Bool {
return article.status.boolStatus(forKey:.userDeleted)
}
@objc(imageURL)
var imageURL:String {
return article.imageURL ?? ""
}
@objc(authors)
var authors:NSArray {
let articleAuthors = article.authors ?? []
return articleAuthors.map { ScriptableAuthor($0, container:self) } as NSArray
}
}

View File

@ -90,5 +90,15 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
let feedAuthors = feed.authors ?? []
return feedAuthors.map { ScriptableAuthor($0, container:self) } as NSArray
}
@objc(articles)
var articles:NSArray {
let feedArticles = feed.fetchArticles()
// the articles are a set, use the sorting algorithm from the viewer
let sortedArticles = feedArticles.sorted(by:{
return $0.logicalDatePublished > $1.logicalDatePublished
})
return sortedArticles.map { ScriptableArticle($0, container:self) } as NSArray
}
}

View File

@ -8,6 +8,7 @@
import Cocoa
import Account
import Data
extension NSApplication : ScriptingObjectContainer {
@ -25,6 +26,21 @@ extension NSApplication : ScriptingObjectContainer {
return accounts.map { ScriptableAccount($0) } as NSArray
}
/*
accessing feeds from the application object skips the 'account' containment hierarchy
this allows a script like 'articles of feed "The Shape of Everything"' as a shorthand
for 'articles of feed "The Shape of Everything" of account "On My Mac"'
*/
@objc(feeds)
func feeds() -> NSArray {
let accounts = AccountManager.shared.accounts
let emptyFeeds:[Feed] = []
let feeds = accounts.reduce(emptyFeeds) { (result, nthAccount) -> [Feed] in
let accountFeeds = nthAccount.children.flatMap { $0 as? Feed }
return result + accountFeeds
}
return feeds.map { ScriptableFeed($0, container:self) } as NSArray
}
}

View File

@ -94,8 +94,12 @@ class ScriptingTests: XCTestCase {
_ = doIndividualScript(filename: "testNameOfAuthors")
}
func testNameOfAuthorsScript() {
func testFeedOPML() {
_ = doIndividualScript(filename: "testFeedOPML")
}
func testTitleOfArticlesWhoseScript() {
_ = doIndividualScript(filename: "testTitleOfArticlesWhose")
}
}

View File

@ -0,0 +1,10 @@
-- this script just tests that no error was generated from the script
try
tell application "Evergreen"
title of every article of feed "Six Colors" where read is true
end tell
on error message
return {test_result:false, script_result:message}
end try
return {test_result:true}

View File

@ -26,7 +26,7 @@ PROVISIONING_PROFILE_SPECIFIER =
// /Users/Shared/git/SharedXcodeSettings/DeveloperSettings.xcconfig
//
#include "../../SharedXcodeSettings/DeveloperSettings.xcconfig"
#include? "../../SharedXcodeSettings/DeveloperSettings.xcconfig"
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
COMBINE_HIDPI_IMAGES = YES