├── CHANGELOG.md ├── Package.resolved ├── remind ├── RuntimeError.swift ├── Notifier+Doc.swift ├── Bundle+Swizzle.swift ├── Notifier+Utility.swift ├── Date+Offsets.swift └── main.swift ├── Package.swift ├── LICENSE.txt ├── README.md └── remind.xcodeproj └── project.pbxproj /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 1.1 4 | 5 | Rolled Swift tools back to 5.1 to support Mojave 6 | 7 | ## 1.0 8 | 9 | Initial Commit -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "swift-argument-parser", 6 | "repositoryURL": "https://github.com/apple/swift-argument-parser", 7 | "state": { 8 | "branch": null, 9 | "revision": "223d62adc52d51669ae2ee19bdb8b7d9fd6fcd9c", 10 | "version": "0.0.6" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /remind/RuntimeError.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Erica Sadun. All rights reserved. 2 | 3 | import Foundation 4 | 5 | /// Errors encountered while running this command 6 | enum RuntimeError: String, Error, CustomStringConvertible { 7 | var description: String { rawValue } 8 | 9 | // Time string could not be converted to date 10 | case timeStringError = "Failed to convert time string" 11 | 12 | /// Time components cannot be adjusted 13 | case timeAdjustError = "Failed to adjust time." 14 | 15 | } 16 | 17 | -------------------------------------------------------------------------------- /remind/Notifier+Doc.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Erica Sadun. All rights reserved. 2 | 3 | import Foundation 4 | 5 | extension Notifier { 6 | /// A succinct description of the utility 7 | static let abstract = "Command-line notifier" 8 | 9 | /// An extended discussion of the utility 10 | static let discussion = """ 11 | Schedule reminders from the command line. "remind -m 20 Stand up and stretch", 12 | "remind --at 1:30P Call Bob", "remind --at 13:30 Leave for Dr visit". Any 13 | notifications scheduled earlier in the day (say it is now 2PM and you set a 14 | reminder for 11:30), are pushed forward one day. 15 | """ 16 | } 17 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "remind", 8 | platforms: [ 9 | .macOS(.v10_12) 10 | ], 11 | products: [ 12 | .executable( 13 | name: "remind", 14 | targets: ["remind"]), 15 | ], 16 | dependencies: [ 17 | .package(url: "https://github.com/apple/swift-argument-parser", .exact("0.0.6")), 18 | ], 19 | targets: [ 20 | .target( 21 | name: "remind", 22 | dependencies: [.product(name: "ArgumentParser", package: "swift-argument-parser")], 23 | path: "remind/" 24 | ), 25 | ], 26 | swiftLanguageVersions: [ 27 | .v5 28 | ] 29 | ) 30 | 31 | -------------------------------------------------------------------------------- /remind/Bundle+Swizzle.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Erica Sadun. All rights reserved. 2 | 3 | import Cocoa 4 | 5 | // Impersonate Finder from the command line 6 | 7 | extension Bundle { 8 | /// The posed identifier. 9 | @objc 10 | public var __bundleIdentifier: NSString { 11 | return "com.apple.finder" 12 | } 13 | 14 | /// Exchanges bundle identifiers to impersonate an app 15 | static func swizzle() { 16 | // This method is sufficiently evil that I won't apologize for the force-unwraps. Sue me. 17 | method_exchangeImplementations( 18 | class_getInstanceMethod(Bundle.classForCoder(), 19 | #selector(getter:bundleIdentifier))!, 20 | class_getInstanceMethod(Bundle.classForCoder(), 21 | #selector(getter:__bundleIdentifier))! 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Erica Sadun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # remind 2 | 3 | A simple notification-center reminder app that uses the [Swift Argument Parser](https://github.com/apple/swift-argument-parser). 4 | 5 | This project uses a build phase to install to /usr/local/bin. Make sure you can write to that folder or change the build phase. 6 | 7 | ``` 8 | OVERVIEW: Command-line notifier 9 | 10 | Schedule reminders from the command line. "remind -m 20 Stand up and stretch", 11 | "remind --at 1:30P Call Bob", "remind --at 13:30 Leave for Dr visit". Any 12 | notifications scheduled earlier in the day (say it is now 2PM and you set a 13 | reminder for 11:30), are pushed forward one day. 14 | 15 | USAGE: remind [ ...] [--minutes ] [--hours ] [--days ] [--when ] [--list] 16 | 17 | ARGUMENTS: 18 | Notification message. 19 | 20 | OPTIONS: 21 | -m, --minutes Delay in minutes. (default: 0) 22 | -h, --hours Delay in hours. (default: 0) 23 | -d, --days Delay in days. (default: 0) 24 | -t, --at, --time, --when 25 | Sets time for scheduled reminder. 26 | -l, --list Lists scheduled notifications then quits. 27 | --help Show help information. 28 | 29 | ``` 30 | 31 | ## Installation 32 | 33 | * Install [homebrew](https://brew.sh). 34 | * Install [mint](https://github.com/yonaskolb/Mint) with homebrew (`brew install mint`). 35 | * From command line: `mint install erica/remind` 36 | 37 | ## Build notes 38 | 39 | * This project includes a build phase that writes to /usr/local/bin 40 | * Make sure your /usr/local/bin is writable: `chmod u+w /usr/local/bin` 41 | -------------------------------------------------------------------------------- /remind/Notifier+Utility.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Erica Sadun. All rights reserved. 2 | 3 | import Foundation 4 | import Darwin 5 | 6 | extension Notifier { 7 | 8 | /// Waits for a synchronous task to execute before quitting with a success (0) status 9 | static func waitThenQuit() { 10 | let dT = DispatchTime(uptimeNanoseconds: DispatchTime.now().rawValue + 1 * NSEC_PER_SEC) 11 | DispatchQueue.global(qos: .background) 12 | .asyncAfter(deadline: dT) { 13 | Darwin.exit(0) 14 | } 15 | dispatchMain() 16 | } 17 | 18 | /// Lists all future "Finder" notifications currently scheduled with the default `NSUserNotificationCenter` to stdout. 19 | static func listNotifications() { 20 | let center = NSUserNotificationCenter.default 21 | let scheduled = center.scheduledNotifications.sorted { 22 | if let d0 = $0.deliveryDate, let d1 = $1.deliveryDate { 23 | return d0.compare(d1) == .orderedAscending 24 | } 25 | return false // this shouldn't happen 26 | } 27 | 28 | guard scheduled.count > 0 else { 29 | print("No scheduled notifications.") 30 | return 31 | } 32 | 33 | let dateFormatter = DateFormatter() 34 | dateFormatter.locale = Locale.autoupdatingCurrent 35 | (dateFormatter.dateStyle, dateFormatter.timeStyle) = (.short, .short) 36 | 37 | let dayOfWeekFormatter = DateFormatter() 38 | dayOfWeekFormatter.locale = Locale.autoupdatingCurrent 39 | dayOfWeekFormatter.dateFormat = "E" 40 | 41 | for notification in scheduled { 42 | if let date = notification.deliveryDate { 43 | let dayOfWeekString = dayOfWeekFormatter.string(from: date) 44 | let dateString = dateFormatter.string(from: date) 45 | let noteString = notification.subtitle ?? "No message" 46 | print("\(dayOfWeekString) \(dateString): \(noteString)") 47 | } 48 | } 49 | 50 | Notifier.waitThenQuit() 51 | } 52 | 53 | 54 | /// Removes all "Finder" notifications currently scheduled with the default `NSUserNotificationCenter`. 55 | static func removeNotifications() { 56 | let center = NSUserNotificationCenter.default 57 | guard !center.scheduledNotifications.isEmpty else { 58 | print("No scheduled notifications.") 59 | return 60 | } 61 | for notification in center.scheduledNotifications { 62 | center.removeScheduledNotification(notification) 63 | } 64 | print("Removed all scheduled notifications.") 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /remind/Date+Offsets.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Erica Sadun. All rights reserved. 2 | 3 | import Foundation 4 | 5 | extension Date { 6 | 7 | /// Returns a reminder date from a string formatted either hour-24:minute or hour:minute-meridian. 8 | /// 9 | /// This method constructs a date from an hour and minute representation. 10 | /// The date is calculated from "now", moving to midnight and adding the hour and minute components. 11 | /// If the new date is earlier than "now", it's pushed forward 24 hours, producing the first possible 12 | /// instance of that hour/minute time in the future. 13 | /// 14 | /// - Parameter string: a string formatted either as "h:ma" or "H:m" 15 | /// - Throws: `RuntimeError`s when unable to parse the input string. 16 | /// - Returns: A new date, initialized to the offset of the date either today or tomorrow. 17 | static func date(from string: String) throws -> Date { 18 | 19 | // Establish partial date from string or throw. The string 20 | // provides hour and minute offsets from midnight. 21 | let dateFormatter = DateFormatter() 22 | var date: Date? 23 | for format in ["h:ma", "ha", "H:m", "HH", "Hm", "HHmm"] { 24 | dateFormatter.dateFormat = format 25 | if let parsed = dateFormatter.date(from: string) { 26 | date = parsed 27 | break 28 | } 29 | } 30 | guard let componentDate = date 31 | else { throw RuntimeError.timeStringError } 32 | 33 | // Construct YMD components from now 34 | let calendar = Calendar.autoupdatingCurrent 35 | let now = Date() 36 | let year = calendar.component(.year, from: now) 37 | let month = calendar.component(.month, from: now) 38 | let day = calendar.component(.day, from: now) 39 | 40 | // Construct HM components from constructed date 41 | let hour = calendar.component(.hour, from: componentDate) 42 | let minute = calendar.component(.minute, from: componentDate) 43 | 44 | // Combine 45 | guard let adjustedDate = calendar.date(from: DateComponents(year: year, month: month, day: day, hour: hour, minute: minute)) 46 | else { throw RuntimeError.timeAdjustError } 47 | 48 | // If ascending (the new date is in the future), done. Return the target date 49 | if Date().compare(adjustedDate) == .orderedAscending { 50 | return adjustedDate 51 | } 52 | 53 | // Push forward one day to the _next_ instance of this time in a way 54 | // that will not horrify Dave DeLong 55 | let components = calendar.dateComponents([.hour, .minute], from: adjustedDate) 56 | guard let newDateDay = calendar.nextDate(after: Date(), matching: components, matchingPolicy: .strict, direction: .forward) 57 | else { throw RuntimeError.timeAdjustError } 58 | return newDateDay 59 | } 60 | 61 | 62 | /// Creates a new date for reminder scheduling. 63 | /// 64 | /// - Parameters: 65 | /// - days: the number of days from now 66 | /// - hours: the number of hours from now 67 | /// - minutes: the number of minutes from now 68 | /// - when: a string describing an hour:minute offset relative to the start of today (when present, overrides d/m/h) 69 | /// - Throws: `RuntimeError`s when encountering data misconfigurations 70 | /// - Returns: A new date that represents the time a reminder should be scheduled 71 | static func dateWith(d days: Int, h hours: Int, minutes: Int, format when: String?) throws -> Date { 72 | let calendar = NSCalendar.autoupdatingCurrent 73 | var date = Date() 74 | date = calendar.date(byAdding: .minute, value: minutes, to: date) ?? date 75 | date = calendar.date(byAdding: .hour, value: hours, to: date) ?? date 76 | date = calendar.date(byAdding: .day, value: days, to: date) ?? date 77 | 78 | // Handle "when" formatting, replacing/ignoring date offsets 79 | if let when = when { date = try self.date(from: when) } 80 | return date 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /remind/main.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Erica Sadun. All rights reserved. 2 | 3 | import Cocoa 4 | import ArgumentParser 5 | 6 | /// A type responsible for scheduling local notifications by spoofing the Finder 7 | struct Notifier: ParsableCommand { 8 | 9 | // MARK: - Configure parsable command 10 | 11 | /// The configuration for a command. 12 | static let configuration = CommandConfiguration( 13 | commandName: "remind", 14 | abstract: Notifier.abstract, 15 | discussion: Notifier.discussion, 16 | shouldDisplay: true, 17 | helpNames: [.long] 18 | ) 19 | 20 | // MARK: - Message 21 | 22 | /// Positional strings that make up the message to be displayed on firing the notification. 23 | @Argument( 24 | parsing: .remaining, 25 | help: "Notification message.") 26 | var message: [String] 27 | 28 | // MARK: - Delay 29 | 30 | /// A command-line option to set a delay in integer-increments of minutes. 31 | @Option( 32 | name: [.long, .short], 33 | default: 0, 34 | help: "Delay in minutes." 35 | ) 36 | var minutes: Int 37 | 38 | /// A command-line option to set a delay in integer-increments of hours. 39 | @Option( 40 | name: [.long, .short], 41 | default: 0, 42 | help: "Delay in hours." 43 | ) 44 | var hours: Int 45 | 46 | /// A command-line option to set a delay in integer-increments of days. 47 | @Option( 48 | name: [.long, .short], 49 | default: 0, 50 | help: "Delay in days." 51 | ) 52 | var days: Int 53 | 54 | // MARK: - Time 55 | 56 | /// A command-line option to set a delay using `H:m` or `h:ma` formatting. 57 | @Option( 58 | name: [.customLong("time"), .customLong("at"), .long, .customShort("t")], 59 | help: "Sets time for scheduled reminder." 60 | ) 61 | var when: String? 62 | 63 | 64 | // MARK: - Sounds 65 | 66 | /// A command-line option to override the default sound with another system library sound. 67 | @Option( 68 | default: "Glass", 69 | help: .hidden // "Sets notification sound." 70 | ) 71 | var sound: String 72 | 73 | /// A command-line flag for listing the available sounds in /System/Library/Sounds 74 | @Flag(name: [.customLong("sounds")], 75 | help: .hidden) // "Lists available sounds then quits." 76 | var listSounds: Bool 77 | 78 | // MARK: - Report 79 | 80 | /// A command-line flag for listing any user notifications scheduled for Finder 81 | @Flag(name: [.long, .short], 82 | help: "Lists scheduled notifications then quits.") 83 | var list: Bool 84 | 85 | // MARK: - Clean 86 | 87 | /// A command-line flag for bulk-removing user notifications scheduled for Finder 88 | @Flag( 89 | name: [.customLong("clean"), .customShort("x")], 90 | help: .hidden) // "Removes utility-scheduled notifications from center then quits." 91 | var removeScheduled: Bool 92 | 93 | // MARK: - Main 94 | 95 | func run() throws { 96 | 97 | // Throw help on calls to `remind` without arguments 98 | guard 99 | CommandLine.argc > 1 100 | else { throw CleanExit.helpRequest() } 101 | 102 | // Be evil but effective 103 | Bundle.swizzle() 104 | 105 | // Act and dash 106 | guard !removeScheduled else { Self.removeNotifications(); return } 107 | guard !list else { Self.listNotifications(); return } 108 | let sounds = try FileManager.default 109 | .contentsOfDirectory(atPath: "/System/Library/Sounds") 110 | .map({ ($0 as NSString).deletingPathExtension }) 111 | guard !listSounds else { print(sounds.joined(separator: ", ")); return } 112 | 113 | // Sounds can use any capitalization. Glass is default. 114 | let adjustedSound = sound.capitalized 115 | if !sounds.contains(adjustedSound) { 116 | print("Replacing unknown sound with default.") 117 | } 118 | 119 | let note = NSUserNotification() 120 | note.title = "Reminder" 121 | note.soundName = sounds.contains(adjustedSound) ? adjustedSound : "Glass" 122 | note.subtitle = message.joined(separator: " ") 123 | 124 | note.deliveryDate = try Date.dateWith(d: days, h: hours, minutes: minutes, format: when) 125 | let formatter = DateFormatter() 126 | (formatter.dateStyle, formatter.timeStyle) = (.short, .short) 127 | formatter.locale = Locale.current 128 | if let deliveryDate = note.deliveryDate { 129 | let dateString = formatter.string(from: deliveryDate) 130 | print("Scheduling note for \(dateString).") 131 | } 132 | 133 | let center = NSUserNotificationCenter.default 134 | center.scheduleNotification(note) 135 | Notifier.waitThenQuit() 136 | } 137 | } 138 | 139 | 140 | // MARK: - Main 141 | 142 | // Note: see SE-0281 @main: Type-Based Program Entry Points 143 | // https://github.com/apple/swift-evolution/blob/master/proposals/0281-main-attribute.md 144 | Notifier.main() 145 | -------------------------------------------------------------------------------- /remind.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8E284A662458C3DA001BDC2B /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E284A652458C3DA001BDC2B /* main.swift */; }; 11 | 8E284A6D2458C52F001BDC2B /* Bundle+Swizzle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E284A6C2458C52F001BDC2B /* Bundle+Swizzle.swift */; }; 12 | 8E284A6F2458C537001BDC2B /* RuntimeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E284A6E2458C537001BDC2B /* RuntimeError.swift */; }; 13 | 8E2876FB24609DAD00EB8416 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = 8E2876FA24609DAD00EB8416 /* ArgumentParser */; }; 14 | 8E2876FC2460A07400EB8416 /* remind in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8E284A622458C3D9001BDC2B /* remind */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 15 | 8E64A29824623187006D53C4 /* Date+Offsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E64A29724623187006D53C4 /* Date+Offsets.swift */; }; 16 | 8E64A29A24623206006D53C4 /* Notifier+Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E64A29924623206006D53C4 /* Notifier+Utility.swift */; }; 17 | 8EB060C124645C7100D0C2D9 /* Notifier+Doc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EB060C024645C7100D0C2D9 /* Notifier+Doc.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXCopyFilesBuildPhase section */ 21 | 8E284A602458C3D9001BDC2B /* CopyFiles */ = { 22 | isa = PBXCopyFilesBuildPhase; 23 | buildActionMask = 12; 24 | dstPath = /usr/local/bin; 25 | dstSubfolderSpec = 0; 26 | files = ( 27 | 8E2876FC2460A07400EB8416 /* remind in CopyFiles */, 28 | ); 29 | runOnlyForDeploymentPostprocessing = 0; 30 | }; 31 | /* End PBXCopyFilesBuildPhase section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 8E284A622458C3D9001BDC2B /* remind */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = remind; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 8E284A652458C3DA001BDC2B /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 36 | 8E284A6C2458C52F001BDC2B /* Bundle+Swizzle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Swizzle.swift"; sourceTree = ""; }; 37 | 8E284A6E2458C537001BDC2B /* RuntimeError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeError.swift; sourceTree = ""; }; 38 | 8E64A29724623187006D53C4 /* Date+Offsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Offsets.swift"; sourceTree = ""; }; 39 | 8E64A29924623206006D53C4 /* Notifier+Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notifier+Utility.swift"; sourceTree = ""; }; 40 | 8EB060C024645C7100D0C2D9 /* Notifier+Doc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notifier+Doc.swift"; sourceTree = ""; }; 41 | /* End PBXFileReference section */ 42 | 43 | /* Begin PBXFrameworksBuildPhase section */ 44 | 8E284A5F2458C3D9001BDC2B /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | 8E2876FB24609DAD00EB8416 /* ArgumentParser in Frameworks */, 49 | ); 50 | runOnlyForDeploymentPostprocessing = 0; 51 | }; 52 | /* End PBXFrameworksBuildPhase section */ 53 | 54 | /* Begin PBXGroup section */ 55 | 8E284A592458C3D9001BDC2B = { 56 | isa = PBXGroup; 57 | children = ( 58 | 8E284A642458C3DA001BDC2B /* remind */, 59 | 8E284A632458C3D9001BDC2B /* Products */, 60 | ); 61 | sourceTree = ""; 62 | }; 63 | 8E284A632458C3D9001BDC2B /* Products */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | 8E284A622458C3D9001BDC2B /* remind */, 67 | ); 68 | name = Products; 69 | sourceTree = ""; 70 | }; 71 | 8E284A642458C3DA001BDC2B /* remind */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 8E284A652458C3DA001BDC2B /* main.swift */, 75 | 8EB060C024645C7100D0C2D9 /* Notifier+Doc.swift */, 76 | 8E64A29924623206006D53C4 /* Notifier+Utility.swift */, 77 | 8E64A29724623187006D53C4 /* Date+Offsets.swift */, 78 | 8E284A6C2458C52F001BDC2B /* Bundle+Swizzle.swift */, 79 | 8E284A6E2458C537001BDC2B /* RuntimeError.swift */, 80 | ); 81 | path = remind; 82 | sourceTree = ""; 83 | }; 84 | /* End PBXGroup section */ 85 | 86 | /* Begin PBXNativeTarget section */ 87 | 8E284A612458C3D9001BDC2B /* remind */ = { 88 | isa = PBXNativeTarget; 89 | buildConfigurationList = 8E284A692458C3DA001BDC2B /* Build configuration list for PBXNativeTarget "remind" */; 90 | buildPhases = ( 91 | 8E284A5E2458C3D9001BDC2B /* Sources */, 92 | 8E284A5F2458C3D9001BDC2B /* Frameworks */, 93 | 8E284A602458C3D9001BDC2B /* CopyFiles */, 94 | ); 95 | buildRules = ( 96 | ); 97 | dependencies = ( 98 | ); 99 | name = remind; 100 | packageProductDependencies = ( 101 | 8E2876FA24609DAD00EB8416 /* ArgumentParser */, 102 | ); 103 | productName = remind; 104 | productReference = 8E284A622458C3D9001BDC2B /* remind */; 105 | productType = "com.apple.product-type.tool"; 106 | }; 107 | /* End PBXNativeTarget section */ 108 | 109 | /* Begin PBXProject section */ 110 | 8E284A5A2458C3D9001BDC2B /* Project object */ = { 111 | isa = PBXProject; 112 | attributes = { 113 | LastSwiftUpdateCheck = 1140; 114 | LastUpgradeCheck = 1150; 115 | ORGANIZATIONNAME = "Erica Sadun"; 116 | TargetAttributes = { 117 | 8E284A612458C3D9001BDC2B = { 118 | CreatedOnToolsVersion = 11.4.1; 119 | }; 120 | }; 121 | }; 122 | buildConfigurationList = 8E284A5D2458C3D9001BDC2B /* Build configuration list for PBXProject "remind" */; 123 | compatibilityVersion = "Xcode 9.3"; 124 | developmentRegion = en; 125 | hasScannedForEncodings = 0; 126 | knownRegions = ( 127 | en, 128 | Base, 129 | ); 130 | mainGroup = 8E284A592458C3D9001BDC2B; 131 | packageReferences = ( 132 | 8E2876F924609DAD00EB8416 /* XCRemoteSwiftPackageReference "swift-argument-parser" */, 133 | ); 134 | productRefGroup = 8E284A632458C3D9001BDC2B /* Products */; 135 | projectDirPath = ""; 136 | projectRoot = ""; 137 | targets = ( 138 | 8E284A612458C3D9001BDC2B /* remind */, 139 | ); 140 | }; 141 | /* End PBXProject section */ 142 | 143 | /* Begin PBXSourcesBuildPhase section */ 144 | 8E284A5E2458C3D9001BDC2B /* Sources */ = { 145 | isa = PBXSourcesBuildPhase; 146 | buildActionMask = 2147483647; 147 | files = ( 148 | 8E64A29A24623206006D53C4 /* Notifier+Utility.swift in Sources */, 149 | 8E284A6D2458C52F001BDC2B /* Bundle+Swizzle.swift in Sources */, 150 | 8E64A29824623187006D53C4 /* Date+Offsets.swift in Sources */, 151 | 8E284A6F2458C537001BDC2B /* RuntimeError.swift in Sources */, 152 | 8EB060C124645C7100D0C2D9 /* Notifier+Doc.swift in Sources */, 153 | 8E284A662458C3DA001BDC2B /* main.swift in Sources */, 154 | ); 155 | runOnlyForDeploymentPostprocessing = 0; 156 | }; 157 | /* End PBXSourcesBuildPhase section */ 158 | 159 | /* Begin XCBuildConfiguration section */ 160 | 8E284A672458C3DA001BDC2B /* Debug */ = { 161 | isa = XCBuildConfiguration; 162 | buildSettings = { 163 | ALWAYS_SEARCH_USER_PATHS = NO; 164 | CLANG_ANALYZER_NONNULL = YES; 165 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 166 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 167 | CLANG_CXX_LIBRARY = "libc++"; 168 | CLANG_ENABLE_MODULES = YES; 169 | CLANG_ENABLE_OBJC_ARC = YES; 170 | CLANG_ENABLE_OBJC_WEAK = YES; 171 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 172 | CLANG_WARN_BOOL_CONVERSION = YES; 173 | CLANG_WARN_COMMA = YES; 174 | CLANG_WARN_CONSTANT_CONVERSION = YES; 175 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 176 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 177 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 178 | CLANG_WARN_EMPTY_BODY = YES; 179 | CLANG_WARN_ENUM_CONVERSION = YES; 180 | CLANG_WARN_INFINITE_RECURSION = YES; 181 | CLANG_WARN_INT_CONVERSION = YES; 182 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 183 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 184 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 185 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 186 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 187 | CLANG_WARN_STRICT_PROTOTYPES = YES; 188 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 189 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 190 | CLANG_WARN_UNREACHABLE_CODE = YES; 191 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 192 | COPY_PHASE_STRIP = NO; 193 | DEBUG_INFORMATION_FORMAT = dwarf; 194 | ENABLE_STRICT_OBJC_MSGSEND = YES; 195 | ENABLE_TESTABILITY = YES; 196 | GCC_C_LANGUAGE_STANDARD = gnu11; 197 | GCC_DYNAMIC_NO_PIC = NO; 198 | GCC_NO_COMMON_BLOCKS = YES; 199 | GCC_OPTIMIZATION_LEVEL = 0; 200 | GCC_PREPROCESSOR_DEFINITIONS = ( 201 | "DEBUG=1", 202 | "$(inherited)", 203 | ); 204 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 205 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 206 | GCC_WARN_UNDECLARED_SELECTOR = YES; 207 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 208 | GCC_WARN_UNUSED_FUNCTION = YES; 209 | GCC_WARN_UNUSED_VARIABLE = YES; 210 | MACOSX_DEPLOYMENT_TARGET = 10.15; 211 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 212 | MTL_FAST_MATH = YES; 213 | ONLY_ACTIVE_ARCH = YES; 214 | SDKROOT = macosx; 215 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 216 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 217 | }; 218 | name = Debug; 219 | }; 220 | 8E284A682458C3DA001BDC2B /* Release */ = { 221 | isa = XCBuildConfiguration; 222 | buildSettings = { 223 | ALWAYS_SEARCH_USER_PATHS = NO; 224 | CLANG_ANALYZER_NONNULL = YES; 225 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 226 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 227 | CLANG_CXX_LIBRARY = "libc++"; 228 | CLANG_ENABLE_MODULES = YES; 229 | CLANG_ENABLE_OBJC_ARC = YES; 230 | CLANG_ENABLE_OBJC_WEAK = YES; 231 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 232 | CLANG_WARN_BOOL_CONVERSION = YES; 233 | CLANG_WARN_COMMA = YES; 234 | CLANG_WARN_CONSTANT_CONVERSION = YES; 235 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 236 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 237 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 238 | CLANG_WARN_EMPTY_BODY = YES; 239 | CLANG_WARN_ENUM_CONVERSION = YES; 240 | CLANG_WARN_INFINITE_RECURSION = YES; 241 | CLANG_WARN_INT_CONVERSION = YES; 242 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 243 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 244 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 245 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 246 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 247 | CLANG_WARN_STRICT_PROTOTYPES = YES; 248 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 249 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 250 | CLANG_WARN_UNREACHABLE_CODE = YES; 251 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 252 | COPY_PHASE_STRIP = NO; 253 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 254 | ENABLE_NS_ASSERTIONS = NO; 255 | ENABLE_STRICT_OBJC_MSGSEND = YES; 256 | GCC_C_LANGUAGE_STANDARD = gnu11; 257 | GCC_NO_COMMON_BLOCKS = YES; 258 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 259 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 260 | GCC_WARN_UNDECLARED_SELECTOR = YES; 261 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 262 | GCC_WARN_UNUSED_FUNCTION = YES; 263 | GCC_WARN_UNUSED_VARIABLE = YES; 264 | MACOSX_DEPLOYMENT_TARGET = 10.15; 265 | MTL_ENABLE_DEBUG_INFO = NO; 266 | MTL_FAST_MATH = YES; 267 | SDKROOT = macosx; 268 | SWIFT_COMPILATION_MODE = wholemodule; 269 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 270 | }; 271 | name = Release; 272 | }; 273 | 8E284A6A2458C3DA001BDC2B /* Debug */ = { 274 | isa = XCBuildConfiguration; 275 | buildSettings = { 276 | CODE_SIGN_IDENTITY = "-"; 277 | CODE_SIGN_STYLE = Automatic; 278 | DEVELOPMENT_TEAM = 2W4DVPEQ39; 279 | ENABLE_HARDENED_RUNTIME = YES; 280 | PRODUCT_NAME = "$(TARGET_NAME)"; 281 | SWIFT_VERSION = 5.0; 282 | }; 283 | name = Debug; 284 | }; 285 | 8E284A6B2458C3DA001BDC2B /* Release */ = { 286 | isa = XCBuildConfiguration; 287 | buildSettings = { 288 | CODE_SIGN_IDENTITY = "-"; 289 | CODE_SIGN_STYLE = Automatic; 290 | DEVELOPMENT_TEAM = 2W4DVPEQ39; 291 | ENABLE_HARDENED_RUNTIME = YES; 292 | PRODUCT_NAME = "$(TARGET_NAME)"; 293 | SWIFT_VERSION = 5.0; 294 | }; 295 | name = Release; 296 | }; 297 | /* End XCBuildConfiguration section */ 298 | 299 | /* Begin XCConfigurationList section */ 300 | 8E284A5D2458C3D9001BDC2B /* Build configuration list for PBXProject "remind" */ = { 301 | isa = XCConfigurationList; 302 | buildConfigurations = ( 303 | 8E284A672458C3DA001BDC2B /* Debug */, 304 | 8E284A682458C3DA001BDC2B /* Release */, 305 | ); 306 | defaultConfigurationIsVisible = 0; 307 | defaultConfigurationName = Release; 308 | }; 309 | 8E284A692458C3DA001BDC2B /* Build configuration list for PBXNativeTarget "remind" */ = { 310 | isa = XCConfigurationList; 311 | buildConfigurations = ( 312 | 8E284A6A2458C3DA001BDC2B /* Debug */, 313 | 8E284A6B2458C3DA001BDC2B /* Release */, 314 | ); 315 | defaultConfigurationIsVisible = 0; 316 | defaultConfigurationName = Release; 317 | }; 318 | /* End XCConfigurationList section */ 319 | 320 | /* Begin XCRemoteSwiftPackageReference section */ 321 | 8E2876F924609DAD00EB8416 /* XCRemoteSwiftPackageReference "swift-argument-parser" */ = { 322 | isa = XCRemoteSwiftPackageReference; 323 | repositoryURL = "http://github.com/apple/swift-argument-parser"; 324 | requirement = { 325 | kind = exactVersion; 326 | version = 0.0.6; 327 | }; 328 | }; 329 | /* End XCRemoteSwiftPackageReference section */ 330 | 331 | /* Begin XCSwiftPackageProductDependency section */ 332 | 8E2876FA24609DAD00EB8416 /* ArgumentParser */ = { 333 | isa = XCSwiftPackageProductDependency; 334 | package = 8E2876F924609DAD00EB8416 /* XCRemoteSwiftPackageReference "swift-argument-parser" */; 335 | productName = ArgumentParser; 336 | }; 337 | /* End XCSwiftPackageProductDependency section */ 338 | }; 339 | rootObject = 8E284A5A2458C3D9001BDC2B /* Project object */; 340 | } 341 | --------------------------------------------------------------------------------