Add RSCore framework.
This commit is contained in:
parent
b506900e3d
commit
02503fadb9
|
@ -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;
|
||||
|
|
|
@ -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/
|
|
@ -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.
|
|
@ -0,0 +1,8 @@
|
|||
# RSCore
|
||||
Utility code for Mac and iOS apps.
|
||||
|
||||
This builds a Mac framework and an iOS framework.
|
||||
|
||||
There’s 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
7
Frameworks/RSCore/RSCore.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Executable file
7
Frameworks/RSCore/RSCore.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Executable file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:RSCore.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
|
@ -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)
|
||||
}
|
|
@ -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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 won’t 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
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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:@""" options:NSLiteralSearch range:NSMakeRange(0, self.length)];
|
||||
[s replaceOccurrencesOfString:@"<" withString:@"<" options:NSLiteralSearch range:NSMakeRange(0, s.length)];
|
||||
[s replaceOccurrencesOfString:@">" withString:@">" options:NSLiteralSearch range:NSMakeRange(0, s.length)];
|
||||
[s replaceOccurrencesOfString:@"&" withString:@"&" 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
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -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"
|
|
@ -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";
|
|
@ -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>
|
||||
|
|
@ -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);
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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];
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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*/
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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]
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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 men’s souls.";
|
||||
NSString *s2 = @"These are the times that men’s souls.";
|
||||
NSString *s3 = @"These ar th time that try men’s souls.";
|
||||
NSString *s4 = @"These are the times that try men’s.";
|
||||
NSString *s5 = @"These are the that try men’s souls.";
|
||||
NSString *s6 = @"These are times that try men’s souls.";
|
||||
NSString *s7 = @"are the times that try men’s souls.";
|
||||
NSString *s8 = @"These the times that try men’s souls.";
|
||||
NSString *s9 = @"These are the times tht try men’s 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
|
|
@ -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
|
|
@ -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>
|
Loading…
Reference in New Issue