Add RSCore framework.

This commit is contained in:
Brent Simmons 2017-05-22 13:13:40 -07:00
parent b506900e3d
commit 02503fadb9
88 changed files with 4957 additions and 29 deletions

View File

@ -14,8 +14,10 @@
849C64761ED37A5D003D8FC0 /* EvergreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849C64751ED37A5D003D8FC0 /* EvergreenTests.swift */; };
84B06F821ED37BDD00F0B54B /* RSXML.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06F7D1ED37BCA00F0B54B /* RSXML.framework */; };
84B06F831ED37BDD00F0B54B /* RSXML.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06F7D1ED37BCA00F0B54B /* RSXML.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
84B06F921ED37CB300F0B54B /* RSDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06F8D1ED37CA100F0B54B /* RSDatabase.framework */; };
84B06F931ED37CB300F0B54B /* RSDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06F8D1ED37CA100F0B54B /* RSDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
84B06FAE1ED37DBD00F0B54B /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FA91ED37DAD00F0B54B /* RSCore.framework */; };
84B06FAF1ED37DBD00F0B54B /* RSCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FA91ED37DAD00F0B54B /* RSCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
84B06FB21ED37DBD00F0B54B /* RSDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06F9D1ED37DA000F0B54B /* RSDatabase.framework */; };
84B06FB31ED37DBD00F0B54B /* RSDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06F9D1ED37DA000F0B54B /* RSDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -54,30 +56,58 @@
remoteGlobalIDString = 84F22C0C1B52DDEA000060CE;
remoteInfo = RSXML;
};
84B06F8C1ED37CA100F0B54B /* PBXContainerItemProxy */ = {
84B06F9C1ED37DA000F0B54B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84B06F861ED37CA000F0B54B /* RSDatabase.xcodeproj */;
containerPortal = 84B06F961ED37DA000F0B54B /* RSDatabase.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 84F22C551B52E0D9000060CE;
remoteInfo = RSDatabase;
};
84B06F8E1ED37CA100F0B54B /* PBXContainerItemProxy */ = {
84B06F9E1ED37DA000F0B54B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84B06F861ED37CA000F0B54B /* RSDatabase.xcodeproj */;
containerPortal = 84B06F961ED37DA000F0B54B /* RSDatabase.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 84F22C5F1B52E0D9000060CE;
remoteInfo = RSDatabaseTests;
};
84B06F901ED37CA100F0B54B /* PBXContainerItemProxy */ = {
84B06FA01ED37DA000F0B54B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84B06F861ED37CA000F0B54B /* RSDatabase.xcodeproj */;
containerPortal = 84B06F961ED37DA000F0B54B /* RSDatabase.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 8400ABF71E0CFBD800AA7C57;
remoteInfo = RSDatabaseiOS;
};
84B06F941ED37CB300F0B54B /* PBXContainerItemProxy */ = {
84B06FA81ED37DAD00F0B54B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84B06F861ED37CA000F0B54B /* RSDatabase.xcodeproj */;
containerPortal = 84B06FA21ED37DAC00F0B54B /* RSCore.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 84CFF4F41AC3C69700CEA6C8;
remoteInfo = RSCore;
};
84B06FAA1ED37DAD00F0B54B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84B06FA21ED37DAC00F0B54B /* RSCore.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 84CFF4FF1AC3C69700CEA6C8;
remoteInfo = RSCoreTests;
};
84B06FAC1ED37DAD00F0B54B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84B06FA21ED37DAC00F0B54B /* RSCore.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 842DD7BC1E14993900E061EB;
remoteInfo = RSCoreiOS;
};
84B06FB01ED37DBD00F0B54B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84B06FA21ED37DAC00F0B54B /* RSCore.xcodeproj */;
proxyType = 1;
remoteGlobalIDString = 84CFF4F31AC3C69700CEA6C8;
remoteInfo = RSCore;
};
84B06FB41ED37DBD00F0B54B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 84B06F961ED37DA000F0B54B /* RSDatabase.xcodeproj */;
proxyType = 1;
remoteGlobalIDString = 84F22C541B52E0D9000060CE;
remoteInfo = RSDatabase;
@ -91,7 +121,8 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
84B06F931ED37CB300F0B54B /* RSDatabase.framework in Embed Frameworks */,
84B06FB31ED37DBD00F0B54B /* RSDatabase.framework in Embed Frameworks */,
84B06FAF1ED37DBD00F0B54B /* RSCore.framework in Embed Frameworks */,
84B06F831ED37BDD00F0B54B /* RSXML.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
@ -110,7 +141,8 @@
849C64751ED37A5D003D8FC0 /* EvergreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EvergreenTests.swift; sourceTree = "<group>"; };
849C64771ED37A5D003D8FC0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
84B06F761ED37BCA00F0B54B /* RSXML.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSXML.xcodeproj; path = Frameworks/RSXML/RSXML.xcodeproj; sourceTree = "<group>"; };
84B06F861ED37CA000F0B54B /* RSDatabase.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSDatabase.xcodeproj; path = Frameworks/RSDatabase/RSDatabase.xcodeproj; sourceTree = "<group>"; };
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>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -118,7 +150,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
84B06F921ED37CB300F0B54B /* RSDatabase.framework in Frameworks */,
84B06FB21ED37DBD00F0B54B /* RSDatabase.framework in Frameworks */,
84B06FAE1ED37DBD00F0B54B /* RSCore.framework in Frameworks */,
84B06F821ED37BDD00F0B54B /* RSXML.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -143,7 +176,8 @@
849C646C1ED37A5D003D8FC0 /* Info.plist */,
849C64741ED37A5D003D8FC0 /* EvergreenTests */,
849C64611ED37A5D003D8FC0 /* Products */,
84B06F861ED37CA000F0B54B /* RSDatabase.xcodeproj */,
84B06FA21ED37DAC00F0B54B /* RSCore.xcodeproj */,
84B06F961ED37DA000F0B54B /* RSDatabase.xcodeproj */,
84B06F761ED37BCA00F0B54B /* RSXML.xcodeproj */,
);
sourceTree = "<group>";
@ -176,12 +210,22 @@
name = Products;
sourceTree = "<group>";
};
84B06F871ED37CA000F0B54B /* Products */ = {
84B06F971ED37DA000F0B54B /* Products */ = {
isa = PBXGroup;
children = (
84B06F8D1ED37CA100F0B54B /* RSDatabase.framework */,
84B06F8F1ED37CA100F0B54B /* RSDatabaseTests.xctest */,
84B06F911ED37CA100F0B54B /* RSDatabase.framework */,
84B06F9D1ED37DA000F0B54B /* RSDatabase.framework */,
84B06F9F1ED37DA000F0B54B /* RSDatabaseTests.xctest */,
84B06FA11ED37DA000F0B54B /* RSDatabase.framework */,
);
name = Products;
sourceTree = "<group>";
};
84B06FA31ED37DAC00F0B54B /* Products */ = {
isa = PBXGroup;
children = (
84B06FA91ED37DAD00F0B54B /* RSCore.framework */,
84B06FAB1ED37DAD00F0B54B /* RSCoreTests.xctest */,
84B06FAD1ED37DAD00F0B54B /* RSCore.framework */,
);
name = Products;
sourceTree = "<group>";
@ -202,7 +246,8 @@
);
dependencies = (
84B06F851ED37BDD00F0B54B /* PBXTargetDependency */,
84B06F951ED37CB300F0B54B /* PBXTargetDependency */,
84B06FB11ED37DBD00F0B54B /* PBXTargetDependency */,
84B06FB51ED37DBD00F0B54B /* PBXTargetDependency */,
);
name = Evergreen;
productName = Evergreen;
@ -263,8 +308,12 @@
projectDirPath = "";
projectReferences = (
{
ProductGroup = 84B06F871ED37CA000F0B54B /* Products */;
ProjectRef = 84B06F861ED37CA000F0B54B /* RSDatabase.xcodeproj */;
ProductGroup = 84B06FA31ED37DAC00F0B54B /* Products */;
ProjectRef = 84B06FA21ED37DAC00F0B54B /* RSCore.xcodeproj */;
},
{
ProductGroup = 84B06F971ED37DA000F0B54B /* Products */;
ProjectRef = 84B06F961ED37DA000F0B54B /* RSDatabase.xcodeproj */;
},
{
ProductGroup = 84B06F771ED37BCA00F0B54B /* Products */;
@ -301,25 +350,46 @@
remoteRef = 84B06F801ED37BCA00F0B54B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
84B06F8D1ED37CA100F0B54B /* RSDatabase.framework */ = {
84B06F9D1ED37DA000F0B54B /* RSDatabase.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = RSDatabase.framework;
remoteRef = 84B06F8C1ED37CA100F0B54B /* PBXContainerItemProxy */;
remoteRef = 84B06F9C1ED37DA000F0B54B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
84B06F8F1ED37CA100F0B54B /* RSDatabaseTests.xctest */ = {
84B06F9F1ED37DA000F0B54B /* RSDatabaseTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = RSDatabaseTests.xctest;
remoteRef = 84B06F8E1ED37CA100F0B54B /* PBXContainerItemProxy */;
remoteRef = 84B06F9E1ED37DA000F0B54B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
84B06F911ED37CA100F0B54B /* RSDatabase.framework */ = {
84B06FA11ED37DA000F0B54B /* RSDatabase.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = RSDatabase.framework;
remoteRef = 84B06F901ED37CA100F0B54B /* PBXContainerItemProxy */;
remoteRef = 84B06FA01ED37DA000F0B54B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
84B06FA91ED37DAD00F0B54B /* RSCore.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = RSCore.framework;
remoteRef = 84B06FA81ED37DAD00F0B54B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
84B06FAB1ED37DAD00F0B54B /* RSCoreTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = RSCoreTests.xctest;
remoteRef = 84B06FAA1ED37DAD00F0B54B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
84B06FAD1ED37DAD00F0B54B /* RSCore.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = RSCore.framework;
remoteRef = 84B06FAC1ED37DAD00F0B54B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
@ -374,10 +444,15 @@
name = RSXML;
targetProxy = 84B06F841ED37BDD00F0B54B /* PBXContainerItemProxy */;
};
84B06F951ED37CB300F0B54B /* PBXTargetDependency */ = {
84B06FB11ED37DBD00F0B54B /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = RSCore;
targetProxy = 84B06FB01ED37DBD00F0B54B /* PBXContainerItemProxy */;
};
84B06FB51ED37DBD00F0B54B /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = RSDatabase;
targetProxy = 84B06F941ED37CB300F0B54B /* PBXContainerItemProxy */;
targetProxy = 84B06FB41ED37DBD00F0B54B /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
@ -487,6 +562,7 @@
849C647B1ED37A5D003D8FC0 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = 9C84TZ7Q6Z;
@ -501,6 +577,7 @@
849C647C1ED37A5D003D8FC0 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = 9C84TZ7Q6Z;

60
Frameworks/RSCore/.gitignore vendored Executable file
View File

@ -0,0 +1,60 @@
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
build/
DerivedData/
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
## Other
*.moved-aside
*.xcuserstate
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
fastlane/report.xml
fastlane/screenshots
#Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/

21
Frameworks/RSCore/LICENSE Executable file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 brentsimmons
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

8
Frameworks/RSCore/README.md Executable file
View File

@ -0,0 +1,8 @@
# RSCore
Utility code for Mac and iOS apps.
This builds a Mac framework and an iOS framework.
Theres a whole bunch of stuff in here. There are categories on Foundation and AppKit objects plus a few miscellaneous things.
(More notes will be coming.)

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,29 @@
//
// Date+Extensions.swift
// RSCore
//
// Created by Brent Simmons on 6/21/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
import Foundation
public extension Date {
// Below are for rough use only they don't use the calendar.
public mutating func subtract(days: Int) {
addTimeInterval(0.0 - timeIntervalWithDays(days))
}
public mutating func add(days: Int) {
addTimeInterval(timeIntervalWithDays(days))
}
}
private func timeIntervalWithDays(_ days: Int) -> TimeInterval {
return TimeInterval(days * 24 * 60 * 60)
}

View File

@ -0,0 +1,73 @@
//
// DiskSaver.swift
// RSCore
//
// Created by Brent Simmons on 12/28/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
import Foundation
public final class DiskSaver: NSObject {
private let path: String
public weak var delegate: PlistProvider?
private var coalescedSaveTimer: Timer?
public var dirty = false {
didSet {
if dirty {
coalescedSaveToDisk()
}
else {
invalidateSaveTimer()
}
}
}
public init(path: String) {
self.path = path
}
deinit {
if let timer = coalescedSaveTimer, timer.isValid {
timer.invalidate()
}
}
private func invalidateSaveTimer() {
if let timer = coalescedSaveTimer, timer.isValid {
timer.invalidate()
}
coalescedSaveTimer = nil
}
private let coalescedSaveInterval = 1.0
private func coalescedSaveToDisk() {
invalidateSaveTimer()
coalescedSaveTimer = Timer.scheduledTimer(timeInterval: coalescedSaveInterval, target: self, selector: #selector(saveToDisk), userInfo: nil, repeats: false)
}
public dynamic func saveToDisk() {
invalidateSaveTimer()
if !dirty {
return
}
if let d = delegate?.plist {
do {
try RSPlist.write(d, filePath: path)
dirty = false
}
catch {
print("DiskSaver: error writing \(path) to disk.")
}
}
}
}

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>com.ranchero.$(PRODUCT_NAME:rfc1034identifier)</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,48 @@
//
// NSArray+RSCore.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
#import <RSCore/RSBlocks.h>
BOOL RSEqualArrays(NSArray *array1, NSArray *array2); /*Yes if both nil, identical, or equal*/
@interface NSArray (RSCore)
/*Returns nil if out of bounds instead of throwing an exception.*/
- (id)rs_safeObjectAtIndex:(NSUInteger)anIndex;
/*Does valueForKey:key. When value isEqual, returns YES.*/
- (id)rs_firstObjectWhereValueForKey:(NSString *)key equalsValue:(id)value;
- (id)rs_firstObjectPassingTest:(RSTestBlock)testBlock;
typedef id (^RSMapBlock)(id obj);
- (NSArray *)rs_map:(RSMapBlock)mapBlock;
typedef BOOL (^RSFilterBlock)(id obj);
- (NSArray *)rs_filter:(RSFilterBlock)filterBlock;
- (NSArray *)rs_arrayWithCopyOfEachObject;
/*Does [valueForKey:key] on each object and uses that as the key in the dictionary.*/
- (NSDictionary *)rs_dictionaryUsingKey:(id)key;
@end

View File

@ -0,0 +1,103 @@
//
// NSArray+RSCore.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "NSArray+RSCore.h"
BOOL RSEqualArrays(NSArray *array1, NSArray *array2) {
return array1 == array2 || [array1 isEqualToArray:array2];
}
@implementation NSArray (RSCore)
- (id)rs_safeObjectAtIndex:(NSUInteger)ix {
if (self.count < 1 || ix >= self.count) {
return nil;
}
return self[ix];
}
- (id)rs_firstObjectWhereValueForKey:(NSString *)key equalsValue:(id)value {
return [self rs_firstObjectPassingTest:^BOOL(id obj) {
return [[obj valueForKey:key] isEqual:value];
}];
}
- (id)rs_firstObjectPassingTest:(RSTestBlock)testBlock {
for (id oneObject in self) {
if (testBlock(oneObject)) {
return oneObject;
}
}
return nil;
}
- (NSArray *)rs_map:(RSMapBlock)mapBlock {
NSMutableArray *mappedArray = [NSMutableArray new];
for (id oneObject in self) {
id objectToAdd = mapBlock(oneObject);
if (objectToAdd) {
[mappedArray addObject:objectToAdd];
}
}
return [mappedArray copy];
}
- (NSArray *)rs_filter:(RSFilterBlock)filterBlock {
NSMutableArray *filteredArray = [NSMutableArray new];
for (id oneObject in self) {
if (filterBlock(oneObject)) {
[filteredArray addObject:oneObject];
}
}
return [filteredArray copy];
}
- (NSArray *)rs_arrayWithCopyOfEachObject {
return [self rs_map:^id(id obj) {
return [obj copy];
}];
}
- (NSDictionary *)rs_dictionaryUsingKey:(id)key {
NSMutableDictionary *d = [NSMutableDictionary new];
for (id oneObject in self) {
id oneUniqueID = [oneObject valueForKey:key];
d[oneUniqueID] = oneObject;
}
return [d copy];
}
@end

View File

@ -0,0 +1,16 @@
//
// NSCalendar+RSCore.h
// RSCore
//
// Created by Brent Simmons on 1/27/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
@interface NSCalendar (RSCore)
+ (NSCalendar *)rs_cachedCalendar;
+ (BOOL)rs_dateIsToday:(NSDate *)d;
@end

View File

@ -0,0 +1,76 @@
//
// NSCalendar+RSCore.m
// RSCore
//
// Created by Brent Simmons on 1/27/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
#import "NSCalendar+RSCore.h"
#if TARGET_OS_IPHONE
@import UIKit;
#else
@import Cocoa;
#endif
@implementation NSCalendar (RSCore)
static NSCalendar *cachedCalendar = nil;
+ (NSCalendar *)rs_cachedCalendar {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cachedCalendar = [NSCalendar autoupdatingCurrentCalendar];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(rs_significantTimeChange:) name:NSSystemTimeZoneDidChangeNotification object:nil];
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(rs_significantTimeChange:) name:UIApplicationDidBecomeActiveNotification object:nil];
#else
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(rs_significantTimeChange:) name:NSApplicationDidBecomeActiveNotification object:nil];
#endif
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(rs_significantTimeChange:) name:NSCalendarDayChangedNotification object:nil];
});
NSLock *lock = [self rs_cachedCalendarLock];
[lock lock];
NSCalendar *calendar = cachedCalendar;
[lock unlock];
return calendar;
}
+ (void)rs_significantTimeChange:(NSNotification *)note {
#pragma unused(note)
NSLock *lock = [self rs_cachedCalendarLock];
[lock lock];
cachedCalendar = [NSCalendar autoupdatingCurrentCalendar];
[lock unlock];
}
+ (NSLock *)rs_cachedCalendarLock {
static NSLock *lock = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
lock = [[NSLock alloc] init];
});
return lock;
}
+ (BOOL)rs_dateIsToday:(NSDate *)d {
return [[self rs_cachedCalendar] isDateInToday:d];
}
@end

View File

@ -0,0 +1,18 @@
//
// NSColor+RSCore.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Cocoa;
@interface NSColor (RSCore)
+ (NSColor *)rs_colorWithHexString:(NSString *)hexString;
@end

View File

@ -0,0 +1,24 @@
//
// NSColor+RSCore.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "NSColor+RSCore.h"
#import "NSString+RSCore.h"
@implementation NSColor (RSCore)
+ (NSColor *)rs_colorWithHexString:(NSString *)hexString {
RSRGBAComponents components = [hexString rs_rgbaComponents];
return [NSColor colorWithRed:components.red green:components.green blue:components.blue alpha:components.alpha];
}
@end

View File

@ -0,0 +1,37 @@
//
// NSData+RSCore.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
BOOL RSEqualBytes(const void *bytes1, const void *bytes2, size_t length);
NSString *RSHexadecimalStringWithBytes(const unsigned char *bytes, NSUInteger numberOfBytes);
@interface NSData (RSCore)
- (NSData *)rs_md5Hash;
- (NSString *)rs_md5HashString;
- (BOOL)rs_dataIsPNG;
- (BOOL)rs_dataIsGIF;
- (BOOL)rs_dataIsJPEG;
- (BOOL)rs_dataIsImage;
- (BOOL)rs_dataIsProbablyHTML;
- (BOOL)rs_dataBeginsWithBytes:(const void *)bytes length:(size_t)numberOfBytes;
- (NSString *)rs_noCopyString; //This data object must out-live returned string. May return nil.
/*If bytes are deadbeef, then string is @"deadbeef". Returns nil for empty data.*/
- (NSString *)rs_hexadecimalString;
@end

View File

@ -0,0 +1,153 @@
//
// NSData+RSCore.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import <CommonCrypto/CommonDigest.h>
#import "NSData+RSCore.h"
@implementation NSData (RSCore)
- (NSData *)rs_md5Hash {
unsigned char hash[CC_MD5_DIGEST_LENGTH];
CC_MD5([self bytes], (CC_LONG)[self length], hash);
return [NSData dataWithBytes:(const void *)hash length:CC_MD5_DIGEST_LENGTH];
}
- (NSString *)rs_md5HashString {
NSData *d = [self rs_md5Hash];
return [d rs_hexadecimalString];
}
BOOL RSEqualBytes(const void *bytes1, const void *bytes2, size_t length) {
return memcmp(bytes1, bytes2, length) == 0;
}
- (BOOL)rs_dataBeginsWithBytes:(const void *)bytes length:(size_t)numberOfBytes {
if ([self length] < numberOfBytes) {
return NO;
}
return RSEqualBytes([self bytes], bytes, numberOfBytes);
}
- (BOOL)rs_dataIsPNG {
/* http://www.w3.org/TR/PNG/#5PNG-file-signature : "The first eight bytes of a PNG datastream always contain the following (decimal) values: 137 80 78 71 13 10 26 10" */
static const Byte pngHeader[] = {137, 'P', 'N', 'G', 13, 10, 26, 10};
return [self rs_dataBeginsWithBytes:pngHeader length:sizeof(pngHeader)];
}
- (BOOL)rs_dataIsGIF {
/* http://www.onicos.com/staff/iz/formats/gif.html */
static const Byte gifHeader1[] = {'G', 'I', 'F', '8', '7', 'a'};
if ([self rs_dataBeginsWithBytes:gifHeader1 length:sizeof(gifHeader1)]) {
return YES;
}
static const Byte gifHeader2[] = {'G', 'I', 'F', '8', '9', 'a'};
return [self rs_dataBeginsWithBytes:gifHeader2 length:sizeof(gifHeader2)];
}
- (BOOL)rs_dataIsJPEG {
const void *bytes = [self bytes];
static const Byte jpegHeader1[] = {'J', 'F', 'I', 'F'};
if (RSEqualBytes(bytes + 6, jpegHeader1, sizeof(jpegHeader1))) {
return YES;
}
static const Byte jpegHeader2[] = {'E', 'x', 'i', 'f'};
return RSEqualBytes(bytes + 6, jpegHeader2, sizeof(jpegHeader2));
}
- (BOOL)rs_dataIsImage {
return [self rs_dataIsPNG] || [self rs_dataIsJPEG] || [self rs_dataIsGIF];
}
- (BOOL)rs_dataIsProbablyHTML {
NSString *s = [self rs_noCopyString];
if (!s) {
return NO;
}
if (![s containsString:@">"] || ![s containsString:@">"]) {
return NO;
}
for (NSString *oneString in @[@"html", @"body"]) {
NSRange range = [s rangeOfString:oneString options:NSCaseInsensitiveSearch];
if (range.location == NSNotFound) {
return NO;
}
}
return YES;
}
- (NSString *)rs_noCopyString {
NSDictionary *options = @{NSStringEncodingDetectionSuggestedEncodingsKey : @[@(NSUTF8StringEncoding)]};
BOOL usedLossyConversion = NO;
NSStringEncoding encoding = [NSString stringEncodingForData:self encodingOptions:options convertedString:nil usedLossyConversion:&usedLossyConversion];
if (encoding == 0) {
return nil;
}
return [[NSString alloc] initWithBytesNoCopy:(void *)self.bytes length:self.length encoding:encoding freeWhenDone:NO];
}
NSString *RSHexadecimalStringWithBytes(const Byte *bytes, NSUInteger numberOfBytes) {
if (numberOfBytes < 1) {
return nil;
}
if (numberOfBytes == 16) {
// Common case  MD5 hash, for example.
return [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]];
}
NSMutableString *s = [[NSMutableString alloc] initWithString:@""];
NSUInteger i = 0;
for (i = 0; i < numberOfBytes; i++) {
[s appendString:[NSString stringWithFormat:@"%02x", bytes[i]]];
}
return [s copy];
}
- (NSString *)rs_hexadecimalString {
return RSHexadecimalStringWithBytes([self bytes], [self length]);
}
@end

View File

@ -0,0 +1,24 @@
//
// NSDate+RSCore.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
@interface NSDate (RSCore)
- (NSString *)rs_unixTimestampStringWithNoDecimal;
- (NSString *)rs_iso8601DateString;
/*Not intended for calendar-perfect use.*/
+ (NSDate *)rs_dateWithNumberOfDaysInThePast:(NSUInteger)numberOfDays;
@end

View File

@ -0,0 +1,49 @@
//
// NSDate+RSCore.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "NSDate+RSCore.h"
@implementation NSDate (RSCore)
- (NSString *)rs_unixTimestampStringWithNoDecimal {
return [NSString stringWithFormat:@"%.0f", [self timeIntervalSince1970]]; /*%.0f means no decimal*/
}
- (NSString *)rs_iso8601DateString {
/*NSDateFormatters are not thread-safe.*/
static NSDateFormatter *dateFormatter = nil;
static NSLock *lock = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
lock = [[NSLock alloc] init];
dateFormatter = [NSDateFormatter new];
NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
[dateFormatter setLocale:enUSPOSIXLocale];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"];
});
[lock lock];
NSString *dateString = [dateFormatter stringFromDate:self];
[lock unlock];
return dateString;
}
+ (NSDate *)rs_dateWithNumberOfDaysInThePast:(NSUInteger)numberOfDays {
NSTimeInterval timeInterval = 60 * 60 * 24 * numberOfDays;
return [NSDate dateWithTimeIntervalSinceNow:-timeInterval];
}
@end

View File

@ -0,0 +1,21 @@
//
// NSDictionary+RSCore.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
@interface NSDictionary (RSCore)
/*Keys that aren't strings are ignored. No coercion.*/
- (id)rs_objectForCaseInsensitiveKey:(NSString *)key;
- (BOOL)rs_boolForKey:(NSString *)key; /*NO if doesn't exist.*/
- (int64_t)rs_int64ForKey:(NSString *)key; /*0 if doesn't exist.*/
@end

View File

@ -0,0 +1,59 @@
//
// NSDictionary+RSCore.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "NSDictionary+RSCore.h"
@implementation NSDictionary (RSCore)
- (id)rs_objectForCaseInsensitiveKey:(NSString *)key {
id obj = self[key];
if (obj) {
return obj;
}
for (NSString *oneKey in self.allKeys) {
if ([oneKey isKindOfClass:[NSString class]] && [key caseInsensitiveCompare:oneKey] == NSOrderedSame) {
return self[oneKey];
}
}
return nil;
}
- (BOOL)rs_boolForKey:(NSString *)key {
id obj = self[key];
if ([obj respondsToSelector:@selector(boolValue)]) {
return [obj boolValue];
}
return NO;
}
- (int64_t)rs_int64ForKey:(NSString *)key {
id obj = self[key];
if (!obj) {
return 0LL;
}
if ([obj respondsToSelector:@selector(longLongValue)]) {
return ((NSNumber *)(obj)).longLongValue;
}
return 0LL;
}
@end

View File

@ -0,0 +1,25 @@
//
// NSEvent+RSCore.h
// RSCore
//
// Created by Brent Simmons on 11/14/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
@import AppKit;
NS_ASSUME_NONNULL_BEGIN
@interface NSEvent (RSCore)
- (void)rs_getCommandKeyDown:(BOOL *)commandKeyDown optionKeyDown:(BOOL *)optionKeyDown controlKeyDown:(BOOL *)controlKeyDown shiftKeyDown:(BOOL *)shiftKeyDown;
- (BOOL)rs_keyIsModified;
- (unichar)rs_unmodifiedCharacter; //The one and only key pressed, if just one. NSNotFound otherwise.
- (nullable NSString *)rs_unmodifiedCharacterString; // The one and only key, if just one.
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,61 @@
//
// NSEvent+RSCore.m
// RSCore
//
// Created by Brent Simmons on 11/14/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
#import "NSEvent+RSCore.h"
#import "NSString+RSCore.h"
@implementation NSEvent (RSCore)
- (void)rs_getCommandKeyDown:(BOOL *)commandKeyDown optionKeyDown:(BOOL *)optionKeyDown controlKeyDown:(BOOL *)controlKeyDown shiftKeyDown:(BOOL *)shiftKeyDown {
NSEventModifierFlags flags = self.modifierFlags;
*shiftKeyDown = ((flags & NSShiftKeyMask) != 0);
*optionKeyDown = ((flags & NSAlternateKeyMask) != 0);
*commandKeyDown = ((flags & NSCommandKeyMask) != 0);
*controlKeyDown = ((flags & NSControlKeyMask) != 0);
}
- (BOOL)rs_keyIsModified {
BOOL commandKeyDown = NO;
BOOL optionKeyDown = NO;
BOOL controlKeyDown = NO;
BOOL shiftKeyDown = NO;
[self rs_getCommandKeyDown:&commandKeyDown optionKeyDown:&optionKeyDown controlKeyDown:&controlKeyDown shiftKeyDown:&shiftKeyDown];
return commandKeyDown || optionKeyDown || controlKeyDown || shiftKeyDown;
}
- (unichar)rs_unmodifiedCharacter {
NSString *s = self.charactersIgnoringModifiers;
if (RSStringIsEmpty(s) || s.length > 1) {
return (unichar)NSNotFound;
}
return [s characterAtIndex:0];
}
- (NSString *)rs_unmodifiedCharacterString {
NSString *s = self.charactersIgnoringModifiers;
if (RSStringIsEmpty(s) || s.length > 1) {
return nil;
}
return s;
}
@end

View File

@ -0,0 +1,24 @@
//
// NSFileManager+RSCore.h
// RSCore
//
// Created by Brent Simmons on 9/27/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
NS_ASSUME_NONNULL_BEGIN
@interface NSFileManager (RSCore)
- (BOOL)rs_copyFilesInFolder:(NSString *)source destination:(NSString *)destination error:(NSError * _Nullable * _Nullable)error;
- (NSArray<NSString *> *)rs_filenamesInFolder:(NSString *)folder;
- (NSArray<NSString *> *)rs_filepathsInFolder:(NSString *)folder;
- (BOOL)rs_fileIsFolder:(NSString *)f; // Returns NO if file doesn't exist.
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,134 @@
//
// NSFileManager+RSCore.m
// RSCore
//
// Created by Brent Simmons on 9/27/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
#import "NSFileManager+RSCore.h"
static BOOL fileExists(NSString *f) {
NSCParameterAssert(f);
return f && [[NSFileManager defaultManager] fileExistsAtPath:f];
}
static BOOL fileIsFolder(NSString *f) {
NSCParameterAssert(f);
BOOL isFolder = NO;
if (![[NSFileManager defaultManager] fileExistsAtPath:f isDirectory:&isFolder]) {
return NO;
}
return isFolder;
}
static BOOL deleteFile(NSString *f, NSError **error) {
NSCParameterAssert(f);
NSCAssert(fileExists, f);
if (!f || !fileExists(f)) {
return NO;
}
return [[NSFileManager defaultManager] removeItemAtPath:f error:error];
}
static BOOL copyFile(NSString *source, NSString *dest, BOOL overwriteIfNecessary, NSError **error) {
NSCParameterAssert(source);
NSCParameterAssert(dest);
NSCAssert(fileExists(source), nil);
if (!dest || !source || !fileExists(source)) {
return NO;
}
if (fileExists(dest)) {
if (overwriteIfNecessary) {
deleteFile(dest, error);
}
else {
return NO;
}
}
return [[NSFileManager defaultManager] copyItemAtPath:source toPath:dest error:error];
}
@implementation NSFileManager (RSCore)
- (BOOL)rs_copyFilesInFolder:(NSString *)source destination:(NSString *)destination error:(NSError **)error {
NSAssert(fileIsFolder(source), nil);
NSAssert(fileIsFolder(destination), nil);
if (!fileIsFolder(source) || !fileIsFolder(destination)) {
return NO;
}
NSArray *filenames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:source error:error];
if (!filenames) {
return NO;
}
for (NSString *oneFilename in filenames) {
if ([oneFilename hasPrefix:@"."]) {
continue;
}
NSString *sourceFile = [source stringByAppendingPathComponent:oneFilename];
NSString *destFile = [destination stringByAppendingPathComponent:oneFilename];
if (!copyFile(sourceFile, destFile, YES, error)) {
return NO;
}
}
return YES;
}
- (NSArray *)rs_filenamesInFolder:(NSString *)folder {
NSParameterAssert(folder);
NSAssert(fileIsFolder(folder), nil);
if (!folder || !fileIsFolder(folder)) {
return @[];
}
return [[NSFileManager defaultManager] contentsOfDirectoryAtPath:folder error:nil];
}
- (NSArray *)rs_filepathsInFolder:(NSString *)folder {
NSArray *filenames = [self rs_filenamesInFolder:folder];
if (!filenames) {
return @[];
}
NSMutableArray *filepaths = [NSMutableArray new];
for (NSString *oneFilename in filenames) {
NSString *onePath = [oneFilename stringByAppendingPathComponent:oneFilename];
[filepaths addObject:onePath];
}
return filepaths;
}
- (BOOL)rs_fileIsFolder:(NSString *)f {
return fileIsFolder(f);
}
@end

View File

@ -0,0 +1,23 @@
//
// NSImage+RSCore.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Cocoa;
#import "RSBlocks.h"
@interface NSImage (RSCore)
/*Calls -initWithData in background queue. data and imageResultBlock must be non-nil.*/
+ (void)rs_imageWithData:(NSData *)data imageResultBlock:(RSImageResultBlock)imageResultBlock;
+ (instancetype)imageWithContentsOfFile:(NSString *)f;
@end

View File

@ -0,0 +1,31 @@
//
// NSImage+RSCore.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "NSImage+RSCore.h"
@implementation NSImage (RSCore)
+ (void)rs_imageWithData:(NSData *)data imageResultBlock:(RSImageResultBlock)imageResultBlock {
NSParameterAssert(data != nil);
NSImage *image = [[NSImage alloc] initWithData:data];
RSCallBlockWithParameter(imageResultBlock, image);
}
+ (instancetype)imageWithContentsOfFile:(NSString *)f {
return [[self alloc] initWithContentsOfFile:f];
}
@end

View File

@ -0,0 +1,19 @@
//
// NSMutableArray+RSCore.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
@interface NSMutableArray (RSCore)
/*Does nothing if obj == nil. No exception thrown.*/
- (void)rs_safeAddObject:(id)obj;
@end

View File

@ -0,0 +1,22 @@
//
// NSMutableArray+RSCore.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "NSMutableArray+RSCore.h"
@implementation NSMutableArray (RSCore)
- (void)rs_safeAddObject:(id)obj {
if (obj != nil) {
[self addObject:obj];
}
}
@end

View File

@ -0,0 +1,22 @@
//
// NSMutableDictionary+RSCore.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
@interface NSMutableDictionary (RSCore)
/*If obj or key are nil, does nothing. No exception thrown.*/
- (void)rs_safeSetObject:(id)obj forKey:(id)key;
- (void)rs_removeObjectsForKeys:(NSArray *)keys;
@end

View File

@ -0,0 +1,28 @@
//
// NSMutableDictionary+RSCore.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "NSMutableDictionary+RSCore.h"
@implementation NSMutableDictionary (RSCore)
- (void)rs_safeSetObject:(id)obj forKey:(id)key {
if (obj != nil & key != nil) {
[self setObject:obj forKey:key];
}
}
- (void)rs_removeObjectsForKeys:(NSArray *)keys {
for (id oneKey in keys) {
[self removeObjectForKey:oneKey];
}
}
@end

View File

@ -0,0 +1,24 @@
//
// NSMutableDictionary-Extensions.swift
// RSCore
//
// Created by Brent Simmons on 8/20/16.
// Copyright © 2016 Ranchero Software. All rights reserved.
//
import Foundation
public extension NSMutableDictionary {
public func setOptionalStringValue(_ stringValue: String?, _ key: String) {
if let s = stringValue {
setObjectWithStringKey(s as NSString, key)
}
}
public func setObjectWithStringKey(_ obj: Any, _ key: String) {
setObject(obj, forKey: key as NSString)
}
}

View File

@ -0,0 +1,20 @@
//
// NSMutableSet+RSCore.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
@interface NSMutableSet (RSCore)
/*Does nothing if obj == nil. No exception thrown.*/
- (void)rs_safeAddObject:(id)obj;
@end

View File

@ -0,0 +1,22 @@
//
// NSMutableSet+RSCore.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "NSMutableSet+RSCore.h"
@implementation NSMutableSet (RSCore)
- (void)rs_safeAddObject:(id)obj {
if (obj != nil) {
[self addObject:obj];
}
}
@end

View File

@ -0,0 +1,20 @@
//
// NSNotificationCenter+RSCore.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
@interface NSNotificationCenter (RSCore)
/*Posts immediately if already on the main thread.*/
- (void)rs_postNotificationNameOnMainThread:(NSString *)notificationName object:(id)obj userInfo:(NSDictionary *)userInfo;
@end

View File

@ -0,0 +1,31 @@
//
// NSNotificationCenter+RSCore.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "NSNotificationCenter+RSCore.h"
@implementation NSNotificationCenter (RSCore)
- (void)rs_postNotificationNameOnMainThread:(NSString *)notificationName object:(id)obj userInfo:(NSDictionary *)userInfo {
if (![NSThread isMainThread]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self postNotificationName:notificationName object:obj userInfo:userInfo];
});
}
else {
[self postNotificationName:notificationName object:obj userInfo:userInfo];
}
}
@end

View File

@ -0,0 +1,35 @@
//
// NSObject+RSCore.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
BOOL RSIsNil(id __nullable obj); // YES if nil or NSNull.
BOOL RSIsEmpty(id __nullable obj); /*YES if nil or NSNull -- or length or count < 1*/
BOOL RSEqualValues(id __nullable obj1, id __nullable obj2); // YES if both are nil or NSNull or isEqual: returns YES.
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (RSCore)
/*Cancels any previous and does a new -performSelector:withObject:afterDelay:. Experimental.*/
- (void)rs_performSelectorCoalesced:(SEL)selector withObject:(id _Nullable)obj afterDelay:(NSTimeInterval)delay;
- (void)rs_takeValuesFromObject:(id)object propertyNames:(NSArray *)propertyNames;
- (NSDictionary *)rs_mergeValuesWithObjectReturningChanges:(id)object propertyNames:(NSArray <NSString *>*)propertyNames;
- (NSDictionary *)rs_dictionaryOfNonNilValues:(NSArray *)propertyNames;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,114 @@
//
// NSObject+RSCore.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "NSObject+RSCore.h"
BOOL RSIsNil(id obj) {
return obj == nil || obj == [NSNull null];
}
BOOL RSIsEmpty(id obj) {
if (RSIsNil(obj)) {
return YES;
}
if ([obj respondsToSelector:@selector(count)]) {
return [obj count] < 1;
}
if ([obj respondsToSelector:@selector(length)]) {
return [obj length] < 1;
}
return NO; /*Shouldn't get here very often.*/
}
BOOL RSEqualValues(id obj1, id obj2) {
BOOL obj1IsNil = RSIsNil(obj1);
BOOL obj2IsNil = RSIsNil(obj2);
if (obj1IsNil && obj2IsNil) {
return YES;
}
if (obj1IsNil != obj2IsNil) {
return NO;
}
return [obj1 isEqual:obj2];
}
@implementation NSObject (RSCore)
- (void)rs_performSelectorCoalesced:(SEL)selector withObject:(id)obj afterDelay:(NSTimeInterval)delay {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:selector object:obj];
[self performSelector:selector withObject:obj afterDelay:delay];
}
- (void)rs_takeValuesFromObject:(id)object propertyNames:(NSArray *)propertyNames {
for (NSString *onePropertyName in propertyNames) {
id oneValue = [object valueForKey:onePropertyName];
if (oneValue == (id)[NSNull null]) {
[self setValue:nil forKey:onePropertyName];
}
else {
[self setValue:oneValue forKey:onePropertyName];
}
}
}
- (NSDictionary *)rs_mergeValuesWithObjectReturningChanges:(id)object propertyNames:(NSArray *)propertyNames {
NSMutableDictionary *changes = [NSMutableDictionary new];
for (NSString *onePropertyName in propertyNames) {
id oneLocalValue = [self valueForKey:onePropertyName];
id oneRemoteValue = [object valueForKey:onePropertyName];
if (RSEqualValues(oneLocalValue, oneRemoteValue)) {
continue;
}
[self setValue:oneRemoteValue forKey:onePropertyName];
changes[onePropertyName] = oneRemoteValue;
}
return [changes copy];
}
- (NSDictionary *)rs_dictionaryOfNonNilValues:(NSArray *)propertyNames {
NSMutableDictionary *d = [NSMutableDictionary new];
for (NSString *onePropertyName in propertyNames) {
id oneValue = [self valueForKey:onePropertyName];
if (RSIsNil(oneValue)) {
continue;
}
d[onePropertyName] = oneValue;
}
return [d copy];
}
@end

View File

@ -0,0 +1,25 @@
//
// NSOutlineView+Extensions.swift
// RSCore
//
// Created by Brent Simmons on 9/6/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
import Cocoa
public extension NSOutlineView {
var selectedItems: [AnyObject] {
get {
if selectionIsEmpty {
return [AnyObject]()
}
return selectedRowIndexes.flatMap { (oneIndex) -> AnyObject? in
return item(atRow: oneIndex) as AnyObject
}
}
}
}

View File

@ -0,0 +1,23 @@
//
// NSPasteboard+RSCore.h
// RSCore
//
// Created by Brent Simmons on 11/14/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
@import AppKit;
NS_ASSUME_NONNULL_BEGIN
@interface NSPasteboard (RSCore)
/*Pulls something that looks like a URL from the pasteboard. May return nil.
The string wont be normalized for instance, it could return "apple.com".
And the string may not really be a URL.*/
+ (nullable NSString *)rs_urlStringFromPasteboard:(NSPasteboard *)pasteboard;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,39 @@
//
// NSPasteboard+RSCore.m
// RSCore
//
// Created by Brent Simmons on 11/14/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
#import "NSPasteboard+RSCore.h"
#import "NSString+RSCore.h"
@implementation NSPasteboard (RSCore)
+ (nullable NSString *)rs_urlStringFromPasteboard:(NSPasteboard *)pasteboard {
return [pasteboard rs_urlString];
}
- (nullable NSString *)rs_urlString {
NSString *type = [self availableTypeFromArray:@[NSStringPboardType]];
if (!type) {
return nil;
}
NSString *s = [self stringForType:type];
if (RSStringIsEmpty(s)) {
return nil;
}
if ([s rs_stringMayBeURL]) {
return s;
}
return nil;
}
@end

View File

@ -0,0 +1,30 @@
//
// NSResponder-Extensions.swift
// RSCore
//
// Created by Brent Simmons on 10/10/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
import Cocoa
public extension NSResponder {
public func hasAncestor(_ ancestor: NSResponder) -> Bool {
var nomad: NSResponder = self
while(true) {
if nomad === ancestor {
return true
}
if let _ = nomad.nextResponder {
nomad = nomad.nextResponder!
}
else {
break
}
}
return false
}
}

View File

@ -0,0 +1,23 @@
//
// NSSet+RSCore.h
// RSCore
//
// Created by Brent Simmons on 8/15/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
#import <RSCore/RSBlocks.h>
#import <RSCore/NSArray+RSCore.h>
@interface NSSet (RSCore)
- (id)rs_anyObjectPassingTest:(RSTestBlock)testBlock;
- (NSSet *)rs_filter:(RSFilterBlock)filterBlock;
- (NSSet *)rs_objectsConformingToProtocol:(Protocol *)p;
@end

View File

@ -0,0 +1,48 @@
//
// NSSet+RSCore.m
// RSCore
//
// Created by Brent Simmons on 8/15/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
#import "NSSet+RSCore.h"
@implementation NSSet (RSCore)
- (id)rs_anyObjectPassingTest:(RSTestBlock)testBlock {
for (id oneObject in self) {
if (testBlock(oneObject)) {
return oneObject;
}
}
return nil;
}
- (NSSet *)rs_filter:(RSFilterBlock)filterBlock {
NSMutableSet *filteredSet = [NSMutableSet new];
for (id oneObject in self) {
if (filterBlock(oneObject)) {
[filteredSet addObject:oneObject];
}
}
return [filteredSet copy];
}
- (NSSet *)rs_objectsConformingToProtocol:(Protocol *)p {
return [self rs_filter:^BOOL(id obj) {
return [obj conformsToProtocol:p];
}];
}
@end

View File

@ -0,0 +1,15 @@
//
// NSStoryboard+RSCore.h
// RSCore
//
// Created by Brent Simmons on 11/20/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
@import Cocoa;
@interface NSStoryboard (RSCore)
+ (id)rs_initialControllerWithStoryboardName:(NSString *)storyboardName;
@end

View File

@ -0,0 +1,19 @@
//
// NSStoryboard+RSCore.m
// RSCore
//
// Created by Brent Simmons on 11/20/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
#import "NSStoryboard+RSCore.h"
@implementation NSStoryboard (RSCore)
+ (id)rs_initialControllerWithStoryboardName:(NSString *)storyboardName {
NSStoryboard *storyboard = [self storyboardWithName:storyboardName bundle:nil];
return [storyboard instantiateInitialController];
}
@end

View File

@ -0,0 +1,76 @@
//
// NSString+RSCore.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
@import CoreGraphics;
BOOL RSStringIsEmpty(NSString * _Nullable s); /*Yes if null, NSNull, or length < 1*/
BOOL RSEqualStrings(NSString * _Nullable s1, NSString * _Nullable s2); /*Equal if both are nil*/
NS_ASSUME_NONNULL_BEGIN
NSString *RSStringReplaceAll(NSString *stringToSearch, NSString *searchFor, NSString *replaceWith); /*Literal search*/
@interface NSString (RSCore)
/*The hashed data is a UTF-8 encoded version of the string.*/
- (NSData *)rs_md5Hash;
- (NSString *)rs_md5HashString;
/*Trims whitespace from leading and trailing ends. Collapses internal whitespace to single @" " character.
Whitespace is space, tag, cr, and lf characters.*/
- (NSString *)rs_stringWithCollapsedWhitespace;
- (NSString *)rs_stringByTrimmingWhitespace;
- (BOOL)rs_stringMayBeURL;
- (NSString *)rs_normalizedURLString; //Change feed: to http:, etc.
/*0.0f to 1.0f for each.*/
typedef struct {
CGFloat red;
CGFloat green;
CGFloat blue;
CGFloat alpha;
} RSRGBAComponents;
/*red, green, blue components default to 1.0 if not specified.
alpha defaults to 1.0 if not specified.*/
- (RSRGBAComponents)rs_rgbaComponents;
/*If string doesn't have the prefix or suffix, it returns self. If prefix or suffix is nil or empty, returns self. If self and prefix or suffix are equal, returns @"".*/
- (NSString *)rs_stringByStrippingPrefix:(NSString *)prefix caseSensitive:(BOOL)caseSensitive;
- (NSString *)rs_stringByStrippingSuffix:(NSString *)suffix caseSensitive:(BOOL)caseSensitive;
- (NSString *)rs_stringByStrippingHTML:(NSUInteger)maxCharacters;
/*Filename from path, file URL string, or external URL string.*/
- (NSString *)rs_filename;
- (BOOL)rs_caseInsensitiveContains:(NSString *)s;
- (NSString *)rs_stringByEscapingSpecialXMLCharacters;
+ (NSString *)rs_stringWithNumberOfTabs:(NSInteger)numberOfTabs;
- (NSString *)rs_stringByPrependingNumberOfTabs:(NSInteger)numberOfTabs;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,370 @@
//
// NSString+RSCore.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "NSString+RSCore.h"
#import "NSData+RSCore.h"
BOOL RSStringIsEmpty(NSString *s) {
if (s == nil || (id)s == [NSNull null]) {
return YES;
}
return s.length < 1;
}
BOOL RSEqualStrings(NSString *s1, NSString *s2) {
return (s1 == nil && s2 == nil) || [s1 isEqualToString:s2];
}
NSString *RSStringReplaceAll(NSString *stringToSearch, NSString *searchFor, NSString *replaceWith) {
if (RSStringIsEmpty(stringToSearch)) {
return stringToSearch;
}
if (searchFor == nil || replaceWith == nil) {
return stringToSearch;
}
NSMutableString *s = [stringToSearch mutableCopy];
[s replaceOccurrencesOfString:searchFor withString:replaceWith options:NSLiteralSearch range:NSMakeRange(0, [s length])];
return s;
}
@implementation NSString (RSCore)
- (NSData *)rs_md5Hash {
NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
return [data rs_md5Hash];
}
- (NSString *)rs_md5HashString {
NSData *d = [self rs_md5Hash];
return [d rs_hexadecimalString];
}
- (NSString *)rs_stringWithCollapsedWhitespace {
NSMutableString *dest = [self mutableCopy];
CFStringTrimWhitespace((__bridge CFMutableStringRef)dest);
[dest replaceOccurrencesOfString:@"\t" withString:@" " options:NSLiteralSearch range:NSMakeRange(0, [dest length])];
[dest replaceOccurrencesOfString:@"\r" withString:@" " options:NSLiteralSearch range:NSMakeRange(0, [dest length])];
[dest replaceOccurrencesOfString:@"\n" withString:@" " options:NSLiteralSearch range:NSMakeRange(0, [dest length])];
while ([dest rangeOfString:@" " options:NSLiteralSearch].location != NSNotFound) {
[dest replaceOccurrencesOfString:@" " withString:@" " options:NSLiteralSearch range:NSMakeRange(0, [dest length])];
}
return dest;
}
- (NSString *)rs_stringByTrimmingWhitespace {
return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
- (BOOL)rs_stringContainsAnyCharacterFromSet:(NSCharacterSet *)characterSet {
NSRange range = [self rangeOfCharacterFromSet:characterSet];
return range.length > 0;
}
- (BOOL)rs_stringMayBeURL {
NSString *s = [self rs_stringByTrimmingWhitespace];
if (RSStringIsEmpty(s)) {
return NO;
}
if (![s containsString:@"."]) {
return NO;
}
if ([s rs_stringContainsAnyCharacterFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]) {
return NO;
}
if ([s rs_stringContainsAnyCharacterFromSet:[NSCharacterSet controlCharacterSet]]) {
return NO;
}
if ([s rs_stringContainsAnyCharacterFromSet:[NSCharacterSet illegalCharacterSet]]) {
return NO;
}
return YES;
}
- (NSString *)rs_stringByReplacingPrefix:(NSString *)prefix withHTTPPrefix:(NSString *)httpPrefix {
if ([self.lowercaseString hasPrefix:prefix]) {
NSString *s = [self rs_stringByStrippingPrefix:prefix caseSensitive:NO];
if (![s hasPrefix:@"//"]) {
s = [NSString stringWithFormat:@"//%@", s];
}
s = [NSString stringWithFormat:@"%@%@", httpPrefix, s];
return s;
}
return self;
}
- (NSString *)rs_normalizedURLString {
NSString *s = [self rs_stringByTrimmingWhitespace];
static NSString *feedPrefix = @"feed:";
static NSString *feedsPrefix = @"feeds:";
static NSString *httpPrefix = @"http:";
static NSString *httpsPrefix = @"https:";
s = [s rs_stringByReplacingPrefix:feedPrefix withHTTPPrefix:httpPrefix];
s = [s rs_stringByReplacingPrefix:feedsPrefix withHTTPPrefix:httpsPrefix];
if (![s hasPrefix:@"http"]) {
s = [NSString stringWithFormat:@"http://%@", s];
}
return s;
}
- (RSRGBAComponents)rs_rgbaComponents {
RSRGBAComponents components = {0.0f, 0.0f, 0.0f, 1.0f};
NSMutableString *s = [self mutableCopy];
[s replaceOccurrencesOfString:@"#" withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [s length])];
CFStringTrimWhitespace((__bridge CFMutableStringRef)s);
unsigned int red = 0, green = 0, blue = 0, alpha = 0;
if ([s length] >= 2) {
if ([[NSScanner scannerWithString:[s substringWithRange:NSMakeRange(0, 2)]] scanHexInt:&red]) {
components.red = (CGFloat)red / 255.0f;
}
}
if ([s length] >= 4) {
if ([[NSScanner scannerWithString:[s substringWithRange:NSMakeRange(2, 2)]] scanHexInt:&green]) {
components.green = (CGFloat)green / 255.0f;
}
}
if ([s length] >= 6) {
if ([[NSScanner scannerWithString:[s substringWithRange:NSMakeRange(4, 2)]] scanHexInt:&blue]) {
components.blue = (CGFloat)blue / 255.0f;
}
}
if ([s length] >= 8) {
if ([[NSScanner scannerWithString:[s substringWithRange:NSMakeRange(6, 2)]] scanHexInt:&alpha]) {
components.alpha = (CGFloat)alpha / 255.0f;
}
}
return components;
}
- (NSString *)rs_stringByStrippingPrefix:(NSString *)prefix caseSensitive:(BOOL)caseSensitive {
if (RSStringIsEmpty(prefix)) {
return self;
}
if (!caseSensitive) {
if (![self.lowercaseString hasPrefix:prefix.lowercaseString])
return self;
}
else if (![self hasPrefix:prefix]) {
return self;
}
if ([self isEqualToString:prefix]) {
return @"";
}
if (!caseSensitive && [self caseInsensitiveCompare:prefix] == NSOrderedSame) {
return @"";
}
return [self substringFromIndex:[prefix length]];
}
- (NSString *)rs_stringByStrippingSuffix:(NSString *)suffix caseSensitive:(BOOL)caseSensitive {
if (RSStringIsEmpty(suffix)) {
return self;
}
if (!caseSensitive) {
if (![self.lowercaseString hasSuffix:suffix.lowercaseString]) {
return self;
}
}
else if (![self hasSuffix:suffix]) {
return self;
}
if ([self isEqualToString:suffix]) {
return @"";
}
if (!caseSensitive && [self caseInsensitiveCompare:suffix] == NSOrderedSame) {
return @"";
}
return [self substringToIndex:self.length - suffix.length];
}
- (NSString *)rs_stringByStrippingHTML:(NSUInteger)maxCharacters {
if (![self containsString:@"<"]) {
if (maxCharacters > 0 && [self length] > maxCharacters) {
return [self substringToIndex:maxCharacters];
}
return self;
}
NSMutableString *preflightedCopy = [self mutableCopy];
[preflightedCopy replaceOccurrencesOfString:@"<blockquote>" withString:@" " options:NSCaseInsensitiveSearch range:NSMakeRange(0, preflightedCopy.length)];
[preflightedCopy replaceOccurrencesOfString:@"</blockquote>" withString:@" " options:NSCaseInsensitiveSearch range:NSMakeRange(0, preflightedCopy.length)];
[preflightedCopy replaceOccurrencesOfString:@"<p>" withString:@" " options:NSCaseInsensitiveSearch range:NSMakeRange(0, preflightedCopy.length)];
[preflightedCopy replaceOccurrencesOfString:@"</p>" withString:@" " options:NSCaseInsensitiveSearch range:NSMakeRange(0, preflightedCopy.length)];
[preflightedCopy replaceOccurrencesOfString:@"<div>" withString:@" " options:NSCaseInsensitiveSearch range:NSMakeRange(0, preflightedCopy.length)];
[preflightedCopy replaceOccurrencesOfString:@"</div>" withString:@" " options:NSCaseInsensitiveSearch range:NSMakeRange(0, preflightedCopy.length)];
CFMutableStringRef s = CFStringCreateMutable(kCFAllocatorDefault, (CFIndex)preflightedCopy.length);
NSUInteger i = 0;
NSUInteger level = 0;
BOOL lastCharacterWasSpace = NO;
unichar ch;
const unichar chspace = ' ';
NSUInteger charactersAdded = 0;
for (i = 0; i < preflightedCopy.length; i++) {
ch = [preflightedCopy characterAtIndex:i];
if (ch == '<') {
level++;
}
else if (ch == '>') {
level--;
}
else if (level == 0) {
if (ch == ' ' || ch == '\r' || ch == '\t' || ch == '\n') {
if (lastCharacterWasSpace) {
continue;
}
else {
lastCharacterWasSpace = YES;
}
ch = chspace;
}
else {
lastCharacterWasSpace = NO;
}
CFStringAppendCharacters(s, &ch, 1);
if (maxCharacters > 0) {
charactersAdded++;
if (charactersAdded >= maxCharacters) {
break;
}
}
}
}
return (__bridge_transfer NSString *)s;
}
- (NSString *)rs_filename {
NSArray *components = [self componentsSeparatedByString:@"/"];
NSString *filename = components.lastObject;
if (RSStringIsEmpty(filename)) {
if (components.count > 1) {
filename = components[components.count - 2];
}
}
return filename;
}
- (BOOL)rs_caseInsensitiveContains:(NSString *)s {
NSRange range = [self rangeOfString:s options:NSCaseInsensitiveSearch];
return range.location != NSNotFound;
}
- (NSString *)rs_stringByEscapingSpecialXMLCharacters {
NSMutableString *s = [self mutableCopy];
[s replaceOccurrencesOfString:@"\"" withString:@"&quot;" options:NSLiteralSearch range:NSMakeRange(0, self.length)];
[s replaceOccurrencesOfString:@"<" withString:@"&lt;" options:NSLiteralSearch range:NSMakeRange(0, s.length)];
[s replaceOccurrencesOfString:@">" withString:@"&gt;" options:NSLiteralSearch range:NSMakeRange(0, s.length)];
[s replaceOccurrencesOfString:@"&" withString:@"&amp;" options:NSLiteralSearch range:NSMakeRange(0, s.length)];
return s;
}
+ (NSString *)rs_stringWithNumberOfTabs:(NSInteger)numberOfTabs {
static dispatch_once_t onceToken;
static NSMutableDictionary *cache = nil;
dispatch_once(&onceToken, ^{
cache = [NSMutableDictionary new];
});
NSString *cachedString = cache[@(numberOfTabs)];
if (cachedString) {
return cachedString;
}
NSMutableString *s = [@"" mutableCopy];
for (NSInteger i = 0; i < numberOfTabs; i++) {
[s appendString:@"\t"];
}
cache[@(numberOfTabs)] = s;
return s;
}
- (NSString *)rs_stringByPrependingNumberOfTabs:(NSInteger)numberOfTabs {
NSString *tabs = [NSString rs_stringWithNumberOfTabs:numberOfTabs];
return [tabs stringByAppendingString:self];
}
@end

View File

@ -0,0 +1,18 @@
//
// NSTableView+Extensions.swift
// RSCore
//
// Created by Brent Simmons on 9/6/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
import Cocoa
public extension NSTableView {
var selectionIsEmpty: Bool {
get {
return selectedRowIndexes.startIndex == selectedRowIndexes.endIndex
}
}
}

View File

@ -0,0 +1,19 @@
//
// NSTableView+RSCore.h
// RSCore
//
// Created by Brent Simmons on 3/29/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Cocoa;
@interface NSTableView (RSCore)
- (void)rs_selectRow:(NSInteger)row;
- (void)rs_selectRowAndScrollToVisible:(NSInteger)row;
@end

View File

@ -0,0 +1,26 @@
//
// NSTableView+RSCore.m
// RSCore
//
// Created by Brent Simmons on 3/29/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "NSTableView+RSCore.h"
@implementation NSTableView (RSCore)
- (void)rs_selectRow:(NSInteger)row {
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:(NSUInteger)row] byExtendingSelection:NO];
}
- (void)rs_selectRowAndScrollToVisible:(NSInteger)row {
[self rs_selectRow:row];
[self scrollRowToVisible:row];
}
@end

View File

@ -0,0 +1,18 @@
//
// NSTimer+RSCore.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
@interface NSTimer (RSCore)
- (void)rs_invalidateIfValid;
@end

View File

@ -0,0 +1,23 @@
//
// NSTimer+RSCore.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "NSTimer+RSCore.h"
@implementation NSTimer (RSCore)
- (void)rs_invalidateIfValid {
if ([self isValid]) {
[self invalidate];
}
}
@end

View File

@ -0,0 +1,31 @@
//
// NSView+RSCore.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Cocoa;
@interface NSView (RSCore)
/*Keeps subview at same size as receiver.*/
- (void)rs_addFullSizeConstraintsForSubview:(NSView *)view;
- (void)rs_setFrameIfNotEqual:(NSRect)r;
@property (nonatomic, readonly) BOOL rs_isOrIsDescendedFromFirstResponder;
@property (nonatomic, readonly) BOOL rs_shouldDrawAsActive;
- (NSRect)rs_rectCenteredVertically:(NSRect)originalRect;
- (NSRect)rs_rectCenteredHorizontally:(NSRect)originalRect;
- (NSRect)rs_rectCentered:(NSRect)originalRect;
@end

View File

@ -0,0 +1,68 @@
//
// NSView+RSCore.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "NSView+RSCore.h"
#import "RSGeometry.h"
@implementation NSView (RSCore)
- (void)rs_addFullSizeConstraintsForSubview:(NSView *)view {
NSDictionary *d = NSDictionaryOfVariableBindings(view);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-0-[view]-0-|" options:0 metrics:nil views:d];
[self addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[view]-0-|" options:0 metrics:nil views:d];
[self addConstraints:constraints];
}
- (void)rs_setFrameIfNotEqual:(NSRect)r {
if (!NSEqualRects(self.frame, r)) {
self.frame = r;
}
}
- (BOOL)rs_isOrIsDescendedFromFirstResponder {
NSView *firstResponder = (NSView *)(self.window.firstResponder);
if (![firstResponder isKindOfClass:[NSView class]]) {
return NO;
}
return [self isDescendantOf:firstResponder];
}
- (BOOL)rs_shouldDrawAsActive {
return self.window.isMainWindow && self.rs_isOrIsDescendedFromFirstResponder;
}
- (NSRect)rs_rectCenteredVertically:(NSRect)originalRect {
return RSRectCenteredVerticallyInRect(originalRect, self.bounds);
}
- (NSRect)rs_rectCenteredHorizontally:(NSRect)originalRect {
return RSRectCenteredHorizontallyInRect(originalRect, self.bounds);
}
- (NSRect)rs_rectCentered:(NSRect)originalRect {
return RSRectCenteredInRect(originalRect, self.bounds);
}
@end

View File

@ -0,0 +1,19 @@
//
// NSWindow-Extensions.swift
// RSCore
//
// Created by Brent Simmons on 10/10/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
import Cocoa
public extension NSWindow {
public func makeFirstResponderUnlessDescendantIsFirstResponder(_ responder: NSResponder) {
if !firstResponder.hasAncestor(responder) {
makeFirstResponder(responder)
}
}
}

View File

@ -0,0 +1,19 @@
//
// PlistProviderProtocol.swift
// RSCore
//
// Created by Brent Simmons on 7/31/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
import Foundation
// For objects that can be serialized as an array or dictionary.
// Mainly used for objects that can be stored on disk.
// Unlike NSCoder it provides human-readable archives.
// Does not do any checking on the contents, but they must be plist objects.
public protocol PlistProvider: class {
var plist: AnyObject? {get}
}

View File

@ -0,0 +1,19 @@
//
// RSBackgroundColorView.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Cocoa;
IB_DESIGNABLE
@interface RSBackgroundColorView : NSView
@property (nonatomic) IBInspectable NSColor *backgroundColor;
@end

View File

@ -0,0 +1,67 @@
//
// RSBackgroundColorView.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "RSBackgroundColorView.h"
@implementation RSBackgroundColorView
- (BOOL)isOpaque {
return YES;
}
//- (BOOL)preservesContentDuringLiveResize {
//
// return YES;
//}
//
//
//- (BOOL)wantsDefaultClipping {
//
// return NO;
//}
//
//
//- (void)setFrameSize:(NSSize)newSize {
//
// if (NSEqualSizes(newSize, self.frame.size)) {
// return;
// }
// [super setFrameSize:newSize];
//
// if (self.inLiveResize) {
// NSRect rects[4];
// NSInteger count = 0;
// [self getRectsExposedDuringLiveResize:rects count:&count];
// while (count-- > 0) {
// [self setNeedsDisplayInRect:rects[count]];
// }
// } else {
// self.needsDisplay = YES;
// }
//}
- (void)drawRect:(NSRect)r {
// const NSRect *rects;
// NSInteger count = 0;
//
// [self getRectsBeingDrawn:&rects count:&count];
// if (count < 1) {
// return;
// }
[self.backgroundColor setFill];
NSRectFill(r);
// NSRectFillList(rects, count);
}
@end

View File

@ -0,0 +1,41 @@
//
// RSBinaryCache.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
/*The folder this manages must already exist.
Doesn't do any locking or queueing -- caller is responsible.*/
@interface RSBinaryCache : NSObject
- (instancetype)initWithFolder:(NSString *)folder;
- (NSString *)filePathForKey:(NSString *)key;
- (BOOL)setBinaryData:(NSData *)data key:(NSString *)key error:(NSError **)error;
- (NSData *)binaryDataForKey:(NSString *)key error:(NSError **)error;
- (BOOL)removeBinaryDataForKey:(NSString *)key error:(NSError **)error;
- (BOOL)binaryForKeyExists:(NSString *)key;
- (UInt64)lengthOfBinaryDataForKey:(NSString *)key error:(NSError **)error;
- (NSArray *)allKeys:(NSError **)error;
extern NSString *RSBinaryKey;
extern NSString *RSBinaryLength;
- (NSArray *)allObjects:(NSError **)error; /*NSDictionary objects with RSBinaryKey and RSBinaryLength. Key is filename.*/
@end

View File

@ -0,0 +1,129 @@
//
// RSBinaryCache.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "RSBinaryCache.h"
@interface RSBinaryCache ()
@property (nonatomic) NSString *folder;
@end
@implementation RSBinaryCache
#pragma mark - Init
- (instancetype)initWithFolder:(NSString *)folder {
self = [super init];
if (!self) {
return nil;
}
_folder = folder;
return self;
}
#pragma mark - API
- (NSString *)filePathForKey:(NSString *)key {
return [self.folder stringByAppendingPathComponent:key];
}
- (BOOL)setBinaryData:(NSData *)data key:(NSString *)key error:(NSError **)error {
NSString *f = [self filePathForKey:key];
return [data writeToFile:f options:NSDataWritingAtomic error:error];
}
- (NSData *)binaryDataForKey:(NSString *)key error:(NSError **)error {
NSString *f = [self filePathForKey:key];
return [NSData dataWithContentsOfFile:f options:0 error:error];
}
- (BOOL)removeBinaryDataForKey:(NSString *)key error:(NSError **)error {
NSString *f = [self filePathForKey:key];
return [[NSFileManager defaultManager] removeItemAtPath:f error:error];
}
- (BOOL)binaryForKeyExists:(NSString *)key {
NSString *f = [self filePathForKey:key];
BOOL isDirectory = NO;
return [[NSFileManager defaultManager] fileExistsAtPath:f isDirectory:&isDirectory];
}
- (UInt64)lengthOfBinaryDataForKey:(NSString *)key error:(NSError **)error {
NSString *f = [self filePathForKey:key];
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:f error:error];
return [fileAttributes fileSize];
}
- (NSArray *)allKeys:(NSError **)error {
NSMutableArray *keys = [NSMutableArray new];
NSArray *filenames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.folder error:error];
for (NSString *oneFilename in filenames) {
if ([oneFilename hasPrefix:@"."]) {
continue;
}
[keys addObject:oneFilename];
}
return [keys copy];
}
NSString *RSBinaryKey = @"key";
NSString *RSBinaryLength = @"length";
- (NSArray *)allObjects:(NSError **)error {
NSMutableArray *objects = [NSMutableArray new];
NSArray *filenames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.folder error:error];
if (!filenames && error) {
return nil;
}
for (NSString *oneFilename in filenames) {
if ([oneFilename hasPrefix:@"."]) {
continue;
}
NSMutableDictionary *oneObject = [NSMutableDictionary new];
oneObject[RSBinaryKey] = oneFilename;
UInt64 length = [self lengthOfBinaryDataForKey:oneFilename error:nil];
oneObject[RSBinaryLength] = @(length);
[objects addObject:[oneObject copy]];
}
return [objects copy];
}
@end

View File

@ -0,0 +1,46 @@
//
// RSBlocks.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software LLC. All rights reserved.
//
@import Foundation;
#import <RSCore/RSPlatform.h>
#if TARGET_OS_IPHONE
@import UIKit;
#endif
typedef void (^RSVoidBlock)(void);
typedef RSVoidBlock RSVoidCompletionBlock;
typedef BOOL (^RSBoolBlock)(void);
typedef void (^RSFetchResultsBlock)(NSArray *fetchedObjects);
typedef void (^RSDataResultBlock)(NSData *d);
typedef void (^RSObjectResultBlock)(id obj);
typedef void (^RSSetResultBlock)(NSSet *set);
typedef void (^RSBoolResultBlock)(BOOL flag);
typedef BOOL (^RSTestBlock)(id obj);
/*Images*/
typedef void (^RSImageResultBlock)(RS_IMAGE *image);
typedef RS_IMAGE *(^RSImageRenderBlock)(RS_IMAGE *imageToRender);
/*Calls on main thread. Ignores if nil.*/
void RSCallCompletionBlock(RSVoidCompletionBlock completion);
void RSCallBlockWithParameter(RSObjectResultBlock block, id obj);

View File

@ -0,0 +1,36 @@
//
// RSBlocks.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software LLC. All rights reserved.
//
#import "RSBlocks.h"
void RSCallCompletionBlock(RSVoidCompletionBlock completion) {
if (!completion) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
@autoreleasepool {
completion();
}
});
}
void RSCallBlockWithParameter(RSObjectResultBlock block, id obj) {
if (!block) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
@autoreleasepool {
block(obj);
}
});
}

View File

@ -0,0 +1,25 @@
//
// RSConstants.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
typedef NS_ENUM(NSUInteger, RSPosition) {
RSPositionFirst,
RSPositionMiddle,
RSPositionLast,
RSPositionOnly
};
extern NSString *RSUniqueIDKey; //@"uniqueID"
extern NSString *RSImageKey; //@"image"
extern NSString *RSChildrenKey; //@"children"
extern NSString *RSNameKey; //@"name"
extern NSString *RSTypeKey; //@"type"
extern NSString *RSFolderKey; //@"folder"
extern NSString *RSURLKey; //@"url"

View File

@ -0,0 +1,17 @@
//
// RSConstants.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "RSConstants.h"
NSString *RSUniqueIDKey = @"uniqueID";
NSString *RSImageKey = @"image";
NSString *RSChildrenKey = @"children";
NSString *RSNameKey = @"name";
NSString *RSTypeKey = @"type";
NSString *RSFolderKey = @"folder";
NSString *RSURLKey = @"url";

View File

@ -0,0 +1,68 @@
//
// RSCore.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
#import <RSCore/RSBlocks.h>
#import <RSCore/RSConstants.h>
#import <RSCore/RSPlatform.h>
#import <RSCore/RSBinaryCache.h>
/*Foundation*/
#import <RSCore/NSArray+RSCore.h>
#import <RSCore/NSCalendar+RSCore.h>
#import <RSCore/NSData+RSCore.h>
#import <RSCore/NSDate+RSCore.h>
#import <RSCore/NSDictionary+RSCore.h>
#import <RSCore/NSFileManager+RSCore.h>
#import <RSCore/NSMutableArray+RSCore.h>
#import <RSCore/NSMutableDictionary+RSCore.h>
#import <RSCore/NSMutableSet+RSCore.h>
#import <RSCore/NSNotificationCenter+RSCore.h>
#import <RSCore/NSObject+RSCore.h>
#import <RSCore/NSSet+RSCore.h>
#import <RSCore/NSTimer+RSCore.h>
#import <RSCore/NSString+RSCore.h>
#import <RSCore/RSPlist.h>
#if !TARGET_OS_IPHONE
/*AppKit*/
#import <RSCore/NSColor+RSCore.h>
#import <RSCore/NSEvent+RSCore.h>
#import <RSCore/NSPasteboard+RSCore.h>
#import <RSCore/NSStoryboard+RSCore.h>
#import <RSCore/NSTableView+RSCore.h>
#import <RSCore/NSView+RSCore.h>
#import <RSCore/RSBackgroundColorView.h>
#import <RSCore/RSOpaqueContainerView.h>
#import <RSCore/RSTransparentContainerView.h>
#import <RSCore/NSImage+RSCore.h>
#import <RSCore/RSGeometry.h>
#endif
/*Images*/
#import <RSCore/RSImageRenderer.h>
#import <RSCore/RSScaling.h>
/*Text*/
#import <RSCore/RSMacroProcessor.h>

View File

@ -0,0 +1,15 @@
//
// RSGeometry.h
// RSCore
//
// Created by Brent Simmons on 3/13/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
NSRect RSRectCenteredVerticallyInRect(NSRect originalRect, NSRect containerRect);
NSRect RSRectCenteredHorizontallyInRect(NSRect originalRect, NSRect containerRect);
NSRect RSRectCenteredInRect(NSRect originalRect, NSRect containerRect);

View File

@ -0,0 +1,36 @@
//
// RSGeometry.m
// RSCore
//
// Created by Brent Simmons on 3/13/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
#import "RSGeometry.h"
NSRect RSRectCenteredVerticallyInRect(NSRect originalRect, NSRect containerRect) {
NSRect r = originalRect;
r.origin.y = NSMidY(containerRect) - (NSHeight(r) / 2.0);
r = NSIntegralRect(r);
r.size = originalRect.size;
return r;
}
NSRect RSRectCenteredHorizontallyInRect(NSRect originalRect, NSRect containerRect) {
NSRect r = originalRect;
r.origin.x = NSMidX(containerRect) - (NSWidth(r) / 2.0);
r = NSIntegralRect(r);
r.size = originalRect.size;
return r;
}
NSRect RSRectCenteredInRect(NSRect originalRect, NSRect containerRect) {
NSRect r = RSRectCenteredVerticallyInRect(originalRect, containerRect);
return RSRectCenteredHorizontallyInRect(r, containerRect);
}

View File

@ -0,0 +1,32 @@
//
// RSImageRenderer.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
#import "RSBlocks.h"
/*Used to render an image based on another image. (Thumbnails, for instance.)
Thread-safe. Renders in a background queue.
imageRenderBlock is responsible for dealing with graphics context; it returns the rendered image.
imageResultBlock may be called on any thread.
None of the parameters may be nil.
*/
@interface RSImageRenderer : NSObject
- (instancetype)initWithRenderer:(RSImageRenderBlock)imageRenderBlock;
- (void)renderImage:(RS_IMAGE *)originalImage imageResultBlock:(RSImageResultBlock)imageResultBlock;
@end

View File

@ -0,0 +1,69 @@
//
// RSImageRenderer.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "RSImageRenderer.h"
static void RSImageRender(RS_IMAGE *originalImage, RSImageRenderBlock renderer, RSImageResultBlock imageCallback) {
assert(originalImage != nil);
assert(renderer != nil);
assert(imageCallback != nil);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
@autoreleasepool {
RS_IMAGE *renderedImage = renderer(originalImage);
imageCallback(renderedImage);
}
});
}
@interface RSImageRenderer ()
@property (nonatomic, copy) RSImageRenderBlock imageRenderBlock;
@end
@implementation RSImageRenderer
#pragma mark - Init
- (instancetype)initWithRenderer:(RSImageRenderBlock)imageRenderBlock {
NSParameterAssert(imageRenderBlock != nil);
self = [super init];
if (self == nil) {
return nil;
}
_imageRenderBlock = imageRenderBlock;
return self;
}
#pragma mark - API
- (void)renderImage:(RS_IMAGE *)originalImage imageResultBlock:(RSImageResultBlock)imageResultBlock {
NSParameterAssert(originalImage != nil);
NSParameterAssert(imageResultBlock != nil);
RSImageRender(originalImage, self.imageRenderBlock, imageResultBlock);
}
@end

View File

@ -0,0 +1,19 @@
//
// RSMacroProcessor.h
// RSCore
//
// Created by Brent Simmons on 10/26/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
NS_ASSUME_NONNULL_BEGIN
@interface RSMacroProcessor : NSObject
+ (NSString *)renderedTextWithTemplate:(NSString *)templateString substitutions:(NSDictionary *)substitutions macroStart:(NSString *)macroStart macroEnd:(NSString *)macroEnd;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,120 @@
//
// RSMacroProcessor.m
// RSCore
//
// Created by Brent Simmons on 10/26/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
#import "RSMacroProcessor.h"
@interface RSMacroProcessor ()
@property (nonatomic, readonly) NSString *template;
@property (nonatomic, readonly) NSDictionary *substitutions;
@property (nonatomic, readonly) NSString *macroStart;
@property (nonatomic, readonly) NSString *macroEnd;
@property (nonatomic, readonly) NSUInteger numberOfMacroStartCharacters;
@property (nonatomic, readonly) NSUInteger numberOfMacroEndCharacters;
@property (nonatomic) NSString *renderedText;
@end
@implementation RSMacroProcessor
#pragma mark - Class Methods
+ (NSString *)renderedTextWithTemplate:(NSString *)templateString substitutions:(NSDictionary *)substitutions macroStart:(NSString *)macroStart macroEnd:(NSString *)macroEnd {
RSMacroProcessor *macroProcessor = [[self alloc] initWithTemplate:templateString substitutions:substitutions macroStart:macroStart macroEnd:macroEnd];
return macroProcessor.renderedText;
}
#pragma mark - Init
- (instancetype)initWithTemplate:(NSString *)templateString substitutions:(NSDictionary *)substitutions macroStart:(NSString *)macroStart macroEnd:(NSString *)macroEnd {
self = [super init];
if (!self) {
return nil;
}
_template = templateString;
_substitutions = substitutions;
_macroStart = macroStart;
_macroEnd = macroEnd;
_numberOfMacroStartCharacters = _macroStart.length;
_numberOfMacroEndCharacters = _macroEnd.length;
return self;
}
#pragma mark - Accessors
- (NSString *)renderedText {
if (!_renderedText) {
_renderedText = [self processMacros];
}
return _renderedText;
}
#pragma mark - Private
- (NSUInteger)indexOfString:(NSString *)s beforeIndex:(NSUInteger)ix inString:(NSString *)stringToSearch {
if (ix < s.length) {
return NSNotFound;
}
NSRange range = [stringToSearch rangeOfString:s options:NSBackwardsSearch range:NSMakeRange(0, ix)];
if (range.length < s.length) {
return NSNotFound;
}
return range.location;
}
- (NSString *)processMacros {
NSMutableString *s = [self.template mutableCopy];
NSUInteger lastIndexOfMacroStart = s.length;
while (true) {
NSUInteger ixMacroEnd = [self indexOfString:self.macroEnd beforeIndex:lastIndexOfMacroStart inString:s];
if (ixMacroEnd == NSNotFound) {
break;
}
NSUInteger ixMacroStart = [self indexOfString:self.macroStart beforeIndex:ixMacroEnd inString:s];
if (ixMacroStart == NSNotFound) {
break;
}
NSRange range = NSMakeRange(ixMacroStart, (ixMacroEnd - ixMacroStart) + self.numberOfMacroEndCharacters);
NSRange keyRange = range;
keyRange.location += self.numberOfMacroStartCharacters;
keyRange.length -= (self.numberOfMacroStartCharacters + self.numberOfMacroEndCharacters);
NSString *key = [s substringWithRange:keyRange];
NSString *substition = [self.substitutions objectForKey:key];
if (substition) {
[s replaceCharactersInRange:range withString:substition];
}
lastIndexOfMacroStart = ixMacroStart;
}
return [s copy];
}
@end

View File

@ -0,0 +1,21 @@
//
// RSOpaqueContainerView.h
// RSCore
//
// Created by Brent Simmons on 3/27/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Cocoa;
#import <RSCore/RSBackgroundColorView.h>
/*This view has one subview, which it resizes to fit the bounds of this view.*/
@interface RSOpaqueContainerView : RSBackgroundColorView
@property (nonatomic) NSView *containedView; /*Removes old.*/
@end

View File

@ -0,0 +1,52 @@
//
// RSOpaqueContainerView.m
// RSCore
//
// Created by Brent Simmons on 3/27/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "RSOpaqueContainerView.h"
#import "NSView+RSCore.h"
@implementation RSOpaqueContainerView
+ (BOOL)requiresConstraintBasedLayout {
return NO;
}
- (NSView *)containedView {
return self.subviews.firstObject;
}
- (void)setContainedView:(NSView *)containedView {
[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperviewWithoutNeedingDisplay)];
[self addSubview:containedView];
self.needsLayout = YES;
self.needsDisplay = YES;
}
- (void)layout {
[self resizeSubviewsWithOldSize:NSZeroSize];
}
- (void)resizeSubviewsWithOldSize:(NSSize)oldSize {
#pragma unused(oldSize)
NSView *subview = self.subviews.firstObject;
[subview rs_setFrameIfNotEqual:self.bounds];
}
@end

View File

@ -0,0 +1,48 @@
//
// RSPlatform.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
/*Mac: ~/Application Support/AppName/
If nil, gets appName from Info.plist -- @"CFBundleExecutable" key.
It creates the folder and intermediate folders if necessary.
If something goes wrong it returns nil. The error is NSLogged.
Panic, at that point, is strongly indicated.*/
NS_ASSUME_NONNULL_BEGIN
NSString * _Nullable RSDataFolder(NSString * _Nullable appName);
/*Path to file in folder specified by RSDataFolder.
appName may be nil -- it's passed to RSDataFolder.*/
NSString * _Nullable RSDataFile(NSString * _Nullable appName, NSString *fileName);
/* app support/appName/folderName/ */
NSString * _Nullable RSDataSubfolder(NSString * _Nullable appName, NSString *folderName);
/* app support/appName/folderName/filename */
NSString * _Nullable RSDataSubfolderFile(NSString * _Nullable appName, NSString *folderName, NSString *filename);
NS_ASSUME_NONNULL_END
#if TARGET_OS_IPHONE
#define RS_IMAGE UIImage
#else
#define RS_IMAGE NSImage
#endif

View File

@ -0,0 +1,62 @@
//
// RSPlatform.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "RSPlatform.h"
NSString *RSDataFolder(NSString *appName) {
NSString *dataFolder = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0];
if (appName == nil) {
appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleExecutable"];
}
dataFolder = [dataFolder stringByAppendingPathComponent:appName];
NSError *error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtPath:dataFolder withIntermediateDirectories:YES attributes:nil error:&error]) {
NSLog(@"RSDataFolder error: %@", error);
return nil;
}
return dataFolder;
}
NSString *RSDataFile(NSString *appName, NSString *fileName) {
NSCParameterAssert(fileName != nil);
NSString *dataFolder = RSDataFolder(appName);
return [dataFolder stringByAppendingPathComponent:fileName];
}
NSString *RSDataSubfolder(NSString *appName, NSString *folderName) {
NSCParameterAssert(folderName != nil);
NSString *dataFolder = RSDataFile(appName, folderName);
NSError *error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtPath:dataFolder withIntermediateDirectories:YES attributes:nil error:&error]) {
NSLog(@"RSDataFolder error: %@", error);
return nil;
}
return dataFolder;
}
NSString *RSDataSubfolderFile(NSString *appName, NSString *folderName, NSString *filename) {
NSCParameterAssert(folderName != nil);
NSCParameterAssert(filename != nil);
NSString *dataFolder = RSDataSubfolder(appName, folderName);
return [dataFolder stringByAppendingPathComponent:filename];
}

View File

@ -0,0 +1,17 @@
//
// RSPlist.h
// RSCore
//
// Created by Brent Simmons on 7/26/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
@interface RSPlist : NSObject
// Writes using NSPropertyListBinaryFormat_v1_0.
+ (BOOL)writePlist:(id)obj filePath:(NSString *)filePath error:(NSError **)error;
@end

View File

@ -0,0 +1,40 @@
//
// RSPlist.m
// RSCore
//
// Created by Brent Simmons on 7/26/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
#import "RSPlist.h"
@implementation RSPlist
+ (BOOL)writePlist:(id)obj filePath:(NSString *)filePath error:(NSError **)error {
NSData *propertyListData = [NSPropertyListSerialization dataWithPropertyList:obj format:NSPropertyListBinaryFormat_v1_0 options:0 error:error];
static NSLock *lock = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
lock = [[NSLock alloc] init];
});
[lock lock];
BOOL success = [propertyListData writeToFile:filePath options:NSDataWritingAtomic error:error];
[lock unlock];
if (!success) {
if (*error) {
NSLog(@"Error writing property list: %@", *error);
}
else {
NSLog(@"Unknown error writing property list.");
}
}
return success;
}
@end

View File

@ -0,0 +1,16 @@
//
// RSScaling.h
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
@import CoreGraphics;
CGSize RSScaledSizeForImageFittingSize(CGSize imageSize, CGSize constrainingSize); /*does a ceil on size.*/
CGFloat RSZoomScaleToFitSize(CGSize imageSize, CGSize constrainingSize); /*Aspect fit*/
CGFloat RSZoomScaleToFillSize(CGSize imageSize, CGSize constrainingSize); /*Aspect fill*/

View File

@ -0,0 +1,61 @@
//
// RSScaling.m
// RSCore
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import "RSScaling.h"
static CGFloat RSScaleFactorToFillSize(CGSize imageSize, CGSize constrainingSize) {
if (CGSizeEqualToSize(imageSize, constrainingSize))
return 1.0f;
CGFloat scaleFactorHeight = imageSize.height / constrainingSize.height;
CGFloat scaleFactorWidth = imageSize.width / constrainingSize.width;
CGFloat scaleFactor = MIN(scaleFactorHeight, scaleFactorWidth);
return scaleFactor;
}
static CGFloat RSScaleFactorToFitSize(CGSize imageSize, CGSize constrainingSize) {
if (CGSizeEqualToSize(imageSize, constrainingSize))
return 1.0f;
CGFloat scaleFactorHeight = imageSize.height / constrainingSize.height;
CGFloat scaleFactorWidth = imageSize.width / constrainingSize.width;
CGFloat scaleFactor = MAX(scaleFactorHeight, scaleFactorWidth);
return scaleFactor;
}
CGFloat RSZoomScaleToFillSize(CGSize imageSize, CGSize constrainingSize) {
CGFloat scaleFactor = RSScaleFactorToFillSize(imageSize, constrainingSize);
return 1.0f / scaleFactor;
}
CGFloat RSZoomScaleToFitSize(CGSize imageSize, CGSize constrainingSize) {
CGFloat scaleFactor = RSScaleFactorToFitSize(imageSize, constrainingSize);
return 1.0f / scaleFactor;
}
CGSize RSScaledSizeForImageFittingSize(CGSize imageSize, CGSize constrainingSize) {
CGFloat scaleFactor = RSScaleFactorToFitSize(imageSize, constrainingSize);
CGSize scaledSize = CGSizeZero;
scaledSize.height = ceil(imageSize.height / scaleFactor);
scaledSize.width = ceil(imageSize.width / scaleFactor);
return scaledSize;
}

View File

@ -0,0 +1,64 @@
//
// RSToolbarItem.swift
// RSCore
//
// Created by Brent Simmons on 10/16/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
import Cocoa
public class RSToolbarItem: NSToolbarItem {
override public func validate() {
guard let view = view, let _ = view.window else {
isEnabled = false
return
}
isEnabled = isValidAsUserInterfaceItem()
}
}
private extension RSToolbarItem {
func isValidAsUserInterfaceItem() -> Bool {
// Use NSValidatedUserInterfaceItem protocol rather than calling validateToolbarItem:.
if let target = target as? NSResponder {
return validateWithResponder(target) ?? false
}
var responder = view?.window?.firstResponder
if responder == nil {
return false
}
while(true) {
if let validated = validateWithResponder(responder!) {
return validated
}
responder = responder?.nextResponder
if responder == nil {
break
}
}
if let appDelegate = NSApplication.shared().delegate {
if let validated = validateWithResponder(appDelegate) {
return validated
}
}
return false
}
func validateWithResponder(_ responder: NSObjectProtocol) -> Bool? {
guard responder.responds(to: action), let target = responder as? NSUserInterfaceValidations else {
return nil
}
return target.validateUserInterfaceItem(self)
}
}

View File

@ -0,0 +1,17 @@
//
// RSTransparentContainerView.h
// RSCore
//
// Created by Brent Simmons on 9/19/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
@import Cocoa;
/*This view has one subview, which it resizes to fit the bounds of this view.*/
@interface RSTransparentContainerView : NSView
@property (nonatomic) NSView *containedView; /*Removes old.*/
@end

View File

@ -0,0 +1,50 @@
//
// RSTransparentContainerView.m
// RSCore
//
// Created by Brent Simmons on 9/19/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
#import "RSTransparentContainerView.h"
#import "NSView+RSCore.h"
@implementation RSTransparentContainerView
+ (BOOL)requiresConstraintBasedLayout {
return NO;
}
- (NSView *)containedView {
return self.subviews.firstObject;
}
- (void)setContainedView:(NSView *)containedView {
[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperviewWithoutNeedingDisplay)];
[self addSubview:containedView];
self.needsLayout = YES;
self.needsDisplay = YES;
}
- (void)layout {
[self resizeSubviewsWithOldSize:NSZeroSize];
}
- (void)resizeSubviewsWithOldSize:(NSSize)oldSize {
#pragma unused(oldSize)
NSView *subview = self.subviews.firstObject;
[subview rs_setFrameIfNotEqual:self.bounds];
}
@end

View File

@ -0,0 +1,29 @@
//
// Set+Extensions.swift
// RSCore
//
// Created by Brent Simmons on 3/13/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
import Foundation
public extension Set {
public func anyObjectPassingTest( _ test: (Element) -> Bool) -> Element? {
guard let index = self.index(where: test) else {
return nil
}
return self[index]
}
func anyObject() -> Element? {
if self.isEmpty {
return nil
}
return self[startIndex]
}
}

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>com.ranchero.$(PRODUCT_NAME:rfc1034identifier)</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,84 @@
//
// NSString+ExtrasTests.m
// RSCore
//
// Created by Brent Simmons on 1/27/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
#import <XCTest/XCTest.h>
@import RSCore;
@interface NSString_ExtrasTests : XCTestCase
@end
@implementation NSString_ExtrasTests
- (void)testTrimmingWhitespace {
NSString *s = @"\tfoo\n\n\t\r\t";
NSString *result = [s rs_stringByTrimmingWhitespace];
XCTAssertEqualObjects(result, @"foo");
s = @"\t\n\n\t\r\t";
result = [s rs_stringByTrimmingWhitespace];
XCTAssertEqualObjects(result, @"");
s = @"\t";
result = [s rs_stringByTrimmingWhitespace];
XCTAssertEqualObjects(result, @"");
s = @"";
result = [s rs_stringByTrimmingWhitespace];
XCTAssertEqualObjects(result, @"");
s = @"\nfoo\n";
result = [s rs_stringByTrimmingWhitespace];
XCTAssertEqualObjects(result, @"foo");
s = @"\nfoo";
result = [s rs_stringByTrimmingWhitespace];
XCTAssertEqualObjects(result, @"foo");
s = @"foo\n";
result = [s rs_stringByTrimmingWhitespace];
XCTAssertEqualObjects(result, @"foo");
s = @"fo\n\n\n\n\n\no\n";
result = [s rs_stringByTrimmingWhitespace];
XCTAssertEqualObjects(result, @"fo\n\n\n\n\n\no");
}
- (void)testMD5HashStringPerformance {
NSString *s1 = @"These are the times that try mens souls.";
NSString *s2 = @"These are the times that mens souls.";
NSString *s3 = @"These ar th time that try mens souls.";
NSString *s4 = @"These are the times that try mens.";
NSString *s5 = @"These are the that try mens souls.";
NSString *s6 = @"These are times that try mens souls.";
NSString *s7 = @"are the times that try mens souls.";
NSString *s8 = @"These the times that try mens souls.";
NSString *s9 = @"These are the times tht try mens souls.";
NSString *s10 = @"These are the times that try men's souls.";
[self measureBlock:^{
for (NSInteger i = 0; i < 1000; i++) {
(void)[s1 rs_md5HashString];
(void)[s2 rs_md5HashString];
(void)[s3 rs_md5HashString];
(void)[s4 rs_md5HashString];
(void)[s5 rs_md5HashString];
(void)[s6 rs_md5HashString];
(void)[s7 rs_md5HashString];
(void)[s8 rs_md5HashString];
(void)[s9 rs_md5HashString];
(void)[s10 rs_md5HashString];
}
}];
}
@end

View File

@ -0,0 +1,40 @@
//
// RSCoreTests.m
// RSCoreTests
//
// Created by Brent Simmons on 3/25/15.
// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import <XCTest/XCTest.h>
@interface RSCoreTests : XCTestCase
@end
@implementation RSCoreTests
- (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.
XCTAssert(YES, @"Pass");
}
- (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,26 @@
<?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>CFBundleDisplayName</key>
<string>RSCore</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>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>