Add RSFeedFinder framework.

This commit is contained in:
Brent Simmons 2017-05-22 13:27:54 -07:00
parent 074bbca652
commit 5c3870a03c
25 changed files with 5299 additions and 0 deletions

View File

@ -20,6 +20,10 @@
84B06FB31ED37DBD00F0B54B /* RSDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06F9D1ED37DA000F0B54B /* RSDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
84B06FC21ED37E9600F0B54B /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FBD1ED37E8C00F0B54B /* RSWeb.framework */; };
84B06FC31ED37E9600F0B54B /* RSWeb.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FBD1ED37E8C00F0B54B /* RSWeb.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
84B06FCF1ED37F7D00F0B54B /* DB5.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FCC1ED37F7200F0B54B /* DB5.framework */; };
84B06FD01ED37F7D00F0B54B /* DB5.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FCC1ED37F7200F0B54B /* DB5.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
84B06FE91ED3803A00F0B54B /* RSFeedFinder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FE61ED3803200F0B54B /* RSFeedFinder.framework */; };
84B06FEA1ED3803A00F0B54B /* RSFeedFinder.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FE61ED3803200F0B54B /* RSFeedFinder.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -142,6 +146,48 @@
remoteGlobalIDString = 849C08B51E0CAC85006B03FA;
remoteInfo = RSWeb;
};
84B06FCB1ED37F7200F0B54B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84B06FC61ED37F7200F0B54B /* DB5.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 84F22BD11B52DC2E000060CE;
remoteInfo = DB5;
};
84B06FCD1ED37F7200F0B54B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84B06FC61ED37F7200F0B54B /* DB5.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 84F22BDB1B52DC2E000060CE;
remoteInfo = DB5Tests;
};
84B06FD11ED37F7D00F0B54B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84B06FC61ED37F7200F0B54B /* DB5.xcodeproj */;
proxyType = 1;
remoteGlobalIDString = 84F22BD01B52DC2E000060CE;
remoteInfo = DB5;
};
84B06FE51ED3803200F0B54B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84B06FE01ED3803200F0B54B /* RSFeedFinder.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 84BAAE1F1C8E6B3B009F5239;
remoteInfo = RSFeedFinder;
};
84B06FE71ED3803200F0B54B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84B06FE01ED3803200F0B54B /* RSFeedFinder.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 84BAAE291C8E6B3B009F5239;
remoteInfo = RSFeedFinderTests;
};
84B06FEB1ED3803A00F0B54B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84B06FE01ED3803200F0B54B /* RSFeedFinder.xcodeproj */;
proxyType = 1;
remoteGlobalIDString = 84BAAE1E1C8E6B3B009F5239;
remoteInfo = RSFeedFinder;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@ -152,7 +198,9 @@
dstSubfolderSpec = 10;
files = (
84B06FB31ED37DBD00F0B54B /* RSDatabase.framework in Embed Frameworks */,
84B06FEA1ED3803A00F0B54B /* RSFeedFinder.framework in Embed Frameworks */,
84B06FAF1ED37DBD00F0B54B /* RSCore.framework in Embed Frameworks */,
84B06FD01ED37F7D00F0B54B /* DB5.framework in Embed Frameworks */,
84B06FC31ED37E9600F0B54B /* RSWeb.framework in Embed Frameworks */,
84B06F831ED37BDD00F0B54B /* RSXML.framework in Embed Frameworks */,
);
@ -175,6 +223,8 @@
84B06F961ED37DA000F0B54B /* RSDatabase.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSDatabase.xcodeproj; path = Frameworks/RSDatabase/RSDatabase.xcodeproj; sourceTree = "<group>"; };
84B06FA21ED37DAC00F0B54B /* RSCore.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSCore.xcodeproj; path = Frameworks/RSCore/RSCore.xcodeproj; sourceTree = "<group>"; };
84B06FB61ED37E8B00F0B54B /* RSWeb.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSWeb.xcodeproj; path = Frameworks/RSWeb/RSWeb.xcodeproj; sourceTree = "<group>"; };
84B06FC61ED37F7200F0B54B /* DB5.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = DB5.xcodeproj; path = Frameworks/DB5/DB5.xcodeproj; sourceTree = "<group>"; };
84B06FE01ED3803200F0B54B /* RSFeedFinder.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSFeedFinder.xcodeproj; path = Frameworks/RSFeedFinder/RSFeedFinder.xcodeproj; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -183,7 +233,9 @@
buildActionMask = 2147483647;
files = (
84B06FB21ED37DBD00F0B54B /* RSDatabase.framework in Frameworks */,
84B06FE91ED3803A00F0B54B /* RSFeedFinder.framework in Frameworks */,
84B06FAE1ED37DBD00F0B54B /* RSCore.framework in Frameworks */,
84B06FCF1ED37F7D00F0B54B /* DB5.framework in Frameworks */,
84B06FC21ED37E9600F0B54B /* RSWeb.framework in Frameworks */,
84B06F821ED37BDD00F0B54B /* RSXML.framework in Frameworks */,
);
@ -209,8 +261,10 @@
849C646C1ED37A5D003D8FC0 /* Info.plist */,
849C64741ED37A5D003D8FC0 /* EvergreenTests */,
849C64611ED37A5D003D8FC0 /* Products */,
84B06FC61ED37F7200F0B54B /* DB5.xcodeproj */,
84B06FA21ED37DAC00F0B54B /* RSCore.xcodeproj */,
84B06F961ED37DA000F0B54B /* RSDatabase.xcodeproj */,
84B06FE01ED3803200F0B54B /* RSFeedFinder.xcodeproj */,
84B06FB61ED37E8B00F0B54B /* RSWeb.xcodeproj */,
84B06F761ED37BCA00F0B54B /* RSXML.xcodeproj */,
);
@ -274,6 +328,24 @@
name = Products;
sourceTree = "<group>";
};
84B06FC71ED37F7200F0B54B /* Products */ = {
isa = PBXGroup;
children = (
84B06FCC1ED37F7200F0B54B /* DB5.framework */,
84B06FCE1ED37F7200F0B54B /* DB5Tests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
84B06FE11ED3803200F0B54B /* Products */ = {
isa = PBXGroup;
children = (
84B06FE61ED3803200F0B54B /* RSFeedFinder.framework */,
84B06FE81ED3803200F0B54B /* RSFeedFinderTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -293,6 +365,8 @@
84B06FB11ED37DBD00F0B54B /* PBXTargetDependency */,
84B06FB51ED37DBD00F0B54B /* PBXTargetDependency */,
84B06FC51ED37E9600F0B54B /* PBXTargetDependency */,
84B06FD21ED37F7D00F0B54B /* PBXTargetDependency */,
84B06FEC1ED3803A00F0B54B /* PBXTargetDependency */,
);
name = Evergreen;
productName = Evergreen;
@ -352,6 +426,10 @@
productRefGroup = 849C64611ED37A5D003D8FC0 /* Products */;
projectDirPath = "";
projectReferences = (
{
ProductGroup = 84B06FC71ED37F7200F0B54B /* Products */;
ProjectRef = 84B06FC61ED37F7200F0B54B /* DB5.xcodeproj */;
},
{
ProductGroup = 84B06FA31ED37DAC00F0B54B /* Products */;
ProjectRef = 84B06FA21ED37DAC00F0B54B /* RSCore.xcodeproj */;
@ -360,6 +438,10 @@
ProductGroup = 84B06F971ED37DA000F0B54B /* Products */;
ProjectRef = 84B06F961ED37DA000F0B54B /* RSDatabase.xcodeproj */;
},
{
ProductGroup = 84B06FE11ED3803200F0B54B /* Products */;
ProjectRef = 84B06FE01ED3803200F0B54B /* RSFeedFinder.xcodeproj */;
},
{
ProductGroup = 84B06FB71ED37E8B00F0B54B /* Products */;
ProjectRef = 84B06FB61ED37E8B00F0B54B /* RSWeb.xcodeproj */;
@ -462,6 +544,34 @@
remoteRef = 84B06FC01ED37E8C00F0B54B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
84B06FCC1ED37F7200F0B54B /* DB5.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = DB5.framework;
remoteRef = 84B06FCB1ED37F7200F0B54B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
84B06FCE1ED37F7200F0B54B /* DB5Tests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = DB5Tests.xctest;
remoteRef = 84B06FCD1ED37F7200F0B54B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
84B06FE61ED3803200F0B54B /* RSFeedFinder.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = RSFeedFinder.framework;
remoteRef = 84B06FE51ED3803200F0B54B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
84B06FE81ED3803200F0B54B /* RSFeedFinderTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = RSFeedFinderTests.xctest;
remoteRef = 84B06FE71ED3803200F0B54B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */
@ -529,6 +639,16 @@
name = RSWeb;
targetProxy = 84B06FC41ED37E9600F0B54B /* PBXContainerItemProxy */;
};
84B06FD21ED37F7D00F0B54B /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = DB5;
targetProxy = 84B06FD11ED37F7D00F0B54B /* PBXContainerItemProxy */;
};
84B06FEC1ED3803A00F0B54B /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = RSFeedFinder;
targetProxy = 84B06FEB1ED3803A00F0B54B /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */

View File

@ -0,0 +1,408 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
84F22BD51B52DC2E000060CE /* DB5.h in Headers */ = {isa = PBXBuildFile; fileRef = 84F22BD41B52DC2E000060CE /* DB5.h */; settings = {ATTRIBUTES = (Public, ); }; };
84F22BDC1B52DC2E000060CE /* DB5.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84F22BD11B52DC2E000060CE /* DB5.framework */; };
84F22BE11B52DC2E000060CE /* DB5Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 84F22BE01B52DC2E000060CE /* DB5Tests.m */; };
84F22BEF1B52DC48000060CE /* VSTheme.h in Headers */ = {isa = PBXBuildFile; fileRef = 84F22BEB1B52DC48000060CE /* VSTheme.h */; settings = {ATTRIBUTES = (Public, ); }; };
84F22BF01B52DC48000060CE /* VSTheme.m in Sources */ = {isa = PBXBuildFile; fileRef = 84F22BEC1B52DC48000060CE /* VSTheme.m */; };
84F22BF11B52DC48000060CE /* VSThemeLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 84F22BED1B52DC48000060CE /* VSThemeLoader.h */; settings = {ATTRIBUTES = (Public, ); }; };
84F22BF21B52DC48000060CE /* VSThemeLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 84F22BEE1B52DC48000060CE /* VSThemeLoader.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
84F22BDD1B52DC2E000060CE /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84F22BC81B52DC2E000060CE /* Project object */;
proxyType = 1;
remoteGlobalIDString = 84F22BD01B52DC2E000060CE;
remoteInfo = DB5;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
84F22BD11B52DC2E000060CE /* DB5.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DB5.framework; sourceTree = BUILT_PRODUCTS_DIR; };
84F22BD41B52DC2E000060CE /* DB5.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = DB5.h; path = DB5/DB5.h; sourceTree = "<group>"; };
84F22BD61B52DC2E000060CE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = DB5/Info.plist; sourceTree = "<group>"; };
84F22BDB1B52DC2E000060CE /* DB5Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DB5Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
84F22BE01B52DC2E000060CE /* DB5Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DB5Tests.m; sourceTree = "<group>"; };
84F22BE21B52DC2E000060CE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
84F22BEB1B52DC48000060CE /* VSTheme.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VSTheme.h; path = DB5/VSTheme.h; sourceTree = "<group>"; };
84F22BEC1B52DC48000060CE /* VSTheme.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VSTheme.m; path = DB5/VSTheme.m; sourceTree = "<group>"; };
84F22BED1B52DC48000060CE /* VSThemeLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VSThemeLoader.h; path = DB5/VSThemeLoader.h; sourceTree = "<group>"; };
84F22BEE1B52DC48000060CE /* VSThemeLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VSThemeLoader.m; path = DB5/VSThemeLoader.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
84F22BCD1B52DC2E000060CE /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
84F22BD81B52DC2E000060CE /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
84F22BDC1B52DC2E000060CE /* DB5.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
84F22BC71B52DC2E000060CE = {
isa = PBXGroup;
children = (
84F22BD41B52DC2E000060CE /* DB5.h */,
84F22BEB1B52DC48000060CE /* VSTheme.h */,
84F22BEC1B52DC48000060CE /* VSTheme.m */,
84F22BED1B52DC48000060CE /* VSThemeLoader.h */,
84F22BEE1B52DC48000060CE /* VSThemeLoader.m */,
84F22BD61B52DC2E000060CE /* Info.plist */,
84F22BDF1B52DC2E000060CE /* DB5Tests */,
84F22BD21B52DC2E000060CE /* Products */,
);
sourceTree = "<group>";
};
84F22BD21B52DC2E000060CE /* Products */ = {
isa = PBXGroup;
children = (
84F22BD11B52DC2E000060CE /* DB5.framework */,
84F22BDB1B52DC2E000060CE /* DB5Tests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
84F22BDF1B52DC2E000060CE /* DB5Tests */ = {
isa = PBXGroup;
children = (
84F22BE01B52DC2E000060CE /* DB5Tests.m */,
84F22BE21B52DC2E000060CE /* Info.plist */,
);
path = DB5Tests;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
84F22BCE1B52DC2E000060CE /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
84F22BD51B52DC2E000060CE /* DB5.h in Headers */,
84F22BEF1B52DC48000060CE /* VSTheme.h in Headers */,
84F22BF11B52DC48000060CE /* VSThemeLoader.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
84F22BD01B52DC2E000060CE /* DB5 */ = {
isa = PBXNativeTarget;
buildConfigurationList = 84F22BE51B52DC2E000060CE /* Build configuration list for PBXNativeTarget "DB5" */;
buildPhases = (
84F22BCC1B52DC2E000060CE /* Sources */,
84F22BCD1B52DC2E000060CE /* Frameworks */,
84F22BCE1B52DC2E000060CE /* Headers */,
84F22BCF1B52DC2E000060CE /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = DB5;
productName = DB5;
productReference = 84F22BD11B52DC2E000060CE /* DB5.framework */;
productType = "com.apple.product-type.framework";
};
84F22BDA1B52DC2E000060CE /* DB5Tests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 84F22BE81B52DC2E000060CE /* Build configuration list for PBXNativeTarget "DB5Tests" */;
buildPhases = (
84F22BD71B52DC2E000060CE /* Sources */,
84F22BD81B52DC2E000060CE /* Frameworks */,
84F22BD91B52DC2E000060CE /* Resources */,
);
buildRules = (
);
dependencies = (
84F22BDE1B52DC2E000060CE /* PBXTargetDependency */,
);
name = DB5Tests;
productName = DB5Tests;
productReference = 84F22BDB1B52DC2E000060CE /* DB5Tests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
84F22BC81B52DC2E000060CE /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0700;
ORGANIZATIONNAME = "Ranchero Software, LLC";
TargetAttributes = {
84F22BD01B52DC2E000060CE = {
CreatedOnToolsVersion = 7.0;
LastSwiftMigration = 0800;
};
84F22BDA1B52DC2E000060CE = {
CreatedOnToolsVersion = 7.0;
LastSwiftMigration = 0800;
};
};
};
buildConfigurationList = 84F22BCB1B52DC2E000060CE /* Build configuration list for PBXProject "DB5" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 84F22BC71B52DC2E000060CE;
productRefGroup = 84F22BD21B52DC2E000060CE /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
84F22BD01B52DC2E000060CE /* DB5 */,
84F22BDA1B52DC2E000060CE /* DB5Tests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
84F22BCF1B52DC2E000060CE /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
84F22BD91B52DC2E000060CE /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
84F22BCC1B52DC2E000060CE /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
84F22BF01B52DC48000060CE /* VSTheme.m in Sources */,
84F22BF21B52DC48000060CE /* VSThemeLoader.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
84F22BD71B52DC2E000060CE /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
84F22BE11B52DC2E000060CE /* DB5Tests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
84F22BDE1B52DC2E000060CE /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 84F22BD01B52DC2E000060CE /* DB5 */;
targetProxy = 84F22BDD1B52DC2E000060CE /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
84F22BE31B52DC2E000060CE /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
84F22BE41B52DC2E000060CE /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
84F22BE61B52DC2E000060CE /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES;
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_VERSION = A;
INFOPLIST_FILE = DB5/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.DB5;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
};
name = Debug;
};
84F22BE71B52DC2E000060CE /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES;
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_VERSION = A;
INFOPLIST_FILE = DB5/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.DB5;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
};
name = Release;
};
84F22BE91B52DC2E000060CE /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = DB5Tests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.DB5Tests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
};
name = Debug;
};
84F22BEA1B52DC2E000060CE /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = DB5Tests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.DB5Tests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
84F22BCB1B52DC2E000060CE /* Build configuration list for PBXProject "DB5" */ = {
isa = XCConfigurationList;
buildConfigurations = (
84F22BE31B52DC2E000060CE /* Debug */,
84F22BE41B52DC2E000060CE /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
84F22BE51B52DC2E000060CE /* Build configuration list for PBXNativeTarget "DB5" */ = {
isa = XCConfigurationList;
buildConfigurations = (
84F22BE61B52DC2E000060CE /* Debug */,
84F22BE71B52DC2E000060CE /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
84F22BE81B52DC2E000060CE /* Build configuration list for PBXNativeTarget "DB5Tests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
84F22BE91B52DC2E000060CE /* Debug */,
84F22BEA1B52DC2E000060CE /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 84F22BC81B52DC2E000060CE /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:DB5.xcodeproj">
</FileRef>
</Workspace>

21
Frameworks/DB5/DB5/DB5.h Normal file
View File

@ -0,0 +1,21 @@
//
// DB5.h
// DB5
//
// Created by Brent Simmons on 7/12/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
#import <Cocoa/Cocoa.h>
//! Project version number for DB5.
FOUNDATION_EXPORT double DB5VersionNumber;
//! Project version string for DB5.
FOUNDATION_EXPORT const unsigned char DB5VersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <DB5/PublicHeader.h>
#import <DB5/VSThemeLoader.h>
#import <DB5/VSTheme.h>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2015 Ranchero Software, LLC. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -0,0 +1,92 @@
//
// VSTheme.h
// Q Branch LLC
//
// Created by Brent Simmons on 6/26/13.
// Copyright (c) 2012 Q Branch LLC. All rights reserved.
//
@import Cocoa;
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, VSTextCaseTransform) {
VSTextCaseTransformNone,
VSTextCaseTransformUpper,
VSTextCaseTransformLower
};
@class VSAnimationSpecifier;
@interface VSTheme : NSObject
- (id)initWithDictionary:(NSDictionary *)themeDictionary;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, weak) VSTheme *parentTheme; /*can inherit*/
- (nullable id)objectForKey:(NSString *)key;
- (BOOL)boolForKey:(NSString *)key;
- (nullable NSString *)stringForKey:(NSString *)key;
- (NSInteger)integerForKey:(NSString *)key;
- (CGFloat)floatForKey:(NSString *)key;
#if TARGET_OS_IPHONE
- (nullable UIImage *)imageForKey:(NSString *)key; /*Via UIImage imageNamed:*/
- (UIColor *)colorForKey:(NSString *)key; /*123ABC or #123ABC: 6 digits, leading # allowed but not required*/
- (UIEdgeInsets)edgeInsetsForKey:(NSString *)key; /*xTop, xLeft, xRight, xBottom keys*/
- (UIFont *)fontForKey:(NSString *)key; /*x and xSize keys*/
#else /*Mac*/
- (nullable NSImage *)imageForKey:(NSString *)key; /*Via NSImage imageNamed:*/
- (NSColor *)colorForKey:(NSString *)key; /*123ABC or #123ABC: 6 digits, leading # allowed but not required*/
- (NSColor *)colorWithAlphaForKey:(NSString *)key; /*key and keyAlpha*/
- (NSEdgeInsets)edgeInsetsForKey:(NSString *)key; /*xTop, xLeft, xRight, xBottom keys*/
- (NSFont *)fontForKey:(NSString *)key; /*x and xSize keys*/
#endif
- (CGPoint)pointForKey:(NSString *)key; /*xX and xY keys*/
- (CGSize)sizeForKey:(NSString *)key; /*xWidth and xHeight keys*/
- (NSTimeInterval)timeIntervalForKey:(NSString *)key;
#if TARGET_OS_IPHONE
- (UIViewAnimationOptions)curveForKey:(NSString *)key; /*Possible values: easeinout, easeout, easein, linear*/
- (VSAnimationSpecifier *)animationSpecifierForKey:(NSString *)key; /*xDuration, xDelay, xCurve*/
#endif
- (VSTextCaseTransform)textCaseTransformForKey:(NSString *)key; /*lowercase or uppercase -- returns VSTextCaseTransformNone*/
- (NSString *)string:(NSString *)s transformedWithTextCaseTransformKey:(NSString *)key;
@end
NSString *VSThemeSpecifierPlusKey(NSString *specifier, NSString *key); /*specifier + . + key*/
#if TARGET_OS_IPHONE
@interface VSTheme (Animations)
- (void)animateWithAnimationSpecifierKey:(NSString *)animationSpecifierKey animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion;
@end
@interface VSAnimationSpecifier : NSObject
@property (nonatomic, assign) NSTimeInterval delay;
@property (nonatomic, assign) NSTimeInterval duration;
@property (nonatomic, assign) UIViewAnimationOptions curve;
@end
#endif
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,367 @@
//
// VSTheme.m
// Q Branch LLC
//
// Created by Brent Simmons on 6/26/13.
// Copyright (c) 2012 Q Branch LLC. All rights reserved.
//
#import "VSTheme.h"
#if TARGET_OS_IPHONE
#define VS_COLOR UIColor
#define VS_IMAGE UIImage
#define VS_FONT UIFont
#define VS_EDGE_INSETS UIEdgeInsets
#define VSEdgeInsetsMake UIEdgeInsetsMake
#else /*Mac*/
#define VS_COLOR NSColor
#define VS_IMAGE NSImage
#define VS_FONT NSFont
#define VS_EDGE_INSETS NSEdgeInsets
#define VSEdgeInsetsMake NSEdgeInsetsMake
#endif
static BOOL stringIsEmpty(NSString *s);
static VS_COLOR *colorWithHexString(NSString *hexString);
@interface VSTheme ()
@property (nonatomic, strong) NSDictionary *themeDictionary;
@property (nonatomic, strong) NSMutableDictionary *colorCache;
@property (nonatomic, strong) NSMutableDictionary *colorWithAlphaCache;
@property (nonatomic, strong) NSMutableDictionary *fontCache;
@end
@implementation VSTheme
#pragma mark Init
- (id)initWithDictionary:(NSDictionary *)themeDictionary {
self = [super init];
if (self == nil)
return nil;
_themeDictionary = themeDictionary;
_colorCache = [NSMutableDictionary new];
_fontCache = [NSMutableDictionary new];
return self;
}
- (id)objectForKey:(NSString *)key {
id obj = [self.themeDictionary valueForKeyPath:key];
if (obj == nil && self.parentTheme != nil)
obj = [self.parentTheme objectForKey:key];
return obj;
}
- (BOOL)boolForKey:(NSString *)key {
id obj = [self objectForKey:key];
if (obj == nil)
return NO;
return [obj boolValue];
}
- (NSString *)stringForKey:(NSString *)key {
id obj = [self objectForKey:key];
if (obj == nil)
return nil;
if ([obj isKindOfClass:[NSString class]])
return obj;
if ([obj isKindOfClass:[NSNumber class]])
return [obj stringValue];
return nil;
}
- (NSInteger)integerForKey:(NSString *)key {
id obj = [self objectForKey:key];
if (obj == nil)
return 0;
return [obj integerValue];
}
- (CGFloat)floatForKey:(NSString *)key {
id obj = [self objectForKey:key];
if (obj == nil)
return 0.0f;
return [obj floatValue];
}
- (NSTimeInterval)timeIntervalForKey:(NSString *)key {
id obj = [self objectForKey:key];
if (obj == nil)
return 0.0;
return [obj doubleValue];
}
- (VS_IMAGE *)imageForKey:(NSString *)key {
NSString *imageName = [self stringForKey:key];
if (stringIsEmpty(imageName))
return nil;
return [VS_IMAGE imageNamed:imageName];
}
- (VS_COLOR *)colorForKey:(NSString *)key {
VS_COLOR *cachedColor = self.colorCache[key];
if (cachedColor != nil)
return cachedColor;
NSString *colorString = [self stringForKey:key];
VS_COLOR *color = nil;
if ([colorString isEqualToString:@"clear"])
color = [VS_COLOR clearColor];
else
color = colorWithHexString(colorString);
if (color == nil)
color = [VS_COLOR blackColor];
self.colorCache[key] = color;
return color;
}
- (VS_COLOR *)colorWithAlphaForKey:(NSString *)key {
VS_COLOR *cachedColor = self.colorWithAlphaCache[key];
if (cachedColor != nil) {
return cachedColor;
}
VS_COLOR *color = [self colorForKey:key];
CGFloat alpha = [self floatForKey:[key stringByAppendingString:@"Alpha"]];
color = [color colorWithAlphaComponent:alpha];
self.colorWithAlphaCache[key] = color;
return color;
}
- (VS_EDGE_INSETS)edgeInsetsForKey:(NSString *)key {
CGFloat left = [self floatForKey:[key stringByAppendingString:@"Left"]];
CGFloat top = [self floatForKey:[key stringByAppendingString:@"Top"]];
CGFloat right = [self floatForKey:[key stringByAppendingString:@"Right"]];
CGFloat bottom = [self floatForKey:[key stringByAppendingString:@"Bottom"]];
VS_EDGE_INSETS edgeInsets = VSEdgeInsetsMake(top, left, bottom, right);
return edgeInsets;
}
- (VS_FONT *)fontForKey:(NSString *)key {
VS_FONT *cachedFont = self.fontCache[key];
if (cachedFont != nil)
return cachedFont;
NSString *fontName = [self stringForKey:key];
CGFloat fontSize = [self floatForKey:[key stringByAppendingString:@"Size"]];
if (fontSize < 1.0f)
fontSize = 15.0f;
VS_FONT *font = nil;
if (stringIsEmpty(fontName))
font = [VS_FONT systemFontOfSize:fontSize];
else
font = [VS_FONT fontWithName:fontName size:fontSize];
if (font == nil)
font = [VS_FONT systemFontOfSize:fontSize];
self.fontCache[key] = font;
return font;
}
- (CGPoint)pointForKey:(NSString *)key {
CGFloat pointX = [self floatForKey:[key stringByAppendingString:@"X"]];
CGFloat pointY = [self floatForKey:[key stringByAppendingString:@"Y"]];
CGPoint point = CGPointMake(pointX, pointY);
return point;
}
- (CGSize)sizeForKey:(NSString *)key {
CGFloat width = [self floatForKey:[key stringByAppendingString:@"Width"]];
CGFloat height = [self floatForKey:[key stringByAppendingString:@"Height"]];
CGSize size = CGSizeMake(width, height);
return size;
}
#if TARGET_OS_IPHONE
- (UIViewAnimationOptions)curveForKey:(NSString *)key {
NSString *curveString = [self stringForKey:key];
if (stringIsEmpty(curveString))
return UIViewAnimationOptionCurveEaseInOut;
curveString = [curveString lowercaseString];
if ([curveString isEqualToString:@"easeinout"])
return UIViewAnimationOptionCurveEaseInOut;
else if ([curveString isEqualToString:@"easeout"])
return UIViewAnimationOptionCurveEaseOut;
else if ([curveString isEqualToString:@"easein"])
return UIViewAnimationOptionCurveEaseIn;
else if ([curveString isEqualToString:@"linear"])
return UIViewAnimationOptionCurveLinear;
return UIViewAnimationOptionCurveEaseInOut;
}
- (VSAnimationSpecifier *)animationSpecifierForKey:(NSString *)key {
VSAnimationSpecifier *animationSpecifier = [VSAnimationSpecifier new];
animationSpecifier.duration = [self timeIntervalForKey:[key stringByAppendingString:@"Duration"]];
animationSpecifier.delay = [self timeIntervalForKey:[key stringByAppendingString:@"Delay"]];
animationSpecifier.curve = [self curveForKey:[key stringByAppendingString:@"Curve"]];
return animationSpecifier;
}
#endif
- (VSTextCaseTransform)textCaseTransformForKey:(NSString *)key {
NSString *s = [self stringForKey:key];
if (s == nil)
return VSTextCaseTransformNone;
if ([s caseInsensitiveCompare:@"lowercase"] == NSOrderedSame)
return VSTextCaseTransformLower;
else if ([s caseInsensitiveCompare:@"uppercase"] == NSOrderedSame)
return VSTextCaseTransformUpper;
return VSTextCaseTransformNone;
}
- (NSString *)string:(NSString *)s transformedWithTextCaseTransformKey:(NSString *)key {
VSTextCaseTransform textCaseTransform = [self textCaseTransformForKey:key];
NSString *transformedString = nil;
switch (textCaseTransform) {
case VSTextCaseTransformNone:
transformedString = s;
break;
case VSTextCaseTransformLower:
transformedString = [s lowercaseString];
break;
case VSTextCaseTransformUpper:
transformedString = [s uppercaseString];
break;
default:
break;
}
return transformedString;
}
@end
NSString *VSThemeSpecifierPlusKey(NSString *specifier, NSString *key) {
return [NSString stringWithFormat:@"%@.%@", specifier, key];
}
#if TARGET_OS_IPHONE
@implementation VSTheme (Animations)
- (void)animateWithAnimationSpecifierKey:(NSString *)animationSpecifierKey animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion {
VSAnimationSpecifier *animationSpecifier = [self animationSpecifierForKey:animationSpecifierKey];
[UIView animateWithDuration:animationSpecifier.duration delay:animationSpecifier.delay options:animationSpecifier.curve animations:animations completion:completion];
}
@end
#pragma mark -
@implementation VSAnimationSpecifier
@end
#endif
static BOOL stringIsEmpty(NSString *s) {
return s == nil || [s length] == 0;
}
static VS_COLOR *colorWithHexString(NSString *hexString) {
/*Picky. Crashes by design.*/
if (stringIsEmpty(hexString))
return [VS_COLOR blackColor];
NSMutableString *s = [hexString mutableCopy];
[s replaceOccurrencesOfString:@"#" withString:@"" options:0 range:NSMakeRange(0, [hexString length])];
CFStringTrimWhitespace((__bridge CFMutableStringRef)s);
NSString *redString = [s substringToIndex:2];
NSString *greenString = [s substringWithRange:NSMakeRange(2, 2)];
NSString *blueString = [s substringWithRange:NSMakeRange(4, 2)];
unsigned int red = 0, green = 0, blue = 0;
[[NSScanner scannerWithString:redString] scanHexInt:&red];
[[NSScanner scannerWithString:greenString] scanHexInt:&green];
[[NSScanner scannerWithString:blueString] scanHexInt:&blue];
return [VS_COLOR colorWithRed:(CGFloat)red/255.0f green:(CGFloat)green/255.0f blue:(CGFloat)blue/255.0f alpha:1.0f];
}

View File

@ -0,0 +1,24 @@
//
// VSThemeLoader.h
// Q Branch LLC
//
// Created by Brent Simmons on 6/26/13.
// Copyright (c) 2012 Q Branch LLC. All rights reserved.
//
#import <Foundation/Foundation.h>
@class VSTheme;
@interface VSThemeLoader : NSObject
// Just use -init if you want the default DB5.plist in the normal place.
- (instancetype)initWithFilepath:(NSString *)f;
@property (nonatomic, strong, readonly) VSTheme *defaultTheme;
@property (nonatomic, strong, readonly) NSArray *themes;
- (VSTheme *)themeNamed:(NSString *)themeName;
@end

View File

@ -0,0 +1,71 @@
//
// VSThemeLoader.h
// Q Branch LLC
//
// Created by Brent Simmons on 6/26/13.
// Copyright (c) 2012 Q Branch LLC. All rights reserved.
//
#import "VSThemeLoader.h"
#import "VSTheme.h"
@interface VSThemeLoader ()
@property (nonatomic, strong, readwrite) VSTheme *defaultTheme;
@property (nonatomic, strong, readwrite) NSArray *themes;
@end
@implementation VSThemeLoader
- (instancetype)init {
NSString *themesFilePath = [[NSBundle mainBundle] pathForResource:@"DB5" ofType:@"plist"];
return [self initWithFilepath:themesFilePath];
}
- (instancetype)initWithFilepath:(NSString *)f {
self = [super init];
if (!self) {
return nil;
}
NSDictionary *themesDictionary = [NSDictionary dictionaryWithContentsOfFile:f];
NSMutableArray *themes = [NSMutableArray array];
for (NSString *oneKey in themesDictionary) {
VSTheme *theme = [[VSTheme alloc] initWithDictionary:themesDictionary[oneKey]];
if ([[oneKey lowercaseString] isEqualToString:@"default"])
_defaultTheme = theme;
theme.name = oneKey;
[themes addObject:theme];
}
for (VSTheme *oneTheme in themes) { /*All themes inherit from the default theme.*/
if (oneTheme != _defaultTheme)
oneTheme.parentTheme = _defaultTheme;
}
_themes = themes;
return self;
}
- (VSTheme *)themeNamed:(NSString *)themeName {
for (VSTheme *oneTheme in self.themes) {
if ([themeName isEqualToString:oneTheme.name])
return oneTheme;
}
return nil;
}
@end

View File

@ -0,0 +1,39 @@
//
// DB5Tests.m
// DB5Tests
//
// Created by Brent Simmons on 7/12/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
#import <XCTest/XCTest.h>
@interface DB5Tests : XCTestCase
@end
@implementation DB5Tests
- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}
- (void)testExample {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
- (void)testPerformanceExample {
// This is an example of a performance test case.
[self measureBlock:^{
// Put the code you want to measure the time of here.
}];
}
@end

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View File

@ -0,0 +1,454 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
8430C4701D57ED6D00E02399 /* FeedSpecifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8430C46F1D57ED6D00E02399 /* FeedSpecifier.swift */; };
8430C4721D57F88600E02399 /* HTMLFeedFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8430C4711D57F88600E02399 /* HTMLFeedFinder.swift */; };
8430C4741D57FE9600E02399 /* HTMLFeedFinderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8430C4731D57FE9600E02399 /* HTMLFeedFinderTests.swift */; };
8430C4761D57FF0600E02399 /* DaringFireball.html in Resources */ = {isa = PBXBuildFile; fileRef = 8430C4751D57FF0600E02399 /* DaringFireball.html */; };
8430C47A1D58033D00E02399 /* furbo.html in Resources */ = {isa = PBXBuildFile; fileRef = 8430C4771D58033D00E02399 /* furbo.html */; };
8430C47B1D58033D00E02399 /* inessential.html in Resources */ = {isa = PBXBuildFile; fileRef = 8430C4781D58033D00E02399 /* inessential.html */; };
8430C47C1D58033D00E02399 /* sixcolors.html in Resources */ = {isa = PBXBuildFile; fileRef = 8430C4791D58033D00E02399 /* sixcolors.html */; };
8444267C1D5138A00092EDD4 /* FeedFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8444267B1D5138A00092EDD4 /* FeedFinder.swift */; };
84B06FEF1ED3808F00F0B54B /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FEE1ED3808F00F0B54B /* RSWeb.framework */; };
84B06FF11ED380A700F0B54B /* RSXML.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FF01ED380A700F0B54B /* RSXML.framework */; };
84B06FF31ED3812600F0B54B /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FF21ED3812600F0B54B /* RSCore.framework */; };
84BAAE231C8E6B3B009F5239 /* RSFeedFinder.h in Headers */ = {isa = PBXBuildFile; fileRef = 84BAAE221C8E6B3B009F5239 /* RSFeedFinder.h */; settings = {ATTRIBUTES = (Public, ); }; };
84BAAE2A1C8E6B3B009F5239 /* RSFeedFinder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84BAAE1F1C8E6B3B009F5239 /* RSFeedFinder.framework */; };
84BAAE2F1C8E6B3B009F5239 /* RSFeedFinderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 84BAAE2E1C8E6B3B009F5239 /* RSFeedFinderTests.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
84BAAE2B1C8E6B3B009F5239 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84BAAE161C8E6B3B009F5239 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 84BAAE1E1C8E6B3B009F5239;
remoteInfo = RSFeedFinder;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
8430C46F1D57ED6D00E02399 /* FeedSpecifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FeedSpecifier.swift; path = RSFeedFinder/FeedSpecifier.swift; sourceTree = "<group>"; };
8430C4711D57F88600E02399 /* HTMLFeedFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HTMLFeedFinder.swift; path = RSFeedFinder/HTMLFeedFinder.swift; sourceTree = "<group>"; };
8430C4731D57FE9600E02399 /* HTMLFeedFinderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLFeedFinderTests.swift; sourceTree = "<group>"; };
8430C4751D57FF0600E02399 /* DaringFireball.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = DaringFireball.html; path = Resources/DaringFireball.html; sourceTree = "<group>"; };
8430C4771D58033D00E02399 /* furbo.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = furbo.html; path = Resources/furbo.html; sourceTree = "<group>"; };
8430C4781D58033D00E02399 /* inessential.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = inessential.html; path = Resources/inessential.html; sourceTree = "<group>"; };
8430C4791D58033D00E02399 /* sixcolors.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = sixcolors.html; path = Resources/sixcolors.html; sourceTree = "<group>"; };
8444267B1D5138A00092EDD4 /* FeedFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FeedFinder.swift; path = RSFeedFinder/FeedFinder.swift; sourceTree = "<group>"; };
84B06FEE1ED3808F00F0B54B /* RSWeb.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RSWeb.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Evergreen-edcdqyvewhytnmcfaiesnqiqeynn/Build/Products/Debug/RSWeb.framework"; sourceTree = "<group>"; };
84B06FF01ED380A700F0B54B /* RSXML.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RSXML.framework; path = ../RSXML/build/Debug/RSXML.framework; sourceTree = "<group>"; };
84B06FF21ED3812600F0B54B /* RSCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RSCore.framework; path = ../RSCore/build/Debug/RSCore.framework; sourceTree = "<group>"; };
84BAAE1F1C8E6B3B009F5239 /* RSFeedFinder.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RSFeedFinder.framework; sourceTree = BUILT_PRODUCTS_DIR; };
84BAAE221C8E6B3B009F5239 /* RSFeedFinder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RSFeedFinder.h; path = RSFeedFinder/RSFeedFinder.h; sourceTree = "<group>"; };
84BAAE241C8E6B3B009F5239 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = RSFeedFinder/Info.plist; sourceTree = "<group>"; };
84BAAE291C8E6B3B009F5239 /* RSFeedFinderTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RSFeedFinderTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
84BAAE2E1C8E6B3B009F5239 /* RSFeedFinderTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RSFeedFinderTests.m; sourceTree = "<group>"; };
84BAAE301C8E6B3B009F5239 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
84E697E51C8E6C10009C585A /* RSCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RSCore.framework; path = ../RSCore/build/Debug/RSCore.framework; sourceTree = "<group>"; };
84E697E71C8E6C16009C585A /* RSXML.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RSXML.framework; path = ../RSXML/build/Debug/RSXML.framework; sourceTree = "<group>"; };
84E697E91C8E6C20009C585A /* RSWeb.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RSWeb.framework; path = ../RSWeb/build/Debug/RSWeb.framework; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
84BAAE1B1C8E6B3B009F5239 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
84B06FF31ED3812600F0B54B /* RSCore.framework in Frameworks */,
84B06FF11ED380A700F0B54B /* RSXML.framework in Frameworks */,
84B06FEF1ED3808F00F0B54B /* RSWeb.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
84BAAE261C8E6B3B009F5239 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
84BAAE2A1C8E6B3B009F5239 /* RSFeedFinder.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
84B06FED1ED3808E00F0B54B /* Frameworks */ = {
isa = PBXGroup;
children = (
84B06FF21ED3812600F0B54B /* RSCore.framework */,
84B06FF01ED380A700F0B54B /* RSXML.framework */,
84B06FEE1ED3808F00F0B54B /* RSWeb.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
84BAAE151C8E6B3B009F5239 = {
isa = PBXGroup;
children = (
84BAAE221C8E6B3B009F5239 /* RSFeedFinder.h */,
8444267B1D5138A00092EDD4 /* FeedFinder.swift */,
8430C46F1D57ED6D00E02399 /* FeedSpecifier.swift */,
8430C4711D57F88600E02399 /* HTMLFeedFinder.swift */,
84BAAE241C8E6B3B009F5239 /* Info.plist */,
84E697E91C8E6C20009C585A /* RSWeb.framework */,
84E697E71C8E6C16009C585A /* RSXML.framework */,
84E697E51C8E6C10009C585A /* RSCore.framework */,
84BAAE2D1C8E6B3B009F5239 /* RSFeedFinderTests */,
84BAAE201C8E6B3B009F5239 /* Products */,
84B06FED1ED3808E00F0B54B /* Frameworks */,
);
sourceTree = "<group>";
};
84BAAE201C8E6B3B009F5239 /* Products */ = {
isa = PBXGroup;
children = (
84BAAE1F1C8E6B3B009F5239 /* RSFeedFinder.framework */,
84BAAE291C8E6B3B009F5239 /* RSFeedFinderTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
84BAAE2D1C8E6B3B009F5239 /* RSFeedFinderTests */ = {
isa = PBXGroup;
children = (
84BAAE2E1C8E6B3B009F5239 /* RSFeedFinderTests.m */,
8430C4731D57FE9600E02399 /* HTMLFeedFinderTests.swift */,
8430C4751D57FF0600E02399 /* DaringFireball.html */,
8430C4771D58033D00E02399 /* furbo.html */,
8430C4781D58033D00E02399 /* inessential.html */,
8430C4791D58033D00E02399 /* sixcolors.html */,
84BAAE301C8E6B3B009F5239 /* Info.plist */,
);
path = RSFeedFinderTests;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
84BAAE1C1C8E6B3B009F5239 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
84BAAE231C8E6B3B009F5239 /* RSFeedFinder.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
84BAAE1E1C8E6B3B009F5239 /* RSFeedFinder */ = {
isa = PBXNativeTarget;
buildConfigurationList = 84BAAE331C8E6B3B009F5239 /* Build configuration list for PBXNativeTarget "RSFeedFinder" */;
buildPhases = (
84BAAE1A1C8E6B3B009F5239 /* Sources */,
84BAAE1B1C8E6B3B009F5239 /* Frameworks */,
84BAAE1C1C8E6B3B009F5239 /* Headers */,
84BAAE1D1C8E6B3B009F5239 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = RSFeedFinder;
productName = RSFeedFinder;
productReference = 84BAAE1F1C8E6B3B009F5239 /* RSFeedFinder.framework */;
productType = "com.apple.product-type.framework";
};
84BAAE281C8E6B3B009F5239 /* RSFeedFinderTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 84BAAE361C8E6B3B009F5239 /* Build configuration list for PBXNativeTarget "RSFeedFinderTests" */;
buildPhases = (
84BAAE251C8E6B3B009F5239 /* Sources */,
84BAAE261C8E6B3B009F5239 /* Frameworks */,
84BAAE271C8E6B3B009F5239 /* Resources */,
);
buildRules = (
);
dependencies = (
84BAAE2C1C8E6B3B009F5239 /* PBXTargetDependency */,
);
name = RSFeedFinderTests;
productName = RSFeedFinderTests;
productReference = 84BAAE291C8E6B3B009F5239 /* RSFeedFinderTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
84BAAE161C8E6B3B009F5239 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0720;
ORGANIZATIONNAME = "Ranchero Software";
TargetAttributes = {
84BAAE1E1C8E6B3B009F5239 = {
CreatedOnToolsVersion = 7.2.1;
LastSwiftMigration = 0800;
};
84BAAE281C8E6B3B009F5239 = {
CreatedOnToolsVersion = 7.2.1;
LastSwiftMigration = 0800;
};
};
};
buildConfigurationList = 84BAAE191C8E6B3B009F5239 /* Build configuration list for PBXProject "RSFeedFinder" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 84BAAE151C8E6B3B009F5239;
productRefGroup = 84BAAE201C8E6B3B009F5239 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
84BAAE1E1C8E6B3B009F5239 /* RSFeedFinder */,
84BAAE281C8E6B3B009F5239 /* RSFeedFinderTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
84BAAE1D1C8E6B3B009F5239 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
84BAAE271C8E6B3B009F5239 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8430C47B1D58033D00E02399 /* inessential.html in Resources */,
8430C4761D57FF0600E02399 /* DaringFireball.html in Resources */,
8430C47A1D58033D00E02399 /* furbo.html in Resources */,
8430C47C1D58033D00E02399 /* sixcolors.html in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
84BAAE1A1C8E6B3B009F5239 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8430C4721D57F88600E02399 /* HTMLFeedFinder.swift in Sources */,
8430C4701D57ED6D00E02399 /* FeedSpecifier.swift in Sources */,
8444267C1D5138A00092EDD4 /* FeedFinder.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
84BAAE251C8E6B3B009F5239 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8430C4741D57FE9600E02399 /* HTMLFeedFinderTests.swift in Sources */,
84BAAE2F1C8E6B3B009F5239 /* RSFeedFinderTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
84BAAE2C1C8E6B3B009F5239 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 84BAAE1E1C8E6B3B009F5239 /* RSFeedFinder */;
targetProxy = 84BAAE2B1C8E6B3B009F5239 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
84BAAE311C8E6B3B009F5239 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
84BAAE321C8E6B3B009F5239 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
84BAAE341C8E6B3B009F5239 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_VERSION = A;
INFOPLIST_FILE = RSFeedFinder/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.RSFeedFinder;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
};
name = Debug;
};
84BAAE351C8E6B3B009F5239 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_VERSION = A;
INFOPLIST_FILE = RSFeedFinder/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.RSFeedFinder;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
};
name = Release;
};
84BAAE371C8E6B3B009F5239 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = RSFeedFinderTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.RSFeedFinderTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
};
name = Debug;
};
84BAAE381C8E6B3B009F5239 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = RSFeedFinderTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.RSFeedFinderTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
84BAAE191C8E6B3B009F5239 /* Build configuration list for PBXProject "RSFeedFinder" */ = {
isa = XCConfigurationList;
buildConfigurations = (
84BAAE311C8E6B3B009F5239 /* Debug */,
84BAAE321C8E6B3B009F5239 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
84BAAE331C8E6B3B009F5239 /* Build configuration list for PBXNativeTarget "RSFeedFinder" */ = {
isa = XCConfigurationList;
buildConfigurations = (
84BAAE341C8E6B3B009F5239 /* Debug */,
84BAAE351C8E6B3B009F5239 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
84BAAE361C8E6B3B009F5239 /* Build configuration list for PBXNativeTarget "RSFeedFinderTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
84BAAE371C8E6B3B009F5239 /* Debug */,
84BAAE381C8E6B3B009F5239 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 84BAAE161C8E6B3B009F5239 /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:RSFeedFinder.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,208 @@
//
// FeedFinder.swift
// RSFeedFinder
//
// Created by Brent Simmons on 8/2/16.
// Copyright © 2016 Ranchero Software. All rights reserved.
//
import Foundation
import RSXML
import RSWeb
import RSCore
public protocol FeedFinderDelegate {
func feedFinder(_: FeedFinder, didFindFeeds: Set<FeedSpecifier>)
}
public class FeedFinder {
fileprivate let delegate: FeedFinderDelegate
fileprivate var feedSpecifiers = [String: FeedSpecifier]()
fileprivate var didNotifyDelegate = false
public var initialDownloadError: Error?
public var initialDownloadStatusCode = -1
public init(url: URL, delegate: FeedFinderDelegate) {
self.delegate = delegate
DispatchQueue.main.async() { () -> Void in
self.findFeeds(url)
}
}
deinit {
notifyDelegateIfNeeded()
}
}
private extension FeedFinder {
func addFeedSpecifier(_ feedSpecifier: FeedSpecifier) {
// If theres an existing feed specifier, merge the two so that we have the best data. If one has a title and one doesnt, use that non-nil title. Use the better source.
if let existingFeedSpecifier = feedSpecifiers[feedSpecifier.urlString] {
let mergedFeedSpecifier = existingFeedSpecifier.feedSpecifierByMerging(feedSpecifier)
feedSpecifiers[feedSpecifier.urlString] = mergedFeedSpecifier
}
else {
feedSpecifiers[feedSpecifier.urlString] = feedSpecifier
}
}
func findFeedsInHTMLPage(htmlData: Data, urlString: String) {
// Feeds in the <head> section we automatically assume are feeds.
// If there are none from the <head> section,
// then possible feeds in <body> section are downloaded individually
// and added once we determine they are feeds.
let possibleFeedSpecifiers = possibleFeedsInHTMLPage(htmlData: htmlData, urlString: urlString)
var feedSpecifiersToDownload = Set<FeedSpecifier>()
var didFindFeedInHTMLHead = false
for oneFeedSpecifier in possibleFeedSpecifiers {
if oneFeedSpecifier.source == .HTMLHead {
addFeedSpecifier(oneFeedSpecifier)
didFindFeedInHTMLHead = true
}
else {
if !feedSpecifiersContainsURLString(oneFeedSpecifier.urlString) {
feedSpecifiersToDownload.insert(oneFeedSpecifier)
}
}
}
if didFindFeedInHTMLHead || feedSpecifiersToDownload.isEmpty {
stopFinding()
}
else {
downloadFeedSpecifiers(feedSpecifiersToDownload)
}
}
func possibleFeedsInHTMLPage(htmlData: Data, urlString: String) -> Set<FeedSpecifier> {
let xmlData = RSXMLData(data: htmlData, urlString: urlString)
var feedSpecifiers = HTMLFeedFinder(xmlData: xmlData).feedSpecifiers
if feedSpecifiers.isEmpty {
// Odds are decent its a WordPress site, and just adding /feed/ will work.
if let url = URL(string: urlString) {
let feedURL = url.appendingPathComponent("feed", isDirectory: true)
let wordpressFeedSpecifier = FeedSpecifier(title: nil, urlString: feedURL.absoluteString, source: .HTMLLink)
feedSpecifiers.insert(wordpressFeedSpecifier)
}
}
return feedSpecifiers
}
func feedSpecifiersContainsURLString(_ urlString: String) -> Bool {
if let _ = feedSpecifiers[urlString] {
return true
}
return false
}
func isHTML(_ data: Data) -> Bool {
return (data as NSData).rs_dataIsProbablyHTML()
}
func findFeeds(_ initialURL: URL) {
downloadInitialFeed(initialURL)
}
func downloadInitialFeed(_ initialURL: URL) {
download(initialURL) { (data, response, error) in
self.initialDownloadStatusCode = response?.forcedStatusCode ?? -1
if let error = error {
self.initialDownloadError = error
self.stopFinding()
return
}
guard let data = data, let response = response else {
self.stopFinding()
return
}
if !response.statusIsOK || data.isEmpty {
self.stopFinding()
return
}
if self.isFeed(data, initialURL.absoluteString) {
let feedSpecifier = FeedSpecifier(title: nil, urlString: initialURL.absoluteString, source: .UserEntered)
self.addFeedSpecifier(feedSpecifier)
self.stopFinding()
return
}
if !self.isHTML(data) {
self.stopFinding()
return
}
self.findFeedsInHTMLPage(htmlData: data, urlString: initialURL.absoluteString)
}
}
func downloadFeedSpecifiers(_ feedSpecifiers: Set<FeedSpecifier>) {
var pendingDownloads = feedSpecifiers
for oneFeedSpecifier in feedSpecifiers {
guard let url = URL(string: oneFeedSpecifier.urlString) else {
pendingDownloads.remove(oneFeedSpecifier)
continue
}
download(url) { (data, response, error) in
pendingDownloads.remove(oneFeedSpecifier)
if let data = data, let response = response, response.statusIsOK, error == nil {
if self.isFeed(data, oneFeedSpecifier.urlString) {
self.addFeedSpecifier(oneFeedSpecifier)
}
}
if pendingDownloads.isEmpty {
self.stopFinding()
}
}
}
}
func stopFinding() {
notifyDelegateIfNeeded()
}
func notifyDelegateIfNeeded() {
if !didNotifyDelegate {
delegate.feedFinder(self, didFindFeeds: Set(feedSpecifiers.values))
didNotifyDelegate = true
}
}
func isFeed(_ data: Data, _ urlString: String) -> Bool {
let xmlData = RSXMLData(data: data, urlString: urlString)
return RSCanParseFeed(xmlData)
}
}

View File

@ -0,0 +1,124 @@
//
// FeedSpecifier.swift
// RSFeedFinder
//
// Created by Brent Simmons on 8/7/16.
// Copyright © 2016 Ranchero Software. All rights reserved.
//
import Foundation
public struct FeedSpecifier: Hashable {
public enum Source: Int {
case UserEntered = 0, HTMLHead, HTMLLink
func equalToOrBetterThan(_ otherSource: Source) -> Bool {
return self.rawValue <= otherSource.rawValue
}
}
public let title: String?
public let urlString: String
public let source: Source
public let hashValue: Int
public var score: Int {
get {
return calculatedScore()
}
}
init(title: String?, urlString: String, source: Source) {
self.title = title
self.urlString = urlString
self.source = source
self.hashValue = urlString.hashValue
}
public static func ==(lhs: FeedSpecifier, rhs: FeedSpecifier) -> Bool {
return lhs.urlString == rhs.urlString && lhs.title == rhs.title && lhs.source == rhs.source
}
func feedSpecifierByMerging(_ feedSpecifier: FeedSpecifier) -> FeedSpecifier {
// Take the best data (non-nil title, better source) to create a new feed specifier;
let mergedTitle = title ?? feedSpecifier.title
let mergedSource = source.equalToOrBetterThan(feedSpecifier.source) ? source : feedSpecifier.source
return FeedSpecifier(title: mergedTitle, urlString: urlString, source: mergedSource)
}
public static func bestFeed(in feedSpecifiers: Set<FeedSpecifier>) -> FeedSpecifier? {
if feedSpecifiers.isEmpty {
return nil
}
if feedSpecifiers.count == 1 {
return feedSpecifiers.anyObject()
}
var currentHighScore = 0
var currentBestFeed = feedSpecifiers.anyObject()
for oneFeedSpecifier in feedSpecifiers {
let oneScore = oneFeedSpecifier.score
if oneScore > currentHighScore {
currentHighScore = oneScore
currentBestFeed = oneFeedSpecifier
}
}
return currentBestFeed
}
}
private extension FeedSpecifier {
func calculatedScore() -> Int {
var score = 0
if source == .UserEntered {
return 1000
}
else if source == .HTMLHead {
score = score + 50
}
if urlString.rs_caseInsensitiveContains("comments") {
score = score - 10
}
if urlString.rs_caseInsensitiveContains("rss") {
score = score + 5
}
if urlString.rs_caseInsensitiveContains("atom") {
score = score - 5
}
if urlString.hasSuffix("/feed/") {
score = score + 5
}
if urlString.hasSuffix("/feed") {
score = score + 4
}
if let title = title {
if title.rs_caseInsensitiveContains("comments") {
score = score - 10
}
if title.rs_caseInsensitiveContains("rss") {
score = score + 10
}
if title.rs_caseInsensitiveContains("atom") {
score = score - 5
}
}
return score
}
}

View File

@ -0,0 +1,83 @@
//
// HTMLFeedFinder.swift
// RSFeedFinder
//
// Created by Brent Simmons on 8/7/16.
// Copyright © 2016 Ranchero Software. All rights reserved.
//
import Foundation
import RSXML
private let feedURLWordsToMatch = ["feed", "xml", "rss", "atom"]
class HTMLFeedFinder {
var feedSpecifiers: Set<FeedSpecifier> {
get {
return Set(feedSpecifiersDictionary.values)
}
}
fileprivate var feedSpecifiersDictionary = [String: FeedSpecifier]()
init(xmlData: RSXMLData) {
let metadata = RSHTMLMetadataParser(xmlData: xmlData).metadata
for oneFeedLink in metadata.feedLinks {
if let oneURLString = oneFeedLink.urlString {
let oneFeedSpecifier = FeedSpecifier(title: oneFeedLink.title, urlString: oneURLString, source: .HTMLHead)
addFeedSpecifier(oneFeedSpecifier)
}
}
if let bodyLinks = RSHTMLLinkParser.htmlLinks(with: xmlData) {
for oneBodyLink in bodyLinks {
if linkMightBeFeed(oneBodyLink) {
let oneFeedSpecifier = FeedSpecifier(title: oneBodyLink.text, urlString: oneBodyLink.urlString!, source: .HTMLLink)
addFeedSpecifier(oneFeedSpecifier)
}
}
}
}
}
private extension HTMLFeedFinder {
func addFeedSpecifier(_ feedSpecifier: FeedSpecifier) {
// If theres an existing feed specifier, merge the two so that we have the best data. If one has a title and one doesnt, use that non-nil title. Use the better source.
if let existingFeedSpecifier = feedSpecifiersDictionary[feedSpecifier.urlString] {
let mergedFeedSpecifier = existingFeedSpecifier.feedSpecifierByMerging(feedSpecifier)
feedSpecifiersDictionary[feedSpecifier.urlString] = mergedFeedSpecifier
}
else {
feedSpecifiersDictionary[feedSpecifier.urlString] = feedSpecifier
}
}
func urlStringMightBeFeed(_ urlString: String) -> Bool {
let massagedURLString = urlString.replacingOccurrences(of: "buzzfeed", with: "_")
for oneMatch in feedURLWordsToMatch {
let range = (massagedURLString as NSString).range(of: oneMatch, options: .caseInsensitive)
if range.length > 0 {
return true
}
}
return false
}
func linkMightBeFeed(_ link: RSHTMLLink) -> Bool {
if let linkURLString = link.urlString, urlStringMightBeFeed(linkURLString) {
return true
}
return false
}
}

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2016 Ranchero Software. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -0,0 +1,29 @@
//
// RSFeedFinder.h
// RSFeedFinder
//
// Created by Brent Simmons on 3/7/16.
// Copyright © 2016 Ranchero Software. All rights reserved.
//
#import <Cocoa/Cocoa.h>
//! Project version number for RSFeedFinder.
FOUNDATION_EXPORT double RSFeedFinderVersionNumber;
//! Project version string for RSFeedFinder.
FOUNDATION_EXPORT const unsigned char RSFeedFinderVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <RSFeedFinder/PublicHeader.h>
/* Given a URL, find one or more feeds.
Download URL
If is feed, return URL
If is web page, find possible feeds in web page
Download each possible URL
If URL is feed, add to URLs list
return URLs list
*/

View File

@ -0,0 +1,115 @@
//
// HTMLFeedFinderTests.swift
// RSFeedFinder
//
// Created by Brent Simmons on 8/7/16.
// Copyright © 2016 Ranchero Software. All rights reserved.
//
import XCTest
@testable import RSFeedFinder
import RSXML
class HTMLFeedFinderTests: XCTestCase {
private var xmlDataCache = [String: RSXMLData]()
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
func xmlDataFor(title: String, urlString: String) -> RSXMLData? {
if let cachedXMLData = xmlDataCache[title] {
return cachedXMLData
}
if let s = Bundle(for: self.dynamicType).url(forResource: title, withExtension: "html") {
let d = try! Data(contentsOf: s)
let xmlData = RSXMLData(data: d, urlString: urlString)
xmlDataCache[title] = xmlData
return xmlData
}
return nil
}
func daringFireballData() -> RSXMLData {
return xmlDataFor(title:"DaringFireball", urlString:"http://daringfireball.net/")!
}
func furboData() -> RSXMLData {
return xmlDataFor(title:"furbo", urlString:"http://furbo.org/")!
}
func inessentialData() -> RSXMLData {
return xmlDataFor(title:"inessential", urlString:"http://inessential.com/")!
}
func sixColorsData() -> RSXMLData {
return xmlDataFor(title:"sixcolors", urlString:"https://sixcolors.com/")!
}
func testPerformanceWithDaringFireball() {
let xmlData = daringFireballData()
self.measure {
let finder = HTMLFeedFinder(xmlData: xmlData)
let _ = finder.feedSpecifiers
}
}
func testHTMLParserWithDaringFireBall() {
let finder = HTMLFeedFinder(xmlData: daringFireballData())
let feedSpecifiers = finder.feedSpecifiers
let bestFeedSpecifier = FeedFinder.bestFeed(in: feedSpecifiers)
print(bestFeedSpecifier)
}
func testHTMLParserWithFurbo() {
let finder = HTMLFeedFinder(xmlData: furboData())
let feedSpecifiers = finder.feedSpecifiers
let bestFeedSpecifier = FeedFinder.bestFeed(in: feedSpecifiers)
print(bestFeedSpecifier)
}
func testHTMLParserWithInessential() {
let finder = HTMLFeedFinder(xmlData: inessentialData())
let feedSpecifiers = finder.feedSpecifiers
let bestFeedSpecifier = FeedFinder.bestFeed(in: feedSpecifiers)
print(bestFeedSpecifier)
}
func testHTMLParserWithSixColors() {
let finder = HTMLFeedFinder(xmlData: sixColorsData())
let feedSpecifiers = finder.feedSpecifiers
let bestFeedSpecifier = FeedFinder.bestFeed(in: feedSpecifiers)
print(bestFeedSpecifier)
}
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View File

@ -0,0 +1,39 @@
//
// RSFeedFinderTests.m
// RSFeedFinderTests
//
// Created by Brent Simmons on 3/7/16.
// Copyright © 2016 Ranchero Software. All rights reserved.
//
#import <XCTest/XCTest.h>
@interface RSFeedFinderTests : XCTestCase
@end
@implementation RSFeedFinderTests
- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}
- (void)testExample {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
- (void)testPerformanceExample {
// This is an example of a performance test case.
[self measureBlock:^{
// Put the code you want to measure the time of here.
}];
}
@end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,372 @@
<!DOCTYPE html>
<html lang="en-US" class="no-js">
<head>
<meta charset="UTF-8">
<!-- <meta name="viewport" content="width=device-width">-->
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<meta name="description" content="furbo.org is Craig Hockenberry's place to write for the web. He makes app and runs websites." />
<meta name="keywords" content="chock, woot" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="NOODP">
<title>furbo.org by Craig Hockenberry</title>
<link rel="alternate" type="application/rss+xml" title="Iconfactory News Feed" href="http://furbo.org/feed/" />
<link href="http://furbo.org/wp-content/themes/furbo/reset.css?ver=20150205" media="screen" rel="Stylesheet" type="text/css" />
<link href="http://furbo.org/wp-content/themes/furbo/style.css?ver=20150206" media="screen" rel="Stylesheet" type="text/css" />
<link rel="shortcut icon" href="http://furbo.org/favicon.ico" />
<script type="text/javascript" src="http://furbo.org/wp-content/themes/furbo/scripts/minified.js"></script>
<script type="text/javascript" src="http://furbo.org/wp-content/themes/furbo/scripts/furbo.js"></script>
</head>
<body class="home blog">
<div id="page" class="hfeed site">
<a class="skip-link screen-reader-text" href="#content">Skip to content</a>
<header id="masthead" class="site-header" role="banner">
<h1 class="site-title screen-reader-text">
<a href="http://furbo.org/" rel="home">
furbo.org </a>
</h1>
<div id="logo">
<a href="http://furbo.org/" rel="home">
furbo.org </a>
</div>
<div id="collapsed-logo">
<a href="http://furbo.org/" rel="home">
furbo.org </a>
</div>
<div id="controls">
<p id="control-info" onclick="toggleSidebar();">info</p>
</div>
</header><!-- .site-header -->
<div id="background">
</div>
<div id="sidebar" class="sidebar">
<div id="secondary" class="secondary">
<aside id="about-me" class="widget widget_text">
<h2 class="widget-title">About Me</h2>
<div class="textwidget">
<p>
I'm Craig Hockenberry and this is where I write for the web. I make <a href="http://iconfactoryapps.com">apps</a> and run <a href="http://iconfactory.com">websites</a>.
</p>
<p>
You can learn <a href="/about">more about me</a>, view <a href="/resume">my résumé</a>, or <a href="http://twitter.com/chockenberry">follow me</a>.
</p>
</div>
</aside>
<div id="widget-area" class="widget-area" role="complementary">
<aside id="search-3" class="widget widget_search"><form role="search" method="get" class="search-form" action="http://furbo.org/">
<label>
<span class="screen-reader-text">Search for:</span>
<input type="search" class="search-field" placeholder="Search &hellip;" value="" name="s" title="Search for:" />
</label>
<input type="submit" class="search-submit" value="Search" />
</form></aside> <aside id="recent-writing-3" class="widget widget_recent_entries"> <h2 class="widget-title">Recent Writing</h2> <ul>
<li>
<a href="http://furbo.org/2016/02/20/the-forensic-shit-show/">The Forensic Shit Show</a>
</li>
<li>
<a href="http://furbo.org/2015/12/29/strapped-in/">Strapped In</a>
</li>
<li>
<a href="http://furbo.org/2015/12/28/the-new-ipod/">The New iPod</a>
</li>
<li>
<a href="http://furbo.org/2015/11/04/a-responsive-factory/">A Responsive Factory</a>
</li>
<li>
<a href="http://furbo.org/2015/10/07/ipulse-3/">iPulse 3</a>
</li>
<li>
<a href="http://furbo.org/2015/07/23/code-signing-in-el-capitan/">Code Signing in El Capitan</a>
</li>
<li>
<a href="http://furbo.org/2015/07/22/half-assed/">Half-Assed</a>
</li>
<li>
<a href="http://furbo.org/2015/07/14/a-watch-water-and-workouts/">A Watch, Water and Workouts</a>
</li>
<li>
<a href="http://furbo.org/2015/07/09/i-left-my-system-fonts-in-san-francisco/">I Left My System Fonts in San Francisco</a>
</li>
<li>
<a href="http://furbo.org/2015/06/15/bug-writing-season/">Bug Writing Season</a>
</li>
</ul>
</aside><aside id="history-2" class="widget widget_archive"><h2 class="widget-title">Archives</h2> <ul>
<li><a href='http://furbo.org/2016/'>2016</a></li>
<li><a href='http://furbo.org/2015/'>2015</a></li>
<li><a href='http://furbo.org/2014/'>2014</a></li>
<li><a href='http://furbo.org/2013/'>2013</a></li>
<li><a href='http://furbo.org/2012/'>2012</a></li>
<li><a href='http://furbo.org/2011/'>2011</a></li>
<li><a href='http://furbo.org/2010/'>2010</a></li>
<li><a href='http://furbo.org/2009/'>2009</a></li>
<li><a href='http://furbo.org/2008/'>2008</a></li>
<li><a href='http://furbo.org/2007/'>2007</a></li>
</ul>
</aside> </div><!-- .widget-area -->
<aside id="other" class="widget widget_text">
<div class="textwidget">
<p>
<a href="http://furbo.org/feed">RSS</a>
</p>
<p>
&copy; 2016 Craig Hockenberry
</p>
<p>
All rights reserved
</p>
</div>
</aside>
</div><!-- .secondary -->
</div><!-- .sidebar -->
<div id="content" class="site-content">
<div id="primary" class="content-area">
<main id="main" class="site-main" role="main">
<!-- index is_home = 1 is_front_page = 1-->
<article id="post-1984" class="post-1984 post type-post status-publish format-standard hentry category-miscellaneous category-opinion">
<div class="entry-header">
<h2 class="entry-title"><a href="http://furbo.org/2016/02/20/the-forensic-shit-show/" rel="bookmark">The Forensic Shit Show</a></h2> </div><!-- .entry-header -->
<div class="entry-content">
<p>It turns out someone at the FBI advised another law enforcement officer in San Bernardino to <a href="https://twitter.com/countywire/status/700887823482630144" onclick="javascript:pageTracker._trackPageview('/outbound/article/twitter.com');">reset the iPhone</a> that the government wants Apple to unlock.</p>
<p>This is just another episode in a complete forensic shit show.</p>
<p>Remember, this is the same case where <a href="http://www.nbcnews.com/storyline/san-bernardino-shooting/go-inside-home-syed-farook-tashfeen-malik-n474601" onclick="javascript:pageTracker._trackPageview('/outbound/article/www.nbcnews.com');">the media was allowed to roam freely through a crime scene</a>. One of the photos in that gallery <a href="http://media4.s-nbcnews.com/j/msnbc/components/photo/_new/ss-151204-farook-apartment-jsw-13.nbcnews-ux-1024-900.jpg" onclick="javascript:pageTracker._trackPageview('/outbound/article/media4.s-nbcnews.com');">shows a computer without an Ethernet connection on the wall</a> (the age of the apartment also suggests that there would be no wired Internet.)</p>
<p>What are the chances that there was a wireless network in that apartment? What are the chances that there are IP logs on that router? Or maybe some kind of data backed up to a disk on the router? Here&#8217;s another wild guess: maybe that router was used to <a href="https://twitter.com/kennwhite/status/700865678274752512" onclick="javascript:pageTracker._trackPageview('/outbound/article/twitter.com');">connect to an online backup service</a>.</p>
<p>Yep, someone did the equivalent of a &#8220;restore factory defaults&#8221; on a device under active investigation.</p>
<p>What we&#8217;re seeing here is law enforcement&#8217;s complete lack of understanding of how digital devices store and transmit data. This new evidence is much more intricate than smoking guns or blood splatters. The important stuff is what you don&#8217;t see: it&#8217;s a hard problem where the people dealing with it are untrained. Shit, I work in this business and trying to decipher what&#8217;s going on makes my head spin.</p>
<p>Yet law enforcement is asking Apple to not only provide data, but also to create a forensic <strong>instrument</strong> that allows them to extract information from any device. And by its very nature, <a href="http://www.zdziarski.com/blog/?p=5645" onclick="javascript:pageTracker._trackPageview('/outbound/article/www.zdziarski.com');">this tool would be made widely available throughout the forensic and law enforcement community</a>.</p>
<p>Basically, the government is asking Apple to hand over a golden key that can defeat the security of <strong>any</strong> device to folks that can&#8217;t even secure a wireless network. Worse, this whole process is being overseen by politicians that think the problem is <a href="http://thehill.com/policy/cybersecurity/262658-feinstein-vows-to-offer-encryption-piercing-bill" onclick="javascript:pageTracker._trackPageview('/outbound/article/thehill.com');">predators getting access to their grandkid&#8217;s Playstation</a>.</p>
<p>This is why the entire tech community is saying &#8220;No fucking way.&#8221;</p>
<p><strong>Updated February 21st, 2016:</strong> Several people have commented about my use of &#8220;restore factory defaults&#8221; in the post above. My intention was figurative, not literal.</p>
<p>The folks involved with the investigation were pressing buttons without understanding the consequences of their actions. To me, it feels like a &#8220;reboot to fix&#8221; approach. The password reset did not damage any data, it just made automatic backups stop working because iCloud information on the device needed to be updated, and that can&#8217;t be done without a passcode.</p>
<p>Others have reminded me that the FBI had cleared the crime scene. That&#8217;s true, but since the Wi-Fi equipment was not collected as evidence, it still shows that the investigators were out of their league. In an electronic investigation, a router is a key piece of the puzzle.</p>
<p>Both of these things are details in a bigger picture: the FBI wants to hold the private keys to a public key encryption system that affects the privacy of hundreds of millions people. If they can&#8217;t get the details of an online backup service right, how the hell do we expect them to guard a back door?</p>
<p>There&#8217;s also <a href="http://daringfireball.net/2016/02/san_bernardino_password_reset" onclick="javascript:pageTracker._trackPageview('/outbound/article/daringfireball.net');">a possibility that the iCloud password reset was intentional</a>. If this is the case, we have a government that is extorting Apple by essentially planting evidence. Imagine what they could do with a private key.</p>
</div><!-- .entry-content -->
<div class="entry-footer">
Written on February 20th, 2016 for <a href="http://furbo.org/category/miscellaneous/" title="View all posts in Miscellaneous" rel="category tag">Miscellaneous</a>, <a href="http://furbo.org/category/opinion/" title="View all posts in Opinion" rel="category tag">Opinion</a> </div><!-- .entry-footer -->
</article><!-- #post-## -->
<article id="post-1953" class="post-1953 post type-post status-publish format-standard hentry category-miscellaneous category-observation">
<div class="entry-header">
<h2 class="entry-title"><a href="http://furbo.org/2015/12/29/strapped-in/" rel="bookmark">Strapped In</a></h2> </div><!-- .entry-header -->
<div class="entry-content">
<p>A lot has happened since I purchased <a href="http://www.apple.com/shop/buy-watch/apple-watch-sport/42mm-silver-aluminum-case-with-blue-sport-band?product=MLC52LL/A&#038;step=detail" onclick="javascript:pageTracker._trackPageview('/outbound/article/www.apple.com');">my Apple Watch</a> on April 10th, 2015. One unexpected aspect to owning this device is my fascination with watch bands:</p>
<figure>
<a href="http://furbo.org/wp-content/uploads/2015/12/AppleWatchBands.jpeg" ><img src="http://furbo.org/wp-content/uploads/2015/12/AppleWatchBands.jpeg" alt="Apple Watch Bands" width="1120" height="862" class="aligncenter size-full wp-image-1954" /></a></p>
<figcaption>My current collection of watch bands. And no, <a href="http://furbo.org/2015/05/22/apple-watch-ergonomics/" >the watch isn&#8217;t upside down</a>.</figcaption>
</figure>
<p>From left to right, in order of date purchased:</p>
<ul>
<li>
<a href="http://www.apple.com/shop/buy-watch/apple-watch-sport/42mm-silver-aluminum-case-with-blue-sport-band?product=MLC52LL/A&#038;step=detail" onclick="javascript:pageTracker._trackPageview('/outbound/article/www.apple.com');">Sport Model with Blue Band</a> ($400) &#8211; I picked the aluminum watch with a blue band because I knew it would be spending a lot of time in the water. To date, I&#8217;ve <a href="http://furbo.org/2015/07/14/a-watch-water-and-workouts/" >used it 110 times for over 35 hours of swimming</a>.
</li>
<li>
<a href="http://www.apple.com/shop/product/MJ5F2/42mm-milanese-loop" onclick="javascript:pageTracker._trackPageview('/outbound/article/www.apple.com');">Milanese Loop</a> ($150) &#8211; I was intrigued by this band as soon as I saw it during the video at the product announcement. I love how the metal feels a lot like fabric. It also dresses up the utilitarian Sport model so it doesn&#8217;t look out of place when I&#8217;m someplace nice.
</li>
<li>
<a href="http://www.clockworksynergy.com/shop/tech-straps/straps-for-apple-watch/2-piece-heavy-nato-straps/black-grey-two-piece-heavy-nato-nylon-watch-strap-fits-apple-watch/" onclick="javascript:pageTracker._trackPageview('/outbound/article/www.clockworksynergy.com');">Black &#038; Silver Nylon</a> ($30) &#8211; This NATO-style band from Clockwork Synergy popped up on my <a href="https://twitter.com/capttaco/status/625339467080040448" onclick="javascript:pageTracker._trackPageview('/outbound/article/twitter.com');">Twitter timeline</a> thanks to my pal <a href="https://twitter.com/capttaco" onclick="javascript:pageTracker._trackPageview('/outbound/article/twitter.com');">Rob Rhyne</a>. I love that it dresses up the watch <strong>and</strong> is waterproof.
</li>
<li>
<a href="http://www.apple.com/shop/product/MLDJ2/42mm-productred-band" onclick="javascript:pageTracker._trackPageview('/outbound/article/www.apple.com');">Red Sport</a> ($50) &#8211; When Apple started selling additional colors for the sport bands, getting one in my favorite color was a no-brainer. I also like that a little of my purchase goes to a worthwhile charity.
</li>
<li>
<a href="http://www.amazon.com/gp/product/B00JP09C7I/ref=as_li_tl?ie=UTF8&#038;camp=1789&#038;creative=390957&#038;creativeASIN=B00JP09C7I&#038;linkCode=as2&#038;tag=furboorg-20&#038;linkId=4LVZVTJFJG5OY4UU" onclick="javascript:pageTracker._trackPageview('/outbound/article/www.amazon.com');">Orange Silicone</a> ($20) &#8211; This band by MoKo was <a href="https://twitter.com/mrgan/status/643847960850247680" onclick="javascript:pageTracker._trackPageview('/outbound/article/twitter.com');">another recommendation</a> from Twitter by <a href="https://twitter.com/mrgan" onclick="javascript:pageTracker._trackPageview('/outbound/article/twitter.com');">Neven Mrgan</a>. To me, the most interesting thing about this band is that it shows why Apple went with <a href="https://en.wikipedia.org/wiki/Fluoroelastomer" onclick="javascript:pageTracker._trackPageview('/outbound/article/en.wikipedia.org');"> fluoroelastomer</a> for their bands: it&#8217;s stiffer and &#8220;breathes&#8221; better than silicone.
</li>
<li>
<a href="https://www.lucrin.com/straps-and-watches/watch-straps/apple-watch/apple-band-42mm-elegance.htm" onclick="javascript:pageTracker._trackPageview('/outbound/article/www.lucrin.com');">Black Goat Leather</a> ($200) &#8211; The leather bands from Apple are nice, but I prefer the classic look of this one from Lucrin. The company also offers a huge range of colors: my wife loves the dark green one I gave at Christmas.
</li>
</ul>
<p>In this survey of my growing collection, there&#8217;s an interesting datapoint: the value of these bands ($450) exceeds the cost of the watch itself ($400).</p>
<p>If Apple decides to change the interchange mechanism in some future version of the watch, I will have very little desire to upgrade. As I continue to &#8220;work in&#8221; my leather band, I hope I&#8217;ll be using them for a long time.</p>
</div><!-- .entry-content -->
<div class="entry-footer">
Written on December 29th, 2015 for <a href="http://furbo.org/category/miscellaneous/" title="View all posts in Miscellaneous" rel="category tag">Miscellaneous</a>, <a href="http://furbo.org/category/observation/" title="View all posts in Observation" rel="category tag">Observation</a> </div><!-- .entry-footer -->
</article><!-- #post-## -->
<article id="post-1942" class="post-1942 post type-post status-publish format-standard hentry category-business category-observation">
<div class="entry-header">
<h2 class="entry-title"><a href="http://furbo.org/2015/12/28/the-new-ipod/" rel="bookmark">The New iPod</a></h2> </div><!-- .entry-header -->
<div class="entry-content">
<p>Something tells me that there were a lot of Apple Watches under the tree this year:</p>
<p><a href="http://furbo.org/wp-content/uploads/2015/09/ClickerDownloads.jpeg" ><img src="http://furbo.org/wp-content/uploads/2015/09/ClickerDownloads.jpeg" alt="Clicker Downloads for December 2015" width="560" height="966" class="aligncenter size-full wp-image-1948" /></a></p>
<p>That graph shows the last month of downloads for my free <a href="https://itunes.apple.com/us/app/clicker-count-anything/id1043951998?ls=1&#038;mt=8&#038;uo=4&#038;at=10l4G7&#038;ct=FURBO" onclick="javascript:pageTracker._trackPageview('/outbound/article/itunes.apple.com');">Clicker</a> app for watchOS. Since this app does nothing on an iPhone or iPad, the only reason to get it is if you have a new watch.</p>
<p>Many of us, myself included, originally thought of the Apple Watch as a device in and of itself. But the more I use the computer on my wrist, the more it feels like a satellite to the computer that&#8217;s sitting in my pocket.</p>
<p>Accessories have always made great gifts for folks who love their computers. Giving the watch as a gift is a perfect option for someone who&#8217;s always playing around with the apps on their iPhone. Just like the iPod was an ideal match for someone who loved playing music on their desktop computer.</p>
</div><!-- .entry-content -->
<div class="entry-footer">
Written on December 28th, 2015 for <a href="http://furbo.org/category/business/" title="View all posts in Business" rel="category tag">Business</a>, <a href="http://furbo.org/category/observation/" title="View all posts in Observation" rel="category tag">Observation</a> </div><!-- .entry-footer -->
</article><!-- #post-## -->
<article id="post-1932" class="post-1932 post type-post status-publish format-link hentry category-miscellaneous">
<div class="entry-header">
<h2 class="entry-title"><a href="http://furbo.org/2015/11/19/clicker-1-1/" title="Permalink" class="permalink">&#x25ba;</a> <a href="https://itunes.apple.com/us/app/clicker-count-anything/id1043951998?ls=1&#038;mt=8&#038;uo=4&#038;at=10l4G7&#038;ct=FURBO">Clicker 1.1</a></h2> </div>
<!-- .entry-header -->
<div class="entry-content">
<p>The <a href="https://itunes.apple.com/us/app/clicker-count-anything/id1043951998?ls=1&#038;mt=8&#038;uo=4&#038;at=10l4G7&#038;ct=FURBO" onclick="javascript:pageTracker._trackPageview('/outbound/article/itunes.apple.com');">Make John Gruber Happy Edition™</a> of Clicker is now available. <a href="http://daringfireball.net/linked/2015/10/14/clicker" onclick="javascript:pageTracker._trackPageview('/outbound/article/daringfireball.net');">Finally.</a></p>
</div>
<!-- .entry-content -->
</article><!-- #post-## -->
<article id="post-1920" class="post-1920 post type-post status-publish format-standard hentry category-design category-development">
<div class="entry-header">
<h2 class="entry-title"><a href="http://furbo.org/2015/11/04/a-responsive-factory/" rel="bookmark">A Responsive Factory</a></h2> </div><!-- .entry-header -->
<div class="entry-content">
<p>Back in May 2014, we introduced a new <a href="http://iconfactory.com" onclick="javascript:pageTracker._trackPageview('/outbound/article/iconfactory.com');">Iconfactory home page</a>. One of the main design goals for that site was to make the layout a <a href="https://en.wikipedia.org/wiki/Responsive_web_design" onclick="javascript:pageTracker._trackPageview('/outbound/article/en.wikipedia.org');">responsive web design</a>: the same site looked great whether you were looking at it on a desktop PC or an iPhone. Reading <a href="http://abookapart.com/products/responsive-web-design" onclick="javascript:pageTracker._trackPageview('/outbound/article/abookapart.com');">Ethan Marcotte&#8217;s book</a> was a revelation.</p>
<p>Of course, that site was just a beginning. <a href="http://design.iconfactory.com/" onclick="javascript:pageTracker._trackPageview('/outbound/article/design.iconfactory.com');">We</a> <a href="http://iconfactoryapps.com/" onclick="javascript:pageTracker._trackPageview('/outbound/article/iconfactoryapps.com');">run</a> <a href="http://blog.iconfactory.com/" onclick="javascript:pageTracker._trackPageview('/outbound/article/blog.iconfactory.com');">a</a> <a href="http://flareapp.com/" onclick="javascript:pageTracker._trackPageview('/outbound/article/flareapp.com');">lot</a> <a href="http://twitterrific.com/ios" onclick="javascript:pageTracker._trackPageview('/outbound/article/twitterrific.com');">of</a> <a href="http://xscopeapp.com/" onclick="javascript:pageTracker._trackPageview('/outbound/article/xscopeapp.com');">web</a> <a href="http://ipulseapp.com/" onclick="javascript:pageTracker._trackPageview('/outbound/article/ipulseapp.com');">sites</a> (<a href="http://appdevmanual.com/" onclick="javascript:pageTracker._trackPageview('/outbound/article/appdevmanual.com');">including</a> <a href="http://astronutapp.com/" onclick="javascript:pageTracker._trackPageview('/outbound/article/astronutapp.com');">some</a> <a href="http://chameleonproject.org/" onclick="javascript:pageTracker._trackPageview('/outbound/article/chameleonproject.org');">you&#8217;ve</a> <a href="http://dine-o-matic.com/" onclick="javascript:pageTracker._trackPageview('/outbound/article/dine-o-matic.com');">probably</a> <a href="http://frenzic.com/" onclick="javascript:pageTracker._trackPageview('/outbound/article/frenzic.com');">never</a> <a href="http://pickintimeapp.com/" onclick="javascript:pageTracker._trackPageview('/outbound/article/pickintimeapp.com');">heard</a> <a href="http://pixelpalooza.com/" onclick="javascript:pageTracker._trackPageview('/outbound/article/pixelpalooza.com');">of</a> <a href="http://takefiveapp.com/" onclick="javascript:pageTracker._trackPageview('/outbound/article/takefiveapp.com');">before</a>). Clearly we had to pick our responsive battles.</p>
<p>We started with an <a href="http://blog.iconfactory.com/2015/01/welcome-to-the-new-iconfactory-blog/" onclick="javascript:pageTracker._trackPageview('/outbound/article/blog.iconfactory.com');">update to our blog</a> in January 2015. In October, we updated our <a href="http://iconfactoryapps.com/" onclick="javascript:pageTracker._trackPageview('/outbound/article/iconfactoryapps.com');">iOS and OS X app catalog</a>. And yesterday we launched a <a href="http://design.iconfactory.com/" onclick="javascript:pageTracker._trackPageview('/outbound/article/design.iconfactory.com');">responsive design portfolio</a>.</p>
<p>A year and a half after our first responsive design, we&#8217;ve hit a milestone. All of the sites listed in the Iconfactory&#8217;s red navigation bar are responsive designs and will display correctly on any device. Woo hoo!</p>
<p>Along the way, we cleaned up some of our branding elements and worked toward a more consistent experience across all the sites. Check out the post at the Iconfactory about <a href="http://blog.iconfactory.com/2015/11/the-new-favicon/" onclick="javascript:pageTracker._trackPageview('/outbound/article/blog.iconfactory.com');">the new SVG icons in Safari</a> to see what that&#8217;s all about.</p>
<p>It&#8217;s clear we&#8217;re at a point in time where the vast assortment of screens is daunting. If you haven&#8217;t thought about how your site works on this wide variety of devices, <a href="http://alistapart.com/article/responsive-web-design" onclick="javascript:pageTracker._trackPageview('/outbound/article/alistapart.com');">now is a great time to start</a>.</p>
</div><!-- .entry-content -->
<div class="entry-footer">
Written on November 4th, 2015 for <a href="http://furbo.org/category/design/" title="View all posts in Design" rel="category tag">Design</a>, <a href="http://furbo.org/category/development/" title="View all posts in Development" rel="category tag">Development</a> </div><!-- .entry-footer -->
</article><!-- #post-## -->
<div id="post-navigation">
<div class="older"><a href="http://furbo.org/page/2/" >Older</a></div>
</div>
</main><!-- .site-main -->
</div><!-- .content-area -->
</div><!-- .site-content -->
<footer id="footer" class="site-footer" role="contentinfo">
<div class="features">
<div class="feature">
<script type="text/javascript">
var content = [];
content.push('\
<a href="http://design.iconfactory.com">\
<img src="/footer/designsite.png" width="198" height="101" />\
</a>\
<h5>Do you write awesome code?</h5>\
<p>Of course, you do!</p>\
<p>But if you\'re like me, you struggle with design and opening Photoshop makes you break out in a cold sweat.</p>\
<p>\
<a href="http://design.iconfactory.com">\
The designers that help me can also help you…</a>\
</p>\
');
content.push('\
<a href="http://xscopeapp.com">\
<img src="/footer/xscope4.png" width="198" height="101" />\
</a>\
<h5>I made this app because I needed it.</h5>\
<p>\
If you do any kind of Web, iOS or Mac development, you need these \
<a href="http://xscopeapp.com/guide">\
awesome tools</a>. I did.\
</p>\
<p>\
After a few days with xScope\'s \
<a href="http://xscopeapp.com">\
FREE trial</a>, you\'ll wonder how you ever did work without it. Trust me.\
</p>\
');
content.push('\
<a href="http://furbo.org/2014/09/03/the-terminal/">\
<img src="/footer/terminal.png" width="200" height="100" />\
</a>\
<h5>$ sudo rm -rf /</h5>\
<p>You use this app every day, but do you know what happens when you option click in it?</p>\
<p>\
Find out in my magnum opus on \
<a href="http://furbo.org/2014/09/03/the-terminal/">\
The Terminal</a>.\
</p>\
');
content.push('\
<a href="https://itunes.apple.com/us/app/ipulse/id1028916583?ls=1&mt=12&uo=4&at=10l4G7&ct=FURBO_FOOTER">\
<img src="/footer/ipulse3.png" width="200" height="100" />\
</a>\
<h5>What is your Mac doing right now?</h5>\
<p>If you\'re reading this site, chances are good that you know <em>a lot</em> about your computer.</p>\
<p>\
But do you know what\'s going on behind the scenes? \
<a href="https://itunes.apple.com/us/app/ipulse/id1028916583?ls=1&mt=12&uo=4&at=10l4G7&ct=FURBO_FOOTER">\
iPulse does</a>.\
</p>\
');
var min = 0;
var max = content.length - 1;
var index = Math.floor(Math.random() * (max - min + 1)) + min;
document.write(content[index]);
</script>
</div><!-- .feature -->
</div><!-- .features -->
</footer><!-- .site-footer -->
</div><!-- .site -->
<!-- Google Analytics for WordPress | http://yoast.com/wordpress/google-analytics/ -->
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
try {
var pageTracker = _gat._getTracker("UA-12748460-3");
// Cookied already:
pageTracker._trackPageview();
} catch(err) {}
</script><!-- End of Google Analytics code -->
</body>
</html>
<!-- Dynamic Page Served (once) in 0.171 seconds -->
<!-- super cache -->

View File

@ -0,0 +1,172 @@
<!DOCTYPE html>
<html>
<head>
<title>inessential: weblog</title>
<meta name="MSSmartTagsPreventParsing" content="true" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width" />
<style type="text/css" media="all">@import "http://inessential.com/styles/styleSheet.css";</style>
<script type="text/javascript"> </script> <!-- FOUC hack: http://www.bluerobot.com/web/css/fouc.asp -->
<link rel="alternate" type="application/rss+xml" title="RSS" href="http://inessential.com/xml/rss.xml" />
</head>
<body>
<div id="banner"><div id="innerbanner"><span id="biglink"><a href="http://inessential.com/"><img src="http://inessential.com/images/inessential_logo@2x.png" height=57 width=263 alt="inessential.com" /></a></span> <span id="bigbyline">by Brent Simmons</span></div>
</div>
<div id="content">
<div class="weblogPost">
<h3><a href="http://inessential.com/2016/03/04/lizcast">Lizcast</a></h3>
<div class="weblogPostBody"><p>The Omni Groups Liz Marley, who recently transitioned from testing to engineering, <a href="https://overcast.fm/+BO9vrOfGc">appears on the NSNorth 2016 podcast</a>. She talks about…</p>
<blockquote><p>…challenges in engineering school, working with office cats, making the transition from software engineering to testing to developing and how knitting, like code, has the ultimate undo.</p></blockquote>
<p>Knitting is serious (though not somber) business here at Omni.</p></div>
<div class="weblogPostDateline"><span class="weblogPostDisplayDate"><a href="http://inessential.com/2016/03/04/lizcast">04 Mar 2016</a></span></div>
</div><div class="weblogPost">
<h3><a href="http://inessential.com/2016/03/01/omnioutliner_4_5">OmniOutliner 4.5</a></h3>
<div class="weblogPostBody"><p><a href="https://www.omnigroup.com/omnioutliner">OmniOutliner 4.5</a> is up on Omnis site, and should be in the Mac App Store within days.</p>
<p>With this release — <a href="https://www.omnigroup.com/releasenotes/omnioutliner-mac">see the release notes</a> I helped work on, of all things, <em>printing</em> bugs and features. This is the first time in my entire career where I worked on printing support that was more than just the most basic possible thing.</p>
<p>And that sounds weird for the year 2016, I realize. But heres the thing: working on printing support is far from glamorous. You wouldnt call it <em>fun</em>. But the people who need these features really do need them, and its a matter of respect for OmniOutliner users that we do a great job even with printing.</p>
<p>But I sure am glad to get it finished and shipping. And Im proud of the work we did — more proud than I expected to be. Its solid, and I think the people who print from OmniOutliner will be very pleased.</p>
<p>Now were on to <a href="https://www.omnigroup.com/blog/looking-back-looking-ahead-2016-edition">other new features</a>, including editing Markdown documents with OmniOutliner.</p></div>
<div class="weblogPostDateline"><span class="weblogPostDisplayDate"><a href="http://inessential.com/2016/03/01/omnioutliner_4_5">01 Mar 2016</a></span></div>
</div><div class="weblogPost">
<h3><a href="http://inessential.com/2016/02/25/omnidev">OmniDev</a></h3>
<div class="weblogPostBody"><p><a href="https://www.omnigroup.com/jobs/#mac-and-ios-developer">Omni is hiring a Mac/iOS developer</a>!</p>
<p>Were also hiring a <a href="https://www.omnigroup.com/jobs/#senior-front-end-web-developer">web developer</a>, <a href="https://www.omnigroup.com/jobs/#graphic-designer">graphic designer</a>, and <a href="https://www.omnigroup.com/jobs/#phone-support-human">phone support humans</a>.</p>
<p>Ill let you try out my new beanbag chair.</p></div>
<div class="weblogPostDateline"><span class="weblogPostDisplayDate"><a href="http://inessential.com/2016/02/25/omnidev">25 Feb 2016</a></span></div>
</div><div class="weblogPost">
<h3><a href="http://inessential.com/2016/02/18/omnijobs">OmniJobs</a></h3>
<div class="weblogPostBody"><p><a href="https://www.omnigroup.com/jobs">Were hiring</a> a senior front-end web developer, a graphic designer, and support humans.</p>
<p>You should apply.</p></div>
<div class="weblogPostDateline"><span class="weblogPostDisplayDate"><a href="http://inessential.com/2016/02/18/omnijobs">18 Feb 2016</a></span></div>
</div><div class="weblogPost">
<h3><a href="http://inessential.com/2016/02/11/it_will_be_trump">It Will Be Trump</a></h3>
<div class="weblogPostBody"><p>The South Carolina primary is where the establishment fixes the errors of Iowa and New Hampshire. Its Lee Atwaters firewall.</p>
<p>When Buchanan threatens Dole, South Carolina shuts it down. When McCain threatens Bush, South Carolina applies the kibosh.</p>
<p>But is there any hope that it will function that way this time?</p>
<p>I dont think so. The establishment candidates are Bush, Rubio, and Kasich. They dont have a shot. Nor does Cruz. Trump wins South Carolina.</p>
<p>If thats true, then its all over. If South Carolina fails — if the very primary thats designed to toss the ball back to the establishment fails — then theres no hope at all.</p>
<p>Cruz will go on to win a few states, most notably Texas. But otherwise its going to be Trump. Hell get the delegates he needs, and that will be that.</p></div>
<div class="weblogPostDateline"><span class="weblogPostDisplayDate"><a href="http://inessential.com/2016/02/11/it_will_be_trump">11 Feb 2016</a></span></div>
</div><div class="weblogPost">
<h3><a href="http://inessential.com/2016/02/09/origin_of_good_and_bad_hair_day">Origin of Good (and Bad) Hair Day</a></h3>
<div class="weblogPostBody"><p>When I was in middle school in the late 70s I struggled to get my hair to feather properly. It just didnt want to do it.</p>
<p>Like many kids that age I was newly conscious of my appearance — and I naïvely thought that well-feathered hair was a necessary (though not sufficient) key to fitting in. (Which was probably true, by the way.)</p>
<p>Every morning I would find that my hair behaved, at least somewhat, or it didnt. So I categorized each day as a “good hair day” and a “bad hair day.”</p>
<p>I told my friends about this categorization — including a neighborhood girl named Sarah. She ended up telling other kids at school.</p>
<p>And pretty soon those kids, even kids I didnt really know, would stop me in the halls or at lunch and say, “Hey Brent — good hair day or bad hair day?” Not meanly. Teasingly. It was funny.</p>
<p>Years later I started hearing the phrase on TV, and I was surprised that my little middle-school thing had spread and become <a href="http://www.ecenglish.com/learnenglish/lessons/why-do-we-say-bad-hair-day">part of the culture</a>.</p>
<p style="text-align:center">* * *</p>
<p>Of course, its also possible that I <a href="http://www.word-detective.com/072104.html">picked it up from Jane Pauley</a>. But for all these years Ive believed — no joke — that it was me, that it was my phrase. Maybe Jane Pauley got it (indirectly) from me.</p>
<p>Its <em>highly</em> unlikely — of course, I know this — that Im the originator. But still, it had to be someone, right?</p>
<p>(Not necessarily. Its kind of obvious and could have had many originators.)</p>
<p style="text-align:center">* * *</p>
<p>I stopped categorizing good and bad hair days by the time I got to high school. And these days Im just glad that I still have some hair.</p></div>
<div class="weblogPostDateline"><span class="weblogPostDisplayDate"><a href="http://inessential.com/2016/02/09/origin_of_good_and_bad_hair_day">09 Feb 2016</a></span></div>
</div><div class="weblogPost">
<h3><a href="http://inessential.com/2016/02/08/river5">River5</a></h3>
<div class="weblogPostBody"><p>River5 is Dave Winers <a href="https://github.com/scripting/river5">river-of-news RSS aggregator</a>.</p>
<p>Its a Node app. You can run it on a public machine and access it anywhere, or run it on your desktop and just read your news there.</p></div>
<div class="weblogPostDateline"><span class="weblogPostDisplayDate"><a href="http://inessential.com/2016/02/08/river5">08 Feb 2016</a></span></div>
</div><div class="weblogPost">
<h3><a href="http://inessential.com/2016/02/06/stop_watch">Stop Watch</a></h3>
<div class="weblogPostBody"><p>Some time last week my iPhone started prompting me frequently to re-enter my iCloud password. And then my Watch started doing the same, about once a minute — with a little tap on the wrist each time.</p>
<p>Obviously I <em>did</em> re-enter my password — and have done so a dozen or so times now — but it doesnt seem to matter.</p>
<p>So I stopped wearing my Watch and have switched to a mid-sixties Hamilton that my Dad gave me. (He had gotten it as a high school graduation present.)</p>
<p>Im no watch aficionado — but I do appreciate a good and attractive watch (which this is), and I appreciate even more an old watch thats a family thing.</p>
<p>Heres the thing, though: the Apple Watch contains a hundred miracles of engineering and design, surely, but serious problems with software and services can turn even the most incredible hardware into something you just sit on your desk and ignore.</p></div>
<div class="weblogPostDateline"><span class="weblogPostDisplayDate"><a href="http://inessential.com/2016/02/06/stop_watch">06 Feb 2016</a></span></div>
</div><div class="weblogPost">
<h3><a href="http://inessential.com/2016/02/05/on_sanders_governing">On Sanders Governing</a></h3>
<div class="weblogPostBody"><p>The Atlantic, <a href="http://www.theatlantic.com/politics/archive/2016/02/why-bernie-sanders-cant-win-and-cant-govern/460182/">Norm Ornstein</a>:</p>
<blockquote><p>But is there any real evidence that there is a hidden “sleeper cell” of potential voters who are waiting for the signal to emerge and transform the electorate? No.</p></blockquote>
<p>Pure candidates on both sides of the spectrum often claim that their purity will bring in the checked-out voters, because theyre just waiting for a <em>real</em> conservative or a <em>real</em> liberal.</p>
<p>Its an enduring fairy tale with terrible consequences. To put faith in it is to lose to the other party.</p></div>
<div class="weblogPostDateline"><span class="weblogPostDisplayDate"><a href="http://inessential.com/2016/02/05/on_sanders_governing">05 Feb 2016</a></span></div>
</div><div class="weblogPost">
<h3><a href="http://inessential.com/2016/02/05/cocoaconf_podcast_with_me">CocoaConf Podcast with Me</a></h3>
<div class="weblogPostBody"><p>Cesare Rocchi interviewed me for the latest <a href="http://cocoaconf.com/podcast/16">CocoaConf Podcast</a> on life before the App Store.</p>
<p>There <em>was</em> a life, by the way. It was fun! We could release software any time we wanted to.</p></div>
<div class="weblogPostDateline"><span class="weblogPostDisplayDate"><a href="http://inessential.com/2016/02/05/cocoaconf_podcast_with_me">05 Feb 2016</a></span></div>
</div>
<p><a href="http://inessential.com/archive">Archive</a></p>
</div> <!-- content -->
<div id="deckad">
<div id="innerdeckad">
<span id="adsViaTheDeck"><a href="http://decknetwork.net/">Ads via The Deck</a></span>
<script type="text/javascript">
//<![CDATA[
(function(id) {
document.write('<script type="text/javascript" src="' +
'http://connect.decknetwork.net/deck' + id + '_js.php?' +
(new Date().getTime()) + '"></' + 'script>');
})("IE");
//&#93;&#93;>
</script>
</div>
</div>
<div id="sidebar">
<!-- <p class="sidebarText"><a id="vesperlink" href="http://vesperapp.co/appstore">Vesper</a> - iOS app • <a href="http://therecord.co/">The Record</a> - podcast</p> -->
<!-- <p class="sidebarText">Twitter: <a href="https://twitter.com/brentsimmons">brentsimmons</a> • <a href="https://twitter.com/inessential">inessential</a></p> -->
<p class="sidebarText">What I work on at Omni<br /><a href="http://www.omnigroup.com/omnifocus">OmniFocus for Mac</a><a href="http://www.omnigroup.com/omnioutliner">OmniOutliner for Mac</a></p>
<!-- <p class="sidebarText"> Open source<br /><a href="https://github.com/quartermaster/QSKit">Q Branch Standard Kit</a> •
<a href="https://github.com/quartermaster/DB5">DB5</a></p> -->
<p class="sidebarText"><a href="http://inessential.com/swiftdiary">Swift Diary</a><br />
<a href="http://inessential.com/vespersyncdiary">Vesper Sync Diary</a><a href="http://inessential.com/hownottocrash">How Not to Crash</a><br />
<a href="http://inessential.com/apps_ive_made">Apps Ive Made</a><a href="http://inessential.com/xml/rss.xml">RSS</a></p>
</div> <!-- sidebar -->
<div id="footer">
<p>© 1999-2016 Brent Simmons</p>
<p>Made in Seattle. Go Hawks!</p>
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff