git.fiddlerwoaroof.com
Browse code

first commit

SamRothCA authored on 29/10/2014 03:43:47
Showing 38 changed files
2 2
new file mode 100644
... ...
@@ -0,0 +1,667 @@
1
+// !$*UTF8*$!
2
+{
3
+	archiveVersion = 1;
4
+	classes = {
5
+	};
6
+	objectVersion = 46;
7
+	objects = {
8
+
9
+/* Begin PBXBuildFile section */
10
+		223F9A0A19F894AF000802FB /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A0919F894AF000802FB /* AppDelegate.m */; };
11
+		223F9A0C19F894AF000802FB /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A0B19F894AF000802FB /* main.m */; };
12
+		223F9A2D19F894DB000802FB /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 223F9A2C19F894DB000802FB /* NotificationCenter.framework */; };
13
+		223F9A3419F894DB000802FB /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 223F9A3219F894DB000802FB /* InfoPlist.strings */; };
14
+		223F9A4119F894DB000802FB /* Widget.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 223F9A2A19F894DB000802FB /* Widget.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
15
+		223F9A5D19F895AF000802FB /* TodayScripts.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A5C19F895AF000802FB /* TodayScripts.m */; };
16
+		223F9A5E19F895AF000802FB /* TodayScripts.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A5C19F895AF000802FB /* TodayScripts.m */; };
17
+		223F9A6C19F8963C000802FB /* AMR_ANSIEscapeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A6019F8963C000802FB /* AMR_ANSIEscapeHelper.m */; };
18
+		223F9A6D19F8963C000802FB /* ListRowViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A6219F8963C000802FB /* ListRowViewController.m */; };
19
+		223F9A6E19F8963C000802FB /* ListRowViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 223F9A6319F8963C000802FB /* ListRowViewController.xib */; };
20
+		223F9A6F19F8963C000802FB /* EditViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A6519F8963C000802FB /* EditViewController.m */; };
21
+		223F9A7019F8963C000802FB /* EditViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 223F9A6619F8963C000802FB /* EditViewController.xib */; };
22
+		223F9A7119F8963C000802FB /* TodayScript.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A6819F8963C000802FB /* TodayScript.m */; };
23
+		223F9A7219F8963C000802FB /* TodayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A6A19F8963C000802FB /* TodayViewController.m */; };
24
+		223F9A7319F8963C000802FB /* TodayViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 223F9A6B19F8963C000802FB /* TodayViewController.xib */; };
25
+		223F9A7719F896BF000802FB /* XPCHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A7419F896BF000802FB /* XPCHelper.m */; };
26
+		223F9A7819F896BF000802FB /* XPCMain.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A7619F896BF000802FB /* XPCMain.m */; };
27
+		2265B37619FC519C000802FB /* AppIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 2265B37519FC519C000802FB /* AppIcon.icns */; };
28
+		2265B37819FC51DF000802FB /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2265B37719FC51DF000802FB /* Images.xcassets */; };
29
+		2265CC7319F89D18000802FB /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2265CC7219F89D18000802FB /* MainMenu.xib */; };
30
+		2265CC7619F89D86000802FB /* XPC.xpc in CopyFiles */ = {isa = PBXBuildFile; fileRef = 223F9A4A19F89532000802FB /* XPC.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
31
+		22A1BFCD1A004D8C000802FB /* Icon Credit.txt in Resources */ = {isa = PBXBuildFile; fileRef = 22A1BFCC1A004D8C000802FB /* Icon Credit.txt */; };
32
+/* End PBXBuildFile section */
33
+
34
+/* Begin PBXContainerItemProxy section */
35
+		223F9A3F19F894DB000802FB /* PBXContainerItemProxy */ = {
36
+			isa = PBXContainerItemProxy;
37
+			containerPortal = 223F99FB19F894AE000802FB /* Project object */;
38
+			proxyType = 1;
39
+			remoteGlobalIDString = 223F9A2919F894DB000802FB;
40
+			remoteInfo = Widget;
41
+		};
42
+		223F9A5419F89532000802FB /* PBXContainerItemProxy */ = {
43
+			isa = PBXContainerItemProxy;
44
+			containerPortal = 223F99FB19F894AE000802FB /* Project object */;
45
+			proxyType = 1;
46
+			remoteGlobalIDString = 223F9A4919F89532000802FB;
47
+			remoteInfo = XPC;
48
+		};
49
+		223F9A7919F89745000802FB /* PBXContainerItemProxy */ = {
50
+			isa = PBXContainerItemProxy;
51
+			containerPortal = 223F99FB19F894AE000802FB /* Project object */;
52
+			proxyType = 1;
53
+			remoteGlobalIDString = 223F9A4919F89532000802FB;
54
+			remoteInfo = XPC;
55
+		};
56
+/* End PBXContainerItemProxy section */
57
+
58
+/* Begin PBXCopyFilesBuildPhase section */
59
+		223F9A4519F894DB000802FB /* Embed App Extensions */ = {
60
+			isa = PBXCopyFilesBuildPhase;
61
+			buildActionMask = 2147483647;
62
+			dstPath = "";
63
+			dstSubfolderSpec = 13;
64
+			files = (
65
+				223F9A4119F894DB000802FB /* Widget.appex in Embed App Extensions */,
66
+			);
67
+			name = "Embed App Extensions";
68
+			runOnlyForDeploymentPostprocessing = 0;
69
+		};
70
+		223F9A5A19F89532000802FB /* Embed XPC Services */ = {
71
+			isa = PBXCopyFilesBuildPhase;
72
+			buildActionMask = 2147483647;
73
+			dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices";
74
+			dstSubfolderSpec = 16;
75
+			files = (
76
+			);
77
+			name = "Embed XPC Services";
78
+			runOnlyForDeploymentPostprocessing = 0;
79
+		};
80
+		2265CC7519F89D7F000802FB /* CopyFiles */ = {
81
+			isa = PBXCopyFilesBuildPhase;
82
+			buildActionMask = 2147483647;
83
+			dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices";
84
+			dstSubfolderSpec = 16;
85
+			files = (
86
+				2265CC7619F89D86000802FB /* XPC.xpc in CopyFiles */,
87
+			);
88
+			runOnlyForDeploymentPostprocessing = 0;
89
+		};
90
+/* End PBXCopyFilesBuildPhase section */
91
+
92
+/* Begin PBXFileReference section */
93
+		223F9A0319F894AE000802FB /* Today Scripts.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Today Scripts.app"; sourceTree = BUILT_PRODUCTS_DIR; };
94
+		223F9A0719F894AF000802FB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
95
+		223F9A0819F894AF000802FB /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
96
+		223F9A0919F894AF000802FB /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
97
+		223F9A0B19F894AF000802FB /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
98
+		223F9A2A19F894DB000802FB /* Widget.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Widget.appex; sourceTree = BUILT_PRODUCTS_DIR; };
99
+		223F9A2C19F894DB000802FB /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; };
100
+		223F9A3019F894DB000802FB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
101
+		223F9A3119F894DB000802FB /* Widget.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Widget.entitlements; sourceTree = "<group>"; };
102
+		223F9A3319F894DB000802FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
103
+		223F9A4A19F89532000802FB /* XPC.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = XPC.xpc; sourceTree = BUILT_PRODUCTS_DIR; };
104
+		223F9A4D19F89532000802FB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
105
+		223F9A5B19F895AF000802FB /* TodayScripts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TodayScripts.h; sourceTree = "<group>"; };
106
+		223F9A5C19F895AF000802FB /* TodayScripts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TodayScripts.m; sourceTree = "<group>"; };
107
+		223F9A5F19F8963C000802FB /* AMR_ANSIEscapeHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AMR_ANSIEscapeHelper.h; sourceTree = "<group>"; };
108
+		223F9A6019F8963C000802FB /* AMR_ANSIEscapeHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AMR_ANSIEscapeHelper.m; sourceTree = "<group>"; };
109
+		223F9A6119F8963C000802FB /* ListRowViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ListRowViewController.h; sourceTree = "<group>"; };
110
+		223F9A6219F8963C000802FB /* ListRowViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ListRowViewController.m; sourceTree = "<group>"; };
111
+		223F9A6319F8963C000802FB /* ListRowViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ListRowViewController.xib; sourceTree = "<group>"; };
112
+		223F9A6419F8963C000802FB /* EditViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EditViewController.h; sourceTree = "<group>"; };
113
+		223F9A6519F8963C000802FB /* EditViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EditViewController.m; sourceTree = "<group>"; };
114
+		223F9A6619F8963C000802FB /* EditViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = EditViewController.xib; sourceTree = "<group>"; };
115
+		223F9A6719F8963C000802FB /* TodayScript.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TodayScript.h; sourceTree = "<group>"; };
116
+		223F9A6819F8963C000802FB /* TodayScript.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TodayScript.m; sourceTree = "<group>"; };
117
+		223F9A6919F8963C000802FB /* TodayViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TodayViewController.h; sourceTree = "<group>"; };
118
+		223F9A6A19F8963C000802FB /* TodayViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TodayViewController.m; sourceTree = "<group>"; };
119
+		223F9A6B19F8963C000802FB /* TodayViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TodayViewController.xib; sourceTree = "<group>"; };
120
+		223F9A7419F896BF000802FB /* XPCHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XPCHelper.m; sourceTree = "<group>"; };
121
+		223F9A7519F896BF000802FB /* XPCMain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XPCMain.h; sourceTree = "<group>"; };
122
+		223F9A7619F896BF000802FB /* XPCMain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XPCMain.m; sourceTree = "<group>"; };
123
+		2265B37519FC519C000802FB /* AppIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = AppIcon.icns; sourceTree = "<group>"; };
124
+		2265B37719FC51DF000802FB /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
125
+		2265B37A19FC53CA000802FB /* readme.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = readme.txt; sourceTree = "<group>"; };
126
+		2265CC7219F89D18000802FB /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; };
127
+		22A1BFCC1A004D8C000802FB /* Icon Credit.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "Icon Credit.txt"; sourceTree = "<group>"; };
128
+/* End PBXFileReference section */
129
+
130
+/* Begin PBXFrameworksBuildPhase section */
131
+		223F9A0019F894AE000802FB /* Frameworks */ = {
132
+			isa = PBXFrameworksBuildPhase;
133
+			buildActionMask = 2147483647;
134
+			files = (
135
+			);
136
+			runOnlyForDeploymentPostprocessing = 0;
137
+		};
138
+		223F9A2719F894DB000802FB /* Frameworks */ = {
139
+			isa = PBXFrameworksBuildPhase;
140
+			buildActionMask = 2147483647;
141
+			files = (
142
+				223F9A2D19F894DB000802FB /* NotificationCenter.framework in Frameworks */,
143
+			);
144
+			runOnlyForDeploymentPostprocessing = 0;
145
+		};
146
+		223F9A4719F89532000802FB /* Frameworks */ = {
147
+			isa = PBXFrameworksBuildPhase;
148
+			buildActionMask = 2147483647;
149
+			files = (
150
+			);
151
+			runOnlyForDeploymentPostprocessing = 0;
152
+		};
153
+/* End PBXFrameworksBuildPhase section */
154
+
155
+/* Begin PBXGroup section */
156
+		223F99FA19F894AE000802FB = {
157
+			isa = PBXGroup;
158
+			children = (
159
+				223F9A5B19F895AF000802FB /* TodayScripts.h */,
160
+				223F9A5C19F895AF000802FB /* TodayScripts.m */,
161
+				223F9A0519F894AE000802FB /* Today Scripts */,
162
+				223F9A2E19F894DB000802FB /* Widget */,
163
+				223F9A4B19F89532000802FB /* XPC */,
164
+				223F9A2B19F894DB000802FB /* Frameworks */,
165
+				223F9A0419F894AE000802FB /* Products */,
166
+			);
167
+			sourceTree = "<group>";
168
+		};
169
+		223F9A0419F894AE000802FB /* Products */ = {
170
+			isa = PBXGroup;
171
+			children = (
172
+				223F9A0319F894AE000802FB /* Today Scripts.app */,
173
+				223F9A2A19F894DB000802FB /* Widget.appex */,
174
+				223F9A4A19F89532000802FB /* XPC.xpc */,
175
+			);
176
+			name = Products;
177
+			sourceTree = "<group>";
178
+		};
179
+		223F9A0519F894AE000802FB /* Today Scripts */ = {
180
+			isa = PBXGroup;
181
+			children = (
182
+				223F9A0819F894AF000802FB /* AppDelegate.h */,
183
+				223F9A0919F894AF000802FB /* AppDelegate.m */,
184
+				2265CC7219F89D18000802FB /* MainMenu.xib */,
185
+				223F9A0619F894AF000802FB /* Supporting Files */,
186
+			);
187
+			path = "Today Scripts";
188
+			sourceTree = "<group>";
189
+		};
190
+		223F9A0619F894AF000802FB /* Supporting Files */ = {
191
+			isa = PBXGroup;
192
+			children = (
193
+				22A1BFCC1A004D8C000802FB /* Icon Credit.txt */,
194
+				2265B37519FC519C000802FB /* AppIcon.icns */,
195
+				223F9A0719F894AF000802FB /* Info.plist */,
196
+				223F9A0B19F894AF000802FB /* main.m */,
197
+			);
198
+			name = "Supporting Files";
199
+			sourceTree = "<group>";
200
+		};
201
+		223F9A2B19F894DB000802FB /* Frameworks */ = {
202
+			isa = PBXGroup;
203
+			children = (
204
+				223F9A2C19F894DB000802FB /* NotificationCenter.framework */,
205
+			);
206
+			name = Frameworks;
207
+			sourceTree = "<group>";
208
+		};
209
+		223F9A2E19F894DB000802FB /* Widget */ = {
210
+			isa = PBXGroup;
211
+			children = (
212
+				223F9A6719F8963C000802FB /* TodayScript.h */,
213
+				223F9A6819F8963C000802FB /* TodayScript.m */,
214
+				223F9A6919F8963C000802FB /* TodayViewController.h */,
215
+				223F9A6A19F8963C000802FB /* TodayViewController.m */,
216
+				223F9A6B19F8963C000802FB /* TodayViewController.xib */,
217
+				223F9A6119F8963C000802FB /* ListRowViewController.h */,
218
+				223F9A6219F8963C000802FB /* ListRowViewController.m */,
219
+				223F9A6319F8963C000802FB /* ListRowViewController.xib */,
220
+				223F9A6419F8963C000802FB /* EditViewController.h */,
221
+				223F9A6519F8963C000802FB /* EditViewController.m */,
222
+				223F9A6619F8963C000802FB /* EditViewController.xib */,
223
+				2265B37919FC539E000802FB /* AMR_ANSIEscapeHelper */,
224
+				223F9A2F19F894DB000802FB /* Supporting Files */,
225
+			);
226
+			path = Widget;
227
+			sourceTree = "<group>";
228
+		};
229
+		223F9A2F19F894DB000802FB /* Supporting Files */ = {
230
+			isa = PBXGroup;
231
+			children = (
232
+				2265B37719FC51DF000802FB /* Images.xcassets */,
233
+				223F9A3019F894DB000802FB /* Info.plist */,
234
+				223F9A3119F894DB000802FB /* Widget.entitlements */,
235
+				223F9A3219F894DB000802FB /* InfoPlist.strings */,
236
+			);
237
+			name = "Supporting Files";
238
+			sourceTree = "<group>";
239
+		};
240
+		223F9A4B19F89532000802FB /* XPC */ = {
241
+			isa = PBXGroup;
242
+			children = (
243
+				223F9A7519F896BF000802FB /* XPCMain.h */,
244
+				223F9A7619F896BF000802FB /* XPCMain.m */,
245
+				223F9A7419F896BF000802FB /* XPCHelper.m */,
246
+				223F9A4C19F89532000802FB /* Supporting Files */,
247
+			);
248
+			path = XPC;
249
+			sourceTree = "<group>";
250
+		};
251
+		223F9A4C19F89532000802FB /* Supporting Files */ = {
252
+			isa = PBXGroup;
253
+			children = (
254
+				223F9A4D19F89532000802FB /* Info.plist */,
255
+			);
256
+			name = "Supporting Files";
257
+			sourceTree = "<group>";
258
+		};
259
+		2265B37919FC539E000802FB /* AMR_ANSIEscapeHelper */ = {
260
+			isa = PBXGroup;
261
+			children = (
262
+				2265B37A19FC53CA000802FB /* readme.txt */,
263
+				223F9A5F19F8963C000802FB /* AMR_ANSIEscapeHelper.h */,
264
+				223F9A6019F8963C000802FB /* AMR_ANSIEscapeHelper.m */,
265
+			);
266
+			path = AMR_ANSIEscapeHelper;
267
+			sourceTree = "<group>";
268
+		};
269
+/* End PBXGroup section */
270
+
271
+/* Begin PBXNativeTarget section */
272
+		223F9A0219F894AE000802FB /* Today Scripts */ = {
273
+			isa = PBXNativeTarget;
274
+			buildConfigurationList = 223F9A2019F894AF000802FB /* Build configuration list for PBXNativeTarget "Today Scripts" */;
275
+			buildPhases = (
276
+				223F99FF19F894AE000802FB /* Sources */,
277
+				223F9A0019F894AE000802FB /* Frameworks */,
278
+				223F9A0119F894AE000802FB /* Resources */,
279
+				223F9A4519F894DB000802FB /* Embed App Extensions */,
280
+				223F9A5A19F89532000802FB /* Embed XPC Services */,
281
+			);
282
+			buildRules = (
283
+			);
284
+			dependencies = (
285
+				223F9A4019F894DB000802FB /* PBXTargetDependency */,
286
+				223F9A5519F89532000802FB /* PBXTargetDependency */,
287
+			);
288
+			name = "Today Scripts";
289
+			productName = "Today Scripts";
290
+			productReference = 223F9A0319F894AE000802FB /* Today Scripts.app */;
291
+			productType = "com.apple.product-type.application";
292
+		};
293
+		223F9A2919F894DB000802FB /* Widget */ = {
294
+			isa = PBXNativeTarget;
295
+			buildConfigurationList = 223F9A4219F894DB000802FB /* Build configuration list for PBXNativeTarget "Widget" */;
296
+			buildPhases = (
297
+				223F9A2619F894DB000802FB /* Sources */,
298
+				223F9A2719F894DB000802FB /* Frameworks */,
299
+				223F9A2819F894DB000802FB /* Resources */,
300
+				2265CC7519F89D7F000802FB /* CopyFiles */,
301
+			);
302
+			buildRules = (
303
+			);
304
+			dependencies = (
305
+				223F9A7A19F89745000802FB /* PBXTargetDependency */,
306
+			);
307
+			name = Widget;
308
+			productName = Widget;
309
+			productReference = 223F9A2A19F894DB000802FB /* Widget.appex */;
310
+			productType = "com.apple.product-type.app-extension";
311
+		};
312
+		223F9A4919F89532000802FB /* XPC */ = {
313
+			isa = PBXNativeTarget;
314
+			buildConfigurationList = 223F9A5719F89532000802FB /* Build configuration list for PBXNativeTarget "XPC" */;
315
+			buildPhases = (
316
+				223F9A4619F89532000802FB /* Sources */,
317
+				223F9A4719F89532000802FB /* Frameworks */,
318
+				223F9A4819F89532000802FB /* Resources */,
319
+			);
320
+			buildRules = (
321
+			);
322
+			dependencies = (
323
+			);
324
+			name = XPC;
325
+			productName = XPC;
326
+			productReference = 223F9A4A19F89532000802FB /* XPC.xpc */;
327
+			productType = "com.apple.product-type.xpc-service";
328
+		};
329
+/* End PBXNativeTarget section */
330
+
331
+/* Begin PBXProject section */
332
+		223F99FB19F894AE000802FB /* Project object */ = {
333
+			isa = PBXProject;
334
+			attributes = {
335
+				LastUpgradeCheck = 0610;
336
+				ORGANIZATIONNAME = "Sam Rothenberg";
337
+				TargetAttributes = {
338
+					223F9A0219F894AE000802FB = {
339
+						CreatedOnToolsVersion = 6.1;
340
+					};
341
+					223F9A2919F894DB000802FB = {
342
+						CreatedOnToolsVersion = 6.1;
343
+					};
344
+					223F9A4919F89532000802FB = {
345
+						CreatedOnToolsVersion = 6.1;
346
+						SystemCapabilities = {
347
+							com.apple.Sandbox = {
348
+								enabled = 0;
349
+							};
350
+						};
351
+					};
352
+				};
353
+			};
354
+			buildConfigurationList = 223F99FE19F894AE000802FB /* Build configuration list for PBXProject "Today Scripts" */;
355
+			compatibilityVersion = "Xcode 3.2";
356
+			developmentRegion = English;
357
+			hasScannedForEncodings = 0;
358
+			knownRegions = (
359
+				en,
360
+				Base,
361
+			);
362
+			mainGroup = 223F99FA19F894AE000802FB;
363
+			productRefGroup = 223F9A0419F894AE000802FB /* Products */;
364
+			projectDirPath = "";
365
+			projectRoot = "";
366
+			targets = (
367
+				223F9A0219F894AE000802FB /* Today Scripts */,
368
+				223F9A2919F894DB000802FB /* Widget */,
369
+				223F9A4919F89532000802FB /* XPC */,
370
+			);
371
+		};
372
+/* End PBXProject section */
373
+
374
+/* Begin PBXResourcesBuildPhase section */
375
+		223F9A0119F894AE000802FB /* Resources */ = {
376
+			isa = PBXResourcesBuildPhase;
377
+			buildActionMask = 2147483647;
378
+			files = (
379
+				2265CC7319F89D18000802FB /* MainMenu.xib in Resources */,
380
+				2265B37619FC519C000802FB /* AppIcon.icns in Resources */,
381
+				22A1BFCD1A004D8C000802FB /* Icon Credit.txt in Resources */,
382
+			);
383
+			runOnlyForDeploymentPostprocessing = 0;
384
+		};
385
+		223F9A2819F894DB000802FB /* Resources */ = {
386
+			isa = PBXResourcesBuildPhase;
387
+			buildActionMask = 2147483647;
388
+			files = (
389
+				223F9A6E19F8963C000802FB /* ListRowViewController.xib in Resources */,
390
+				2265B37819FC51DF000802FB /* Images.xcassets in Resources */,
391
+				223F9A7319F8963C000802FB /* TodayViewController.xib in Resources */,
392
+				223F9A7019F8963C000802FB /* EditViewController.xib in Resources */,
393
+				223F9A3419F894DB000802FB /* InfoPlist.strings in Resources */,
394
+			);
395
+			runOnlyForDeploymentPostprocessing = 0;
396
+		};
397
+		223F9A4819F89532000802FB /* Resources */ = {
398
+			isa = PBXResourcesBuildPhase;
399
+			buildActionMask = 2147483647;
400
+			files = (
401
+			);
402
+			runOnlyForDeploymentPostprocessing = 0;
403
+		};
404
+/* End PBXResourcesBuildPhase section */
405
+
406
+/* Begin PBXSourcesBuildPhase section */
407
+		223F99FF19F894AE000802FB /* Sources */ = {
408
+			isa = PBXSourcesBuildPhase;
409
+			buildActionMask = 2147483647;
410
+			files = (
411
+				223F9A0C19F894AF000802FB /* main.m in Sources */,
412
+				223F9A0A19F894AF000802FB /* AppDelegate.m in Sources */,
413
+			);
414
+			runOnlyForDeploymentPostprocessing = 0;
415
+		};
416
+		223F9A2619F894DB000802FB /* Sources */ = {
417
+			isa = PBXSourcesBuildPhase;
418
+			buildActionMask = 2147483647;
419
+			files = (
420
+				223F9A7219F8963C000802FB /* TodayViewController.m in Sources */,
421
+				223F9A6C19F8963C000802FB /* AMR_ANSIEscapeHelper.m in Sources */,
422
+				223F9A5D19F895AF000802FB /* TodayScripts.m in Sources */,
423
+				223F9A6D19F8963C000802FB /* ListRowViewController.m in Sources */,
424
+				223F9A6F19F8963C000802FB /* EditViewController.m in Sources */,
425
+				223F9A7119F8963C000802FB /* TodayScript.m in Sources */,
426
+			);
427
+			runOnlyForDeploymentPostprocessing = 0;
428
+		};
429
+		223F9A4619F89532000802FB /* Sources */ = {
430
+			isa = PBXSourcesBuildPhase;
431
+			buildActionMask = 2147483647;
432
+			files = (
433
+				223F9A5E19F895AF000802FB /* TodayScripts.m in Sources */,
434
+				223F9A7819F896BF000802FB /* XPCMain.m in Sources */,
435
+				223F9A7719F896BF000802FB /* XPCHelper.m in Sources */,
436
+			);
437
+			runOnlyForDeploymentPostprocessing = 0;
438
+		};
439
+/* End PBXSourcesBuildPhase section */
440
+
441
+/* Begin PBXTargetDependency section */
442
+		223F9A4019F894DB000802FB /* PBXTargetDependency */ = {
443
+			isa = PBXTargetDependency;
444
+			target = 223F9A2919F894DB000802FB /* Widget */;
445
+			targetProxy = 223F9A3F19F894DB000802FB /* PBXContainerItemProxy */;
446
+		};
447
+		223F9A5519F89532000802FB /* PBXTargetDependency */ = {
448
+			isa = PBXTargetDependency;
449
+			target = 223F9A4919F89532000802FB /* XPC */;
450
+			targetProxy = 223F9A5419F89532000802FB /* PBXContainerItemProxy */;
451
+		};
452
+		223F9A7A19F89745000802FB /* PBXTargetDependency */ = {
453
+			isa = PBXTargetDependency;
454
+			target = 223F9A4919F89532000802FB /* XPC */;
455
+			targetProxy = 223F9A7919F89745000802FB /* PBXContainerItemProxy */;
456
+		};
457
+/* End PBXTargetDependency section */
458
+
459
+/* Begin PBXVariantGroup section */
460
+		223F9A3219F894DB000802FB /* InfoPlist.strings */ = {
461
+			isa = PBXVariantGroup;
462
+			children = (
463
+				223F9A3319F894DB000802FB /* en */,
464
+			);
465
+			name = InfoPlist.strings;
466
+			sourceTree = "<group>";
467
+		};
468
+/* End PBXVariantGroup section */
469
+
470
+/* Begin XCBuildConfiguration section */
471
+		223F9A1E19F894AF000802FB /* Debug */ = {
472
+			isa = XCBuildConfiguration;
473
+			buildSettings = {
474
+				ALWAYS_SEARCH_USER_PATHS = NO;
475
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
476
+				CLANG_CXX_LIBRARY = "libc++";
477
+				CLANG_ENABLE_MODULES = YES;
478
+				CLANG_ENABLE_OBJC_ARC = YES;
479
+				CLANG_WARN_BOOL_CONVERSION = YES;
480
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
481
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
482
+				CLANG_WARN_EMPTY_BODY = YES;
483
+				CLANG_WARN_ENUM_CONVERSION = YES;
484
+				CLANG_WARN_INT_CONVERSION = YES;
485
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
486
+				CLANG_WARN_UNREACHABLE_CODE = YES;
487
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
488
+				CODE_SIGN_IDENTITY = "-";
489
+				COPY_PHASE_STRIP = NO;
490
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
491
+				GCC_C_LANGUAGE_STANDARD = gnu99;
492
+				GCC_DYNAMIC_NO_PIC = NO;
493
+				GCC_OPTIMIZATION_LEVEL = 0;
494
+				GCC_PREPROCESSOR_DEFINITIONS = (
495
+					"DEBUG=1",
496
+					"$(inherited)",
497
+				);
498
+				GCC_SYMBOLS_PRIVATE_EXTERN = NO;
499
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
500
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
501
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
502
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
503
+				GCC_WARN_UNUSED_FUNCTION = YES;
504
+				GCC_WARN_UNUSED_VARIABLE = YES;
505
+				MACOSX_DEPLOYMENT_TARGET = 10.10;
506
+				MTL_ENABLE_DEBUG_INFO = YES;
507
+				ONLY_ACTIVE_ARCH = YES;
508
+				SDKROOT = macosx;
509
+			};
510
+			name = Debug;
511
+		};
512
+		223F9A1F19F894AF000802FB /* Release */ = {
513
+			isa = XCBuildConfiguration;
514
+			buildSettings = {
515
+				ALWAYS_SEARCH_USER_PATHS = NO;
516
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
517
+				CLANG_CXX_LIBRARY = "libc++";
518
+				CLANG_ENABLE_MODULES = YES;
519
+				CLANG_ENABLE_OBJC_ARC = YES;
520
+				CLANG_WARN_BOOL_CONVERSION = YES;
521
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
522
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
523
+				CLANG_WARN_EMPTY_BODY = YES;
524
+				CLANG_WARN_ENUM_CONVERSION = YES;
525
+				CLANG_WARN_INT_CONVERSION = YES;
526
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
527
+				CLANG_WARN_UNREACHABLE_CODE = YES;
528
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
529
+				CODE_SIGN_IDENTITY = "-";
530
+				COPY_PHASE_STRIP = YES;
531
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
532
+				ENABLE_NS_ASSERTIONS = NO;
533
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
534
+				GCC_C_LANGUAGE_STANDARD = gnu99;
535
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
536
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
537
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
538
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
539
+				GCC_WARN_UNUSED_FUNCTION = YES;
540
+				GCC_WARN_UNUSED_VARIABLE = YES;
541
+				MACOSX_DEPLOYMENT_TARGET = 10.10;
542
+				MTL_ENABLE_DEBUG_INFO = NO;
543
+				SDKROOT = macosx;
544
+			};
545
+			name = Release;
546
+		};
547
+		223F9A2119F894AF000802FB /* Debug */ = {
548
+			isa = XCBuildConfiguration;
549
+			buildSettings = {
550
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
551
+				COMBINE_HIDPI_IMAGES = YES;
552
+				INFOPLIST_FILE = "Today Scripts/Info.plist";
553
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
554
+				PRODUCT_NAME = "$(TARGET_NAME)";
555
+			};
556
+			name = Debug;
557
+		};
558
+		223F9A2219F894AF000802FB /* Release */ = {
559
+			isa = XCBuildConfiguration;
560
+			buildSettings = {
561
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
562
+				COMBINE_HIDPI_IMAGES = YES;
563
+				INFOPLIST_FILE = "Today Scripts/Info.plist";
564
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
565
+				PRODUCT_NAME = "$(TARGET_NAME)";
566
+			};
567
+			name = Release;
568
+		};
569
+		223F9A4319F894DB000802FB /* Debug */ = {
570
+			isa = XCBuildConfiguration;
571
+			buildSettings = {
572
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
573
+				CODE_SIGN_ENTITLEMENTS = Widget/Widget.entitlements;
574
+				CODE_SIGN_IDENTITY = "-";
575
+				COMBINE_HIDPI_IMAGES = YES;
576
+				GCC_PREPROCESSOR_DEFINITIONS = (
577
+					"DEBUG=1",
578
+					"$(inherited)",
579
+				);
580
+				INFOPLIST_FILE = Widget/Info.plist;
581
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks";
582
+				PRODUCT_NAME = "$(TARGET_NAME)";
583
+				SKIP_INSTALL = YES;
584
+			};
585
+			name = Debug;
586
+		};
587
+		223F9A4419F894DB000802FB /* Release */ = {
588
+			isa = XCBuildConfiguration;
589
+			buildSettings = {
590
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
591
+				CODE_SIGN_ENTITLEMENTS = Widget/Widget.entitlements;
592
+				CODE_SIGN_IDENTITY = "-";
593
+				COMBINE_HIDPI_IMAGES = YES;
594
+				INFOPLIST_FILE = Widget/Info.plist;
595
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks";
596
+				PRODUCT_NAME = "$(TARGET_NAME)";
597
+				SKIP_INSTALL = YES;
598
+			};
599
+			name = Release;
600
+		};
601
+		223F9A5819F89532000802FB /* Debug */ = {
602
+			isa = XCBuildConfiguration;
603
+			buildSettings = {
604
+				COMBINE_HIDPI_IMAGES = YES;
605
+				GCC_PREPROCESSOR_DEFINITIONS = (
606
+					"DEBUG=1",
607
+					"$(inherited)",
608
+				);
609
+				INFOPLIST_FILE = XPC/Info.plist;
610
+				PRODUCT_NAME = "$(TARGET_NAME)";
611
+				SKIP_INSTALL = YES;
612
+			};
613
+			name = Debug;
614
+		};
615
+		223F9A5919F89532000802FB /* Release */ = {
616
+			isa = XCBuildConfiguration;
617
+			buildSettings = {
618
+				COMBINE_HIDPI_IMAGES = YES;
619
+				INFOPLIST_FILE = XPC/Info.plist;
620
+				PRODUCT_NAME = "$(TARGET_NAME)";
621
+				SKIP_INSTALL = YES;
622
+			};
623
+			name = Release;
624
+		};
625
+/* End XCBuildConfiguration section */
626
+
627
+/* Begin XCConfigurationList section */
628
+		223F99FE19F894AE000802FB /* Build configuration list for PBXProject "Today Scripts" */ = {
629
+			isa = XCConfigurationList;
630
+			buildConfigurations = (
631
+				223F9A1E19F894AF000802FB /* Debug */,
632
+				223F9A1F19F894AF000802FB /* Release */,
633
+			);
634
+			defaultConfigurationIsVisible = 0;
635
+			defaultConfigurationName = Release;
636
+		};
637
+		223F9A2019F894AF000802FB /* Build configuration list for PBXNativeTarget "Today Scripts" */ = {
638
+			isa = XCConfigurationList;
639
+			buildConfigurations = (
640
+				223F9A2119F894AF000802FB /* Debug */,
641
+				223F9A2219F894AF000802FB /* Release */,
642
+			);
643
+			defaultConfigurationIsVisible = 0;
644
+			defaultConfigurationName = Release;
645
+		};
646
+		223F9A4219F894DB000802FB /* Build configuration list for PBXNativeTarget "Widget" */ = {
647
+			isa = XCConfigurationList;
648
+			buildConfigurations = (
649
+				223F9A4319F894DB000802FB /* Debug */,
650
+				223F9A4419F894DB000802FB /* Release */,
651
+			);
652
+			defaultConfigurationIsVisible = 0;
653
+			defaultConfigurationName = Release;
654
+		};
655
+		223F9A5719F89532000802FB /* Build configuration list for PBXNativeTarget "XPC" */ = {
656
+			isa = XCConfigurationList;
657
+			buildConfigurations = (
658
+				223F9A5819F89532000802FB /* Debug */,
659
+				223F9A5919F89532000802FB /* Release */,
660
+			);
661
+			defaultConfigurationIsVisible = 0;
662
+			defaultConfigurationName = Release;
663
+		};
664
+/* End XCConfigurationList section */
665
+	};
666
+	rootObject = 223F99FB19F894AE000802FB /* Project object */;
667
+}
0 668
new file mode 100644
... ...
@@ -0,0 +1,15 @@
1
+//
2
+//  AppDelegate.h
3
+//  Today Scripts
4
+//
5
+//  Created by Sam Rothenberg on 10/22/14.
6
+//  Copyright (c) 2014 Sam Rothenberg. All rights reserved.
7
+//
8
+
9
+#import <Cocoa/Cocoa.h>
10
+
11
+@interface AppDelegate : NSObject <NSApplicationDelegate>
12
+
13
+
14
+@end
15
+
0 16
new file mode 100644
... ...
@@ -0,0 +1,21 @@
1
+//
2
+//  AppDelegate.m
3
+//  Today Scripts
4
+//
5
+//  Created by Sam Rothenberg on 10/22/14.
6
+//  Copyright (c) 2014 Sam Rothenberg. All rights reserved.
7
+//
8
+
9
+#import "AppDelegate.h"
10
+
11
+@interface AppDelegate ()
12
+
13
+@end
14
+
15
+@implementation AppDelegate
16
+
17
+- (void)applicationWillFinishLaunching:(NSNotification *)aNotification {
18
+//    [NSApplication.sharedApplication terminate:self];
19
+}
20
+
21
+@end
0 22
new file mode 100644
1 23
Binary files /dev/null and b/Today Scripts/AppIcon.icns differ
2 24
new file mode 100644
... ...
@@ -0,0 +1,2 @@
1
+Icon thanks to Friedrich Preuß
2
+http://phriedrich.de
0 3
\ No newline at end of file
1 4
new file mode 100644
... ...
@@ -0,0 +1,36 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+<plist version="1.0">
4
+<dict>
5
+	<key>CFBundleDevelopmentRegion</key>
6
+	<string>en</string>
7
+	<key>CFBundleExecutable</key>
8
+	<string>$(EXECUTABLE_NAME)</string>
9
+	<key>CFBundleIconFile</key>
10
+	<string>AppIcon</string>
11
+	<key>CFBundleIdentifier</key>
12
+	<string>org.samroth.$(PRODUCT_NAME:rfc1034identifier)</string>
13
+	<key>CFBundleInfoDictionaryVersion</key>
14
+	<string>6.0</string>
15
+	<key>CFBundleName</key>
16
+	<string>$(PRODUCT_NAME)</string>
17
+	<key>CFBundlePackageType</key>
18
+	<string>APPL</string>
19
+	<key>CFBundleShortVersionString</key>
20
+	<string>1.0</string>
21
+	<key>CFBundleSignature</key>
22
+	<string>????</string>
23
+	<key>CFBundleVersion</key>
24
+	<string>1</string>
25
+	<key>LSMinimumSystemVersion</key>
26
+	<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
27
+	<key>LSUIElement</key>
28
+	<true/>
29
+	<key>NSHumanReadableCopyright</key>
30
+	<string>Copyright © 2014 Sam Rothenberg. All rights reserved.</string>
31
+	<key>NSMainNibFile</key>
32
+	<string>MainMenu</string>
33
+	<key>NSPrincipalClass</key>
34
+	<string>NSApplication</string>
35
+</dict>
36
+</plist>
0 37
new file mode 100644
... ...
@@ -0,0 +1,16 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="14A389" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
3
+    <dependencies>
4
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/>
5
+    </dependencies>
6
+    <objects>
7
+        <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
8
+            <connections>
9
+                <outlet property="delegate" destination="yPq-Rt-e4H" id="dZ1-Qd-NPf"/>
10
+            </connections>
11
+        </customObject>
12
+        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
13
+        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
14
+        <customObject id="yPq-Rt-e4H" customClass="AppDelegate"/>
15
+    </objects>
16
+</document>
0 17
new file mode 100644
... ...
@@ -0,0 +1,13 @@
1
+//
2
+//  main.m
3
+//  Today Scripts
4
+//
5
+//  Created by Sam Rothenberg on 10/22/14.
6
+//  Copyright (c) 2014 Sam Rothenberg. All rights reserved.
7
+//
8
+
9
+#import <Cocoa/Cocoa.h>
10
+
11
+int main(int argc, const char * argv[]) {
12
+    return NSApplicationMain(argc, argv);
13
+}
0 14
new file mode 100644
... ...
@@ -0,0 +1,29 @@
1
+//
2
+//  XPCHelper.h
3
+//  xpc
4
+//
5
+//  Created by Sam Rothenberg on 8/10/14.
6
+//  Copyright (c) 2014 Sam Rothenberg. All rights reserved.
7
+//
8
+
9
+#import <Foundation/Foundation.h>
10
+
11
+extern const NSString *TodayScriptLabelKey;
12
+extern const NSString *TodayScriptProgramKey;
13
+extern const NSString *TodayScriptScriptKey;
14
+extern const NSString *TodayScriptAutoRunKey;
15
+extern const NSString *TodayScriptShowStatusKey;
16
+
17
+// The protocol that this service will vend as its API. This header file will
18
+// also need to be visible to the process hosting the service. Replace the API
19
+// of this protocol with an API appropriate to the service you are vending.
20
+
21
+typedef void (^ XPCHandler )(int status, NSString *output);
22
+
23
+@protocol XPCHelping
24
+
25
+- (void)launchScript:(NSDictionary *)script forUUID:(NSString *)UUID handler:(XPCHandler)handler;
26
+
27
+- (void)terminateScriptForUUID:(NSString *)UUID;
28
+
29
+@end
0 30
new file mode 100644
... ...
@@ -0,0 +1,15 @@
1
+//
2
+//  ScriptsArray.m
3
+//  Today Scripts
4
+//
5
+//  Created by Sam Rothenberg on 8/14/14.
6
+//  Copyright (c) 2014 Sam Rothenberg. All rights reserved.
7
+//
8
+
9
+#import "TodayScripts.h"
10
+
11
+const NSString *TodayScriptLabelKey      = @"Label";
12
+const NSString *TodayScriptProgramKey    = @"Program";
13
+const NSString *TodayScriptScriptKey     = @"Script";
14
+const NSString *TodayScriptAutoRunKey    = @"AutoRun";
15
+const NSString *TodayScriptShowStatusKey = @"ShowStatus";
0 16
new file mode 100644
... ...
@@ -0,0 +1,326 @@
1
+//
2
+//  ANSIEscapeHelper.h
3
+//  AnsiColorsTest
4
+//
5
+//  Created by Ali Rantakari on 18.3.09.
6
+//
7
+//  Version 0.9.6
8
+//
9
+/*
10
+The MIT License
11
+
12
+Copyright (c) 2008-2009,2013 Ali Rantakari
13
+
14
+Permission is hereby granted, free of charge, to any person obtaining a copy
15
+of this software and associated documentation files (the "Software"), to deal
16
+in the Software without restriction, including without limitation the rights
17
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+copies of the Software, and to permit persons to whom the Software is
19
+furnished to do so, subject to the following conditions:
20
+
21
+The above copyright notice and this permission notice shall be included in
22
+all copies or substantial portions of the Software.
23
+
24
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
30
+THE SOFTWARE.
31
+*/
32
+
33
+#import <Cocoa/Cocoa.h>
34
+
35
+
36
+#if !__has_feature(objc_arc)
37
+#warning "This code requires ARC to be enabled."
38
+#endif
39
+
40
+
41
+// dictionary keys for the SGR code dictionaries that the array
42
+// escapeCodesForString:cleanString: returns contains
43
+#define kAMRCodeDictKey_code           @"code"
44
+#define kAMRCodeDictKey_location       @"location"
45
+
46
+// dictionary keys for the string formatting attribute
47
+// dictionaries that the array attributesForString:cleanString:
48
+// returns contains
49
+#define kAMRAttrDictKey_range          @"range"
50
+#define kAMRAttrDictKey_attrName       @"attributeName"
51
+#define kAMRAttrDictKey_attrValue      @"attributeValue"
52
+
53
+
54
+/*!
55
+ @enum          AMR_SGRCode
56
+
57
+ @abstract      SGR (Select Graphic Rendition) ANSI control codes.
58
+ */
59
+typedef enum
60
+{
61
+    AMR_SGRCodeNoneOrInvalid =      -1,
62
+
63
+    AMR_SGRCodeAllReset =           0,
64
+
65
+    AMR_SGRCodeIntensityBold =      1,
66
+    AMR_SGRCodeIntensityFaint =     2,
67
+    AMR_SGRCodeIntensityNormal =    22,
68
+
69
+    AMR_SGRCodeItalicOn =           3,
70
+
71
+    AMR_SGRCodeUnderlineSingle =    4,
72
+    AMR_SGRCodeUnderlineDouble =    21,
73
+    AMR_SGRCodeUnderlineNone =      24,
74
+
75
+    AMR_SGRCodeFgBlack =            30,
76
+    AMR_SGRCodeFgRed =              31,
77
+    AMR_SGRCodeFgGreen =            32,
78
+    AMR_SGRCodeFgYellow =           33,
79
+    AMR_SGRCodeFgBlue =             34,
80
+    AMR_SGRCodeFgMagenta =          35,
81
+    AMR_SGRCodeFgCyan =             36,
82
+    AMR_SGRCodeFgWhite =            37,
83
+    AMR_SGRCodeFgReset =            39,
84
+
85
+    AMR_SGRCodeBgBlack =            40,
86
+    AMR_SGRCodeBgRed =              41,
87
+    AMR_SGRCodeBgGreen =            42,
88
+    AMR_SGRCodeBgYellow =           43,
89
+    AMR_SGRCodeBgBlue =             44,
90
+    AMR_SGRCodeBgMagenta =          45,
91
+    AMR_SGRCodeBgCyan =             46,
92
+    AMR_SGRCodeBgWhite =            47,
93
+    AMR_SGRCodeBgReset =            49,
94
+
95
+    AMR_SGRCodeFgBrightBlack =      90,
96
+    AMR_SGRCodeFgBrightRed =        91,
97
+    AMR_SGRCodeFgBrightGreen =      92,
98
+    AMR_SGRCodeFgBrightYellow =     93,
99
+    AMR_SGRCodeFgBrightBlue =       94,
100
+    AMR_SGRCodeFgBrightMagenta =    95,
101
+    AMR_SGRCodeFgBrightCyan =       96,
102
+    AMR_SGRCodeFgBrightWhite =      97,
103
+
104
+    AMR_SGRCodeBgBrightBlack =      100,
105
+    AMR_SGRCodeBgBrightRed =        101,
106
+    AMR_SGRCodeBgBrightGreen =      102,
107
+    AMR_SGRCodeBgBrightYellow =     103,
108
+    AMR_SGRCodeBgBrightBlue =       104,
109
+    AMR_SGRCodeBgBrightMagenta =    105,
110
+    AMR_SGRCodeBgBrightCyan =       106,
111
+    AMR_SGRCodeBgBrightWhite =      107
112
+} AMR_SGRCode;
113
+
114
+
115
+
116
+
117
+
118
+
119
+/*!
120
+ @class     AMR_ANSIEscapeHelper
121
+
122
+ @abstract  Contains helper methods for dealing with strings
123
+            that contain ANSI escape sequences for formatting (colors,
124
+            underlining, bold etc.)
125
+ */
126
+@interface AMR_ANSIEscapeHelper : NSObject
127
+
128
+/*!
129
+ @property      defaultStringColor
130
+
131
+ @abstract      The default color used when creating an attributed string (default is black).
132
+ */
133
+@property(copy) NSColor *defaultStringColor;
134
+
135
+
136
+/*!
137
+ @property      font
138
+
139
+ @abstract      The font to use when creating string formatting attribute values.
140
+ */
141
+@property(copy) NSFont *font;
142
+
143
+/*!
144
+ @property      ansiColors
145
+
146
+ @abstract      The colors to use for displaying ANSI colors.
147
+
148
+ @discussion    Keys in this dictionary should be NSNumber objects containing SGR code
149
+                values from the AMR_SGRCode enum. The corresponding values for these keys
150
+                should be NSColor objects. If this property is nil or if it doesn't
151
+                contain a key for a specific SGR code, the default color will be used
152
+                instead.
153
+ */
154
+@property(retain) NSMutableDictionary *ansiColors;
155
+
156
+
157
+/*!
158
+ @method        attributedStringWithANSIEscapedString:
159
+
160
+ @abstract      Returns an attributed string that corresponds both in contents
161
+                and formatting to a given string that contains ANSI escape
162
+                sequences.
163
+
164
+ @param aString         A String containing ANSI escape sequences
165
+
166
+ @result        An attributed string that mimics as closely as possible
167
+                the formatting of the given ANSI-escaped string.
168
+ */
169
+- (NSAttributedString*) attributedStringWithANSIEscapedString:(NSString*)aString;
170
+
171
+
172
+/*!
173
+ @method        ansiEscapedStringWithAttributedString:
174
+
175
+ @abstract      Returns a string containing ANSI escape sequences that corresponds
176
+                both in contents and formatting to a given attributed string.
177
+
178
+ @param aAttributedString       An attributed string
179
+
180
+ @result        A string that mimics as closely as possible
181
+                the formatting of the given attributed string with
182
+                ANSI escape sequences.
183
+ */
184
+- (NSString*) ansiEscapedStringWithAttributedString:(NSAttributedString*)aAttributedString;
185
+
186
+
187
+/*!
188
+ @method        escapeCodesForString:cleanString:
189
+
190
+ @abstract      Returns an array of SGR codes and their locations from a
191
+                string containing ANSI escape sequences as well as a "clean"
192
+                version of the string (i.e. one without the ANSI escape
193
+                sequences.)
194
+
195
+ @param aString         A String containing ANSI escape sequences
196
+ @param aCleanString    Upon return, contains a "clean" version of aString (i.e. aString
197
+                        without the ANSI escape sequences)
198
+
199
+ @result        An array of NSDictionary objects, each of which has
200
+                an NSNumber value for the key "code" (specifying an SGR code) and
201
+                another NSNumber value for the key "location" (specifying the
202
+                location of the code within aCleanString.)
203
+ */
204
+- (NSArray*) escapeCodesForString:(NSString*)aString cleanString:(NSString**)aCleanString;
205
+
206
+
207
+/*!
208
+ @method        ansiEscapedStringWithCodesAndLocations:cleanString:
209
+
210
+ @abstract      Returns a string containing ANSI escape codes for formatting based
211
+                on a string and an array of SGR codes and their locations within
212
+                the given string.
213
+
214
+ @param aCodesArray     An array of NSDictionary objects, each of which should have
215
+                        an NSNumber value for the key "code" (specifying an SGR
216
+                        code) and another NSNumber value for the key "location"
217
+                        (specifying the location of this SGR code in aCleanString.)
218
+ @param aCleanString    The string to which to insert the ANSI escape codes
219
+                        described in aCodesArray.
220
+
221
+ @result        A string containing ANSI escape sequences.
222
+ */
223
+- (NSString*) ansiEscapedStringWithCodesAndLocations:(NSArray*)aCodesArray cleanString:(NSString*)aCleanString;
224
+
225
+
226
+/*!
227
+ @method        attributesForString:cleanString:
228
+
229
+ @abstract      Convert ANSI escape sequences in a string to string formatting attributes.
230
+
231
+ @discussion    Given a string with some ANSI escape sequences in it, this method returns
232
+                attributes for formatting the specified string according to those ANSI
233
+                escape sequences as well as a "clean" (i.e. free of the escape sequences)
234
+                version of this string.
235
+
236
+ @param aString         A String containing ANSI escape sequences
237
+ @param aCleanString    Upon return, contains a "clean" version of aString (i.e. aString
238
+                        without the ANSI escape sequences.) Pass in NULL if you're not
239
+                        interested in this.
240
+
241
+ @result        An array containing NSDictionary objects, each of which has keys "range"
242
+                (an NSValue containing an NSRange, specifying the range for the
243
+                attribute within the "clean" version of aString), "attributeName" (an
244
+                NSString) and "attributeValue" (an NSObject). You may use these as
245
+                arguments for NSMutableAttributedString's methods for setting the
246
+                visual formatting.
247
+ */
248
+- (NSArray*) attributesForString:(NSString*)aString cleanString:(NSString**)aCleanString;
249
+
250
+
251
+/*!
252
+ @method        AMR_SGRCode:endsFormattingIntroducedByCode:
253
+
254
+ @abstract      Whether the occurrence of a given SGR code would end the formatting run
255
+                introduced by another SGR code.
256
+
257
+ @discussion    For example, AMR_SGRCodeFgReset, AMR_SGRCodeAllReset or any SGR code
258
+                specifying a foreground color would end the formatting run
259
+                introduced by a foreground color -specifying SGR code.
260
+
261
+ @param endCode     The SGR code to test as a candidate for ending the formatting run
262
+                    introduced by startCode
263
+ @param startCode   The SGR code that has introduced a formatting run
264
+
265
+ @result        YES if the occurrence of endCode would end the formatting run
266
+                introduced by startCode, NO otherwise.
267
+ */
268
+- (BOOL) AMR_SGRCode:(AMR_SGRCode)endCode endsFormattingIntroducedByCode:(AMR_SGRCode)startCode;
269
+
270
+
271
+/*!
272
+ @method        colorForSGRCode:
273
+
274
+ @abstract      Returns the color to use for displaying a specific ANSI color.
275
+
276
+ @discussion    This method first considers the values set in the ansiColors
277
+                property and only then the standard basic colors (NSColor's
278
+                redColor, blueColor etc.)
279
+
280
+ @param code    An SGR code that specifies an ANSI color.
281
+
282
+ @result        The color to use for displaying the ANSI color specified by code.
283
+ */
284
+- (NSColor*) colorForSGRCode:(AMR_SGRCode)code;
285
+
286
+
287
+/*!
288
+ @method        AMR_SGRCodeForColor:isForegroundColor:
289
+
290
+ @abstract      Returns a color SGR code that corresponds to a given color.
291
+
292
+ @discussion    This method matches colors to their equivalent SGR codes
293
+                by going through the colors specified in the ansiColors
294
+                dictionary, and if ansiColors is null or if a match is
295
+                not found there, by comparing the given color to the
296
+                standard basic colors (NSColor's redColor, blueColor
297
+                etc.) The comparison is done simply by checking for
298
+                equality.
299
+
300
+ @param aColor          The color to get a corresponding SGR code for
301
+ @param aForeground     Whether you want a foreground or background color code
302
+
303
+ @result        SGR code that corresponds with aColor.
304
+ */
305
+- (AMR_SGRCode) AMR_SGRCodeForColor:(NSColor*)aColor isForegroundColor:(BOOL)aForeground;
306
+
307
+
308
+/*!
309
+ @method        closestSGRCodeForColor:isForegroundColor:
310
+
311
+ @abstract      Returns a color SGR code that represents the closest ANSI
312
+                color to a given color.
313
+
314
+ @discussion    This method attempts to find the closest ANSI color to
315
+                aColor and return its SGR code.
316
+
317
+ @param aColor          The color to get a closest color SGR code match for
318
+ @param aForeground     Whether you want a foreground or background color code
319
+
320
+ @result        SGR code for the ANSI color that is closest to aColor.
321
+ */
322
+- (AMR_SGRCode) closestSGRCodeForColor:(NSColor *)color isForegroundColor:(BOOL)foreground;
323
+
324
+
325
+
326
+@end
0 327
new file mode 100644
... ...
@@ -0,0 +1,996 @@
1
+//
2
+//  ANSIEscapeHelper.m
3
+//
4
+//  Created by Ali Rantakari on 18.3.09.
5
+
6
+/*
7
+The MIT License
8
+
9
+Copyright (c) 2008-2009,2013 Ali Rantakari
10
+
11
+Permission is hereby granted, free of charge, to any person obtaining a copy
12
+of this software and associated documentation files (the "Software"), to deal
13
+in the Software without restriction, including without limitation the rights
14
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+copies of the Software, and to permit persons to whom the Software is
16
+furnished to do so, subject to the following conditions:
17
+
18
+The above copyright notice and this permission notice shall be included in
19
+all copies or substantial portions of the Software.
20
+
21
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
+THE SOFTWARE.
28
+*/
29
+
30
+/*
31
+ todo:
32
+
33
+ - don't add useless "reset" escape codes to the string in
34
+   -ansiEscapedStringWithAttributedString:
35
+
36
+ */
37
+
38
+
39
+
40
+#import "AMR_ANSIEscapeHelper.h"
41
+
42
+
43
+// the CSI (Control Sequence Initiator) -- i.e. "escape sequence prefix".
44
+// (add your own CSI:Miami joke here)
45
+#define kANSIEscapeCSI          @"\033["
46
+
47
+// the end byte of an SGR (Select Graphic Rendition)
48
+// ANSI Escape Sequence
49
+#define kANSIEscapeSGREnd       @"m"
50
+
51
+
52
+// color definition helper macros
53
+#define kBrightColorBrightness  1.0
54
+#define kBrightColorSaturation  0.4
55
+#define kBrightColorAlpha       1.0
56
+#define kBrightColorWithHue(h)  [NSColor colorWithCalibratedHue:(h) saturation:kBrightColorSaturation brightness:kBrightColorBrightness alpha:kBrightColorAlpha]
57
+
58
+// default colors
59
+#define kDefaultANSIColorFgBlack    NSColor.blackColor
60
+#define kDefaultANSIColorFgRed      NSColor.redColor
61
+#define kDefaultANSIColorFgGreen    NSColor.greenColor
62
+#define kDefaultANSIColorFgYellow   NSColor.yellowColor
63
+#define kDefaultANSIColorFgBlue     NSColor.blueColor
64
+#define kDefaultANSIColorFgMagenta  NSColor.magentaColor
65
+#define kDefaultANSIColorFgCyan     NSColor.cyanColor
66
+#define kDefaultANSIColorFgWhite    NSColor.whiteColor
67
+
68
+#define kDefaultANSIColorFgBrightBlack      [NSColor colorWithCalibratedWhite:0.337 alpha:1.0]
69
+#define kDefaultANSIColorFgBrightRed        kBrightColorWithHue(1.0)
70
+#define kDefaultANSIColorFgBrightGreen      kBrightColorWithHue(1.0/3.0)
71
+#define kDefaultANSIColorFgBrightYellow     kBrightColorWithHue(1.0/6.0)
72
+#define kDefaultANSIColorFgBrightBlue       kBrightColorWithHue(2.0/3.0)
73
+#define kDefaultANSIColorFgBrightMagenta    kBrightColorWithHue(5.0/6.0)
74
+#define kDefaultANSIColorFgBrightCyan       kBrightColorWithHue(0.5)
75
+#define kDefaultANSIColorFgBrightWhite      NSColor.whiteColor
76
+
77
+#define kDefaultANSIColorBgBlack    NSColor.blackColor
78
+#define kDefaultANSIColorBgRed      NSColor.redColor
79
+#define kDefaultANSIColorBgGreen    NSColor.greenColor
80
+#define kDefaultANSIColorBgYellow   NSColor.yellowColor
81
+#define kDefaultANSIColorBgBlue     NSColor.blueColor
82
+#define kDefaultANSIColorBgMagenta  NSColor.magentaColor
83
+#define kDefaultANSIColorBgCyan     NSColor.cyanColor
84
+#define kDefaultANSIColorBgWhite    NSColor.whiteColor
85
+
86
+#define kDefaultANSIColorBgBrightBlack      kDefaultANSIColorFgBrightBlack
87
+#define kDefaultANSIColorBgBrightRed        kDefaultANSIColorFgBrightRed
88
+#define kDefaultANSIColorBgBrightGreen      kDefaultANSIColorFgBrightGreen
89
+#define kDefaultANSIColorBgBrightYellow     kDefaultANSIColorFgBrightYellow
90
+#define kDefaultANSIColorBgBrightBlue       kDefaultANSIColorFgBrightBlue
91
+#define kDefaultANSIColorBgBrightMagenta    kDefaultANSIColorFgBrightMagenta
92
+#define kDefaultANSIColorBgBrightCyan       kDefaultANSIColorFgBrightCyan
93
+#define kDefaultANSIColorBgBrightWhite      kDefaultANSIColorFgBrightWhite
94
+
95
+#define kDefaultFontSize [NSFont systemFontOfSize:NSFont.systemFontSize]
96
+#define kDefaultForegroundColor NSColor.blackColor
97
+
98
+// minimum weight for an NSFont for it to be considered bold
99
+#define kBoldFontMinWeight          9
100
+
101
+
102
+@implementation AMR_ANSIEscapeHelper
103
+
104
+- (id) init
105
+{
106
+    if (!(self = [super init]))
107
+        return nil;
108
+
109
+    self.ansiColors = [NSMutableDictionary dictionary];
110
+
111
+    return self;
112
+}
113
+
114
+
115
+
116
+- (NSAttributedString*) attributedStringWithANSIEscapedString:(NSString*)aString
117
+{
118
+    if (aString == nil)
119
+        return nil;
120
+
121
+    NSString *cleanString;
122
+    NSArray *attributesAndRanges = [self attributesForString:aString cleanString:&cleanString];
123
+    NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]
124
+                                                   initWithString:cleanString
125
+                                                   attributes:@{
126
+                                                   NSFontAttributeName: self.font ?: kDefaultFontSize,
127
+                                                   NSForegroundColorAttributeName: self.defaultStringColor ?: kDefaultForegroundColor
128
+                                                   }];
129
+
130
+    for (NSDictionary *thisAttributeDict in attributesAndRanges)
131
+    {
132
+        [attributedString
133
+         addAttribute:thisAttributeDict[kAMRAttrDictKey_attrName]
134
+         value:thisAttributeDict[kAMRAttrDictKey_attrValue]
135
+         range:[thisAttributeDict[kAMRAttrDictKey_range] rangeValue]
136
+         ];
137
+    }
138
+
139
+    return attributedString;
140
+}
141
+
142
+
143
+
144
+- (NSString*) ansiEscapedStringWithAttributedString:(NSAttributedString*)aAttributedString
145
+{
146
+    NSMutableArray *codesAndLocations = [NSMutableArray array];
147
+
148
+    NSArray *attrNames = @[
149
+                          NSFontAttributeName, NSForegroundColorAttributeName,
150
+                          NSBackgroundColorAttributeName, NSUnderlineStyleAttributeName,
151
+                          ];
152
+
153
+    for (NSString *thisAttrName in attrNames)
154
+    {
155
+        NSRange limitRange = NSMakeRange(0, aAttributedString.length);
156
+        id attributeValue;
157
+        NSRange effectiveRange;
158
+
159
+        while (limitRange.length > 0)
160
+        {
161
+            attributeValue = [aAttributedString
162
+                              attribute:thisAttrName
163
+                              atIndex:limitRange.location
164
+                              longestEffectiveRange:&effectiveRange
165
+                              inRange:limitRange
166
+                              ];
167
+
168
+            AMR_SGRCode thisSGRCode = AMR_SGRCodeNoneOrInvalid;
169
+
170
+            if ([thisAttrName isEqualToString:NSForegroundColorAttributeName])
171
+            {
172
+                if (attributeValue != nil)
173
+                    thisSGRCode = [self closestSGRCodeForColor:attributeValue isForegroundColor:YES];
174
+                else
175
+                    thisSGRCode = AMR_SGRCodeFgReset;
176
+            }
177
+            else if ([thisAttrName isEqualToString:NSBackgroundColorAttributeName])
178
+            {
179
+                if (attributeValue != nil)
180
+                    thisSGRCode = [self closestSGRCodeForColor:attributeValue isForegroundColor:NO];
181
+                else
182
+                    thisSGRCode = AMR_SGRCodeBgReset;
183
+            }
184
+            else if ([thisAttrName isEqualToString:NSFontAttributeName])
185
+            {
186
+                // we currently only use NSFontAttributeName for bolding so
187
+                // here we assume that the formatting "type" in ANSI SGR
188
+                // terms is indeed intensity
189
+                if (attributeValue != nil)
190
+                    thisSGRCode = ([NSFontManager.sharedFontManager weightOfFont:attributeValue] >= kBoldFontMinWeight)
191
+                                    ? AMR_SGRCodeIntensityBold : AMR_SGRCodeIntensityNormal;
192
+                else
193
+                    thisSGRCode = AMR_SGRCodeIntensityNormal;
194
+            }
195
+            else if ([thisAttrName isEqualToString:NSUnderlineStyleAttributeName])
196
+            {
197
+                if (attributeValue != nil)
198
+                {
199
+                    if ([attributeValue intValue] == NSUnderlineStyleSingle)
200
+                        thisSGRCode = AMR_SGRCodeUnderlineSingle;
201
+                    else if ([attributeValue intValue] == NSUnderlineStyleDouble)
202
+                        thisSGRCode = AMR_SGRCodeUnderlineDouble;
203
+                    else
204
+                        thisSGRCode = AMR_SGRCodeUnderlineNone;
205
+                }
206
+                else
207
+                    thisSGRCode = AMR_SGRCodeUnderlineNone;
208
+            }
209
+
210
+            if (thisSGRCode != AMR_SGRCodeNoneOrInvalid)
211
+            {
212
+                [codesAndLocations addObject: @{
213
+                           kAMRCodeDictKey_code: @(thisSGRCode),
214
+                       kAMRCodeDictKey_location: @(effectiveRange.location),
215
+                 }];
216
+            }
217
+
218
+            limitRange = NSMakeRange(NSMaxRange(effectiveRange),
219
+                                     NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
220
+        }
221
+    }
222
+
223
+    return [self ansiEscapedStringWithCodesAndLocations:codesAndLocations cleanString:aAttributedString.string];
224
+}
225
+
226
+
227
+- (NSArray*) escapeCodesForString:(NSString*)aString cleanString:(NSString**)aCleanString
228
+{
229
+    if (aString == nil)
230
+        return nil;
231
+    if (aString.length <= kANSIEscapeCSI.length)
232
+    {
233
+        if (aCleanString)
234
+            *aCleanString = aString.copy;
235
+        return @[];
236
+    }
237
+
238
+    NSString *cleanString = @"";
239
+
240
+    // find all escape sequence codes from aString and put them in this array
241
+    // along with their start locations within the "clean" version of aString
242
+    NSMutableArray *formatCodes = [NSMutableArray array];
243
+
244
+    NSUInteger aStringLength = aString.length;
245
+    NSUInteger coveredLength = 0;
246
+    NSRange searchRange = NSMakeRange(0,aStringLength);
247
+    NSRange thisEscapeSequenceRange;
248
+    do
249
+    {
250
+        thisEscapeSequenceRange = [aString rangeOfString:kANSIEscapeCSI options:NSLiteralSearch range:searchRange];
251
+        if (thisEscapeSequenceRange.location != NSNotFound)
252
+        {
253
+            // adjust range's length so that it encompasses the whole ANSI escape sequence
254
+            // and not just the Control Sequence Initiator (the "prefix") by finding the
255
+            // final byte of the control sequence (one that has an ASCII decimal value
256
+            // between 64 and 126.) at the same time, read all formatting codes from inside
257
+            // this escape sequence (there may be several, separated by semicolons.)
258
+            NSMutableArray *codes = [NSMutableArray array];
259
+            unsigned int code = 0;
260
+            unsigned int lengthAddition = 1;
261
+            NSUInteger thisIndex;
262
+            for (;;)
263
+            {
264
+                thisIndex = (NSMaxRange(thisEscapeSequenceRange)+lengthAddition-1);
265
+                if (thisIndex >= aStringLength)
266
+                    break;
267
+
268
+                unichar c = [aString characterAtIndex:thisIndex];
269
+
270
+                if (('0' <= c) && (c <= '9'))
271
+                {
272
+                    int digit = c - '0';
273
+                    code = (code == 0) ? digit : code*10+digit;
274
+                }
275
+
276
+                // ASCII decimal 109 is the SGR (Select Graphic Rendition) final byte
277
+                // ("m"). this means that the code value we've just read specifies formatting
278
+                // for the output; exactly what we're interested in.
279
+                if (c == 'm')
280
+                {
281
+                    [codes addObject:@(code)];
282
+                    break;
283
+                }
284
+                else if ((64 <= c) && (c <= 126)) // any other valid final byte
285
+                {
286
+                    [codes removeAllObjects];
287
+                    break;
288
+                }
289
+                else if (c == ';') // separates codes within the same sequence
290
+                {
291
+                    [codes addObject:@(code)];
292
+                    code = 0;
293
+                }
294
+
295
+                lengthAddition++;
296
+            }
297
+            thisEscapeSequenceRange.length += lengthAddition;
298
+
299
+            NSUInteger locationInCleanString = coveredLength+thisEscapeSequenceRange.location-searchRange.location;
300
+
301
+            for (NSNumber *codeToAdd in codes)
302
+            {
303
+                [formatCodes addObject: @{
304
+                     kAMRCodeDictKey_code: codeToAdd,
305
+                 kAMRCodeDictKey_location: @(locationInCleanString)
306
+                 }];
307
+            }
308
+
309
+            NSUInteger thisCoveredLength = thisEscapeSequenceRange.location-searchRange.location;
310
+            if (thisCoveredLength > 0)
311
+                cleanString = [cleanString stringByAppendingString:[aString substringWithRange:NSMakeRange(searchRange.location, thisCoveredLength)]];
312
+
313
+            coveredLength += thisCoveredLength;
314
+            searchRange.location = NSMaxRange(thisEscapeSequenceRange);
315
+            searchRange.length = aStringLength-searchRange.location;
316
+        }
317
+    }
318
+    while(thisEscapeSequenceRange.location != NSNotFound);
319
+
320
+    if (searchRange.length > 0)
321
+        cleanString = [cleanString stringByAppendingString:[aString substringWithRange:searchRange]];
322
+
323
+    if (aCleanString)
324
+        *aCleanString = cleanString;
325
+    return formatCodes;
326
+}
327
+
328
+
329
+
330
+
331
+- (NSString*) ansiEscapedStringWithCodesAndLocations:(NSArray*)aCodesArray cleanString:(NSString*)aCleanString
332
+{
333
+    NSMutableString* retStr = [NSMutableString stringWithCapacity:aCleanString.length];
334
+
335
+    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:kAMRCodeDictKey_location ascending:YES];
336
+    NSArray *codesArray = [aCodesArray sortedArrayUsingDescriptors:@[sortDescriptor]];
337
+
338
+    NSUInteger aCleanStringIndex = 0;
339
+    NSUInteger aCleanStringLength = aCleanString.length;
340
+    for (NSDictionary *thisCodeDict in codesArray)
341
+    {
342
+        if (!(  thisCodeDict[kAMRCodeDictKey_code] &&
343
+                thisCodeDict[kAMRCodeDictKey_location]
344
+            ))
345
+            continue;
346
+
347
+        AMR_SGRCode thisCode = [thisCodeDict[kAMRCodeDictKey_code] unsignedIntValue];
348
+        NSUInteger formattingRunStartLocation = [thisCodeDict[kAMRCodeDictKey_location] unsignedIntegerValue];
349
+
350
+        if (formattingRunStartLocation > aCleanStringLength)
351
+            continue;
352
+
353
+        if (aCleanStringIndex < formattingRunStartLocation)
354
+            [retStr appendString:[aCleanString substringWithRange:NSMakeRange(aCleanStringIndex, formattingRunStartLocation-aCleanStringIndex)]];
355
+        [retStr appendFormat:@"%@%d%@", kANSIEscapeCSI, thisCode, kANSIEscapeSGREnd];
356
+
357
+        aCleanStringIndex = formattingRunStartLocation;
358
+    }
359
+
360
+    if (aCleanStringIndex < aCleanStringLength)
361
+        [retStr appendString:[aCleanString substringFromIndex:aCleanStringIndex]];
362
+
363
+    [retStr appendFormat:@"%@%d%@", kANSIEscapeCSI, AMR_SGRCodeAllReset, kANSIEscapeSGREnd];
364
+
365
+    return retStr;
366
+}
367
+
368
+
369
+
370
+
371
+
372
+- (NSArray*) attributesForString:(NSString*)aString cleanString:(NSString**)aCleanString
373
+{
374
+    if (aString == nil)
375
+        return nil;
376
+    if (aString.length <= kANSIEscapeCSI.length)
377
+    {
378
+        if (aCleanString)
379
+            *aCleanString = aString.copy;
380
+        return @[];
381
+    }
382
+
383
+    NSMutableArray *attrsAndRanges = [NSMutableArray array];
384
+
385
+    NSString *cleanString;
386
+    NSArray *formatCodes = [self escapeCodesForString:aString cleanString:&cleanString];
387
+
388
+    // go through all the found escape sequence codes and for each one, create
389
+    // the string formatting attribute name and value, find the next escape
390
+    // sequence that specifies the end of the formatting run started by
391
+    // the currently handled code, and generate a range from the difference
392
+    // in those codes' locations within the clean aString.
393
+    for (NSUInteger iCode = 0; iCode < formatCodes.count; iCode++)
394
+    {
395
+        NSDictionary *thisCodeDict = formatCodes[iCode];
396
+        AMR_SGRCode thisCode = [thisCodeDict[kAMRCodeDictKey_code] unsignedIntValue];
397
+        NSUInteger formattingRunStartLocation = [thisCodeDict[kAMRCodeDictKey_location] unsignedIntegerValue];
398
+
399
+        // the attributed string attribute name for the formatting run introduced
400
+        // by this code
401
+        NSString *thisAttributeName = nil;
402
+
403
+        // the attributed string attribute value for this formatting run introduced
404
+        // by this code
405
+        NSObject *thisAttributeValue = nil;
406
+
407
+        // set attribute name
408
+        switch(thisCode)
409
+        {
410
+            case AMR_SGRCodeFgBlack:
411
+            case AMR_SGRCodeFgRed:
412
+            case AMR_SGRCodeFgGreen:
413
+            case AMR_SGRCodeFgYellow:
414
+            case AMR_SGRCodeFgBlue:
415
+            case AMR_SGRCodeFgMagenta:
416
+            case AMR_SGRCodeFgCyan:
417
+            case AMR_SGRCodeFgWhite:
418
+            case AMR_SGRCodeFgBrightBlack:
419
+            case AMR_SGRCodeFgBrightRed:
420
+            case AMR_SGRCodeFgBrightGreen:
421
+            case AMR_SGRCodeFgBrightYellow:
422
+            case AMR_SGRCodeFgBrightBlue:
423
+            case AMR_SGRCodeFgBrightMagenta:
424
+            case AMR_SGRCodeFgBrightCyan:
425
+            case AMR_SGRCodeFgBrightWhite:
426
+                thisAttributeName = NSForegroundColorAttributeName;
427
+                break;
428
+            case AMR_SGRCodeBgBlack:
429
+            case AMR_SGRCodeBgRed:
430
+            case AMR_SGRCodeBgGreen:
431
+            case AMR_SGRCodeBgYellow:
432
+            case AMR_SGRCodeBgBlue:
433
+            case AMR_SGRCodeBgMagenta:
434
+            case AMR_SGRCodeBgCyan:
435
+            case AMR_SGRCodeBgWhite:
436
+            case AMR_SGRCodeBgBrightBlack:
437
+            case AMR_SGRCodeBgBrightRed:
438
+            case AMR_SGRCodeBgBrightGreen:
439
+            case AMR_SGRCodeBgBrightYellow:
440
+            case AMR_SGRCodeBgBrightBlue:
441
+            case AMR_SGRCodeBgBrightMagenta:
442
+            case AMR_SGRCodeBgBrightCyan:
443
+            case AMR_SGRCodeBgBrightWhite:
444
+                thisAttributeName = NSBackgroundColorAttributeName;
445
+                break;
446
+            case AMR_SGRCodeIntensityBold:
447
+            case AMR_SGRCodeIntensityNormal:
448
+            case AMR_SGRCodeIntensityFaint:
449
+                thisAttributeName = NSFontAttributeName;
450
+                break;
451
+            case AMR_SGRCodeUnderlineSingle:
452
+            case AMR_SGRCodeUnderlineDouble:
453
+            case AMR_SGRCodeUnderlineNone:
454
+                thisAttributeName = NSUnderlineStyleAttributeName;
455
+                break;
456
+            case AMR_SGRCodeAllReset:
457
+            case AMR_SGRCodeFgReset:
458
+            case AMR_SGRCodeBgReset:
459
+            case AMR_SGRCodeNoneOrInvalid:
460
+            case AMR_SGRCodeItalicOn:
461
+                continue;
462
+        }
463
+
464
+        // set attribute value
465
+        switch(thisCode)
466
+        {
467
+            case AMR_SGRCodeBgBlack:
468
+            case AMR_SGRCodeFgBlack:
469
+            case AMR_SGRCodeBgRed:
470
+            case AMR_SGRCodeFgRed:
471
+            case AMR_SGRCodeBgGreen:
472
+            case AMR_SGRCodeFgGreen:
473
+            case AMR_SGRCodeBgYellow:
474
+            case AMR_SGRCodeFgYellow:
475
+            case AMR_SGRCodeBgBlue:
476
+            case AMR_SGRCodeFgBlue:
477
+            case AMR_SGRCodeBgMagenta:
478
+            case AMR_SGRCodeFgMagenta:
479
+            case AMR_SGRCodeBgCyan:
480
+            case AMR_SGRCodeFgCyan:
481
+            case AMR_SGRCodeBgWhite:
482
+            case AMR_SGRCodeFgWhite:
483
+            case AMR_SGRCodeBgBrightBlack:
484
+            case AMR_SGRCodeFgBrightBlack:
485
+            case AMR_SGRCodeBgBrightRed:
486
+            case AMR_SGRCodeFgBrightRed:
487
+            case AMR_SGRCodeBgBrightGreen:
488
+            case AMR_SGRCodeFgBrightGreen:
489
+            case AMR_SGRCodeBgBrightYellow:
490
+            case AMR_SGRCodeFgBrightYellow:
491
+            case AMR_SGRCodeBgBrightBlue:
492
+            case AMR_SGRCodeFgBrightBlue:
493
+            case AMR_SGRCodeBgBrightMagenta:
494
+            case AMR_SGRCodeFgBrightMagenta:
495
+            case AMR_SGRCodeBgBrightCyan:
496
+            case AMR_SGRCodeFgBrightCyan:
497
+            case AMR_SGRCodeBgBrightWhite:
498
+            case AMR_SGRCodeFgBrightWhite:
499
+                thisAttributeValue = [self colorForSGRCode:thisCode];
500
+                break;
501
+            case AMR_SGRCodeIntensityBold:
502
+                {
503
+                NSFont *boldFont = [NSFontManager.sharedFontManager convertFont:self.font toHaveTrait:NSBoldFontMask];
504
+                thisAttributeValue = boldFont;
505
+                }
506
+                break;
507
+            case AMR_SGRCodeIntensityNormal:
508
+            case AMR_SGRCodeIntensityFaint:
509
+                {
510
+                NSFont *unboldFont = [NSFontManager.sharedFontManager convertFont:self.font toHaveTrait:NSUnboldFontMask];
511
+                thisAttributeValue = unboldFont;
512
+                }
513
+                break;
514
+            case AMR_SGRCodeUnderlineSingle:
515
+                thisAttributeValue = @(NSUnderlineStyleSingle);
516
+                break;
517
+            case AMR_SGRCodeUnderlineDouble:
518
+                thisAttributeValue = @(NSUnderlineStyleDouble);
519
+                break;
520
+            case AMR_SGRCodeUnderlineNone:
521
+                thisAttributeValue = @(NSUnderlineStyleNone);
522
+                break;
523
+            case AMR_SGRCodeAllReset:
524
+            case AMR_SGRCodeFgReset:
525
+            case AMR_SGRCodeBgReset:
526
+            case AMR_SGRCodeNoneOrInvalid:
527
+            case AMR_SGRCodeItalicOn:
528
+                break;
529
+        }
530
+
531
+
532
+        // find the next sequence that specifies the end of this formatting run
533
+        NSInteger formattingRunEndLocation = -1;
534
+        if (iCode < (formatCodes.count - 1))
535
+        {
536
+            NSDictionary *thisEndCodeCandidateDict;
537
+            unichar thisEndCodeCandidate;
538
+            for (NSUInteger iEndCode = iCode+1; iEndCode < formatCodes.count; iEndCode++)
539
+            {
540
+                thisEndCodeCandidateDict = formatCodes[iEndCode];
541
+                thisEndCodeCandidate = [thisEndCodeCandidateDict[kAMRCodeDictKey_code] unsignedIntValue];
542
+
543
+                if ([self AMR_SGRCode:thisEndCodeCandidate endsFormattingIntroducedByCode:thisCode])
544
+                {
545
+                    formattingRunEndLocation = [thisEndCodeCandidateDict[kAMRCodeDictKey_location] unsignedIntegerValue];
546
+                    break;
547
+                }
548
+            }
549
+        }
550
+        if (formattingRunEndLocation == -1)
551
+            formattingRunEndLocation = cleanString.length;
552
+        
553
+        if (thisAttributeName && thisAttributeValue)
554
+        {
555
+            [attrsAndRanges addObject:@{
556
+                kAMRAttrDictKey_range: [NSValue valueWithRange:NSMakeRange(formattingRunStartLocation, (formattingRunEndLocation-formattingRunStartLocation))],
557
+             kAMRAttrDictKey_attrName: thisAttributeName,
558
+            kAMRAttrDictKey_attrValue: thisAttributeValue,
559
+             }];
560
+        }
561
+    }
562
+
563
+    if (aCleanString)
564
+        *aCleanString = cleanString;
565
+    return attrsAndRanges;
566
+}
567
+
568
+
569
+
570
+
571
+
572
+- (BOOL) AMR_SGRCode:(AMR_SGRCode)endCode endsFormattingIntroducedByCode:(AMR_SGRCode)startCode
573
+{
574
+    switch(startCode)
575
+    {
576
+        case AMR_SGRCodeFgBlack:
577
+        case AMR_SGRCodeFgRed:
578
+        case AMR_SGRCodeFgGreen:
579
+        case AMR_SGRCodeFgYellow:
580
+        case AMR_SGRCodeFgBlue:
581
+        case AMR_SGRCodeFgMagenta:
582
+        case AMR_SGRCodeFgCyan:
583
+        case AMR_SGRCodeFgWhite:
584
+        case AMR_SGRCodeFgBrightBlack:
585
+        case AMR_SGRCodeFgBrightRed:
586
+        case AMR_SGRCodeFgBrightGreen:
587
+        case AMR_SGRCodeFgBrightYellow:
588
+        case AMR_SGRCodeFgBrightBlue:
589
+        case AMR_SGRCodeFgBrightMagenta:
590
+        case AMR_SGRCodeFgBrightCyan:
591
+        case AMR_SGRCodeFgBrightWhite:
592
+            return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeFgReset ||
593
+                    endCode == AMR_SGRCodeFgBlack || endCode == AMR_SGRCodeFgRed ||
594
+                    endCode == AMR_SGRCodeFgGreen || endCode == AMR_SGRCodeFgYellow ||
595
+                    endCode == AMR_SGRCodeFgBlue || endCode == AMR_SGRCodeFgMagenta ||
596
+                    endCode == AMR_SGRCodeFgCyan || endCode == AMR_SGRCodeFgWhite ||
597
+                    endCode == AMR_SGRCodeFgBrightBlack || endCode == AMR_SGRCodeFgBrightRed ||
598
+                    endCode == AMR_SGRCodeFgBrightGreen || endCode == AMR_SGRCodeFgBrightYellow ||
599
+                    endCode == AMR_SGRCodeFgBrightBlue || endCode == AMR_SGRCodeFgBrightMagenta ||
600
+                    endCode == AMR_SGRCodeFgBrightCyan || endCode == AMR_SGRCodeFgBrightWhite);
601
+        case AMR_SGRCodeBgBlack:
602
+        case AMR_SGRCodeBgRed:
603
+        case AMR_SGRCodeBgGreen:
604
+        case AMR_SGRCodeBgYellow:
605
+        case AMR_SGRCodeBgBlue:
606
+        case AMR_SGRCodeBgMagenta:
607
+        case AMR_SGRCodeBgCyan:
608
+        case AMR_SGRCodeBgWhite:
609
+        case AMR_SGRCodeBgBrightBlack:
610
+        case AMR_SGRCodeBgBrightRed:
611
+        case AMR_SGRCodeBgBrightGreen:
612
+        case AMR_SGRCodeBgBrightYellow:
613
+        case AMR_SGRCodeBgBrightBlue:
614
+        case AMR_SGRCodeBgBrightMagenta:
615
+        case AMR_SGRCodeBgBrightCyan:
616
+        case AMR_SGRCodeBgBrightWhite:
617
+            return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeBgReset ||
618
+                    endCode == AMR_SGRCodeBgBlack || endCode == AMR_SGRCodeBgRed ||
619
+                    endCode == AMR_SGRCodeBgGreen || endCode == AMR_SGRCodeBgYellow ||
620
+                    endCode == AMR_SGRCodeBgBlue || endCode == AMR_SGRCodeBgMagenta ||
621
+                    endCode == AMR_SGRCodeBgCyan || endCode == AMR_SGRCodeBgWhite ||
622
+                    endCode == AMR_SGRCodeBgBrightBlack || endCode == AMR_SGRCodeBgBrightRed ||
623
+                    endCode == AMR_SGRCodeBgBrightGreen || endCode == AMR_SGRCodeBgBrightYellow ||
624
+                    endCode == AMR_SGRCodeBgBrightBlue || endCode == AMR_SGRCodeBgBrightMagenta ||
625
+                    endCode == AMR_SGRCodeBgBrightCyan || endCode == AMR_SGRCodeBgBrightWhite);
626
+        case AMR_SGRCodeIntensityBold:
627
+        case AMR_SGRCodeIntensityNormal:
628
+            return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeIntensityNormal ||
629
+                    endCode == AMR_SGRCodeIntensityBold || endCode == AMR_SGRCodeIntensityFaint);
630
+        case AMR_SGRCodeUnderlineSingle:
631
+        case AMR_SGRCodeUnderlineDouble:
632
+            return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeUnderlineNone ||
633
+                    endCode == AMR_SGRCodeUnderlineSingle || endCode == AMR_SGRCodeUnderlineDouble);
634
+        case AMR_SGRCodeNoneOrInvalid:
635
+        case AMR_SGRCodeItalicOn:
636
+        case AMR_SGRCodeUnderlineNone:
637
+        case AMR_SGRCodeIntensityFaint:
638
+        case AMR_SGRCodeAllReset:
639
+        case AMR_SGRCodeBgReset:
640
+        case AMR_SGRCodeFgReset:
641
+            return NO;
642
+    }
643
+
644
+    return NO;
645
+}
646
+
647
+
648
+
649
+
650
+- (NSColor*) colorForSGRCode:(AMR_SGRCode)code
651
+{
652
+    if (self.ansiColors)
653
+    {
654
+        NSColor *preferredColor = self.ansiColors[@(code)];
655
+        if (preferredColor)
656
+            return preferredColor;
657
+    }
658
+
659
+    switch(code)
660
+    {
661
+        case AMR_SGRCodeFgBlack:
662
+            return kDefaultANSIColorFgBlack;
663
+        case AMR_SGRCodeFgRed:
664
+            return kDefaultANSIColorFgRed;
665
+        case AMR_SGRCodeFgGreen:
666
+            return kDefaultANSIColorFgGreen;
667
+        case AMR_SGRCodeFgYellow:
668
+            return kDefaultANSIColorFgYellow;
669
+        case AMR_SGRCodeFgBlue:
670
+            return kDefaultANSIColorFgBlue;
671
+        case AMR_SGRCodeFgMagenta:
672
+            return kDefaultANSIColorFgMagenta;
673
+        case AMR_SGRCodeFgCyan:
674
+            return kDefaultANSIColorFgCyan;
675
+        case AMR_SGRCodeFgWhite:
676
+            return kDefaultANSIColorFgWhite;
677
+        case AMR_SGRCodeFgBrightBlack:
678
+            return kDefaultANSIColorFgBrightBlack;
679
+        case AMR_SGRCodeFgBrightRed:
680
+            return kDefaultANSIColorFgBrightRed;
681
+        case AMR_SGRCodeFgBrightGreen:
682
+            return kDefaultANSIColorFgBrightGreen;
683
+        case AMR_SGRCodeFgBrightYellow:
684
+            return kDefaultANSIColorFgBrightYellow;
685
+        case AMR_SGRCodeFgBrightBlue:
686
+            return kDefaultANSIColorFgBrightBlue;
687
+        case AMR_SGRCodeFgBrightMagenta:
688
+            return kDefaultANSIColorFgBrightMagenta;
689
+        case AMR_SGRCodeFgBrightCyan:
690
+            return kDefaultANSIColorFgBrightCyan;
691
+        case AMR_SGRCodeFgBrightWhite:
692
+            return kDefaultANSIColorFgBrightWhite;
693
+        case AMR_SGRCodeBgBlack:
694
+            return kDefaultANSIColorBgBlack;
695
+        case AMR_SGRCodeBgRed:
696
+            return kDefaultANSIColorBgRed;
697
+        case AMR_SGRCodeBgGreen:
698
+            return kDefaultANSIColorBgGreen;
699
+        case AMR_SGRCodeBgYellow:
700
+            return kDefaultANSIColorBgYellow;
701
+        case AMR_SGRCodeBgBlue:
702
+            return kDefaultANSIColorBgBlue;
703
+        case AMR_SGRCodeBgMagenta:
704
+            return kDefaultANSIColorBgMagenta;
705
+        case AMR_SGRCodeBgCyan:
706
+            return kDefaultANSIColorBgCyan;
707
+        case AMR_SGRCodeBgWhite:
708
+            return kDefaultANSIColorBgWhite;
709
+        case AMR_SGRCodeBgBrightBlack:
710
+            return kDefaultANSIColorBgBrightBlack;
711
+        case AMR_SGRCodeBgBrightRed:
712
+            return kDefaultANSIColorBgBrightRed;
713
+        case AMR_SGRCodeBgBrightGreen:
714
+            return kDefaultANSIColorBgBrightGreen;
715
+        case AMR_SGRCodeBgBrightYellow:
716
+            return kDefaultANSIColorBgBrightYellow;
717
+        case AMR_SGRCodeBgBrightBlue:
718
+            return kDefaultANSIColorBgBrightBlue;
719
+        case AMR_SGRCodeBgBrightMagenta:
720
+            return kDefaultANSIColorBgBrightMagenta;
721
+        case AMR_SGRCodeBgBrightCyan:
722
+            return kDefaultANSIColorBgBrightCyan;
723
+        case AMR_SGRCodeBgBrightWhite:
724
+            return kDefaultANSIColorBgBrightWhite;
725
+        case AMR_SGRCodeNoneOrInvalid:
726
+        case AMR_SGRCodeItalicOn:
727
+        case AMR_SGRCodeUnderlineNone:
728
+        case AMR_SGRCodeIntensityFaint:
729
+        case AMR_SGRCodeAllReset:
730
+        case AMR_SGRCodeBgReset:
731
+        case AMR_SGRCodeFgReset:
732
+        case AMR_SGRCodeIntensityBold:
733
+        case AMR_SGRCodeIntensityNormal:
734
+        case AMR_SGRCodeUnderlineSingle:
735
+        case AMR_SGRCodeUnderlineDouble:
736
+            break;
737
+    }
738
+
739
+    return kDefaultANSIColorFgBlack;
740
+}
741
+
742
+
743
+- (AMR_SGRCode) AMR_SGRCodeForColor:(NSColor*)aColor isForegroundColor:(BOOL)aForeground
744
+{
745
+    if (self.ansiColors)
746
+    {
747
+        NSArray *codesForGivenColor = [self.ansiColors allKeysForObject:aColor];
748
+
749
+        if (codesForGivenColor != nil && 0 < codesForGivenColor.count)
750
+        {
751
+            for (NSNumber *thisCode in codesForGivenColor)
752
+            {
753
+                BOOL thisIsForegroundColor = (thisCode.intValue < 40);
754
+                if (aForeground == thisIsForegroundColor)
755
+                    return thisCode.intValue;
756
+            }
757
+        }
758
+    }
759
+
760
+    if (aForeground)
761
+    {
762
+        if ([aColor isEqual:kDefaultANSIColorFgBlack])
763
+            return AMR_SGRCodeFgBlack;
764
+        else if ([aColor isEqual:kDefaultANSIColorFgRed])
765
+            return AMR_SGRCodeFgRed;
766
+        else if ([aColor isEqual:kDefaultANSIColorFgGreen])
767
+            return AMR_SGRCodeFgGreen;
768
+        else if ([aColor isEqual:kDefaultANSIColorFgYellow])
769
+            return AMR_SGRCodeFgYellow;
770
+        else if ([aColor isEqual:kDefaultANSIColorFgBlue])
771
+            return AMR_SGRCodeFgBlue;
772
+        else if ([aColor isEqual:kDefaultANSIColorFgMagenta])
773
+            return AMR_SGRCodeFgMagenta;
774
+        else if ([aColor isEqual:kDefaultANSIColorFgCyan])
775
+            return AMR_SGRCodeFgCyan;
776
+        else if ([aColor isEqual:kDefaultANSIColorFgWhite])
777
+            return AMR_SGRCodeFgWhite;
778
+        else if ([aColor isEqual:kDefaultANSIColorFgBrightBlack])
779
+            return AMR_SGRCodeFgBrightBlack;
780
+        else if ([aColor isEqual:kDefaultANSIColorFgBrightRed])
781
+            return AMR_SGRCodeFgBrightRed;
782
+        else if ([aColor isEqual:kDefaultANSIColorFgBrightGreen])
783
+            return AMR_SGRCodeFgBrightGreen;
784
+        else if ([aColor isEqual:kDefaultANSIColorFgBrightYellow])
785
+            return AMR_SGRCodeFgBrightYellow;
786
+        else if ([aColor isEqual:kDefaultANSIColorFgBrightBlue])
787
+            return AMR_SGRCodeFgBrightBlue;
788
+        else if ([aColor isEqual:kDefaultANSIColorFgBrightMagenta])
789
+            return AMR_SGRCodeFgBrightMagenta;
790
+        else if ([aColor isEqual:kDefaultANSIColorFgBrightCyan])
791
+            return AMR_SGRCodeFgBrightCyan;
792
+        else if ([aColor isEqual:kDefaultANSIColorFgBrightWhite])
793
+            return AMR_SGRCodeFgBrightWhite;
794
+    }
795
+    else
796
+    {
797
+        if ([aColor isEqual:kDefaultANSIColorBgBlack])
798
+            return AMR_SGRCodeBgBlack;
799
+        else if ([aColor isEqual:kDefaultANSIColorBgRed])
800
+            return AMR_SGRCodeBgRed;
801
+        else if ([aColor isEqual:kDefaultANSIColorBgGreen])
802
+            return AMR_SGRCodeBgGreen;
803
+        else if ([aColor isEqual:kDefaultANSIColorBgYellow])
804
+            return AMR_SGRCodeBgYellow;
805
+        else if ([aColor isEqual:kDefaultANSIColorBgBlue])
806
+            return AMR_SGRCodeBgBlue;
807
+        else if ([aColor isEqual:kDefaultANSIColorBgMagenta])
808
+            return AMR_SGRCodeBgMagenta;
809
+        else if ([aColor isEqual:kDefaultANSIColorBgCyan])
810
+            return AMR_SGRCodeBgCyan;
811
+        else if ([aColor isEqual:kDefaultANSIColorBgWhite])
812
+            return AMR_SGRCodeBgWhite;
813
+        else if ([aColor isEqual:kDefaultANSIColorBgBrightBlack])
814
+            return AMR_SGRCodeBgBrightBlack;
815
+        else if ([aColor isEqual:kDefaultANSIColorBgBrightRed])
816
+            return AMR_SGRCodeBgBrightRed;
817
+        else if ([aColor isEqual:kDefaultANSIColorBgBrightGreen])
818
+            return AMR_SGRCodeBgBrightGreen;
819
+        else if ([aColor isEqual:kDefaultANSIColorBgBrightYellow])
820
+            return AMR_SGRCodeBgBrightYellow;
821
+        else if ([aColor isEqual:kDefaultANSIColorBgBrightBlue])
822
+            return AMR_SGRCodeBgBrightBlue;
823
+        else if ([aColor isEqual:kDefaultANSIColorBgBrightMagenta])
824
+            return AMR_SGRCodeBgBrightMagenta;
825
+        else if ([aColor isEqual:kDefaultANSIColorBgBrightCyan])
826
+            return AMR_SGRCodeBgBrightCyan;
827
+        else if ([aColor isEqual:kDefaultANSIColorBgBrightWhite])
828
+            return AMR_SGRCodeBgBrightWhite;
829
+    }
830
+
831
+    return AMR_SGRCodeNoneOrInvalid;
832
+}
833
+
834
+
835
+
836
+// helper struct typedef and a few functions for
837
+// -closestSGRCodeForColor:isForegroundColor:
838
+
839
+typedef struct {
840
+    CGFloat hue;
841
+    CGFloat saturation;
842
+    CGFloat brightness;
843
+} AMR_HSB;
844
+
845
+AMR_HSB makeHSB(CGFloat hue, CGFloat saturation, CGFloat brightness)
846
+{
847
+    AMR_HSB outHSB;
848
+    outHSB.hue = hue;
849
+    outHSB.saturation = saturation;
850
+    outHSB.brightness = brightness;
851
+    return outHSB;
852
+}
853
+
854
+AMR_HSB getHSBFromColor(NSColor *color)
855
+{
856
+    CGFloat hue = 0.0;
857
+    CGFloat saturation = 0.0;
858
+    CGFloat brightness = 0.0;
859
+    [[color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]
860
+        getHue:&hue
861
+        saturation:&saturation
862
+        brightness:&brightness
863
+        alpha:NULL
864
+        ];
865
+    return makeHSB(hue, saturation, brightness);
866
+}
867
+
868
+BOOL floatsEqual(CGFloat first, CGFloat second, CGFloat maxAbsError)
869
+{
870
+    return (fabs(first-second)) < maxAbsError;
871
+}
872
+
873
+#define MAX_HUE_FLOAT_EQUALITY_ABS_ERROR 0.000001
874
+
875
+- (AMR_SGRCode) closestSGRCodeForColor:(NSColor *)color isForegroundColor:(BOOL)foreground
876
+{
877
+    if (color == nil)
878
+        return AMR_SGRCodeNoneOrInvalid;
879
+
880
+    AMR_SGRCode closestColorSGRCode = [self AMR_SGRCodeForColor:color isForegroundColor:foreground];
881
+    if (closestColorSGRCode != AMR_SGRCodeNoneOrInvalid)
882
+        return closestColorSGRCode;
883
+
884
+    AMR_HSB givenColorHSB = getHSBFromColor(color);
885
+
886
+    CGFloat closestColorHueDiff = FLT_MAX;
887
+    CGFloat closestColorSaturationDiff = FLT_MAX;
888
+    CGFloat closestColorBrightnessDiff = FLT_MAX;
889
+
890
+    // (background SGR codes are +10 from foreground ones:)
891
+    NSUInteger AMR_SGRCodeShift = (foreground)?0:10;
892
+    NSArray *ansiFgColorCodes = @[
893
+    @(AMR_SGRCodeFgBlack+AMR_SGRCodeShift),
894
+    @(AMR_SGRCodeFgRed+AMR_SGRCodeShift),
895
+    @(AMR_SGRCodeFgGreen+AMR_SGRCodeShift),
896
+    @(AMR_SGRCodeFgYellow+AMR_SGRCodeShift),
897
+    @(AMR_SGRCodeFgBlue+AMR_SGRCodeShift),
898
+    @(AMR_SGRCodeFgMagenta+AMR_SGRCodeShift),
899
+    @(AMR_SGRCodeFgCyan+AMR_SGRCodeShift),
900
+    @(AMR_SGRCodeFgWhite+AMR_SGRCodeShift),
901
+    @(AMR_SGRCodeFgBrightBlack+AMR_SGRCodeShift),
902
+    @(AMR_SGRCodeFgBrightRed+AMR_SGRCodeShift),
903
+    @(AMR_SGRCodeFgBrightGreen+AMR_SGRCodeShift),
904
+    @(AMR_SGRCodeFgBrightYellow+AMR_SGRCodeShift),
905
+    @(AMR_SGRCodeFgBrightBlue+AMR_SGRCodeShift),
906
+    @(AMR_SGRCodeFgBrightMagenta+AMR_SGRCodeShift),
907
+    @(AMR_SGRCodeFgBrightCyan+AMR_SGRCodeShift),
908
+    @(AMR_SGRCodeFgBrightWhite+AMR_SGRCodeShift),
909
+    ];
910
+    for (NSNumber *thisSGRCodeNumber in ansiFgColorCodes)
911
+    {
912
+        AMR_SGRCode thisSGRCode = thisSGRCodeNumber.intValue;
913
+        NSColor *thisColor = [self colorForSGRCode:thisSGRCode];
914
+
915
+        AMR_HSB thisColorHSB = getHSBFromColor(thisColor);
916
+
917
+        CGFloat hueDiff = fabs(givenColorHSB.hue - thisColorHSB.hue);
918
+        CGFloat saturationDiff = fabs(givenColorHSB.saturation - thisColorHSB.saturation);
919
+        CGFloat brightnessDiff = fabs(givenColorHSB.brightness - thisColorHSB.brightness);
920
+
921
+        // comparison depends on hue, saturation and brightness
922
+        // (strictly in that order):
923
+
924
+        if (!floatsEqual(hueDiff, closestColorHueDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR))
925
+        {
926
+            if (hueDiff > closestColorHueDiff)
927
+                continue;
928
+            closestColorSGRCode = thisSGRCode;
929
+            closestColorHueDiff = hueDiff;
930
+            closestColorSaturationDiff = saturationDiff;
931
+            closestColorBrightnessDiff = brightnessDiff;
932
+            continue;
933
+        }
934
+
935
+        if (!floatsEqual(saturationDiff, closestColorSaturationDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR))
936
+        {
937
+            if (saturationDiff > closestColorSaturationDiff)
938
+                continue;
939
+            closestColorSGRCode = thisSGRCode;
940
+            closestColorHueDiff = hueDiff;
941
+            closestColorSaturationDiff = saturationDiff;
942
+            closestColorBrightnessDiff = brightnessDiff;
943
+            continue;
944
+        }
945
+
946
+        if (!floatsEqual(brightnessDiff, closestColorBrightnessDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR))
947
+        {
948
+            if (brightnessDiff > closestColorBrightnessDiff)
949
+                continue;
950
+            closestColorSGRCode = thisSGRCode;
951
+            closestColorHueDiff = hueDiff;
952
+            closestColorSaturationDiff = saturationDiff;
953
+            closestColorBrightnessDiff = brightnessDiff;
954
+            continue;
955
+        }
956
+
957
+        // If hue (especially hue!), saturation and brightness diffs all
958
+        // are equal to some other color, we need to prefer one or the
959
+        // other so we'll select the more 'distinctive' color of the
960
+        // two (this is *very* subjective, obviously). I basically just
961
+        // looked at the hue chart, went through all the points between
962
+        // our main ANSI colors and decided which side the middle point
963
+        // would lean on. (e.g. the purple color that is exactly between
964
+        // the blue and magenta ANSI colors looks more magenta than
965
+        // blue to me so I put magenta higher than blue in the list
966
+        // below.)
967
+        //
968
+        // subjective ordering of colors from most to least 'distinctive':
969
+        int colorDistinctivenessOrder[6] = {
970
+            AMR_SGRCodeFgRed+(int)AMR_SGRCodeShift,
971
+            AMR_SGRCodeFgMagenta+(int)AMR_SGRCodeShift,
972
+            AMR_SGRCodeFgBlue+(int)AMR_SGRCodeShift,
973
+            AMR_SGRCodeFgGreen+(int)AMR_SGRCodeShift,
974
+            AMR_SGRCodeFgCyan+(int)AMR_SGRCodeShift,
975
+            AMR_SGRCodeFgYellow+(int)AMR_SGRCodeShift
976
+            };
977
+        for (int i = 0; i < 6; i++)
978
+        {
979
+            if (colorDistinctivenessOrder[i] == closestColorSGRCode)
980
+                break;
981
+            else if (colorDistinctivenessOrder[i] == thisSGRCode)
982
+            {
983
+                closestColorSGRCode = thisSGRCode;
984
+                closestColorHueDiff = hueDiff;
985
+                closestColorSaturationDiff = saturationDiff;
986
+                closestColorBrightnessDiff = brightnessDiff;
987
+            }
988
+        }
989
+    }
990
+
991
+    return closestColorSGRCode;
992
+}
993
+
994
+
995
+
996
+@end
0 997
new file mode 100644
... ...
@@ -0,0 +1,326 @@
1
+//
2
+//  ANSIEscapeHelper.h
3
+//  AnsiColorsTest
4
+//
5
+//  Created by Ali Rantakari on 18.3.09.
6
+//
7
+//  Version 0.9.6
8
+//
9
+/*
10
+The MIT License
11
+
12
+Copyright (c) 2008-2009,2013 Ali Rantakari
13
+
14
+Permission is hereby granted, free of charge, to any person obtaining a copy
15
+of this software and associated documentation files (the "Software"), to deal
16
+in the Software without restriction, including without limitation the rights
17
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+copies of the Software, and to permit persons to whom the Software is
19
+furnished to do so, subject to the following conditions:
20
+
21
+The above copyright notice and this permission notice shall be included in
22
+all copies or substantial portions of the Software.
23
+
24
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
30
+THE SOFTWARE.
31
+*/
32
+
33
+#import <Cocoa/Cocoa.h>
34
+
35
+
36
+#if !__has_feature(objc_arc)
37
+#warning "This code requires ARC to be enabled."
38
+#endif
39
+
40
+
41
+// dictionary keys for the SGR code dictionaries that the array
42
+// escapeCodesForString:cleanString: returns contains
43
+#define kAMRCodeDictKey_code           @"code"
44
+#define kAMRCodeDictKey_location       @"location"
45
+
46
+// dictionary keys for the string formatting attribute
47
+// dictionaries that the array attributesForString:cleanString:
48
+// returns contains
49
+#define kAMRAttrDictKey_range          @"range"
50
+#define kAMRAttrDictKey_attrName       @"attributeName"
51
+#define kAMRAttrDictKey_attrValue      @"attributeValue"
52
+
53
+
54
+/*!
55
+ @enum          AMR_SGRCode
56
+
57
+ @abstract      SGR (Select Graphic Rendition) ANSI control codes.
58
+ */
59
+typedef enum
60
+{
61
+    AMR_SGRCodeNoneOrInvalid =      -1,
62
+
63
+    AMR_SGRCodeAllReset =           0,
64
+
65
+    AMR_SGRCodeIntensityBold =      1,
66
+    AMR_SGRCodeIntensityFaint =     2,
67
+    AMR_SGRCodeIntensityNormal =    22,
68
+
69
+    AMR_SGRCodeItalicOn =           3,
70
+
71
+    AMR_SGRCodeUnderlineSingle =    4,
72
+    AMR_SGRCodeUnderlineDouble =    21,
73
+    AMR_SGRCodeUnderlineNone =      24,
74
+
75
+    AMR_SGRCodeFgBlack =            30,
76
+    AMR_SGRCodeFgRed =              31,
77
+    AMR_SGRCodeFgGreen =            32,
78
+    AMR_SGRCodeFgYellow =           33,
79
+    AMR_SGRCodeFgBlue =             34,
80
+    AMR_SGRCodeFgMagenta =          35,
81
+    AMR_SGRCodeFgCyan =             36,
82
+    AMR_SGRCodeFgWhite =            37,
83
+    AMR_SGRCodeFgReset =            39,
84
+
85
+    AMR_SGRCodeBgBlack =            40,
86
+    AMR_SGRCodeBgRed =              41,
87
+    AMR_SGRCodeBgGreen =            42,
88
+    AMR_SGRCodeBgYellow =           43,
89
+    AMR_SGRCodeBgBlue =             44,
90
+    AMR_SGRCodeBgMagenta =          45,
91
+    AMR_SGRCodeBgCyan =             46,
92
+    AMR_SGRCodeBgWhite =            47,
93
+    AMR_SGRCodeBgReset =            49,
94
+
95
+    AMR_SGRCodeFgBrightBlack =      90,
96
+    AMR_SGRCodeFgBrightRed =        91,
97
+    AMR_SGRCodeFgBrightGreen =      92,
98
+    AMR_SGRCodeFgBrightYellow =     93,
99
+    AMR_SGRCodeFgBrightBlue =       94,
100
+    AMR_SGRCodeFgBrightMagenta =    95,
101
+    AMR_SGRCodeFgBrightCyan =       96,
102
+    AMR_SGRCodeFgBrightWhite =      97,
103
+
104
+    AMR_SGRCodeBgBrightBlack =      100,
105
+    AMR_SGRCodeBgBrightRed =        101,
106
+    AMR_SGRCodeBgBrightGreen =      102,
107
+    AMR_SGRCodeBgBrightYellow =     103,
108
+    AMR_SGRCodeBgBrightBlue =       104,
109
+    AMR_SGRCodeBgBrightMagenta =    105,
110
+    AMR_SGRCodeBgBrightCyan =       106,
111
+    AMR_SGRCodeBgBrightWhite =      107
112
+} AMR_SGRCode;
113
+
114
+
115
+
116
+
117
+
118
+
119
+/*!
120
+ @class     AMR_ANSIEscapeHelper
121
+
122
+ @abstract  Contains helper methods for dealing with strings
123
+            that contain ANSI escape sequences for formatting (colors,
124
+            underlining, bold etc.)
125
+ */
126
+@interface AMR_ANSIEscapeHelper : NSObject
127
+
128
+/*!
129
+ @property      defaultStringColor
130
+
131
+ @abstract      The default color used when creating an attributed string (default is black).
132
+ */
133
+@property(copy) NSColor *defaultStringColor;
134
+
135
+
136
+/*!
137
+ @property      font
138
+
139
+ @abstract      The font to use when creating string formatting attribute values.
140
+ */
141
+@property(copy) NSFont *font;
142
+
143
+/*!
144
+ @property      ansiColors
145
+
146
+ @abstract      The colors to use for displaying ANSI colors.
147
+
148
+ @discussion    Keys in this dictionary should be NSNumber objects containing SGR code
149
+                values from the AMR_SGRCode enum. The corresponding values for these keys
150
+                should be NSColor objects. If this property is nil or if it doesn't
151
+                contain a key for a specific SGR code, the default color will be used
152
+                instead.
153
+ */
154
+@property(retain) NSMutableDictionary *ansiColors;
155
+
156
+
157
+/*!
158
+ @method        attributedStringWithANSIEscapedString:
159
+
160
+ @abstract      Returns an attributed string that corresponds both in contents
161
+                and formatting to a given string that contains ANSI escape
162
+                sequences.
163
+
164
+ @param aString         A String containing ANSI escape sequences
165
+
166
+ @result        An attributed string that mimics as closely as possible
167
+                the formatting of the given ANSI-escaped string.
168
+ */
169
+- (NSAttributedString*) attributedStringWithANSIEscapedString:(NSString*)aString;
170
+
171
+
172
+/*!
173
+ @method        ansiEscapedStringWithAttributedString:
174
+
175
+ @abstract      Returns a string containing ANSI escape sequences that corresponds
176
+                both in contents and formatting to a given attributed string.
177
+
178
+ @param aAttributedString       An attributed string
179
+
180
+ @result        A string that mimics as closely as possible
181
+                the formatting of the given attributed string with
182
+                ANSI escape sequences.
183
+ */
184
+- (NSString*) ansiEscapedStringWithAttributedString:(NSAttributedString*)aAttributedString;
185
+
186
+
187
+/*!
188
+ @method        escapeCodesForString:cleanString:
189
+
190
+ @abstract      Returns an array of SGR codes and their locations from a
191
+                string containing ANSI escape sequences as well as a "clean"
192
+                version of the string (i.e. one without the ANSI escape
193
+                sequences.)
194
+
195
+ @param aString         A String containing ANSI escape sequences
196
+ @param aCleanString    Upon return, contains a "clean" version of aString (i.e. aString
197
+                        without the ANSI escape sequences)
198
+
199
+ @result        An array of NSDictionary objects, each of which has
200
+                an NSNumber value for the key "code" (specifying an SGR code) and
201
+                another NSNumber value for the key "location" (specifying the
202
+                location of the code within aCleanString.)
203
+ */
204
+- (NSArray*) escapeCodesForString:(NSString*)aString cleanString:(NSString**)aCleanString;
205
+
206
+
207
+/*!
208
+ @method        ansiEscapedStringWithCodesAndLocations:cleanString:
209
+
210
+ @abstract      Returns a string containing ANSI escape codes for formatting based
211
+                on a string and an array of SGR codes and their locations within
212
+                the given string.
213
+
214
+ @param aCodesArray     An array of NSDictionary objects, each of which should have
215
+                        an NSNumber value for the key "code" (specifying an SGR
216
+                        code) and another NSNumber value for the key "location"
217
+                        (specifying the location of this SGR code in aCleanString.)
218
+ @param aCleanString    The string to which to insert the ANSI escape codes
219
+                        described in aCodesArray.
220
+
221
+ @result        A string containing ANSI escape sequences.
222
+ */
223
+- (NSString*) ansiEscapedStringWithCodesAndLocations:(NSArray*)aCodesArray cleanString:(NSString*)aCleanString;
224
+
225
+
226
+/*!
227
+ @method        attributesForString:cleanString:
228
+
229
+ @abstract      Convert ANSI escape sequences in a string to string formatting attributes.
230
+
231
+ @discussion    Given a string with some ANSI escape sequences in it, this method returns
232
+                attributes for formatting the specified string according to those ANSI
233
+                escape sequences as well as a "clean" (i.e. free of the escape sequences)
234
+                version of this string.
235
+
236
+ @param aString         A String containing ANSI escape sequences
237
+ @param aCleanString    Upon return, contains a "clean" version of aString (i.e. aString
238
+                        without the ANSI escape sequences.) Pass in NULL if you're not
239
+                        interested in this.
240
+
241
+ @result        An array containing NSDictionary objects, each of which has keys "range"
242
+                (an NSValue containing an NSRange, specifying the range for the
243
+                attribute within the "clean" version of aString), "attributeName" (an
244
+                NSString) and "attributeValue" (an NSObject). You may use these as
245
+                arguments for NSMutableAttributedString's methods for setting the
246
+                visual formatting.
247
+ */
248
+- (NSArray*) attributesForString:(NSString*)aString cleanString:(NSString**)aCleanString;
249
+
250
+
251
+/*!
252
+ @method        AMR_SGRCode:endsFormattingIntroducedByCode:
253
+
254
+ @abstract      Whether the occurrence of a given SGR code would end the formatting run
255
+                introduced by another SGR code.
256
+
257
+ @discussion    For example, AMR_SGRCodeFgReset, AMR_SGRCodeAllReset or any SGR code
258
+                specifying a foreground color would end the formatting run
259
+                introduced by a foreground color -specifying SGR code.
260
+
261
+ @param endCode     The SGR code to test as a candidate for ending the formatting run
262
+                    introduced by startCode
263
+ @param startCode   The SGR code that has introduced a formatting run
264
+
265
+ @result        YES if the occurrence of endCode would end the formatting run
266
+                introduced by startCode, NO otherwise.
267
+ */
268
+- (BOOL) AMR_SGRCode:(AMR_SGRCode)endCode endsFormattingIntroducedByCode:(AMR_SGRCode)startCode;
269
+
270
+
271
+/*!
272
+ @method        colorForSGRCode:
273
+
274
+ @abstract      Returns the color to use for displaying a specific ANSI color.
275
+
276
+ @discussion    This method first considers the values set in the ansiColors
277
+                property and only then the standard basic colors (NSColor's
278
+                redColor, blueColor etc.)
279
+
280
+ @param code    An SGR code that specifies an ANSI color.
281
+
282
+ @result        The color to use for displaying the ANSI color specified by code.
283
+ */
284
+- (NSColor*) colorForSGRCode:(AMR_SGRCode)code;
285
+
286
+
287
+/*!
288
+ @method        AMR_SGRCodeForColor:isForegroundColor:
289
+
290
+ @abstract      Returns a color SGR code that corresponds to a given color.
291
+
292
+ @discussion    This method matches colors to their equivalent SGR codes
293
+                by going through the colors specified in the ansiColors
294
+                dictionary, and if ansiColors is null or if a match is
295
+                not found there, by comparing the given color to the
296
+                standard basic colors (NSColor's redColor, blueColor
297
+                etc.) The comparison is done simply by checking for
298
+                equality.
299
+
300
+ @param aColor          The color to get a corresponding SGR code for
301
+ @param aForeground     Whether you want a foreground or background color code
302
+
303
+ @result        SGR code that corresponds with aColor.
304
+ */
305
+- (AMR_SGRCode) AMR_SGRCodeForColor:(NSColor*)aColor isForegroundColor:(BOOL)aForeground;
306
+
307
+
308
+/*!
309
+ @method        closestSGRCodeForColor:isForegroundColor:
310
+
311
+ @abstract      Returns a color SGR code that represents the closest ANSI
312
+                color to a given color.
313
+
314
+ @discussion    This method attempts to find the closest ANSI color to
315
+                aColor and return its SGR code.
316
+
317
+ @param aColor          The color to get a closest color SGR code match for
318
+ @param aForeground     Whether you want a foreground or background color code
319
+
320
+ @result        SGR code for the ANSI color that is closest to aColor.
321
+ */
322
+- (AMR_SGRCode) closestSGRCodeForColor:(NSColor *)color isForegroundColor:(BOOL)foreground;
323
+
324
+
325
+
326
+@end
0 327
new file mode 100644
... ...
@@ -0,0 +1,996 @@
1
+//
2
+//  ANSIEscapeHelper.m
3
+//
4
+//  Created by Ali Rantakari on 18.3.09.
5
+
6
+/*
7
+The MIT License
8
+
9
+Copyright (c) 2008-2009,2013 Ali Rantakari
10
+
11
+Permission is hereby granted, free of charge, to any person obtaining a copy
12
+of this software and associated documentation files (the "Software"), to deal
13
+in the Software without restriction, including without limitation the rights
14
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+copies of the Software, and to permit persons to whom the Software is
16
+furnished to do so, subject to the following conditions:
17
+
18
+The above copyright notice and this permission notice shall be included in
19
+all copies or substantial portions of the Software.
20
+
21
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
+THE SOFTWARE.
28
+*/
29
+
30
+/*
31
+ todo:
32
+
33
+ - don't add useless "reset" escape codes to the string in
34
+   -ansiEscapedStringWithAttributedString:
35
+
36
+ */
37
+
38
+
39
+
40
+#import "AMR_ANSIEscapeHelper.h"
41
+
42
+
43
+// the CSI (Control Sequence Initiator) -- i.e. "escape sequence prefix".
44
+// (add your own CSI:Miami joke here)
45
+#define kANSIEscapeCSI          @"\033["
46
+
47
+// the end byte of an SGR (Select Graphic Rendition)
48
+// ANSI Escape Sequence
49
+#define kANSIEscapeSGREnd       @"m"
50
+
51
+
52
+// color definition helper macros
53
+#define kBrightColorBrightness  1.0
54
+#define kBrightColorSaturation  0.4
55
+#define kBrightColorAlpha       1.0
56
+#define kBrightColorWithHue(h)  [NSColor colorWithCalibratedHue:(h) saturation:kBrightColorSaturation brightness:kBrightColorBrightness alpha:kBrightColorAlpha]
57
+
58
+// default colors
59
+#define kDefaultANSIColorFgBlack    NSColor.blackColor
60
+#define kDefaultANSIColorFgRed      NSColor.redColor
61
+#define kDefaultANSIColorFgGreen    NSColor.greenColor
62
+#define kDefaultANSIColorFgYellow   NSColor.yellowColor
63
+#define kDefaultANSIColorFgBlue     NSColor.blueColor
64
+#define kDefaultANSIColorFgMagenta  NSColor.magentaColor
65
+#define kDefaultANSIColorFgCyan     NSColor.cyanColor
66
+#define kDefaultANSIColorFgWhite    NSColor.whiteColor
67
+
68
+#define kDefaultANSIColorFgBrightBlack      [NSColor colorWithCalibratedWhite:0.337 alpha:1.0]
69
+#define kDefaultANSIColorFgBrightRed        kBrightColorWithHue(1.0)
70
+#define kDefaultANSIColorFgBrightGreen      kBrightColorWithHue(1.0/3.0)
71
+#define kDefaultANSIColorFgBrightYellow     kBrightColorWithHue(1.0/6.0)
72
+#define kDefaultANSIColorFgBrightBlue       kBrightColorWithHue(2.0/3.0)
73
+#define kDefaultANSIColorFgBrightMagenta    kBrightColorWithHue(5.0/6.0)
74
+#define kDefaultANSIColorFgBrightCyan       kBrightColorWithHue(0.5)
75
+#define kDefaultANSIColorFgBrightWhite      NSColor.whiteColor
76
+
77
+#define kDefaultANSIColorBgBlack    NSColor.blackColor
78
+#define kDefaultANSIColorBgRed      NSColor.redColor
79
+#define kDefaultANSIColorBgGreen    NSColor.greenColor
80
+#define kDefaultANSIColorBgYellow   NSColor.yellowColor
81
+#define kDefaultANSIColorBgBlue     NSColor.blueColor
82
+#define kDefaultANSIColorBgMagenta  NSColor.magentaColor
83
+#define kDefaultANSIColorBgCyan     NSColor.cyanColor
84
+#define kDefaultANSIColorBgWhite    NSColor.whiteColor
85
+
86
+#define kDefaultANSIColorBgBrightBlack      kDefaultANSIColorFgBrightBlack
87
+#define kDefaultANSIColorBgBrightRed        kDefaultANSIColorFgBrightRed
88
+#define kDefaultANSIColorBgBrightGreen      kDefaultANSIColorFgBrightGreen
89
+#define kDefaultANSIColorBgBrightYellow     kDefaultANSIColorFgBrightYellow
90
+#define kDefaultANSIColorBgBrightBlue       kDefaultANSIColorFgBrightBlue
91
+#define kDefaultANSIColorBgBrightMagenta    kDefaultANSIColorFgBrightMagenta
92
+#define kDefaultANSIColorBgBrightCyan       kDefaultANSIColorFgBrightCyan
93
+#define kDefaultANSIColorBgBrightWhite      kDefaultANSIColorFgBrightWhite
94
+
95
+#define kDefaultFontSize [NSFont systemFontOfSize:NSFont.systemFontSize]
96
+#define kDefaultForegroundColor NSColor.blackColor
97
+
98
+// minimum weight for an NSFont for it to be considered bold
99
+#define kBoldFontMinWeight          9
100
+
101
+
102
+@implementation AMR_ANSIEscapeHelper
103
+
104
+- (id) init
105
+{
106
+    if (!(self = [super init]))
107
+        return nil;
108
+
109
+    self.ansiColors = [NSMutableDictionary dictionary];
110
+
111
+    return self;
112
+}
113
+
114
+
115
+
116
+- (NSAttributedString*) attributedStringWithANSIEscapedString:(NSString*)aString
117
+{
118
+    if (aString == nil)
119
+        return nil;
120
+
121
+    NSString *cleanString;
122
+    NSArray *attributesAndRanges = [self attributesForString:aString cleanString:&cleanString];
123
+    NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]
124
+                                                   initWithString:cleanString
125
+                                                   attributes:@{
126
+                                                   NSFontAttributeName: self.font ?: kDefaultFontSize,
127
+                                                   NSForegroundColorAttributeName: self.defaultStringColor ?: kDefaultForegroundColor
128
+                                                   }];
129
+
130
+    for (NSDictionary *thisAttributeDict in attributesAndRanges)
131
+    {
132
+        [attributedString
133
+         addAttribute:thisAttributeDict[kAMRAttrDictKey_attrName]
134
+         value:thisAttributeDict[kAMRAttrDictKey_attrValue]
135
+         range:[thisAttributeDict[kAMRAttrDictKey_range] rangeValue]
136
+         ];
137
+    }
138
+
139
+    return attributedString;
140
+}
141
+
142
+
143
+
144
+- (NSString*) ansiEscapedStringWithAttributedString:(NSAttributedString*)aAttributedString
145
+{
146
+    NSMutableArray *codesAndLocations = [NSMutableArray array];
147
+
148
+    NSArray *attrNames = @[
149
+                          NSFontAttributeName, NSForegroundColorAttributeName,
150
+                          NSBackgroundColorAttributeName, NSUnderlineStyleAttributeName,
151
+                          ];
152
+
153
+    for (NSString *thisAttrName in attrNames)
154
+    {
155
+        NSRange limitRange = NSMakeRange(0, aAttributedString.length);
156
+        id attributeValue;
157
+        NSRange effectiveRange;
158
+
159
+        while (limitRange.length > 0)
160
+        {
161
+            attributeValue = [aAttributedString
162
+                              attribute:thisAttrName
163
+                              atIndex:limitRange.location
164
+                              longestEffectiveRange:&effectiveRange
165
+                              inRange:limitRange
166
+                              ];
167
+
168
+            AMR_SGRCode thisSGRCode = AMR_SGRCodeNoneOrInvalid;
169
+
170
+            if ([thisAttrName isEqualToString:NSForegroundColorAttributeName])
171
+            {
172
+                if (attributeValue != nil)
173
+                    thisSGRCode = [self closestSGRCodeForColor:attributeValue isForegroundColor:YES];
174
+                else
175
+                    thisSGRCode = AMR_SGRCodeFgReset;
176
+            }
177
+            else if ([thisAttrName isEqualToString:NSBackgroundColorAttributeName])
178
+            {
179
+                if (attributeValue != nil)
180
+                    thisSGRCode = [self closestSGRCodeForColor:attributeValue isForegroundColor:NO];
181
+                else
182
+                    thisSGRCode = AMR_SGRCodeBgReset;
183
+            }
184
+            else if ([thisAttrName isEqualToString:NSFontAttributeName])
185
+            {
186
+                // we currently only use NSFontAttributeName for bolding so
187
+                // here we assume that the formatting "type" in ANSI SGR
188
+                // terms is indeed intensity
189
+                if (attributeValue != nil)
190
+                    thisSGRCode = ([NSFontManager.sharedFontManager weightOfFont:attributeValue] >= kBoldFontMinWeight)
191
+                                    ? AMR_SGRCodeIntensityBold : AMR_SGRCodeIntensityNormal;
192
+                else
193
+                    thisSGRCode = AMR_SGRCodeIntensityNormal;
194
+            }
195
+            else if ([thisAttrName isEqualToString:NSUnderlineStyleAttributeName])
196
+            {
197
+                if (attributeValue != nil)
198
+                {
199
+                    if ([attributeValue intValue] == NSUnderlineStyleSingle)
200
+                        thisSGRCode = AMR_SGRCodeUnderlineSingle;
201
+                    else if ([attributeValue intValue] == NSUnderlineStyleDouble)
202
+                        thisSGRCode = AMR_SGRCodeUnderlineDouble;
203
+                    else
204
+                        thisSGRCode = AMR_SGRCodeUnderlineNone;
205
+                }
206
+                else
207
+                    thisSGRCode = AMR_SGRCodeUnderlineNone;
208
+            }
209
+
210
+            if (thisSGRCode != AMR_SGRCodeNoneOrInvalid)
211
+            {
212
+                [codesAndLocations addObject: @{
213
+                           kAMRCodeDictKey_code: @(thisSGRCode),
214
+                       kAMRCodeDictKey_location: @(effectiveRange.location),
215
+                 }];
216
+            }
217
+
218
+            limitRange = NSMakeRange(NSMaxRange(effectiveRange),
219
+                                     NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
220
+        }
221
+    }
222
+
223
+    return [self ansiEscapedStringWithCodesAndLocations:codesAndLocations cleanString:aAttributedString.string];
224
+}
225
+
226
+
227
+- (NSArray*) escapeCodesForString:(NSString*)aString cleanString:(NSString**)aCleanString
228
+{
229
+    if (aString == nil)
230
+        return nil;
231
+    if (aString.length <= kANSIEscapeCSI.length)
232
+    {
233
+        if (aCleanString)
234
+            *aCleanString = aString.copy;
235
+        return @[];
236
+    }
237
+
238
+    NSString *cleanString = @"";
239
+
240
+    // find all escape sequence codes from aString and put them in this array
241
+    // along with their start locations within the "clean" version of aString
242
+    NSMutableArray *formatCodes = [NSMutableArray array];
243
+
244
+    NSUInteger aStringLength = aString.length;
245
+    NSUInteger coveredLength = 0;
246
+    NSRange searchRange = NSMakeRange(0,aStringLength);
247
+    NSRange thisEscapeSequenceRange;
248
+    do
249
+    {
250
+        thisEscapeSequenceRange = [aString rangeOfString:kANSIEscapeCSI options:NSLiteralSearch range:searchRange];
251
+        if (thisEscapeSequenceRange.location != NSNotFound)
252
+        {
253
+            // adjust range's length so that it encompasses the whole ANSI escape sequence
254
+            // and not just the Control Sequence Initiator (the "prefix") by finding the
255
+            // final byte of the control sequence (one that has an ASCII decimal value
256
+            // between 64 and 126.) at the same time, read all formatting codes from inside
257
+            // this escape sequence (there may be several, separated by semicolons.)
258
+            NSMutableArray *codes = [NSMutableArray array];
259
+            unsigned int code = 0;
260
+            unsigned int lengthAddition = 1;
261
+            NSUInteger thisIndex;
262
+            for (;;)
263
+            {
264
+                thisIndex = (NSMaxRange(thisEscapeSequenceRange)+lengthAddition-1);
265
+                if (thisIndex >= aStringLength)
266
+                    break;
267
+
268
+                unichar c = [aString characterAtIndex:thisIndex];
269
+
270
+                if (('0' <= c) && (c <= '9'))
271
+                {
272
+                    int digit = c - '0';
273
+                    code = (code == 0) ? digit : code*10+digit;
274
+                }
275
+
276
+                // ASCII decimal 109 is the SGR (Select Graphic Rendition) final byte
277
+                // ("m"). this means that the code value we've just read specifies formatting
278
+                // for the output; exactly what we're interested in.
279
+                if (c == 'm')
280
+                {
281
+                    [codes addObject:@(code)];
282
+                    break;
283
+                }
284
+                else if ((64 <= c) && (c <= 126)) // any other valid final byte
285
+                {
286
+                    [codes removeAllObjects];
287
+                    break;
288
+                }
289
+                else if (c == ';') // separates codes within the same sequence
290
+                {
291
+                    [codes addObject:@(code)];
292
+                    code = 0;
293
+                }
294
+
295
+                lengthAddition++;
296
+            }
297
+            thisEscapeSequenceRange.length += lengthAddition;
298
+
299
+            NSUInteger locationInCleanString = coveredLength+thisEscapeSequenceRange.location-searchRange.location;
300
+
301
+            for (NSNumber *codeToAdd in codes)
302
+            {
303
+                [formatCodes addObject: @{
304
+                     kAMRCodeDictKey_code: codeToAdd,
305
+                 kAMRCodeDictKey_location: @(locationInCleanString)
306
+                 }];
307
+            }
308
+
309
+            NSUInteger thisCoveredLength = thisEscapeSequenceRange.location-searchRange.location;
310
+            if (thisCoveredLength > 0)
311
+                cleanString = [cleanString stringByAppendingString:[aString substringWithRange:NSMakeRange(searchRange.location, thisCoveredLength)]];
312
+
313
+            coveredLength += thisCoveredLength;
314
+            searchRange.location = NSMaxRange(thisEscapeSequenceRange);
315
+            searchRange.length = aStringLength-searchRange.location;
316
+        }
317
+    }
318
+    while(thisEscapeSequenceRange.location != NSNotFound);
319
+
320
+    if (searchRange.length > 0)
321
+        cleanString = [cleanString stringByAppendingString:[aString substringWithRange:searchRange]];
322
+
323
+    if (aCleanString)
324
+        *aCleanString = cleanString;
325
+    return formatCodes;
326
+}
327
+
328
+
329
+
330
+
331
+- (NSString*) ansiEscapedStringWithCodesAndLocations:(NSArray*)aCodesArray cleanString:(NSString*)aCleanString
332
+{
333
+    NSMutableString* retStr = [NSMutableString stringWithCapacity:aCleanString.length];
334
+
335
+    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:kAMRCodeDictKey_location ascending:YES];
336
+    NSArray *codesArray = [aCodesArray sortedArrayUsingDescriptors:@[sortDescriptor]];
337
+
338
+    NSUInteger aCleanStringIndex = 0;
339
+    NSUInteger aCleanStringLength = aCleanString.length;
340
+    for (NSDictionary *thisCodeDict in codesArray)
341
+    {
342
+        if (!(  thisCodeDict[kAMRCodeDictKey_code] &&
343
+                thisCodeDict[kAMRCodeDictKey_location]
344
+            ))
345
+            continue;
346
+
347
+        AMR_SGRCode thisCode = [thisCodeDict[kAMRCodeDictKey_code] unsignedIntValue];
348
+        NSUInteger formattingRunStartLocation = [thisCodeDict[kAMRCodeDictKey_location] unsignedIntegerValue];
349
+
350
+        if (formattingRunStartLocation > aCleanStringLength)
351
+            continue;
352
+
353
+        if (aCleanStringIndex < formattingRunStartLocation)
354
+            [retStr appendString:[aCleanString substringWithRange:NSMakeRange(aCleanStringIndex, formattingRunStartLocation-aCleanStringIndex)]];
355
+        [retStr appendFormat:@"%@%d%@", kANSIEscapeCSI, thisCode, kANSIEscapeSGREnd];
356
+
357
+        aCleanStringIndex = formattingRunStartLocation;
358
+    }
359
+
360
+    if (aCleanStringIndex < aCleanStringLength)
361
+        [retStr appendString:[aCleanString substringFromIndex:aCleanStringIndex]];
362
+
363
+    [retStr appendFormat:@"%@%d%@", kANSIEscapeCSI, AMR_SGRCodeAllReset, kANSIEscapeSGREnd];
364
+
365
+    return retStr;
366
+}
367
+
368
+
369
+
370
+
371
+
372
+- (NSArray*) attributesForString:(NSString*)aString cleanString:(NSString**)aCleanString
373
+{
374
+    if (aString == nil)
375
+        return nil;
376
+    if (aString.length <= kANSIEscapeCSI.length)
377
+    {
378
+        if (aCleanString)
379
+            *aCleanString = aString.copy;
380
+        return @[];
381
+    }
382
+
383
+    NSMutableArray *attrsAndRanges = [NSMutableArray array];
384
+
385
+    NSString *cleanString;
386
+    NSArray *formatCodes = [self escapeCodesForString:aString cleanString:&cleanString];
387
+
388
+    // go through all the found escape sequence codes and for each one, create
389
+    // the string formatting attribute name and value, find the next escape
390
+    // sequence that specifies the end of the formatting run started by
391
+    // the currently handled code, and generate a range from the difference
392
+    // in those codes' locations within the clean aString.
393
+    for (NSUInteger iCode = 0; iCode < formatCodes.count; iCode++)
394
+    {
395
+        NSDictionary *thisCodeDict = formatCodes[iCode];
396
+        AMR_SGRCode thisCode = [thisCodeDict[kAMRCodeDictKey_code] unsignedIntValue];
397
+        NSUInteger formattingRunStartLocation = [thisCodeDict[kAMRCodeDictKey_location] unsignedIntegerValue];
398
+
399
+        // the attributed string attribute name for the formatting run introduced
400
+        // by this code
401
+        NSString *thisAttributeName = nil;
402
+
403
+        // the attributed string attribute value for this formatting run introduced
404
+        // by this code
405
+        NSObject *thisAttributeValue = nil;
406
+
407
+        // set attribute name
408
+        switch(thisCode)
409
+        {
410
+            case AMR_SGRCodeFgBlack:
411
+            case AMR_SGRCodeFgRed:
412
+            case AMR_SGRCodeFgGreen:
413
+            case AMR_SGRCodeFgYellow:
414
+            case AMR_SGRCodeFgBlue:
415
+            case AMR_SGRCodeFgMagenta:
416
+            case AMR_SGRCodeFgCyan:
417
+            case AMR_SGRCodeFgWhite:
418
+            case AMR_SGRCodeFgBrightBlack:
419
+            case AMR_SGRCodeFgBrightRed:
420
+            case AMR_SGRCodeFgBrightGreen:
421
+            case AMR_SGRCodeFgBrightYellow:
422
+            case AMR_SGRCodeFgBrightBlue:
423
+            case AMR_SGRCodeFgBrightMagenta:
424
+            case AMR_SGRCodeFgBrightCyan:
425
+            case AMR_SGRCodeFgBrightWhite:
426
+                thisAttributeName = NSForegroundColorAttributeName;
427
+                break;
428
+            case AMR_SGRCodeBgBlack:
429
+            case AMR_SGRCodeBgRed:
430
+            case AMR_SGRCodeBgGreen:
431
+            case AMR_SGRCodeBgYellow:
432
+            case AMR_SGRCodeBgBlue:
433
+            case AMR_SGRCodeBgMagenta:
434
+            case AMR_SGRCodeBgCyan:
435
+            case AMR_SGRCodeBgWhite:
436
+            case AMR_SGRCodeBgBrightBlack:
437
+            case AMR_SGRCodeBgBrightRed:
438
+            case AMR_SGRCodeBgBrightGreen:
439
+            case AMR_SGRCodeBgBrightYellow:
440
+            case AMR_SGRCodeBgBrightBlue:
441
+            case AMR_SGRCodeBgBrightMagenta:
442
+            case AMR_SGRCodeBgBrightCyan:
443
+            case AMR_SGRCodeBgBrightWhite:
444
+                thisAttributeName = NSBackgroundColorAttributeName;
445
+                break;
446
+            case AMR_SGRCodeIntensityBold:
447
+            case AMR_SGRCodeIntensityNormal:
448
+            case AMR_SGRCodeIntensityFaint:
449
+                thisAttributeName = NSFontAttributeName;
450
+                break;
451
+            case AMR_SGRCodeUnderlineSingle:
452
+            case AMR_SGRCodeUnderlineDouble:
453
+            case AMR_SGRCodeUnderlineNone:
454
+                thisAttributeName = NSUnderlineStyleAttributeName;
455
+                break;
456
+            case AMR_SGRCodeAllReset:
457
+            case AMR_SGRCodeFgReset:
458
+            case AMR_SGRCodeBgReset:
459
+            case AMR_SGRCodeNoneOrInvalid:
460
+            case AMR_SGRCodeItalicOn:
461
+                continue;
462
+        }
463
+
464
+        // set attribute value
465
+        switch(thisCode)
466
+        {
467
+            case AMR_SGRCodeBgBlack:
468
+            case AMR_SGRCodeFgBlack:
469
+            case AMR_SGRCodeBgRed:
470
+            case AMR_SGRCodeFgRed:
471
+            case AMR_SGRCodeBgGreen:
472
+            case AMR_SGRCodeFgGreen:
473
+            case AMR_SGRCodeBgYellow:
474
+            case AMR_SGRCodeFgYellow:
475
+            case AMR_SGRCodeBgBlue:
476
+            case AMR_SGRCodeFgBlue:
477
+            case AMR_SGRCodeBgMagenta:
478
+            case AMR_SGRCodeFgMagenta:
479
+            case AMR_SGRCodeBgCyan:
480
+            case AMR_SGRCodeFgCyan:
481
+            case AMR_SGRCodeBgWhite:
482
+            case AMR_SGRCodeFgWhite:
483
+            case AMR_SGRCodeBgBrightBlack:
484
+            case AMR_SGRCodeFgBrightBlack:
485
+            case AMR_SGRCodeBgBrightRed:
486
+            case AMR_SGRCodeFgBrightRed:
487
+            case AMR_SGRCodeBgBrightGreen:
488
+            case AMR_SGRCodeFgBrightGreen:
489
+            case AMR_SGRCodeBgBrightYellow:
490
+            case AMR_SGRCodeFgBrightYellow:
491
+            case AMR_SGRCodeBgBrightBlue:
492
+            case AMR_SGRCodeFgBrightBlue:
493
+            case AMR_SGRCodeBgBrightMagenta:
494
+            case AMR_SGRCodeFgBrightMagenta:
495
+            case AMR_SGRCodeBgBrightCyan:
496
+            case AMR_SGRCodeFgBrightCyan:
497
+            case AMR_SGRCodeBgBrightWhite:
498
+            case AMR_SGRCodeFgBrightWhite:
499
+                thisAttributeValue = [self colorForSGRCode:thisCode];
500
+                break;
501
+            case AMR_SGRCodeIntensityBold:
502
+                {
503
+                NSFont *boldFont = [NSFontManager.sharedFontManager convertFont:self.font toHaveTrait:NSBoldFontMask];
504
+                thisAttributeValue = boldFont;
505
+                }
506
+                break;
507
+            case AMR_SGRCodeIntensityNormal:
508
+            case AMR_SGRCodeIntensityFaint:
509
+                {
510
+                NSFont *unboldFont = [NSFontManager.sharedFontManager convertFont:self.font toHaveTrait:NSUnboldFontMask];
511
+                thisAttributeValue = unboldFont;
512
+                }
513
+                break;
514
+            case AMR_SGRCodeUnderlineSingle:
515
+                thisAttributeValue = @(NSUnderlineStyleSingle);
516
+                break;
517
+            case AMR_SGRCodeUnderlineDouble:
518
+                thisAttributeValue = @(NSUnderlineStyleDouble);
519
+                break;
520
+            case AMR_SGRCodeUnderlineNone:
521
+                thisAttributeValue = @(NSUnderlineStyleNone);
522
+                break;
523
+            case AMR_SGRCodeAllReset:
524
+            case AMR_SGRCodeFgReset:
525
+            case AMR_SGRCodeBgReset:
526
+            case AMR_SGRCodeNoneOrInvalid:
527
+            case AMR_SGRCodeItalicOn:
528
+                break;
529
+        }
530
+
531
+
532
+        // find the next sequence that specifies the end of this formatting run
533
+        NSInteger formattingRunEndLocation = -1;
534
+        if (iCode < (formatCodes.count - 1))
535
+        {
536
+            NSDictionary *thisEndCodeCandidateDict;
537
+            unichar thisEndCodeCandidate;
538
+            for (NSUInteger iEndCode = iCode+1; iEndCode < formatCodes.count; iEndCode++)
539
+            {
540
+                thisEndCodeCandidateDict = formatCodes[iEndCode];
541
+                thisEndCodeCandidate = [thisEndCodeCandidateDict[kAMRCodeDictKey_code] unsignedIntValue];
542
+
543
+                if ([self AMR_SGRCode:thisEndCodeCandidate endsFormattingIntroducedByCode:thisCode])
544
+                {
545
+                    formattingRunEndLocation = [thisEndCodeCandidateDict[kAMRCodeDictKey_location] unsignedIntegerValue];
546
+                    break;
547
+                }
548
+            }
549
+        }
550
+        if (formattingRunEndLocation == -1)
551
+            formattingRunEndLocation = cleanString.length;
552
+        
553
+        if (thisAttributeName && thisAttributeValue)
554
+        {
555
+            [attrsAndRanges addObject:@{
556
+                kAMRAttrDictKey_range: [NSValue valueWithRange:NSMakeRange(formattingRunStartLocation, (formattingRunEndLocation-formattingRunStartLocation))],
557
+             kAMRAttrDictKey_attrName: thisAttributeName,
558
+            kAMRAttrDictKey_attrValue: thisAttributeValue,
559
+             }];
560
+        }
561
+    }
562
+
563
+    if (aCleanString)
564
+        *aCleanString = cleanString;
565
+    return attrsAndRanges;
566
+}
567
+
568
+
569
+
570
+
571
+
572
+- (BOOL) AMR_SGRCode:(AMR_SGRCode)endCode endsFormattingIntroducedByCode:(AMR_SGRCode)startCode
573
+{
574
+    switch(startCode)
575
+    {
576
+        case AMR_SGRCodeFgBlack:
577
+        case AMR_SGRCodeFgRed:
578
+        case AMR_SGRCodeFgGreen:
579
+        case AMR_SGRCodeFgYellow:
580
+        case AMR_SGRCodeFgBlue:
581
+        case AMR_SGRCodeFgMagenta:
582
+        case AMR_SGRCodeFgCyan:
583
+        case AMR_SGRCodeFgWhite:
584
+        case AMR_SGRCodeFgBrightBlack:
585
+        case AMR_SGRCodeFgBrightRed:
586
+        case AMR_SGRCodeFgBrightGreen:
587
+        case AMR_SGRCodeFgBrightYellow:
588
+        case AMR_SGRCodeFgBrightBlue:
589
+        case AMR_SGRCodeFgBrightMagenta:
590
+        case AMR_SGRCodeFgBrightCyan:
591
+        case AMR_SGRCodeFgBrightWhite:
592
+            return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeFgReset ||
593
+                    endCode == AMR_SGRCodeFgBlack || endCode == AMR_SGRCodeFgRed ||
594
+                    endCode == AMR_SGRCodeFgGreen || endCode == AMR_SGRCodeFgYellow ||
595
+                    endCode == AMR_SGRCodeFgBlue || endCode == AMR_SGRCodeFgMagenta ||
596
+                    endCode == AMR_SGRCodeFgCyan || endCode == AMR_SGRCodeFgWhite ||
597
+                    endCode == AMR_SGRCodeFgBrightBlack || endCode == AMR_SGRCodeFgBrightRed ||
598
+                    endCode == AMR_SGRCodeFgBrightGreen || endCode == AMR_SGRCodeFgBrightYellow ||
599
+                    endCode == AMR_SGRCodeFgBrightBlue || endCode == AMR_SGRCodeFgBrightMagenta ||
600
+                    endCode == AMR_SGRCodeFgBrightCyan || endCode == AMR_SGRCodeFgBrightWhite);
601
+        case AMR_SGRCodeBgBlack:
602
+        case AMR_SGRCodeBgRed:
603
+        case AMR_SGRCodeBgGreen:
604
+        case AMR_SGRCodeBgYellow:
605
+        case AMR_SGRCodeBgBlue:
606
+        case AMR_SGRCodeBgMagenta:
607
+        case AMR_SGRCodeBgCyan:
608
+        case AMR_SGRCodeBgWhite:
609
+        case AMR_SGRCodeBgBrightBlack:
610
+        case AMR_SGRCodeBgBrightRed:
611
+        case AMR_SGRCodeBgBrightGreen:
612
+        case AMR_SGRCodeBgBrightYellow:
613
+        case AMR_SGRCodeBgBrightBlue:
614
+        case AMR_SGRCodeBgBrightMagenta:
615
+        case AMR_SGRCodeBgBrightCyan:
616
+        case AMR_SGRCodeBgBrightWhite:
617
+            return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeBgReset ||
618
+                    endCode == AMR_SGRCodeBgBlack || endCode == AMR_SGRCodeBgRed ||
619
+                    endCode == AMR_SGRCodeBgGreen || endCode == AMR_SGRCodeBgYellow ||
620
+                    endCode == AMR_SGRCodeBgBlue || endCode == AMR_SGRCodeBgMagenta ||
621
+                    endCode == AMR_SGRCodeBgCyan || endCode == AMR_SGRCodeBgWhite ||
622
+                    endCode == AMR_SGRCodeBgBrightBlack || endCode == AMR_SGRCodeBgBrightRed ||
623
+                    endCode == AMR_SGRCodeBgBrightGreen || endCode == AMR_SGRCodeBgBrightYellow ||
624
+                    endCode == AMR_SGRCodeBgBrightBlue || endCode == AMR_SGRCodeBgBrightMagenta ||
625
+                    endCode == AMR_SGRCodeBgBrightCyan || endCode == AMR_SGRCodeBgBrightWhite);
626
+        case AMR_SGRCodeIntensityBold:
627
+        case AMR_SGRCodeIntensityNormal:
628
+            return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeIntensityNormal ||
629
+                    endCode == AMR_SGRCodeIntensityBold || endCode == AMR_SGRCodeIntensityFaint);
630
+        case AMR_SGRCodeUnderlineSingle:
631
+        case AMR_SGRCodeUnderlineDouble:
632
+            return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeUnderlineNone ||
633
+                    endCode == AMR_SGRCodeUnderlineSingle || endCode == AMR_SGRCodeUnderlineDouble);
634
+        case AMR_SGRCodeNoneOrInvalid:
635
+        case AMR_SGRCodeItalicOn:
636
+        case AMR_SGRCodeUnderlineNone:
637
+        case AMR_SGRCodeIntensityFaint:
638
+        case AMR_SGRCodeAllReset:
639
+        case AMR_SGRCodeBgReset:
640
+        case AMR_SGRCodeFgReset:
641
+            return NO;
642
+    }
643
+
644
+    return NO;
645
+}
646
+
647
+
648
+
649
+
650
+- (NSColor*) colorForSGRCode:(AMR_SGRCode)code
651
+{
652
+    if (self.ansiColors)
653
+    {
654
+        NSColor *preferredColor = self.ansiColors[@(code)];
655
+        if (preferredColor)
656
+            return preferredColor;
657
+    }
658
+
659
+    switch(code)
660
+    {
661
+        case AMR_SGRCodeFgBlack:
662
+            return kDefaultANSIColorFgBlack;
663
+        case AMR_SGRCodeFgRed:
664
+            return kDefaultANSIColorFgRed;
665
+        case AMR_SGRCodeFgGreen:
666
+            return kDefaultANSIColorFgGreen;
667
+        case AMR_SGRCodeFgYellow:
668
+            return kDefaultANSIColorFgYellow;
669
+        case AMR_SGRCodeFgBlue:
670
+            return kDefaultANSIColorFgBlue;
671
+        case AMR_SGRCodeFgMagenta:
672
+            return kDefaultANSIColorFgMagenta;
673
+        case AMR_SGRCodeFgCyan:
674
+            return kDefaultANSIColorFgCyan;
675
+        case AMR_SGRCodeFgWhite:
676
+            return kDefaultANSIColorFgWhite;
677
+        case AMR_SGRCodeFgBrightBlack:
678
+            return kDefaultANSIColorFgBrightBlack;
679
+        case AMR_SGRCodeFgBrightRed:
680
+            return kDefaultANSIColorFgBrightRed;
681
+        case AMR_SGRCodeFgBrightGreen:
682
+            return kDefaultANSIColorFgBrightGreen;
683
+        case AMR_SGRCodeFgBrightYellow:
684
+            return kDefaultANSIColorFgBrightYellow;
685
+        case AMR_SGRCodeFgBrightBlue:
686
+            return kDefaultANSIColorFgBrightBlue;
687
+        case AMR_SGRCodeFgBrightMagenta:
688
+            return kDefaultANSIColorFgBrightMagenta;
689
+        case AMR_SGRCodeFgBrightCyan:
690
+            return kDefaultANSIColorFgBrightCyan;
691
+        case AMR_SGRCodeFgBrightWhite:
692
+            return kDefaultANSIColorFgBrightWhite;
693
+        case AMR_SGRCodeBgBlack:
694
+            return kDefaultANSIColorBgBlack;
695
+        case AMR_SGRCodeBgRed:
696
+            return kDefaultANSIColorBgRed;
697
+        case AMR_SGRCodeBgGreen:
698
+            return kDefaultANSIColorBgGreen;
699
+        case AMR_SGRCodeBgYellow:
700
+            return kDefaultANSIColorBgYellow;
701
+        case AMR_SGRCodeBgBlue:
702
+            return kDefaultANSIColorBgBlue;
703
+        case AMR_SGRCodeBgMagenta:
704
+            return kDefaultANSIColorBgMagenta;
705
+        case AMR_SGRCodeBgCyan:
706
+            return kDefaultANSIColorBgCyan;
707
+        case AMR_SGRCodeBgWhite:
708
+            return kDefaultANSIColorBgWhite;
709
+        case AMR_SGRCodeBgBrightBlack:
710
+            return kDefaultANSIColorBgBrightBlack;
711
+        case AMR_SGRCodeBgBrightRed:
712
+            return kDefaultANSIColorBgBrightRed;
713
+        case AMR_SGRCodeBgBrightGreen:
714
+            return kDefaultANSIColorBgBrightGreen;
715
+        case AMR_SGRCodeBgBrightYellow:
716
+            return kDefaultANSIColorBgBrightYellow;
717
+        case AMR_SGRCodeBgBrightBlue:
718
+            return kDefaultANSIColorBgBrightBlue;
719
+        case AMR_SGRCodeBgBrightMagenta:
720
+            return kDefaultANSIColorBgBrightMagenta;
721
+        case AMR_SGRCodeBgBrightCyan:
722
+            return kDefaultANSIColorBgBrightCyan;
723
+        case AMR_SGRCodeBgBrightWhite:
724
+            return kDefaultANSIColorBgBrightWhite;
725
+        case AMR_SGRCodeNoneOrInvalid:
726
+        case AMR_SGRCodeItalicOn:
727
+        case AMR_SGRCodeUnderlineNone:
728
+        case AMR_SGRCodeIntensityFaint:
729
+        case AMR_SGRCodeAllReset:
730
+        case AMR_SGRCodeBgReset:
731
+        case AMR_SGRCodeFgReset:
732
+        case AMR_SGRCodeIntensityBold:
733
+        case AMR_SGRCodeIntensityNormal:
734
+        case AMR_SGRCodeUnderlineSingle:
735
+        case AMR_SGRCodeUnderlineDouble:
736
+            break;
737
+    }
738
+
739
+    return kDefaultANSIColorFgBlack;
740
+}
741
+
742
+
743
+- (AMR_SGRCode) AMR_SGRCodeForColor:(NSColor*)aColor isForegroundColor:(BOOL)aForeground
744
+{
745
+    if (self.ansiColors)
746
+    {
747
+        NSArray *codesForGivenColor = [self.ansiColors allKeysForObject:aColor];
748
+
749
+        if (codesForGivenColor != nil && 0 < codesForGivenColor.count)
750
+        {
751
+            for (NSNumber *thisCode in codesForGivenColor)
752
+            {
753
+                BOOL thisIsForegroundColor = (thisCode.intValue < 40);
754
+                if (aForeground == thisIsForegroundColor)
755
+                    return thisCode.intValue;
756
+            }
757
+        }
758
+    }
759
+
760
+    if (aForeground)
761
+    {
762
+        if ([aColor isEqual:kDefaultANSIColorFgBlack])
763
+            return AMR_SGRCodeFgBlack;
764
+        else if ([aColor isEqual:kDefaultANSIColorFgRed])
765
+            return AMR_SGRCodeFgRed;
766
+        else if ([aColor isEqual:kDefaultANSIColorFgGreen])
767
+            return AMR_SGRCodeFgGreen;
768
+        else if ([aColor isEqual:kDefaultANSIColorFgYellow])
769
+            return AMR_SGRCodeFgYellow;
770
+        else if ([aColor isEqual:kDefaultANSIColorFgBlue])
771
+            return AMR_SGRCodeFgBlue;
772
+        else if ([aColor isEqual:kDefaultANSIColorFgMagenta])
773
+            return AMR_SGRCodeFgMagenta;
774
+        else if ([aColor isEqual:kDefaultANSIColorFgCyan])
775
+            return AMR_SGRCodeFgCyan;
776
+        else if ([aColor isEqual:kDefaultANSIColorFgWhite])
777
+            return AMR_SGRCodeFgWhite;
778
+        else if ([aColor isEqual:kDefaultANSIColorFgBrightBlack])
779
+            return AMR_SGRCodeFgBrightBlack;
780
+        else if ([aColor isEqual:kDefaultANSIColorFgBrightRed])
781
+            return AMR_SGRCodeFgBrightRed;
782
+        else if ([aColor isEqual:kDefaultANSIColorFgBrightGreen])
783
+            return AMR_SGRCodeFgBrightGreen;
784
+        else if ([aColor isEqual:kDefaultANSIColorFgBrightYellow])
785
+            return AMR_SGRCodeFgBrightYellow;
786
+        else if ([aColor isEqual:kDefaultANSIColorFgBrightBlue])
787
+            return AMR_SGRCodeFgBrightBlue;
788
+        else if ([aColor isEqual:kDefaultANSIColorFgBrightMagenta])
789
+            return AMR_SGRCodeFgBrightMagenta;
790
+        else if ([aColor isEqual:kDefaultANSIColorFgBrightCyan])
791
+            return AMR_SGRCodeFgBrightCyan;
792
+        else if ([aColor isEqual:kDefaultANSIColorFgBrightWhite])
793
+            return AMR_SGRCodeFgBrightWhite;
794
+    }
795
+    else
796
+    {
797
+        if ([aColor isEqual:kDefaultANSIColorBgBlack])
798
+            return AMR_SGRCodeBgBlack;
799
+        else if ([aColor isEqual:kDefaultANSIColorBgRed])
800
+            return AMR_SGRCodeBgRed;
801
+        else if ([aColor isEqual:kDefaultANSIColorBgGreen])
802
+            return AMR_SGRCodeBgGreen;
803
+        else if ([aColor isEqual:kDefaultANSIColorBgYellow])
804
+            return AMR_SGRCodeBgYellow;
805
+        else if ([aColor isEqual:kDefaultANSIColorBgBlue])
806
+            return AMR_SGRCodeBgBlue;
807
+        else if ([aColor isEqual:kDefaultANSIColorBgMagenta])
808
+            return AMR_SGRCodeBgMagenta;
809
+        else if ([aColor isEqual:kDefaultANSIColorBgCyan])
810
+            return AMR_SGRCodeBgCyan;
811
+        else if ([aColor isEqual:kDefaultANSIColorBgWhite])
812
+            return AMR_SGRCodeBgWhite;
813
+        else if ([aColor isEqual:kDefaultANSIColorBgBrightBlack])
814
+            return AMR_SGRCodeBgBrightBlack;
815
+        else if ([aColor isEqual:kDefaultANSIColorBgBrightRed])
816
+            return AMR_SGRCodeBgBrightRed;
817
+        else if ([aColor isEqual:kDefaultANSIColorBgBrightGreen])
818
+            return AMR_SGRCodeBgBrightGreen;
819
+        else if ([aColor isEqual:kDefaultANSIColorBgBrightYellow])
820
+            return AMR_SGRCodeBgBrightYellow;
821
+        else if ([aColor isEqual:kDefaultANSIColorBgBrightBlue])
822
+            return AMR_SGRCodeBgBrightBlue;
823
+        else if ([aColor isEqual:kDefaultANSIColorBgBrightMagenta])
824
+            return AMR_SGRCodeBgBrightMagenta;
825
+        else if ([aColor isEqual:kDefaultANSIColorBgBrightCyan])
826
+            return AMR_SGRCodeBgBrightCyan;
827
+        else if ([aColor isEqual:kDefaultANSIColorBgBrightWhite])
828
+            return AMR_SGRCodeBgBrightWhite;
829
+    }
830
+
831
+    return AMR_SGRCodeNoneOrInvalid;
832
+}
833
+
834
+
835
+
836
+// helper struct typedef and a few functions for
837
+// -closestSGRCodeForColor:isForegroundColor:
838
+
839
+typedef struct {
840
+    CGFloat hue;
841
+    CGFloat saturation;
842
+    CGFloat brightness;
843
+} AMR_HSB;
844
+
845
+AMR_HSB makeHSB(CGFloat hue, CGFloat saturation, CGFloat brightness)
846
+{
847
+    AMR_HSB outHSB;
848
+    outHSB.hue = hue;
849
+    outHSB.saturation = saturation;
850
+    outHSB.brightness = brightness;
851
+    return outHSB;
852
+}
853
+
854
+AMR_HSB getHSBFromColor(NSColor *color)
855
+{
856
+    CGFloat hue = 0.0;
857
+    CGFloat saturation = 0.0;
858
+    CGFloat brightness = 0.0;
859
+    [[color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]
860
+        getHue:&hue
861
+        saturation:&saturation
862
+        brightness:&brightness
863
+        alpha:NULL
864
+        ];
865
+    return makeHSB(hue, saturation, brightness);
866
+}
867
+
868
+BOOL floatsEqual(CGFloat first, CGFloat second, CGFloat maxAbsError)
869
+{
870
+    return (fabs(first-second)) < maxAbsError;
871
+}
872
+
873
+#define MAX_HUE_FLOAT_EQUALITY_ABS_ERROR 0.000001
874
+
875
+- (AMR_SGRCode) closestSGRCodeForColor:(NSColor *)color isForegroundColor:(BOOL)foreground
876
+{
877
+    if (color == nil)
878
+        return AMR_SGRCodeNoneOrInvalid;
879
+
880
+    AMR_SGRCode closestColorSGRCode = [self AMR_SGRCodeForColor:color isForegroundColor:foreground];
881
+    if (closestColorSGRCode != AMR_SGRCodeNoneOrInvalid)
882
+        return closestColorSGRCode;
883
+
884
+    AMR_HSB givenColorHSB = getHSBFromColor(color);
885
+
886
+    CGFloat closestColorHueDiff = FLT_MAX;
887
+    CGFloat closestColorSaturationDiff = FLT_MAX;
888
+    CGFloat closestColorBrightnessDiff = FLT_MAX;
889
+
890
+    // (background SGR codes are +10 from foreground ones:)
891
+    NSUInteger AMR_SGRCodeShift = (foreground)?0:10;
892
+    NSArray *ansiFgColorCodes = @[
893
+    @(AMR_SGRCodeFgBlack+AMR_SGRCodeShift),
894
+    @(AMR_SGRCodeFgRed+AMR_SGRCodeShift),
895
+    @(AMR_SGRCodeFgGreen+AMR_SGRCodeShift),
896
+    @(AMR_SGRCodeFgYellow+AMR_SGRCodeShift),
897
+    @(AMR_SGRCodeFgBlue+AMR_SGRCodeShift),
898
+    @(AMR_SGRCodeFgMagenta+AMR_SGRCodeShift),
899
+    @(AMR_SGRCodeFgCyan+AMR_SGRCodeShift),
900
+    @(AMR_SGRCodeFgWhite+AMR_SGRCodeShift),
901
+    @(AMR_SGRCodeFgBrightBlack+AMR_SGRCodeShift),
902
+    @(AMR_SGRCodeFgBrightRed+AMR_SGRCodeShift),
903
+    @(AMR_SGRCodeFgBrightGreen+AMR_SGRCodeShift),
904
+    @(AMR_SGRCodeFgBrightYellow+AMR_SGRCodeShift),
905
+    @(AMR_SGRCodeFgBrightBlue+AMR_SGRCodeShift),
906
+    @(AMR_SGRCodeFgBrightMagenta+AMR_SGRCodeShift),
907
+    @(AMR_SGRCodeFgBrightCyan+AMR_SGRCodeShift),
908
+    @(AMR_SGRCodeFgBrightWhite+AMR_SGRCodeShift),
909
+    ];
910
+    for (NSNumber *thisSGRCodeNumber in ansiFgColorCodes)
911
+    {
912
+        AMR_SGRCode thisSGRCode = thisSGRCodeNumber.intValue;
913
+        NSColor *thisColor = [self colorForSGRCode:thisSGRCode];
914
+
915
+        AMR_HSB thisColorHSB = getHSBFromColor(thisColor);
916
+
917
+        CGFloat hueDiff = fabs(givenColorHSB.hue - thisColorHSB.hue);
918
+        CGFloat saturationDiff = fabs(givenColorHSB.saturation - thisColorHSB.saturation);
919
+        CGFloat brightnessDiff = fabs(givenColorHSB.brightness - thisColorHSB.brightness);
920
+
921
+        // comparison depends on hue, saturation and brightness
922
+        // (strictly in that order):
923
+
924
+        if (!floatsEqual(hueDiff, closestColorHueDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR))
925
+        {
926
+            if (hueDiff > closestColorHueDiff)
927
+                continue;
928
+            closestColorSGRCode = thisSGRCode;
929
+            closestColorHueDiff = hueDiff;
930
+            closestColorSaturationDiff = saturationDiff;
931
+            closestColorBrightnessDiff = brightnessDiff;
932
+            continue;
933
+        }
934
+
935
+        if (!floatsEqual(saturationDiff, closestColorSaturationDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR))
936
+        {
937
+            if (saturationDiff > closestColorSaturationDiff)
938
+                continue;
939
+            closestColorSGRCode = thisSGRCode;
940
+            closestColorHueDiff = hueDiff;
941
+            closestColorSaturationDiff = saturationDiff;
942
+            closestColorBrightnessDiff = brightnessDiff;
943
+            continue;
944
+        }
945
+
946
+        if (!floatsEqual(brightnessDiff, closestColorBrightnessDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR))
947
+        {
948
+            if (brightnessDiff > closestColorBrightnessDiff)
949
+                continue;
950
+            closestColorSGRCode = thisSGRCode;
951
+            closestColorHueDiff = hueDiff;
952
+            closestColorSaturationDiff = saturationDiff;
953
+            closestColorBrightnessDiff = brightnessDiff;
954
+            continue;
955
+        }
956
+
957
+        // If hue (especially hue!), saturation and brightness diffs all
958
+        // are equal to some other color, we need to prefer one or the
959
+        // other so we'll select the more 'distinctive' color of the
960
+        // two (this is *very* subjective, obviously). I basically just
961
+        // looked at the hue chart, went through all the points between
962
+        // our main ANSI colors and decided which side the middle point
963
+        // would lean on. (e.g. the purple color that is exactly between
964
+        // the blue and magenta ANSI colors looks more magenta than
965
+        // blue to me so I put magenta higher than blue in the list
966
+        // below.)
967
+        //
968
+        // subjective ordering of colors from most to least 'distinctive':
969
+        int colorDistinctivenessOrder[6] = {
970
+            AMR_SGRCodeFgRed+(int)AMR_SGRCodeShift,
971
+            AMR_SGRCodeFgMagenta+(int)AMR_SGRCodeShift,
972
+            AMR_SGRCodeFgBlue+(int)AMR_SGRCodeShift,
973
+            AMR_SGRCodeFgGreen+(int)AMR_SGRCodeShift,
974
+            AMR_SGRCodeFgCyan+(int)AMR_SGRCodeShift,
975
+            AMR_SGRCodeFgYellow+(int)AMR_SGRCodeShift
976
+            };
977
+        for (int i = 0; i < 6; i++)
978
+        {
979
+            if (colorDistinctivenessOrder[i] == closestColorSGRCode)
980
+                break;
981
+            else if (colorDistinctivenessOrder[i] == thisSGRCode)
982
+            {
983
+                closestColorSGRCode = thisSGRCode;
984
+                closestColorHueDiff = hueDiff;
985
+                closestColorSaturationDiff = saturationDiff;
986
+                closestColorBrightnessDiff = brightnessDiff;
987
+            }
988
+        }
989
+    }
990
+
991
+    return closestColorSGRCode;
992
+}
993
+
994
+
995
+
996
+@end
0 997
new file mode 100644
... ...
@@ -0,0 +1,50 @@
1
+
2
+DESCRIPTION:
3
+--------------------------------
4
+
5
+ANSIEscapeHelper is an Objective-C class for dealing with ANSI escape
6
+sequences. Its main purpose is to translate between NSStrings
7
+that contain ANSI escape sequences and similarly formatted
8
+NSAttributedStrings.
9
+
10
+The headerdoc directory contains the API documentation in HTML format.
11
+
12
+If you fix something in this code or use it in your software, please
13
+let me know -- I'm curious ;). You can contact me at http://hasseg.org.
14
+Thanks.
15
+
16
+
17
+Copyright 2009 Ali Rantakari
18
+http://hasseg.org/ansiEscapeHelper
19
+
20
+
21
+
22
+
23
+
24
+LICENSE:
25
+--------------------------------
26
+
27
+The MIT License
28
+
29
+Copyright (c) 2009 Ali Rantakari
30
+
31
+Permission is hereby granted, free of charge, to any person obtaining a copy
32
+of this software and associated documentation files (the "Software"), to deal
33
+in the Software without restriction, including without limitation the rights
34
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
35
+copies of the Software, and to permit persons to whom the Software is
36
+furnished to do so, subject to the following conditions:
37
+
38
+The above copyright notice and this permission notice shall be included in
39
+all copies or substantial portions of the Software.
40
+
41
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
42
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
43
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
44
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
45
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
46
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
47
+THE SOFTWARE.
48
+
49
+
50
+
0 51
new file mode 100644
... ...
@@ -0,0 +1,19 @@
1
+//
2
+//  SearchViewController.h
3
+//  Today Scripts
4
+//
5
+//  Created by Sam Rothenberg on 10/21/14.
6
+//  Copyright (c) 2014 Sam Rothenberg. All rights reserved.
7
+//
8
+
9
+#import "TodayScript.h"
10
+
11
+@interface EditViewController : NCWidgetSearchViewController
12
+
13
+- (void)editScript:(TodayScript *)script;
14
+- (void)createScript;
15
+- (void)cancelScript;
16
+
17
+@end
18
+
19
+@interface EditViewProgramField : NSTextField @end
0 20
new file mode 100644
... ...
@@ -0,0 +1,162 @@
1
+//
2
+//  SearchViewController.m
3
+//  Today Scripts
4
+//
5
+//  Created by Sam Rothenberg on 10/21/14.
6
+//  Copyright (c) 2014 Sam Rothenberg. All rights reserved.
7
+//
8
+
9
+#import <Cocoa/Cocoa.h>
10
+
11
+#import "EditViewController.h"
12
+#import "TodayViewController.h"
13
+
14
+@implementation EditViewController
15
+{
16
+    IBOutlet NSTextField *labelField;
17
+    IBOutlet EditViewProgramField *programField;
18
+    IBOutlet NSTextView *scriptField;
19
+    IBOutlet NSButton *autoRunButton;
20
+    IBOutlet NSButton *showStatusButton;
21
+    IBOutlet NSButton *saveButton;
22
+
23
+    NSString *defaultProgram;
24
+
25
+    TodayScript *script;
26
+}
27
+
28
+- (void)viewDidLoad
29
+{
30
+    [super viewDidLoad];
31
+
32
+    // Set up the appearence of the script field.
33
+    scriptField.font = [NSFont fontWithName:@"Menlo" size:10];
34
+    scriptField.textColor = [NSColor colorWithWhite:1 alpha:1];
35
+    scriptField.backgroundColor = NSColor.clearColor;
36
+    // Disable all substitutions in the script field.
37
+    scriptField.automaticDashSubstitutionEnabled   = NO;
38
+    scriptField.automaticQuoteSubstitutionEnabled  = NO;
39
+    scriptField.automaticTextReplacementEnabled    = NO;
40
+    scriptField.automaticLinkDetectionEnabled      = NO;
41
+    scriptField.automaticSpellingCorrectionEnabled = NO;
42
+    scriptField.automaticDataDetectionEnabled      = NO;
43
+}
44
+
45
+- (void)editScript:(TodayScript *)existingScript
46
+{
47
+    // Show ourselves in the widget.
48
+    [todayViewController presentViewControllerInWidget:self];
49
+    // Set the button's title to designate that we are editing a script.
50
+    saveButton.title = @"Save Script";
51
+
52
+    // Set our script variable to the script passed to us.
53
+    script = existingScript;
54
+
55
+    // Set the values in our form to those of the script.
56
+    labelField.stringValue = script.label;
57
+    programField.stringValue = script.program;
58
+    scriptField.string = script.script;
59
+    autoRunButton.state = script.autoRun ? NSOnState : NSOffState;
60
+    showStatusButton.state = script.showStatus ? NSOnState : NSOffState;
61
+}
62
+
63
+- (void)createScript
64
+{
65
+    // We will not be working with an existing script.
66
+    script = nil;
67
+
68
+    // Show ourselves in the widget.
69
+    [todayViewController presentViewControllerInWidget:self];
70
+    // Set the button's title to designate that we are creating a script.
71
+    saveButton.title = @"Add Script";
72
+
73
+    // Set up our fields with the default values.
74
+    labelField.stringValue = @"";
75
+    programField.stringValue = NSProcessInfo.processInfo.environment[@"SHELL"];
76
+    scriptField.string = @"";
77
+    autoRunButton.state = NSOnState;
78
+    showStatusButton.state = NSOnState;
79
+}
80
+
81
+// Method invoked when user presses the "Add Script" button.
82
+- (IBAction)saveScript:(id)sender
83
+{
84
+    // If the interpreter is not a valid executable file, style the text to
85
+    // indicate the error to the user, then abort.
86
+    NSString *programString = programField.stringValue;
87
+    if (! [NSFileManager.defaultManager isExecutableFileAtPath:programString]) {
88
+        programField.textColor = [NSColor colorWithRed:1.0 green:0.2 blue:0.2 alpha:1.0];
89
+        return;
90
+    }
91
+
92
+    // If we were not given an existing dictionary to modify, set that up to
93
+    // work with. Otherwise, create a new one.
94
+    TodayScript *newScript = script ?: [[TodayScript alloc] init];
95
+
96
+    newScript.program = programString.copy;
97
+
98
+    // Set the script to the dictionary if the user provided one. Otherwise,
99
+    // remove any which may have previously existed.
100
+    newScript.script = scriptField.string.copy;
101
+
102
+    // Set the script's title to the user provided one.
103
+    newScript.label = labelField.stringValue.copy;
104
+    // If a title was not provided, use the text of the script, or the name of
105
+    // the program itself if there is no script.
106
+    if (! newScript.label.length)
107
+        newScript.label = newScript.script.length ? newScript.script : newScript.program;
108
+
109
+    // If the checkbox wasn't unchecked, this script is to be run automatically.
110
+    newScript.autoRun = (autoRunButton.state != NSOffState);
111
+
112
+    // If the checkbox wasn't unchecked, this script is to be run automatically.
113
+    newScript.showStatus = (showStatusButton.state != NSOffState);
114
+
115
+    // If we were given a script to work with, remove it from our form, make
116
+    // sure it it's stopped running, then update our defaults.
117
+    if (script)
118
+    {
119
+        script = nil;
120
+        [newScript terminate];
121
+        [TodayScriptArray.sharedScripts saveDefaults];
122
+    }
123
+    // Otherwise, add the new script to the list array and update our list view.
124
+    else {
125
+        [todayViewController.arrayController addObject:newScript];
126
+        todayViewController.listViewController.contents =
127
+            todayViewController.arrayController.arrangedObjects;
128
+    }
129
+    // Hide ourselves.
130
+    [todayViewController dismissViewController:self];
131
+
132
+    // If the newly saved script is set to run automatically, do so now.
133
+    if (newScript.autoRun)
134
+        [newScript run];
135
+}
136
+
137
+- (void)cancelScript
138
+{
139
+    script = nil;
140
+    [todayViewController dismissViewController:self];
141
+}
142
+
143
+@end
144
+
145
+
146
+@implementation EditViewProgramField
147
+
148
+// When the user starts editing the program field, make sure the text color goes
149
+// back to normal in case it was previously changed to indicate an error.
150
+- (void)textDidBeginEditing:(NSNotification *)notification {
151
+    self.textColor = [NSColor colorWithWhite:1.0 alpha:1.0];
152
+}
153
+
154
+// If user enters an invalid program in the program field, set its text as red
155
+// to indicate this.
156
+- (void)textDidEndEditing:(NSNotification *)notification
157
+{
158
+    if (! [NSFileManager.defaultManager isExecutableFileAtPath:self.stringValue])
159
+        self.textColor = [NSColor colorWithRed:1.0 green:0.3 blue:0.3 alpha:1.0];
160
+}
161
+
162
+@end
0 163
new file mode 100644
... ...
@@ -0,0 +1,144 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="14A389" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
3
+    <dependencies>
4
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/>
5
+    </dependencies>
6
+    <objects>
7
+        <customObject id="-2" userLabel="File's Owner" customClass="EditViewController">
8
+            <connections>
9
+                <outlet property="autoRunButton" destination="xfS-In-uVf" id="kpc-do-dBD"/>
10
+                <outlet property="labelField" destination="Cw9-1N-gpc" id="XQR-8Y-lAx"/>
11
+                <outlet property="programField" destination="d8p-jf-eHv" id="x58-P6-Szz"/>
12
+                <outlet property="saveButton" destination="u1c-zf-cvc" id="Bgs-g5-JPM"/>
13
+                <outlet property="scriptField" destination="g23-5c-rM3" id="Bx8-Xp-IHD"/>
14
+                <outlet property="showStatusButton" destination="vcF-tM-LhU" id="1Ru-v6-0Kk"/>
15
+                <outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
16
+            </connections>
17
+        </customObject>
18
+        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
19
+        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
20
+        <customView id="Hz6-mo-xeY" userLabel="Edit View">
21
+            <rect key="frame" x="0.0" y="0.0" width="300" height="213"/>
22
+            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
23
+            <subviews>
24
+                <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WKU-4c-tEB" userLabel="Label Label">
25
+                    <rect key="frame" x="20" y="176" width="49" height="14"/>
26
+                    <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Label" id="zYG-jl-Xmw">
27
+                        <font key="font" metaFont="smallSystem"/>
28
+                        <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
29
+                        <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
30
+                    </textFieldCell>
31
+                </textField>
32
+                <textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Cw9-1N-gpc" userLabel="Label Field">
33
+                    <rect key="frame" x="75" y="174" width="205" height="19"/>
34
+                    <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" focusRingType="none" usesSingleLineMode="YES" id="Axj-1g-Ksw">
35
+                        <font key="font" metaFont="smallSystem"/>
36
+                        <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
37
+                        <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
38
+                    </textFieldCell>
39
+                    <connections>
40
+                        <action selector="performClick:" target="WKU-4c-tEB" id="sER-gA-QiZ"/>
41
+                    </connections>
42
+                </textField>
43
+                <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ppa-Tb-oBt" userLabel="Program Label">
44
+                    <rect key="frame" x="20" y="149" width="49" height="14"/>
45
+                    <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Program" id="YMK-m2-DXR">
46
+                        <font key="font" metaFont="smallSystem"/>
47
+                        <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
48
+                        <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
49
+                    </textFieldCell>
50
+                </textField>
51
+                <textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d8p-jf-eHv" userLabel="Program Field" customClass="EditViewProgramField">
52
+                    <rect key="frame" x="75" y="147" width="205" height="19"/>
53
+                    <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" focusRingType="none" usesSingleLineMode="YES" id="4gu-U4-CFG">
54
+                        <font key="font" metaFont="smallSystem"/>
55
+                        <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
56
+                        <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
57
+                    </textFieldCell>
58
+                    <connections>
59
+                        <action selector="performClick:" target="WKU-4c-tEB" id="wBo-d2-vss"/>
60
+                    </connections>
61
+                </textField>
62
+                <scrollView horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OVb-f0-amM" userLabel="Script Scroll">
63
+                    <rect key="frame" x="20" y="63" width="260" height="76"/>
64
+                    <clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="Fmm-9L-RgW" userLabel="Script Clip">
65
+                        <rect key="frame" x="1" y="1" width="223" height="133"/>
66
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
67
+                        <subviews>
68
+                            <textView importsGraphics="NO" richText="NO" allowsUndo="YES" verticallyResizable="YES" id="g23-5c-rM3" userLabel="Script View">
69
+                                <rect key="frame" x="0.0" y="0.0" width="223" height="133"/>
70
+                                <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
71
+                                <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
72
+                                <size key="minSize" width="258" height="74"/>
73
+                                <size key="maxSize" width="463" height="10000000"/>
74
+                                <color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
75
+                                <size key="minSize" width="258" height="74"/>
76
+                                <size key="maxSize" width="463" height="10000000"/>
77
+                            </textView>
78
+                        </subviews>
79
+                        <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
80
+                    </clipView>
81
+                    <constraints>
82
+                        <constraint firstAttribute="height" constant="76" id="qzD-ga-hai"/>
83
+                    </constraints>
84
+                    <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="RxR-jh-mRq">
85
+                        <rect key="frame" x="-100" y="-100" width="87" height="18"/>
86
+                        <autoresizingMask key="autoresizingMask"/>
87
+                    </scroller>
88
+                    <scroller key="verticalScroller" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="zBW-qB-PIF">
89
+                        <rect key="frame" x="224" y="1" width="15" height="133"/>
90
+                        <autoresizingMask key="autoresizingMask"/>
91
+                    </scroller>
92
+                </scrollView>
93
+                <button translatesAutoresizingMaskIntoConstraints="NO" id="xfS-In-uVf" userLabel="Auto Run Button">
94
+                    <rect key="frame" x="17" y="35" width="117" height="18"/>
95
+                    <buttonCell key="cell" type="check" title="Run automatically" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="b0u-sz-0IN">
96
+                        <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
97
+                        <font key="font" metaFont="smallSystem"/>
98
+                    </buttonCell>
99
+                </button>
100
+                <button translatesAutoresizingMaskIntoConstraints="NO" id="vcF-tM-LhU" userLabel="Show Status Button">
101
+                    <rect key="frame" x="17" y="17" width="137" height="18"/>
102
+                    <buttonCell key="cell" type="check" title="Show status indicator" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="SpY-3Z-Q6J">
103
+                        <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
104
+                        <font key="font" metaFont="smallSystem"/>
105
+                    </buttonCell>
106
+                </button>
107
+                <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="u1c-zf-cvc" userLabel="Save Button">
108
+                    <rect key="frame" x="202" y="14" width="83" height="28"/>
109
+                    <buttonCell key="cell" type="push" title="Add Script" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="1gZ-Fp-eku">
110
+                        <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
111
+                        <font key="font" metaFont="smallSystem"/>
112
+                    </buttonCell>
113
+                    <connections>
114
+                        <action selector="saveScript:" target="-2" id="nKv-Oy-P6z"/>
115
+                    </connections>
116
+                </button>
117
+            </subviews>
118
+            <constraints>
119
+                <constraint firstItem="ppa-Tb-oBt" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="22" id="49q-dw-byP"/>
120
+                <constraint firstAttribute="bottom" secondItem="vcF-tM-LhU" secondAttribute="bottom" constant="20" id="4X7-Ah-lqE"/>
121
+                <constraint firstAttribute="bottom" secondItem="u1c-zf-cvc" secondAttribute="bottom" constant="20" id="8QG-ta-FAH"/>
122
+                <constraint firstAttribute="trailing" secondItem="OVb-f0-amM" secondAttribute="trailing" constant="20" id="AEu-89-C2c"/>
123
+                <constraint firstItem="Cw9-1N-gpc" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="20" id="AxF-bN-coP"/>
124
+                <constraint firstItem="d8p-jf-eHv" firstAttribute="top" secondItem="Cw9-1N-gpc" secondAttribute="bottom" constant="8" id="Bve-CI-2ld"/>
125
+                <constraint firstItem="WKU-4c-tEB" firstAttribute="centerY" secondItem="Cw9-1N-gpc" secondAttribute="centerY" constant="0.5" id="Eto-Z2-p49"/>
126
+                <constraint firstItem="WKU-4c-tEB" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="22" id="GGe-nN-fTe"/>
127
+                <constraint firstItem="vcF-tM-LhU" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="20" id="Mgw-h4-kDB"/>
128
+                <constraint firstItem="OVb-f0-amM" firstAttribute="top" secondItem="d8p-jf-eHv" secondAttribute="bottom" constant="8" id="UGD-ts-dKz"/>
129
+                <constraint firstItem="Cw9-1N-gpc" firstAttribute="leading" secondItem="WKU-4c-tEB" secondAttribute="trailing" constant="8" id="YEm-DM-Tu9"/>
130
+                <constraint firstItem="xfS-In-uVf" firstAttribute="top" secondItem="OVb-f0-amM" secondAttribute="bottom" constant="13" id="aT1-mv-f76"/>
131
+                <constraint firstItem="ppa-Tb-oBt" firstAttribute="width" secondItem="WKU-4c-tEB" secondAttribute="width" id="asr-5j-TmC"/>
132
+                <constraint firstItem="OVb-f0-amM" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="20" id="fdc-EO-NSJ"/>
133
+                <constraint firstAttribute="trailing" secondItem="Cw9-1N-gpc" secondAttribute="trailing" constant="20" id="gnM-Fw-GZp"/>
134
+                <constraint firstItem="vcF-tM-LhU" firstAttribute="top" secondItem="xfS-In-uVf" secondAttribute="bottom" constant="6" id="lxH-us-63N"/>
135
+                <constraint firstAttribute="trailing" secondItem="d8p-jf-eHv" secondAttribute="trailing" constant="20" id="pHf-ne-zuS"/>
136
+                <constraint firstItem="xfS-In-uVf" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="20" id="q2u-73-JgO"/>
137
+                <constraint firstItem="d8p-jf-eHv" firstAttribute="centerY" secondItem="ppa-Tb-oBt" secondAttribute="centerY" constant="-0.5" id="rwG-Fu-XX1"/>
138
+                <constraint firstAttribute="trailing" secondItem="u1c-zf-cvc" secondAttribute="trailing" constant="20" id="vOR-em-HdA"/>
139
+                <constraint firstItem="d8p-jf-eHv" firstAttribute="leading" secondItem="ppa-Tb-oBt" secondAttribute="trailing" constant="8" id="wWK-WK-3gm"/>
140
+            </constraints>
141
+            <point key="canvasLocation" x="169" y="329.5"/>
142
+        </customView>
143
+    </objects>
144
+</document>
0 145
new file mode 100644
... ...
@@ -0,0 +1,58 @@
1
+{
2
+  "images" : [
3
+    {
4
+      "idiom" : "mac",
5
+      "scale" : "1x",
6
+      "size" : "16x16"
7
+    },
8
+    {
9
+      "idiom" : "mac",
10
+      "scale" : "2x",
11
+      "size" : "16x16"
12
+    },
13
+    {
14
+      "idiom" : "mac",
15
+      "scale" : "1x",
16
+      "size" : "32x32"
17
+    },
18
+    {
19
+      "idiom" : "mac",
20
+      "scale" : "2x",
21
+      "size" : "32x32"
22
+    },
23
+    {
24
+      "idiom" : "mac",
25
+      "scale" : "1x",
26
+      "size" : "128x128"
27
+    },
28
+    {
29
+      "idiom" : "mac",
30
+      "scale" : "2x",
31
+      "size" : "128x128"
32
+    },
33
+    {
34
+      "idiom" : "mac",
35
+      "scale" : "1x",
36
+      "size" : "256x256"
37
+    },
38
+    {
39
+      "idiom" : "mac",
40
+      "scale" : "2x",
41
+      "size" : "256x256"
42
+    },
43
+    {
44
+      "idiom" : "mac",
45
+      "scale" : "1x",
46
+      "size" : "512x512"
47
+    },
48
+    {
49
+      "idiom" : "mac",
50
+      "scale" : "2x",
51
+      "size" : "512x512"
52
+    }
53
+  ],
54
+  "info" : {
55
+    "version" : 1,
56
+    "author" : "xcode"
57
+  }
58
+}
0 59
\ No newline at end of file
1 60
new file mode 100644
... ...
@@ -0,0 +1,39 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+<plist version="1.0">
4
+<dict>
5
+	<key>CFBundleDevelopmentRegion</key>
6
+	<string>en</string>
7
+	<key>CFBundleDisplayName</key>
8
+	<string>Widget</string>
9
+	<key>CFBundleExecutable</key>
10
+	<string>$(EXECUTABLE_NAME)</string>
11
+	<key>CFBundleIdentifier</key>
12
+	<string>org.samroth.Today-Scripts.$(PRODUCT_NAME:rfc1034identifier)</string>
13
+	<key>CFBundleInfoDictionaryVersion</key>
14
+	<string>6.0</string>
15
+	<key>CFBundleName</key>
16
+	<string>$(PRODUCT_NAME)</string>
17
+	<key>CFBundlePackageType</key>
18
+	<string>XPC!</string>
19
+	<key>CFBundleShortVersionString</key>
20
+	<string>1.0</string>
21
+	<key>CFBundleSignature</key>
22
+	<string>????</string>
23
+	<key>CFBundleVersion</key>
24
+	<string>1</string>
25
+	<key>LSMinimumSystemVersion</key>
26
+	<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
27
+	<key>NSExtension</key>
28
+	<dict>
29
+		<key>NSExtensionPointIdentifier</key>
30
+		<string>com.apple.widget-extension</string>
31
+		<key>NSExtensionPrincipalClass</key>
32
+		<string>TodayViewController</string>
33
+		<key>com.apple.notificationcenter.widget.description</key>
34
+		<string>Widget</string>
35
+	</dict>
36
+	<key>NSHumanReadableCopyright</key>
37
+	<string>Copyright © 2014 Sam Rothenberg. All rights reserved.</string>
38
+</dict>
39
+</plist>
0 40
new file mode 100644
... ...
@@ -0,0 +1,14 @@
1
+//
2
+//  ListRowViewController.h
3
+//  Scripts
4
+//
5
+//  Created by Sam Rothenberg on 8/14/14.
6
+//  Copyright (c) 2014 Sam Rothenberg. All rights reserved.
7
+//
8
+
9
+#import <Cocoa/Cocoa.h>
10
+
11
+@interface ListRowViewController : NSViewController <NSTextViewDelegate>
12
+
13
+@end
14
+
0 15
new file mode 100644
... ...
@@ -0,0 +1,59 @@
1
+//
2
+//  ListRowViewController.m
3
+//  Scripts
4
+//
5
+//  Created by Sam Rothenberg on 8/14/14.
6
+//  Copyright (c) 2014 Sam Rothenberg. All rights reserved.
7
+//
8
+
9
+#import "ListRowViewController.h"
10
+#import "TodayScript.h"
11
+#import "TodayViewController.h"
12
+#import "EditViewController.h"
13
+
14
+@implementation ListRowViewController
15
+{
16
+    // Our UI objects.
17
+    IBOutlet NSTextView *outputView;
18
+    IBOutlet NSButton *editButton;
19
+}
20
+
21
+- (NSString *)nibName {
22
+    return @"ListRowViewController";
23
+}
24
+
25
+- (void)loadView
26
+{
27
+    [super loadView];
28
+
29
+    // Make the text cursor for the output view (mostly) invisible.
30
+    outputView.insertionPointColor = NSColor.clearColor;
31
+
32
+    // Make the edit button dim.
33
+    editButton.alphaValue = 0.1;
34
+}
35
+
36
+- (BOOL)textShouldBeginEditing:(NSText *)textObject {
37
+    // Prevent editing of the output field.
38
+    return NO;
39
+}
40
+
41
+
42
+- (IBAction)startOrStop:(id)sender
43
+{
44
+    // Get the current script we represent.
45
+    TodayScript *script = self.representedObject;
46
+    // Otherwise if the script is not running, we are being asked to run it.
47
+    if (script.running)
48
+        [script terminate];
49
+    // If it is running however, then we are being asked to terminate it.
50
+    else
51
+        [script run];
52
+}
53
+
54
+- (IBAction)edit:(id)sender {
55
+    // Get the current script we represent and tell the editor to open it.
56
+    [todayViewController.editViewController editScript:(TodayScript *)self.representedObject];
57
+}
58
+
59
+@end
0 60
new file mode 100644
... ...
@@ -0,0 +1,238 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="14A389" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
3
+    <dependencies>
4
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/>
5
+    </dependencies>
6
+    <objects>
7
+        <customObject id="-2" userLabel="File's Owner" customClass="ListRowViewController">
8
+            <connections>
9
+                <outlet property="editButton" destination="vmd-LL-kN7" id="XqT-yI-stO"/>
10
+                <outlet property="outputView" destination="uU1-7d-p6h" id="uCv-j7-UIA"/>
11
+                <outlet property="view" destination="Hz6-mo-xeY" id="yog-AM-u9s"/>
12
+            </connections>
13
+        </customObject>
14
+        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
15
+        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
16
+        <customView translatesAutoresizingMaskIntoConstraints="NO" id="Hz6-mo-xeY" userLabel="List Item View">
17
+            <rect key="frame" x="0.0" y="0.0" width="266" height="31"/>
18
+            <subviews>
19
+                <textField hidden="YES" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DqU-bT-PYy" userLabel="Output Field">
20
+                    <rect key="frame" x="-2" y="0.0" width="177" height="16"/>
21
+                    <constraints>
22
+                        <constraint firstAttribute="width" constant="173" id="4oG-yU-3d5"/>
23
+                        <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="16" id="AYa-dn-812"/>
24
+                    </constraints>
25
+                    <textFieldCell key="cell" sendsActionOnEndEditing="YES" placeholderString="" id="J4j-pX-hd4" userLabel="Output Cell">
26
+                        <font key="font" size="7" name="Menlo-Regular"/>
27
+                        <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
28
+                        <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
29
+                    </textFieldCell>
30
+                    <connections>
31
+                        <binding destination="-2" name="value" keyPath="self.representedObject.unescapedOutput" id="zmr-fB-CLu"/>
32
+                    </connections>
33
+                </textField>
34
+                <progressIndicator horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="q9k-hz-yg9" userLabel="Progress View">
35
+                    <rect key="frame" x="0.0" y="8" width="16" height="16"/>
36
+                    <connections>
37
+                        <binding destination="-2" name="animate" keyPath="representedObject.running" id="Alt-1H-Jjg">
38
+                            <dictionary key="options">
39
+                                <bool key="NSRaisesForNotApplicableKeys" value="NO"/>
40
+                            </dictionary>
41
+                        </binding>
42
+                    </connections>
43
+                </progressIndicator>
44
+                <imageView translatesAutoresizingMaskIntoConstraints="NO" id="8gs-d1-WuT" userLabel="Stopped View">
45
+                    <rect key="frame" x="2" y="10" width="12" height="12"/>
46
+                    <constraints>
47
+                        <constraint firstAttribute="height" constant="12" id="Rvr-8y-uDX"/>
48
+                        <constraint firstAttribute="width" constant="12" id="huW-m8-zEP"/>
49
+                    </constraints>
50
+                    <imageCell key="cell" refusesFirstResponder="YES" alignment="left" animates="YES" imageScaling="proportionallyDown" image="NSStatusPartiallyAvailable" id="Evm-gX-bb1"/>
51
+                    <connections>
52
+                        <binding destination="-2" name="hidden3" keyPath="representedObject.running" previousBinding="VYp-6y-9dH" id="SL8-Cp-EUc">
53
+                            <dictionary key="options">
54
+                                <integer key="NSMultipleValuesPlaceholder" value="-1"/>
55
+                                <integer key="NSNoSelectionPlaceholder" value="-1"/>
56
+                                <integer key="NSNotApplicablePlaceholder" value="-1"/>
57
+                                <integer key="NSNullPlaceholder" value="-1"/>
58
+                            </dictionary>
59
+                        </binding>
60
+                        <binding destination="-2" name="hidden2" keyPath="representedObject.showStatus" previousBinding="5a5-GO-fXA" id="VYp-6y-9dH">
61
+                            <dictionary key="options">
62
+                                <integer key="NSMultipleValuesPlaceholder" value="-1"/>
63
+                                <integer key="NSNoSelectionPlaceholder" value="-1"/>
64
+                                <integer key="NSNotApplicablePlaceholder" value="-1"/>
65
+                                <integer key="NSNullPlaceholder" value="-1"/>
66
+                                <string key="NSValueTransformerName">NSNegateBoolean</string>
67
+                            </dictionary>
68
+                        </binding>
69
+                        <binding destination="-2" name="hidden" keyPath="representedObject.status" id="5a5-GO-fXA">
70
+                            <dictionary key="options">
71
+                                <integer key="NSNullPlaceholder" value="0"/>
72
+                                <bool key="NSRaisesForNotApplicableKeys" value="NO"/>
73
+                            </dictionary>
74
+                        </binding>
75
+                    </connections>
76
+                </imageView>
77
+                <imageView translatesAutoresizingMaskIntoConstraints="NO" id="DcK-US-Y7c" userLabel="Failure View">
78
+                    <rect key="frame" x="2" y="10" width="12" height="12"/>
79
+                    <constraints>
80
+                        <constraint firstAttribute="width" constant="12" id="KiL-Mi-I2A"/>
81
+                        <constraint firstAttribute="height" constant="12" id="ozW-gP-bgw"/>
82
+                    </constraints>
83
+                    <imageCell key="cell" refusesFirstResponder="YES" alignment="left" animates="YES" imageScaling="proportionallyDown" image="NSStatusUnavailable" id="KRf-js-5mY" userLabel="Failure Cell"/>
84
+                    <connections>
85
+                        <binding destination="-2" name="hidden3" keyPath="representedObject.running" previousBinding="lma-N8-GIA" id="G48-5y-dm9">
86
+                            <dictionary key="options">
87
+                                <integer key="NSMultipleValuesPlaceholder" value="-1"/>
88
+                                <integer key="NSNoSelectionPlaceholder" value="-1"/>
89
+                                <integer key="NSNotApplicablePlaceholder" value="-1"/>
90
+                                <integer key="NSNullPlaceholder" value="-1"/>
91
+                            </dictionary>
92
+                        </binding>
93
+                        <binding destination="-2" name="hidden2" keyPath="representedObject.showStatus" previousBinding="tLZ-fA-ZJP" id="lma-N8-GIA">
94
+                            <dictionary key="options">
95
+                                <integer key="NSMultipleValuesPlaceholder" value="-1"/>
96
+                                <integer key="NSNoSelectionPlaceholder" value="-1"/>
97
+                                <integer key="NSNotApplicablePlaceholder" value="-1"/>
98
+                                <integer key="NSNullPlaceholder" value="-1"/>
99
+                                <string key="NSValueTransformerName">NSNegateBoolean</string>
100
+                            </dictionary>
101
+                        </binding>
102
+                        <binding destination="-2" name="hidden" keyPath="representedObject.status" id="tLZ-fA-ZJP">
103
+                            <dictionary key="options">
104
+                                <integer key="NSNullPlaceholder" value="1"/>
105
+                                <bool key="NSRaisesForNotApplicableKeys" value="NO"/>
106
+                                <string key="NSValueTransformerName">NSNegateBoolean</string>
107
+                            </dictionary>
108
+                        </binding>
109
+                    </connections>
110
+                </imageView>
111
+                <imageView translatesAutoresizingMaskIntoConstraints="NO" id="ujW-FR-oxR" userLabel="Success View">
112
+                    <rect key="frame" x="2" y="10" width="12" height="12"/>
113
+                    <constraints>
114
+                        <constraint firstAttribute="width" constant="12" id="lEn-yZ-4tX"/>
115
+                        <constraint firstAttribute="height" constant="12" id="sGr-6n-qLy"/>
116
+                    </constraints>
117
+                    <imageCell key="cell" refusesFirstResponder="YES" alignment="left" animates="YES" imageScaling="proportionallyDown" image="NSStatusAvailable" id="Ubc-Vb-LAb" userLabel="Success Cell"/>
118
+                    <connections>
119
+                        <binding destination="-2" name="hidden3" keyPath="representedObject.running" previousBinding="nCa-Pt-Buo" id="HkU-Oa-dOI">
120
+                            <dictionary key="options">
121
+                                <integer key="NSMultipleValuesPlaceholder" value="-1"/>
122
+                                <integer key="NSNoSelectionPlaceholder" value="-1"/>
123
+                                <integer key="NSNotApplicablePlaceholder" value="-1"/>
124
+                                <integer key="NSNullPlaceholder" value="-1"/>
125
+                            </dictionary>
126
+                        </binding>
127
+                        <binding destination="-2" name="hidden2" keyPath="representedObject.showStatus" previousBinding="7Fs-Mc-Q1G" id="nCa-Pt-Buo">
128
+                            <dictionary key="options">
129
+                                <integer key="NSMultipleValuesPlaceholder" value="-1"/>
130
+                                <integer key="NSNoSelectionPlaceholder" value="-1"/>
131
+                                <integer key="NSNotApplicablePlaceholder" value="-1"/>
132
+                                <integer key="NSNullPlaceholder" value="-1"/>
133
+                                <string key="NSValueTransformerName">NSNegateBoolean</string>
134
+                            </dictionary>
135
+                        </binding>
136
+                        <binding destination="-2" name="hidden" keyPath="representedObject.status" id="7Fs-Mc-Q1G">
137
+                            <dictionary key="options">
138
+                                <integer key="NSNullPlaceholder" value="1"/>
139
+                                <bool key="NSRaisesForNotApplicableKeys" value="NO"/>
140
+                            </dictionary>
141
+                        </binding>
142
+                    </connections>
143
+                </imageView>
144
+                <button translatesAutoresizingMaskIntoConstraints="NO" id="U7F-Hl-Qzm" userLabel="Label Button">
145
+                    <rect key="frame" x="17" y="9" width="43" height="14"/>
146
+                    <constraints>
147
+                        <constraint firstAttribute="height" constant="14" id="QyF-Wz-Bm6"/>
148
+                    </constraints>
149
+                    <buttonCell key="cell" type="inline" title="Label" bezelStyle="inline" alignment="center" inset="2" id="u1I-BO-Xx7">
150
+                        <behavior key="behavior" lightByContents="YES"/>
151
+                        <font key="font" metaFont="smallSystemBold"/>
152
+                    </buttonCell>
153
+                    <connections>
154
+                        <action selector="startOrStop:" target="-2" id="Sbd-pV-Vb3"/>
155
+                        <binding destination="-2" name="title" keyPath="representedObject.label" id="Eeq-2t-eRs"/>
156
+                    </connections>
157
+                </button>
158
+                <scrollView borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mQA-xG-ni4" userLabel="Output Scroll">
159
+                    <rect key="frame" x="17" y="-10" width="256" height="16"/>
160
+                    <clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="war-YV-W7W" userLabel="Output Clip">
161
+                        <rect key="frame" x="1" y="1" width="223" height="133"/>
162
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
163
+                        <subviews>
164
+                            <textView focusRingType="none" verticalCompressionResistancePriority="749" editable="NO" selectable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" id="uU1-7d-p6h" userLabel="Output View">
165
+                                <rect key="frame" x="0.0" y="0.0" width="223" height="133"/>
166
+                                <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
167
+                                <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
168
+                                <size key="minSize" width="256" height="16"/>
169
+                                <size key="maxSize" width="463" height="10000000"/>
170
+                                <color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
171
+                                <size key="minSize" width="256" height="16"/>
172
+                                <size key="maxSize" width="463" height="10000000"/>
173
+                                <connections>
174
+                                    <binding destination="-2" name="attributedString" keyPath="representedObject.attributedOutput" id="qar-ix-3dl"/>
175
+                                    <outlet property="delegate" destination="-2" id="Oop-el-HJe"/>
176
+                                </connections>
177
+                            </textView>
178
+                        </subviews>
179
+                        <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
180
+                    </clipView>
181
+                    <constraints>
182
+                        <constraint firstAttribute="width" constant="256" id="ZEd-2N-132"/>
183
+                    </constraints>
184
+                    <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="ydT-VY-1LJ">
185
+                        <rect key="frame" x="-100" y="-100" width="87" height="18"/>
186
+                        <autoresizingMask key="autoresizingMask"/>
187
+                    </scroller>
188
+                    <scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="h4h-V6-n4r">
189
+                        <rect key="frame" x="224" y="1" width="15" height="133"/>
190
+                        <autoresizingMask key="autoresizingMask"/>
191
+                    </scroller>
192
+                </scrollView>
193
+                <button translatesAutoresizingMaskIntoConstraints="NO" id="vmd-LL-kN7" userLabel="Edit Button">
194
+                    <rect key="frame" x="257" y="7" width="9" height="18"/>
195
+                    <constraints>
196
+                        <constraint firstAttribute="width" constant="9" id="8JI-Ko-wYQ"/>
197
+                    </constraints>
198
+                    <buttonCell key="cell" type="inline" title="Edit" bezelStyle="inline" image="NSSmartBadgeTemplate" imagePosition="only" alignment="center" state="on" imageScaling="proportionallyDown" inset="2" id="HXH-EV-ooF">
199
+                        <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
200
+                        <font key="font" metaFont="smallSystemBold"/>
201
+                    </buttonCell>
202
+                    <connections>
203
+                        <action selector="edit:" target="-2" id="eIS-34-cOA"/>
204
+                        <binding destination="-2" name="hidden" keyPath="self.parentViewController.editing" id="9B0-9N-3kd"/>
205
+                    </connections>
206
+                </button>
207
+            </subviews>
208
+            <constraints>
209
+                <constraint firstItem="DqU-bT-PYy" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" id="1pA-WW-UCS"/>
210
+                <constraint firstItem="vmd-LL-kN7" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="257" id="4dv-Gw-VLT"/>
211
+                <constraint firstItem="q9k-hz-yg9" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="7" id="8su-ML-sis"/>
212
+                <constraint firstItem="mQA-xG-ni4" firstAttribute="top" secondItem="U7F-Hl-Qzm" secondAttribute="bottom" constant="3" id="AcR-pj-FOo"/>
213
+                <constraint firstItem="mQA-xG-ni4" firstAttribute="height" secondItem="DqU-bT-PYy" secondAttribute="height" id="ByQ-Pd-qdq"/>
214
+                <constraint firstItem="q9k-hz-yg9" firstAttribute="centerX" secondItem="ujW-FR-oxR" secondAttribute="centerX" id="CA3-Ub-PaB"/>
215
+                <constraint firstAttribute="bottom" secondItem="DqU-bT-PYy" secondAttribute="bottom" id="Ku8-75-uez"/>
216
+                <constraint firstItem="q9k-hz-yg9" firstAttribute="centerY" secondItem="8gs-d1-WuT" secondAttribute="centerY" id="U3m-5r-1pM"/>
217
+                <constraint firstItem="mQA-xG-ni4" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="17" id="VhH-bc-g81"/>
218
+                <constraint firstItem="U7F-Hl-Qzm" firstAttribute="centerY" secondItem="vmd-LL-kN7" secondAttribute="centerY" id="VlI-hL-vmw"/>
219
+                <constraint firstItem="q9k-hz-yg9" firstAttribute="centerX" secondItem="8gs-d1-WuT" secondAttribute="centerX" id="WBg-at-0L3"/>
220
+                <constraint firstItem="U7F-Hl-Qzm" firstAttribute="leading" secondItem="q9k-hz-yg9" secondAttribute="trailing" constant="1" id="XJc-oO-yUv"/>
221
+                <constraint firstItem="DqU-bT-PYy" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="15" id="eCF-Dt-Wu5"/>
222
+                <constraint firstItem="q9k-hz-yg9" firstAttribute="centerY" secondItem="ujW-FR-oxR" secondAttribute="centerY" id="gLc-Ep-k2m"/>
223
+                <constraint firstItem="q9k-hz-yg9" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" id="q8I-Ev-NjI"/>
224
+                <constraint firstItem="U7F-Hl-Qzm" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="8" id="r2O-d7-pVA"/>
225
+                <constraint firstItem="q9k-hz-yg9" firstAttribute="centerY" secondItem="DcK-US-Y7c" secondAttribute="centerY" id="vmE-Va-egP"/>
226
+                <constraint firstItem="q9k-hz-yg9" firstAttribute="centerX" secondItem="DcK-US-Y7c" secondAttribute="centerX" id="yzP-za-8ju"/>
227
+            </constraints>
228
+            <point key="canvasLocation" x="191" y="345.5"/>
229
+        </customView>
230
+        <userDefaultsController representsSharedInstance="YES" id="62Q-bz-bUL"/>
231
+    </objects>
232
+    <resources>
233
+        <image name="NSSmartBadgeTemplate" width="14" height="14"/>
234
+        <image name="NSStatusAvailable" width="16" height="16"/>
235
+        <image name="NSStatusPartiallyAvailable" width="16" height="16"/>
236
+        <image name="NSStatusUnavailable" width="16" height="16"/>
237
+    </resources>
238
+</document>
0 239
new file mode 100644
... ...
@@ -0,0 +1,20 @@
1
+//
2
+//  SearchViewController.h
3
+//  Today Scripts
4
+//
5
+//  Created by Sam Rothenberg on 10/21/14.
6
+//  Copyright (c) 2014 Sam Rothenberg. All rights reserved.
7
+//
8
+
9
+#import "TodayScript.h"
10
+
11
+@interface SearchViewController : NCWidgetSearchViewController
12
+
13
+@property TodayScript *script;
14
+
15
+@end
16
+
17
+
18
+@interface SearchProgramTextField : NSTextField
19
+
20
+@end
0 21
new file mode 100644
... ...
@@ -0,0 +1,120 @@
1
+//
2
+//  SearchViewController.m
3
+//  Today Scripts
4
+//
5
+//  Created by Sam Rothenberg on 10/21/14.
6
+//  Copyright (c) 2014 Sam Rothenberg. All rights reserved.
7
+//
8
+
9
+#import "SearchViewController.h"
10
+#import <Cocoa/Cocoa.h>
11
+
12
+@implementation SearchViewController
13
+{
14
+    IBOutlet NSTextField *labelField;
15
+    IBOutlet SearchProgramTextField *programField;
16
+    IBOutlet NSTextView *scriptField;
17
+    IBOutlet NSButton *autorunButton;
18
+    IBOutlet NSButton *saveButton;
19
+
20
+    TodayScript *_script;
21
+}
22
+
23
+- (TodayScript *)script {
24
+    return _script;
25
+}
26
+- (void)setScript:(TodayScript *)script
27
+{
28
+    // If we are given a script to edit, set up our fields to its values.
29
+    if (script) {
30
+        labelField.stringValue = script.label;
31
+        programField.stringValue = script.program;
32
+        scriptField.string = script.script;
33
+        autorunButton.state = script.autoRun ? NSOnState : NSOffState;
34
+        saveButton.stringValue = @"Save Script";
35
+    }
36
+    // Otherwise, set up our fields with the default values.
37
+    else {
38
+        labelField.stringValue = @"";
39
+        programField.stringValue = NSProcessInfo.processInfo.environment[@"SHELL"];
40
+        scriptField.string = @"";
41
+        autorunButton.state = NSOnState;
42
+        saveButton.stringValue = @"Add Script";
43
+    }
44
+
45
+    [self willChangeValueForKey:@"script"];
46
+    _script = script;
47
+    [self didChangeValueForKey:@"script"];
48
+}
49
+
50
+- (void)viewDidLoad
51
+{
52
+    [super viewDidLoad];
53
+
54
+    // Set up the appearence of the script field.
55
+    scriptField.font = [NSFont fontWithName:@"Menlo" size:10];
56
+    scriptField.textColor = [NSColor colorWithWhite:1 alpha:1];
57
+    scriptField.backgroundColor = NSColor.clearColor;
58
+    // Disable all substitutions in the script field.
59
+    scriptField.automaticDashSubstitutionEnabled   = NO;
60
+    scriptField.automaticQuoteSubstitutionEnabled  = NO;
61
+    scriptField.automaticTextReplacementEnabled    = NO;
62
+    scriptField.automaticLinkDetectionEnabled      = NO;
63
+    scriptField.automaticSpellingCorrectionEnabled = NO;
64
+    scriptField.automaticDataDetectionEnabled      = NO;
65
+}
66
+
67
+// Method invoked when user presses the "Add Script" button.
68
+- (IBAction)resultSelected:(id)sender
69
+{
70
+    // If the interpreter is not a valid executable file, style the text to
71
+    // indicate the error to the user, then abort.
72
+    NSString *programString = programField.stringValue;
73
+    if (! [NSFileManager.defaultManager isExecutableFileAtPath:programString]) {
74
+        programField.textColor = [NSColor colorWithRed:1.0 green:0.25 blue:0.25 alpha:1];
75
+        return;
76
+    }
77
+
78
+    // If we were not given an existing dictionary to modify, set that up to
79
+    // work with. Otherwise, create a new one.
80
+    TodayScript *script = self.script ?: [[TodayScript alloc] init];
81
+
82
+    script.program = programString;
83
+
84
+    // Set the script to the dictionary if the user provided one. Otherwise,
85
+    // remove any which may have previously existed.
86
+    script.script = scriptField.string;
87
+
88
+    // Set the script's title to the user provided one.
89
+    script.label = labelField.stringValue;
90
+    if (! script.label.length)
91
+        // If they didn't provide a title, use the text of the script, or the
92
+        // name of the program itself if there is no script.
93
+        script.label = script.script.length
94
+            ? script.script
95
+            : script.program;
96
+
97
+    // Set whether the script should run automatically according to the user's
98
+    // specification.
99
+    script.autoRun = (autorunButton.state != NSOffState);
100
+
101
+    // Initialize the script's output and status to empty objects.
102
+    script.output = @"";
103
+    script.status = (id)NSNull.null;
104
+
105
+    // Send the completed dictionary to the ListViewController.
106
+    [self.delegate widgetSearch:self resultSelected:self.script];
107
+}
108
+
109
+@end
110
+
111
+
112
+@implementation SearchProgramTextField
113
+
114
+// When the user starts editing the program field, make sure the text color goes
115
+// back to white if it was changed to indicate an error with the previous input.
116
+- (void)textDidBeginEditing:(NSNotification *)notification {
117
+    self.textColor = [NSColor colorWithWhite:1 alpha:1];
118
+}
119
+
120
+@end
0 121
\ No newline at end of file
1 122
new file mode 100644
... ...
@@ -0,0 +1,142 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="14A389" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
3
+    <dependencies>
4
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/>
5
+    </dependencies>
6
+    <objects>
7
+        <customObject id="-2" userLabel="File's Owner" customClass="SearchViewController">
8
+            <connections>
9
+                <outlet property="autorunButton" destination="xfS-In-uVf" id="SvW-Fg-1ri"/>
10
+                <outlet property="labelField" destination="Cw9-1N-gpc" id="XQR-8Y-lAx"/>
11
+                <outlet property="programField" destination="seC-H6-j6W" id="RgL-3Q-Ijf"/>
12
+                <outlet property="saveButton" destination="u1c-zf-cvc" id="Bgs-g5-JPM"/>
13
+                <outlet property="scriptField" destination="g23-5c-rM3" id="Bx8-Xp-IHD"/>
14
+                <outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
15
+            </connections>
16
+        </customObject>
17
+        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
18
+        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
19
+        <customView id="Hz6-mo-xeY">
20
+            <rect key="frame" x="0.0" y="0.0" width="300" height="196"/>
21
+            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
22
+            <subviews>
23
+                <textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Cw9-1N-gpc" userLabel="Label Field">
24
+                    <rect key="frame" x="75" y="157" width="205" height="19"/>
25
+                    <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" usesSingleLineMode="YES" id="Axj-1g-Ksw">
26
+                        <font key="font" metaFont="smallSystem"/>
27
+                        <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
28
+                        <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
29
+                    </textFieldCell>
30
+                    <connections>
31
+                        <action selector="performClick:" target="WKU-4c-tEB" id="sER-gA-QiZ"/>
32
+                        <outlet property="nextKeyView" destination="seC-H6-j6W" id="ict-Xe-smw"/>
33
+                    </connections>
34
+                </textField>
35
+                <textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="seC-H6-j6W" userLabel="Program Field" customClass="SearchProgramTextField">
36
+                    <rect key="frame" x="75" y="130" width="205" height="19"/>
37
+                    <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" usesSingleLineMode="YES" id="tfX-Np-Dve">
38
+                        <font key="font" metaFont="smallSystem"/>
39
+                        <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
40
+                        <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
41
+                    </textFieldCell>
42
+                    <connections>
43
+                        <action selector="performClick:" target="ppa-Tb-oBt" id="e0B-YE-pZT"/>
44
+                        <outlet property="nextKeyView" destination="g23-5c-rM3" id="GxE-Uf-WWO"/>
45
+                    </connections>
46
+                </textField>
47
+                <scrollView horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OVb-f0-amM" userLabel="Script Scroll">
48
+                    <rect key="frame" x="20" y="46" width="260" height="76"/>
49
+                    <clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="Fmm-9L-RgW" userLabel="Script Clip">
50
+                        <rect key="frame" x="1" y="1" width="223" height="133"/>
51
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
52
+                        <subviews>
53
+                            <textView importsGraphics="NO" richText="NO" allowsUndo="YES" verticallyResizable="YES" id="g23-5c-rM3" userLabel="Script View">
54
+                                <rect key="frame" x="0.0" y="0.0" width="223" height="133"/>
55
+                                <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
56
+                                <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
57
+                                <size key="minSize" width="258" height="74"/>
58
+                                <size key="maxSize" width="463" height="10000000"/>
59
+                                <color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
60
+                                <size key="minSize" width="258" height="74"/>
61
+                                <size key="maxSize" width="463" height="10000000"/>
62
+                            </textView>
63
+                        </subviews>
64
+                        <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
65
+                    </clipView>
66
+                    <constraints>
67
+                        <constraint firstAttribute="height" constant="76" id="qzD-ga-hai"/>
68
+                    </constraints>
69
+                    <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="RxR-jh-mRq">
70
+                        <rect key="frame" x="-100" y="-100" width="87" height="18"/>
71
+                        <autoresizingMask key="autoresizingMask"/>
72
+                    </scroller>
73
+                    <scroller key="verticalScroller" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="zBW-qB-PIF">
74
+                        <rect key="frame" x="224" y="1" width="15" height="133"/>
75
+                        <autoresizingMask key="autoresizingMask"/>
76
+                    </scroller>
77
+                </scrollView>
78
+                <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="u1c-zf-cvc">
79
+                    <rect key="frame" x="181" y="14" width="104" height="28"/>
80
+                    <constraints>
81
+                        <constraint firstAttribute="width" constant="94" id="7LB-TW-HRD"/>
82
+                    </constraints>
83
+                    <buttonCell key="cell" type="push" title="Add Script" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="1gZ-Fp-eku">
84
+                        <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
85
+                        <font key="font" metaFont="smallSystem"/>
86
+                    </buttonCell>
87
+                    <connections>
88
+                        <action selector="resultSelected:" target="-2" id="5W6-KS-fVF"/>
89
+                    </connections>
90
+                </button>
91
+                <button translatesAutoresizingMaskIntoConstraints="NO" id="xfS-In-uVf">
92
+                    <rect key="frame" x="17" y="20" width="134" height="18"/>
93
+                    <constraints>
94
+                        <constraint firstAttribute="width" constant="129" id="Nwk-ve-D1j"/>
95
+                    </constraints>
96
+                    <buttonCell key="cell" type="check" title="Run automatically" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="b0u-sz-0IN">
97
+                        <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
98
+                        <font key="font" metaFont="smallSystem"/>
99
+                    </buttonCell>
100
+                </button>
101
+                <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WKU-4c-tEB">
102
+                    <rect key="frame" x="20" y="159" width="49" height="14"/>
103
+                    <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Label" id="zYG-jl-Xmw">
104
+                        <font key="font" metaFont="smallSystem"/>
105
+                        <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
106
+                        <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
107
+                    </textFieldCell>
108
+                </textField>
109
+                <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ppa-Tb-oBt">
110
+                    <rect key="frame" x="20" y="132" width="49" height="14"/>
111
+                    <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Program" id="YMK-m2-DXR">
112
+                        <font key="font" metaFont="smallSystem"/>
113
+                        <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
114
+                        <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
115
+                    </textFieldCell>
116
+                </textField>
117
+            </subviews>
118
+            <constraints>
119
+                <constraint firstItem="ppa-Tb-oBt" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="22" id="49q-dw-byP"/>
120
+                <constraint firstAttribute="bottom" secondItem="OVb-f0-amM" secondAttribute="bottom" constant="46" id="4Lk-yk-821"/>
121
+                <constraint firstItem="seC-H6-j6W" firstAttribute="leading" secondItem="ppa-Tb-oBt" secondAttribute="trailing" constant="8" id="85S-xF-OVg"/>
122
+                <constraint firstAttribute="trailing" secondItem="OVb-f0-amM" secondAttribute="trailing" constant="20" id="AEu-89-C2c"/>
123
+                <constraint firstItem="Cw9-1N-gpc" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="20" id="AxF-bN-coP"/>
124
+                <constraint firstItem="xfS-In-uVf" firstAttribute="centerY" secondItem="u1c-zf-cvc" secondAttribute="centerY" id="D34-tT-86A"/>
125
+                <constraint firstItem="seC-H6-j6W" firstAttribute="width" secondItem="Cw9-1N-gpc" secondAttribute="width" id="EbR-13-GjG"/>
126
+                <constraint firstItem="WKU-4c-tEB" firstAttribute="centerY" secondItem="Cw9-1N-gpc" secondAttribute="centerY" constant="0.5" id="Eto-Z2-p49"/>
127
+                <constraint firstItem="WKU-4c-tEB" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="22" id="GGe-nN-fTe"/>
128
+                <constraint firstItem="u1c-zf-cvc" firstAttribute="top" secondItem="OVb-f0-amM" secondAttribute="bottom" constant="8" id="IJP-PU-1Av"/>
129
+                <constraint firstItem="seC-H6-j6W" firstAttribute="centerY" secondItem="ppa-Tb-oBt" secondAttribute="centerY" constant="-0.5" id="TjU-0K-rUZ"/>
130
+                <constraint firstItem="Cw9-1N-gpc" firstAttribute="leading" secondItem="WKU-4c-tEB" secondAttribute="trailing" constant="8" id="YEm-DM-Tu9"/>
131
+                <constraint firstItem="OVb-f0-amM" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="20" id="fdc-EO-NSJ"/>
132
+                <constraint firstAttribute="trailing" secondItem="Cw9-1N-gpc" secondAttribute="trailing" constant="20" id="gnM-Fw-GZp"/>
133
+                <constraint firstItem="OVb-f0-amM" firstAttribute="top" secondItem="seC-H6-j6W" secondAttribute="bottom" constant="8" id="mkp-EW-Utf"/>
134
+                <constraint firstAttribute="trailing" secondItem="seC-H6-j6W" secondAttribute="trailing" constant="20" id="my8-8I-UHI"/>
135
+                <constraint firstItem="xfS-In-uVf" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="20" id="q2u-73-JgO"/>
136
+                <constraint firstItem="seC-H6-j6W" firstAttribute="top" secondItem="Cw9-1N-gpc" secondAttribute="bottom" constant="8" id="sju-Wi-Nhx"/>
137
+                <constraint firstAttribute="trailing" secondItem="u1c-zf-cvc" secondAttribute="trailing" constant="20" id="vOR-em-HdA"/>
138
+            </constraints>
139
+            <point key="canvasLocation" x="169" y="321"/>
140
+        </customView>
141
+    </objects>
142
+</document>
0 143
new file mode 100644
... ...
@@ -0,0 +1,54 @@
1
+//
2
+//  ScriptsArray.h
3
+//  Today Scripts
4
+//
5
+//  Created by Sam Rothenberg on 8/14/14.
6
+//  Copyright (c) 2014 Sam Rothenberg. All rights reserved.
7
+//
8
+
9
+#import <Foundation/Foundation.h>
10
+#import <NotificationCenter/NotificationCenter.h>
11
+
12
+#import "TodayScripts.h"
13
+
14
+
15
+@interface TodayScriptArray : NSMutableArray
16
+
17
++ (TodayScriptArray *)sharedScripts;
18
+
19
+- (void)saveDefaults;
20
+
21
+- (NCUpdateResult)autoRun;
22
+
23
+@end
24
+
25
+
26
+@interface TodayScript : NSObject
27
+
28
+// The user-defined settings for the script.
29
+@property NSString *label;
30
+@property NSString *program;
31
+@property NSString *script;
32
+@property BOOL autoRun;
33
+@property BOOL showStatus;
34
+
35
+// The above properties, represented by a dictionary. The dictionary's keys are
36
+// TodayScriptLabelKey, TodayScriptProgramKey, etcetera. Setting this dictionary
37
+// also sets the corresponding properties.
38
+@property NSDictionary *dictionary;
39
+
40
+// Keep track of whether we are running or not.
41
+@property (readonly) BOOL running;
42
+
43
+// The exit status and TTY output of the script. Setting the value output string
44
+// also causes the attributed output string to update accordingly.
45
+@property (readonly) NSNumber *status;
46
+
47
+@property (readonly) NSString *output;
48
+@property (readonly) NSString *unescapedOutput;
49
+@property (readonly) NSAttributedString *attributedOutput;
50
+
51
+- (void)run;
52
+- (void)terminate;
53
+
54
+@end
0 55
new file mode 100644
... ...
@@ -0,0 +1,477 @@
1
+ //
2
+//  ScriptsArray.m
3
+//  Today Scripts
4
+//
5
+//  Created by Sam Rothenberg on 8/14/14.
6
+//  Copyright (c) 2014 Sam Rothenberg. All rights reserved.
7
+//
8
+
9
+#import "TodayScript.h"
10
+#import "AMR_ANSIEscapeHelper.h"
11
+
12
+// The queue we will use for asynchronous tasks.
13
+dispatch_queue_t queue;
14
+
15
+
16
+// The XPC connection.
17
+NSXPCConnection *XPC;
18
+// The XPC proxy object for the helper.
19
+id<XPCHelping> XPCHelper;
20
+// A lock for working with the XPC helper.
21
+NSConditionLock *XPCLock;
22
+
23
+
24
+// Translater from ANSI-escaped NSStrings to NSAttributedStrings.
25
+AMR_ANSIEscapeHelper *ANSIHelper;
26
+
27
+#define PADDING_LEFT 0
28
+#define COLUMN_WIDTH 6.02
29
+#define COLUMN_COUNT 40.0
30
+#define TAB_COLUMNS  8
31
+
32
+// Paragraph style describing our tab stops.
33
+NSMutableParagraphStyle *paragraphStyle;
34
+// Characters to be trimmed from the beginning and end of displayed output.
35
+NSCharacterSet *lineBreaks;
36
+
37
+
38
+@interface TodayScript ()
39
+
40
+@property (readwrite) BOOL running;
41
+
42
+@property (readwrite) NSNumber *status;
43
+
44
+@property (readwrite) NSString *output;
45
+@property (readwrite) NSString *unescapedOutput;
46
+@property (readwrite) NSAttributedString *attributedOutput;
47
+
48
+@end
49
+@implementation TodayScript
50
+{
51
+    NSString *UUID;
52
+}
53
+
54
+- (id)init
55
+{
56
+    if (! (self = super.init)) return nil;
57
+
58
+    // Generate a UUID for consistently identifying this script internally.
59
+    UUID = NSUUID.UUID.UUIDString;
60
+
61
+    // Initialize the result-related properties.
62
+    self.status = (id)NSNull.null;
63
+    self.output = @"";
64
+
65
+    return self;
66
+}
67
+
68
+// When our dictionary version is requested, create one with the relevant items.
69
+- (NSDictionary *)dictionary
70
+{
71
+    return @{
72
+        TodayScriptLabelKey      : self.label,
73
+        TodayScriptProgramKey    : self.program,
74
+        TodayScriptScriptKey     : self.script,
75
+        TodayScriptAutoRunKey    : self.autoRun ? @YES : @NO,
76
+        TodayScriptShowStatusKey : self.showStatus ? @YES : @NO
77
+    };
78
+}
79
+// When we are modeling our values from a dictionary, set the relevant items.
80
+- (void)setDictionary:(NSDictionary *)dictionary
81
+{
82
+    self.label      =  dictionary[ TodayScriptLabelKey      ];
83
+    self.program    =  dictionary[ TodayScriptProgramKey    ];
84
+    self.script     =  dictionary[ TodayScriptScriptKey     ];
85
+    self.autoRun    = [dictionary[ TodayScriptAutoRunKey    ] boolValue];
86
+    self.showStatus = [dictionary[ TodayScriptShowStatusKey ] boolValue];
87
+    // Inititalize our state-related properies as well.
88
+    self.running = NO;
89
+    self.status  = (id)NSNull.null;
90
+    self.output  = @"";
91
+}
92
+
93
+- (void)runWithLock:(NSConditionLock *)lock resultPointer:(NSValue *)resultPointer
94
+{
95
+    // If we were passed a pointer for tracking update results, upwrap it.
96
+    NCUpdateResult *updateResult = resultPointer ? resultPointer.pointerValue : NULL;
97
+
98
+
99
+    // Wait for our turn to influence the XPC connectivity.
100
+    [XPCLock lock];
101
+
102
+    // Set to property to indicate that we are now running.
103
+    self.running = YES;
104
+
105
+    // Check the value of the lock. If it is 0, then the connection will not
106
+    // currently be active, so we should awaken it.
107
+    if (XPCLock.condition == 0)
108
+        [XPC resume];
109
+    // Regardless of whether it was already active or whether we activated it,
110
+    // increment the value of the lock to indicate our needing the connection.
111
+    [XPCLock unlockWithCondition:XPCLock.condition + 1];
112
+
113
+    // Pass ourselves in dictionary form to the XPC helper, with handler block
114
+    // for receiving the script's PID and initiating our observation of it.
115
+    [XPCHelper launchScript:self.dictionary forUUID:UUID handler: ^(int status, NSString *output)
116
+    {
117
+        // Once the process has terminated, we are no longer in need of the
118
+        // XPC connection. Wait for our turn again to influence it.
119
+        [XPCLock lock];
120
+
121
+        // Check the value of the lock. If it is 1, then we were the last
122
+        // ones to be using it, so it may now be suspended.
123
+        if (XPCLock.condition == 1)
124
+            [XPC suspend];
125
+        // Either way, decrement the lock's value to indicate our own lack of
126
+        // need for it.
127
+        [XPCLock unlockWithCondition:XPCLock.condition - 1];
128
+
129
+        // Unset our running status. Use the main thread so as to emit the
130
+        // proper KVO and UI updates.
131
+        [self performSelectorOnMainThread:
132
+            @selector(setRunning:) withObject:nil waitUntilDone:NO];
133
+
134
+        // Create a variable representing whether we have updates to display.
135
+        BOOL newData = NO;
136
+
137
+        // If the script was terminated prematurely, we should set our status to
138
+        // to a null object, and no updates will need to be reported.
139
+        if (status < 0)
140
+            [self performSelectorOnMainThread:
141
+                @selector(setStatus:) withObject:NSNull.null waitUntilDone:NO];
142
+
143
+        // If the script ran to completion, process its results to see whether
144
+        // they will need to be updated.
145
+        else {
146
+            // If we had not run to completion previously, or if the exit status
147
+            // of the previous run was different than this one, our status is to
148
+            // be updated with the new one.
149
+            if (self.status == (id)NSNull.null || self.status.intValue != status)
150
+            {
151
+                [self performSelectorOnMainThread:
152
+                    @selector(setStatus:) withObject:@(status) waitUntilDone:NO];
153
+
154
+                newData = YES;
155
+            }
156
+
157
+            // Check whether the raw character output is identical to that of
158
+            // the last completed run.
159
+            if (! [self.output isEqualToString:output])
160
+            {
161
+                // If the raw output has changed, update the property.
162
+                self.output = output;
163
+
164
+                // Parsed the ANSI escaped string to an attributed string.
165
+                NSMutableAttributedString *attributedOutput = [ANSIHelper
166
+                    attributedStringWithANSIEscapedString:output].mutableCopy;
167
+
168
+                // Get the bare characters from the parsed string.
169
+                NSString *unescapedOutput = attributedOutput.string;
170
+
171
+                // Get the unescaped output with excess line breaks trimmed off.
172
+                NSString *trimmedOutput = [unescapedOutput stringByTrimmingCharactersInSet:lineBreaks];
173
+
174
+                // If the string is empty after being trimmed of line breaks, we
175
+                // will not be displaying an output field, so keep it simple.
176
+                if ([trimmedOutput isEqualToString:@""]) {
177
+                    attributedOutput = [[NSMutableAttributedString alloc] initWithString:@""];
178
+                    unescapedOutput = @"";
179
+                }
180
+
181
+                // If the trimmed output is not empty, format the attributed and
182
+                // unescaped versions of our output. The unescaped one is
183
+                // inserted into a text field which is hidden, but expands with
184
+                // its contents, causing the script's row view to expand with it.
185
+                else {
186
+                    // Determine the range of the untrimmed output, and use it
187
+                    // to trim the line breaks from the attributed version.
188
+                    NSRange trimmedOutputRange = [unescapedOutput rangeOfString:trimmedOutput];
189
+                    attributedOutput = [attributedOutput
190
+                        attributedSubstringFromRange:trimmedOutputRange].mutableCopy;
191
+                    // Apply our tab stops to the formatted string.
192
+
193
+                    [attributedOutput addAttribute:NSParagraphStyleAttributeName
194
+                        value:paragraphStyle range:NSMakeRange(0, attributedOutput.length)];
195
+
196
+                    // This text field needs two extra line breaks to expand
197
+                    // the row properly.
198
+                    unescapedOutput = [@"\n\n" stringByAppendingString:trimmedOutput];
199
+                }
200
+
201
+                // If the final attributed output does not match the one from
202
+                // the previous run, we will need to update our output.
203
+                if (! [attributedOutput isEqualToAttributedString:self.attributedOutput])
204
+                {
205
+                    [self performSelectorOnMainThread:@selector(setAttributedOutput:)
206
+                        withObject:attributedOutput waitUntilDone:NO];
207
+
208
+                    [self performSelectorOnMainThread:@selector(setUnescapedOutput:)
209
+                        withObject:unescapedOutput waitUntilDone:NO];
210
+
211
+                    newData = YES;
212
+                }
213
+            }
214
+        }
215
+
216
+        if (newData) {
217
+            // If we were given a pointer to an update result variable, set it
218
+            // to indicate that there are indeed changes.
219
+            if (updateResult)
220
+                *updateResult = NCUpdateResultNewData;
221
+
222
+            // If we were passed a lock for tracking progress, we can now set it
223
+            // to zero since we've already determined there are updates to show.
224
+            if (lock) {
225
+                [lock lock];
226
+                [lock unlockWithCondition:0];
227
+            }
228
+        }
229
+        // If we were passed a lock for tracking progress, we can now decrement
230
+        // it to indicate we no longer need to be waited on.
231
+        else if (lock) {
232
+            [lock lock];
233
+            [lock unlockWithCondition:lock.condition - 1];
234
+        }
235
+    }];
236
+}
237
+
238
+- (void)run
239
+{
240
+    // Safely determine whether we are already running. If we are not, go
241
+    // through our run routine, no lock or result pointer required.
242
+    [XPCLock lock];
243
+    BOOL running = self.running;
244
+    [XPCLock unlock];
245
+
246
+    if (! running)
247
+        [self runWithLock:nil resultPointer:nil];
248
+}
249
+
250
+- (void)terminate
251
+{
252
+    // Safely determine whether we are currently running. If in fact we are not,
253
+    // ask the helper to terminate the process.
254
+    [XPCLock lock];
255
+    BOOL running = self.running;
256
+    [XPCLock unlock];
257
+
258
+    if (running)
259
+        [XPCHelper terminateScriptForUUID:UUID];
260
+}
261
+
262
+- (oneway void)dealloc {
263
+    [self terminate];
264
+}
265
+
266
+@end
267
+
268
+
269
+
270
+@implementation TodayScriptArray
271
+{
272
+    // Backing store for our array.
273
+    NSMutableArray *array;
274
+}
275
+
276
++ (TodayScriptArray *)sharedScripts
277
+{
278
+    static TodayScriptArray *instance = nil;
279
+    if (! instance) instance = [[TodayScriptArray alloc] init];
280
+    return instance;
281
+}
282
+
283
+- (id)init
284
+{
285
+    if (! (self = super.init)) return nil;
286
+
287
+    // We will use the default priority queue for asynchronous tasks.
288
+    queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
289
+
290
+    // Set up the XPC connection to the helper object.
291
+    XPC = [NSXPCConnection.alloc initWithServiceName:@"org.samroth.Today-Scripts.Widget.XPC"];
292
+    XPC.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCHelping)];
293
+    XPCHelper = XPC.remoteObjectProxy;
294
+    XPCLock = [[NSConditionLock alloc] initWithCondition:0];
295
+
296
+    // Create the ANSI translator.
297
+    ANSIHelper = [[AMR_ANSIEscapeHelper alloc] init];
298
+
299
+    // Define the font to use.
300
+    ANSIHelper.font = [NSFont fontWithName:@"Menlo" size:10];
301
+
302
+#define SET_AMR_SGRCode(CODE, RED, GREEN, BLUE, ALPHA) \
303
+    ANSIHelper.ansiColors[ @(AMR_SGRCode##CODE) ] = \
304
+        [NSColor colorWithRed:RED green:GREEN blue:BLUE alpha:ALPHA]
305
+
306
+    // Define the standard text colors to use.
307
+    SET_AMR_SGRCode( FgBlack,         0.31764706, 0.31764706, 0.31764706, 1.0 );
308
+    SET_AMR_SGRCode( FgRed,           0.94901961, 0.46666667, 0.47843137, 1.0 );
309
+    SET_AMR_SGRCode( FgGreen,         0.60000000, 0.80000000, 0.60000000, 1.0 );
310
+    SET_AMR_SGRCode( FgYellow,        1.00000000, 0.80000000, 0.40000000, 1.0 );
311
+    SET_AMR_SGRCode( FgBlue,          0.40000000, 0.60000000, 0.80000000, 1.0 );
312
+    SET_AMR_SGRCode( FgMagenta,       0.80000000, 0.60000000, 0.80000000, 1.0 );
313
+    SET_AMR_SGRCode( FgCyan,          0.40000000, 0.80000000, 0.80000000, 1.0 );
314
+    SET_AMR_SGRCode( FgWhite,         0.80000000, 0.80000000, 0.80000000, 1.0 );
315
+    // Background colors are the same as the text colors, but with half opacity.
316
+    SET_AMR_SGRCode( BgBlack,         0.31764706, 0.31764706, 0.31764706, 0.5 );
317
+    SET_AMR_SGRCode( BgRed,           0.94901961, 0.46666667, 0.47843137, 0.5 );
318
+    SET_AMR_SGRCode( BgGreen,         0.60000000, 0.80000000, 0.60000000, 0.5 );
319
+    SET_AMR_SGRCode( BgYellow,        1.00000000, 0.80000000, 0.40000000, 0.5 );
320
+    SET_AMR_SGRCode( BgBlue,          0.40000000, 0.60000000, 0.80000000, 0.5 );
321
+    SET_AMR_SGRCode( BgMagenta,       0.80000000, 0.60000000, 0.80000000, 0.5 );
322
+    SET_AMR_SGRCode( BgCyan,          0.40000000, 0.80000000, 0.80000000, 0.5 );
323
+    SET_AMR_SGRCode( BgWhite,         0.80000000, 0.80000000, 0.80000000, 0.5 );
324
+    // Bright text colors are more vivid than normal text colors.
325
+    SET_AMR_SGRCode( FgBrightBlack,   0.00000000, 0.00000000, 0.00000000, 1.0 );
326
+    SET_AMR_SGRCode( FgBrightRed,     0.86666667, 0.40000000, 0.40000000, 1.0 );
327
+    SET_AMR_SGRCode( FgBrightGreen,   0.72941176, 0.77254902, 0.36470588, 1.0 );
328
+    SET_AMR_SGRCode( FgBrightYellow,  0.92156863, 0.80392157, 0.38039216, 1.0 );
329
+    SET_AMR_SGRCode( FgBrightBlue,    0.55686275, 0.71764706, 0.87450980, 1.0 );
330
+    SET_AMR_SGRCode( FgBrightMagenta, 0.80784314, 0.67450980, 0.87058824, 1.0 );
331
+    SET_AMR_SGRCode( FgBrightCyan,    0.51372549, 0.79215686, 0.74901961, 1.0 );
332
+    SET_AMR_SGRCode( FgBrightWhite,   1.00000000, 1.00000000, 1.00000000, 1.0 );
333
+    // Bright backgrounds are the same as bright text, but with half opacity.
334
+    SET_AMR_SGRCode( BgBrightBlack,   0.00000000, 0.00000000, 0.00000000, 0.5 );
335
+    SET_AMR_SGRCode( BgBrightRed,     0.86666667, 0.40000000, 0.40000000, 0.5 );
336
+    SET_AMR_SGRCode( BgBrightGreen,   0.72941176, 0.77254902, 0.36470588, 0.5 );
337
+    SET_AMR_SGRCode( BgBrightYellow,  0.92156863, 0.80392157, 0.38039216, 0.5 );
338
+    SET_AMR_SGRCode( BgBrightBlue,    0.55686275, 0.71764706, 0.87450980, 0.5 );
339
+    SET_AMR_SGRCode( BgBrightMagenta, 0.80784314, 0.67450980, 0.87058824, 0.5 );
340
+    SET_AMR_SGRCode( BgBrightCyan,    0.51372549, 0.79215686, 0.74901961, 0.5 );
341
+    SET_AMR_SGRCode( BgBrightWhite,   1.00000000, 1.00000000, 1.00000000, 0.5 );
342
+
343
+    // Default text color is our non-bright white.
344
+    ANSIHelper.ansiColors[ @(AMR_SGRCodeFgReset) ] = ANSIHelper.defaultStringColor =
345
+        ANSIHelper.ansiColors[ @(AMR_SGRCodeFgWhite) ];
346
+
347
+    // Default background is transparent.
348
+    ANSIHelper.ansiColors[@( AMR_SGRCodeBgReset )] = NSColor.clearColor;
349
+
350
+    paragraphStyle = [[NSMutableParagraphStyle alloc] init];
351
+    paragraphStyle.defaultTabInterval = COLUMN_WIDTH * TAB_COLUMNS;
352
+
353
+    NSMutableArray *tabStops = [[NSMutableArray alloc] init];
354
+    for (NSUInteger tabIndex = 1; tabIndex < COLUMN_COUNT / TAB_COLUMNS; tabIndex++)
355
+    {
356
+        CGFloat tabLocation = COLUMN_WIDTH * TAB_COLUMNS * tabIndex + PADDING_LEFT;
357
+        [tabStops addObject:[NSTextTab.alloc initWithType:NSLeftTabStopType location:tabLocation]];
358
+    }
359
+    paragraphStyle.tabStops = tabStops;
360
+
361
+    // Set up our list of line breaks.
362
+    lineBreaks = [NSCharacterSet characterSetWithCharactersInString:@"\n\r"];
363
+
364
+
365
+
366
+    // Set up the "true" backing array for our live scripts list.
367
+    array = [[NSMutableArray alloc] init];
368
+
369
+//    [NSUserDefaults.standardUserDefaults removeObjectForKey:@"Scripts"];
370
+
371
+    // Get the list of scripts as stored in our defaults.
372
+    NSArray *defaultsScripts = [NSUserDefaults.standardUserDefaults arrayForKey:@"Scripts"];
373
+
374
+    // Iterate through the scripts to translate them to our live representation.
375
+    for (id defaultsScript in defaultsScripts)
376
+    {
377
+        // If the script is not dictionary form, ignore it.
378
+        if (! [defaultsScript isKindOfClass:NSDictionary.class])
379
+            continue;
380
+
381
+        TodayScript *script = [[TodayScript alloc] init];
382
+        script.dictionary = defaultsScript;
383
+        [array addObject:script];
384
+    }
385
+
386
+    return self;
387
+}
388
+
389
+- (NSUInteger)count {
390
+    return array.count;
391
+}
392
+- (id)objectAtIndex:(NSUInteger)index {
393
+    return [array objectAtIndex:index];
394
+}
395
+
396
+// All changes to our array should be saved to the array in our user defaults.
397
+// This method does this for each overridden NSMutableArray method.
398
+- (void)saveDefaults
399
+{
400
+    // Create a new array to be copied to our defaults.
401
+    NSMutableArray *defaultsScripts = [[NSMutableArray alloc] init];
402
+    // Iterate through our own script dictionaries in order to add them to it.
403
+    for (TodayScript *script in array)
404
+        [defaultsScripts addObject:script.dictionary];
405
+
406
+    // Finally, write the array to our defaults.
407
+    [NSUserDefaults.standardUserDefaults setObject:defaultsScripts forKey:@"Scripts"];
408
+}
409
+- (void)insertObject:(id)anObject atIndex:(NSUInteger)index {
410
+    [array insertObject:anObject atIndex:index];
411
+    [self saveDefaults];
412
+}
413
+- (void)removeObjectAtIndex:(NSUInteger)index {
414
+    [array removeObjectAtIndex:index];
415
+    [self saveDefaults];
416
+}
417
+- (void)addObject:(id)anObject {
418
+    [array addObject:anObject];
419
+    [self saveDefaults];
420
+}
421
+- (void)removeLastObject {
422
+    [array removeLastObject];
423
+    [self saveDefaults];
424
+}
425
+- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject {
426
+    [array replaceObjectAtIndex:index withObject:anObject];
427
+    [self saveDefaults];
428
+}
429
+
430
+- (NCUpdateResult)autoRun
431
+{
432
+    // Count how many scripts we will need to run.
433
+    NSUInteger count = 0;
434
+    for (TodayScript *script in array)
435
+        if (script.autoRun)
436
+            count++;
437
+
438
+    // If there are no scripts to run, stop here, and return an indication that
439
+    // there are no changes to display.
440
+    if (! count)
441
+        return NCUpdateResultNoData;
442
+
443
+    // Initialize a lock to use for waiting until each script has decremented it
444
+    // after their completion.
445
+    NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:count];
446
+
447
+    // Create a variable that's modifiable by blocks, to use for tracking
448
+    // whether we will have updates to display in the end.
449
+    __block NCUpdateResult updateResult = NCUpdateResultNoData;
450
+    NSValue *resultPointer = [NSValue valueWithPointer:&updateResult];
451
+
452
+    // Run each script that is marked to automatically run.
453
+    for (TodayScript *script in array)
454
+        if (script.autoRun)
455
+            [script runWithLock:lock resultPointer:resultPointer];
456
+
457
+    // Set up a timeout routine to dispatch after three seconds.
458
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), queue, ^
459
+    {
460
+        // If we time out, we will tell Notification Center to update the view
461
+        // in order to display the ongoing progress to the user.
462
+        updateResult = NCUpdateResultNewData;
463
+        // Set the value of lock to zero in order to allow us to proceed.
464
+        [lock lock];
465
+        [lock unlockWithCondition:0];
466
+    });
467
+
468
+    // Wait on our lock until all of the scripts have decremented it, or until
469
+    // one has updates to display and sets it to zero, or until we've timed out.
470
+    [lock lockWhenCondition:0];
471
+    [lock unlock];
472
+
473
+    // Return the final results to Notification Center.
474
+    return updateResult;
475
+}
476
+
477
+@end
0 478
new file mode 100644
... ...
@@ -0,0 +1,21 @@
1
+//
2
+//  TodayViewController.h
3
+//  Scripts
4
+//
5
+//  Created by Sam Rothenberg on 8/14/14.
6
+//  Copyright (c) 2014 Sam Rothenberg. All rights reserved.
7
+//
8
+
9
+#import "TodayScript.h"
10
+#import "EditViewController.h"
11
+
12
+
13
+@interface TodayViewController : NSViewController <NCWidgetProviding, NCWidgetListViewDelegate>
14
+
15
+@property (strong) IBOutlet NCWidgetListViewController *listViewController;
16
+@property (strong) EditViewController *editViewController;
17
+@property (strong) NSArrayController *arrayController;
18
+
19
+@end
20
+
21
+extern TodayViewController *todayViewController;
0 22
new file mode 100644
... ...
@@ -0,0 +1,131 @@
1
+//
2
+//  TodayViewController.m
3
+//  Scripts
4
+//
5
+//  Created by Sam Rothenberg on 8/14/14.
6
+//  Copyright (c) 2014 Sam Rothenberg. All rights reserved.
7
+//
8
+
9
+#import <Cocoa/Cocoa.h>
10
+
11
+#import "TodayScript.h"
12
+#import "TodayViewController.h"
13
+#import "EditViewController.h"
14
+#import "ListRowViewController.h"
15
+
16
+
17
+TodayViewController *todayViewController;
18
+
19
+
20
+#pragma mark - Widget Implementation
21
+
22
+
23
+@implementation TodayViewController
24
+
25
+- (void)viewDidLoad
26
+{
27
+    [super viewDidLoad];
28
+
29
+    todayViewController = self;
30
+
31
+    // Set up the view controller for adding and editing scripts.
32
+    self.editViewController = [[EditViewController alloc] init];
33
+
34
+    // Set up our array controller to manage the array which coordiates with our
35
+    // persistent user defaults.
36
+    self.arrayController = [NSArrayController.alloc initWithContent:TodayScriptArray.sharedScripts];
37
+
38
+    // If our user defaults contain no scripts, set up an introductory one.
39
+    if (! TodayScriptArray.sharedScripts.count)
40
+    {
41
+        TodayScript *script = [[TodayScript alloc] init];
42
+        script.label      = @"Welcome";
43
+        script.program    = @"/bin/sh";
44
+        script.script     = @"echo 'Click the Info button above to start adding scripts.'";
45
+        script.autoRun    = YES;
46
+        script.showStatus = YES;
47
+        [self.arrayController addObject:script];
48
+    }
49
+
50
+    // Set up the widget list view controller to get its content from our array
51
+    // controller.
52
+    self.listViewController.contents = self.arrayController.arrangedObjects;
53
+}
54
+
55
+
56
+
57
+#pragma mark - Getting and Displaying Updates
58
+
59
+// Notification Center calls this method to give us an opportunity to provide
60
+// updates. Refresh the widget's contents in preparation for a snapshot.
61
+- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult result))handler
62
+{
63
+    // Tell the script array to auto run the scripts as necessary, and return
64
+    // the update status it indicates.
65
+    handler(TodayScriptArray.sharedScripts.autoRun);
66
+}
67
+
68
+- (NSEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(NSEdgeInsets)defaultMarginInset {
69
+    // Override the left margin so that the list view is flush with the edge.
70
+    defaultMarginInset.left = 0;
71
+    defaultMarginInset.right = 0;
72
+    return defaultMarginInset;
73
+}
74
+
75
+
76
+
77
+#pragma mark - Editing the List of Scripts
78
+
79
+- (NSViewController *)widgetList:(NCWidgetListViewController *)list viewControllerForRow:(NSUInteger)row
80
+{
81
+    // Return a new view controller subclass for displaying an item of widget
82
+    // content. The NCWidgetListViewController will set the representedObject
83
+    // of this view controller to one of the objects in its contents array.
84
+    return [[ListRowViewController alloc] init];
85
+}
86
+
87
+- (BOOL)widgetList:(NCWidgetListViewController *)list shouldReorderRow:(NSUInteger)row {
88
+    // Return YES to allow the item to be reordered in the list by the user.
89
+    return YES;
90
+}
91
+
92
+- (void)widgetList:(NCWidgetListViewController *)list didReorderRow:(NSUInteger)row toRow:(NSUInteger)newIndex {
93
+    // The user has reordered an item in the list.
94
+    TodayScript *script = [self.arrayController.arrangedObjects objectAtIndex:row];
95
+    [self.arrayController removeObjectAtArrangedObjectIndex:row];
96
+    [self.arrayController insertObject:script atArrangedObjectIndex:newIndex];
97
+}
98
+
99
+- (BOOL)widgetList:(NCWidgetListViewController *)list shouldRemoveRow:(NSUInteger)row {
100
+    // Return YES to allow the item to be removed from the list by the user.
101
+    return YES;
102
+}
103
+
104
+- (void)widgetList:(NCWidgetListViewController *)list didRemoveRow:(NSUInteger)row {
105
+    // The user has removed an item from the list.
106
+    [self.arrayController removeObjectAtArrangedObjectIndex:row];
107
+}
108
+
109
+- (void)widgetListPerformAddAction:(NCWidgetListViewController *)list
110
+{
111
+    // The user has clicked the add button in the list view.
112
+    [self.editViewController createScript];
113
+}
114
+
115
+- (BOOL)widgetAllowsEditing {
116
+    return YES;
117
+}
118
+
119
+- (void)widgetDidBeginEditing {
120
+    // The user has clicked the edit button. Put the list view into edit mode.
121
+    self.listViewController.editing = YES;
122
+}
123
+
124
+- (void)widgetDidEndEditing {
125
+    // The user has clicked the Done button, begun editing another widget, or NC
126
+    // has been closed. Take the list view out of editing mode.
127
+    self.listViewController.contents = self.arrayController.arrangedObjects;
128
+    self.listViewController.editing = NO;
129
+}
130
+
131
+@end
0 132
new file mode 100644
... ...
@@ -0,0 +1,26 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="14A389" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
3
+    <dependencies>
4
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/>
5
+    </dependencies>
6
+    <objects>
7
+        <customObject id="-2" userLabel="File's Owner" customClass="TodayViewController">
8
+            <connections>
9
+                <outlet property="listViewController" destination="vLu-t9-fwO" id="U9u-yA-ZGU"/>
10
+                <outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
11
+            </connections>
12
+        </customObject>
13
+        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
14
+        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
15
+        <customView simulatedAppContext="notificationCenter" translatesAutoresizingMaskIntoConstraints="NO" id="Hz6-mo-xeY">
16
+            <rect key="frame" x="0.0" y="0.0" width="320" height="288"/>
17
+        </customView>
18
+        <viewController nibName="ListRowViewController" nibBundleIdentifier="org.samroth.Today-Scripts.Widget" id="vLu-t9-fwO" customClass="NCWidgetListViewController">
19
+            <connections>
20
+                <outlet property="delegate" destination="-2" id="yeU-qg-EhZ"/>
21
+                <outlet property="view" destination="Hz6-mo-xeY" id="nq9-gf-VjV"/>
22
+            </connections>
23
+        </viewController>
24
+        <viewController id="V1t-Qa-lVl"/>
25
+    </objects>
26
+</document>
0 27
new file mode 100644
... ...
@@ -0,0 +1,8 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+<plist version="1.0">
4
+<dict>
5
+	<key>com.apple.security.app-sandbox</key>
6
+	<true/>
7
+</dict>
8
+</plist>
0 9
new file mode 100644
... ...
@@ -0,0 +1,4 @@
1
+/* Display name and description for this extension. */
2
+"CFBundleDisplayName" = "Scripts";
3
+"com.apple.notificationcenter.widget.description" = "Observe the results of scripts run when checking Notification Center";
4
+
0 5
new file mode 100644
... ...
@@ -0,0 +1,33 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+<plist version="1.0">
4
+<dict>
5
+	<key>CFBundleDevelopmentRegion</key>
6
+	<string>en</string>
7
+	<key>CFBundleDisplayName</key>
8
+	<string>XPC</string>
9
+	<key>CFBundleExecutable</key>
10
+	<string>$(EXECUTABLE_NAME)</string>
11
+	<key>CFBundleIdentifier</key>
12
+	<string>org.samroth.Today-Scripts.Widget.$(PRODUCT_NAME:rfc1034identifier)</string>
13
+	<key>CFBundleInfoDictionaryVersion</key>
14
+	<string>6.0</string>
15
+	<key>CFBundleName</key>
16
+	<string>$(PRODUCT_NAME)</string>
17
+	<key>CFBundlePackageType</key>
18
+	<string>XPC!</string>
19
+	<key>CFBundleShortVersionString</key>
20
+	<string>1.0</string>
21
+	<key>CFBundleSignature</key>
22
+	<string>????</string>
23
+	<key>CFBundleVersion</key>
24
+	<string>1</string>
25
+	<key>NSHumanReadableCopyright</key>
26
+	<string>Copyright © 2014 Sam Rothenberg. All rights reserved.</string>
27
+	<key>XPCService</key>
28
+	<dict>
29
+		<key>ServiceType</key>
30
+		<string>Application</string>
31
+	</dict>
32
+</dict>
33
+</plist>
0 34
new file mode 100644
... ...
@@ -0,0 +1,138 @@
1
+//
2
+//  XPCHelper.m
3
+//  xpc
4
+//
5
+//  Created by Sam Rothenberg on 8/10/14.
6
+//  Copyright (c) 2014 Sam Rothenberg. All rights reserved.
7
+//
8
+
9
+#import "TodayScripts.h"
10
+#import "XPCMain.h"
11
+#include <util.h>
12
+
13
+
14
+@implementation XPCHelper
15
+{
16
+    // The dictionary of environment variables for spawned tasks.
17
+    NSMutableDictionary *environment;
18
+    // Queue for performing asynchonous tasks.
19
+    dispatch_queue_t queue;
20
+
21
+    // A dictionary of the active tasks.
22
+    NSMutableDictionary *tasks;
23
+}
24
+
25
+- (id)init
26
+{
27
+    if (! (self = super.init)) return nil;
28
+
29
+    // Create an environment for tasks based on our own, and add the details of
30
+    // our "terminal."
31
+    environment = NSProcessInfo.processInfo.environment.mutableCopy;
32
+    environment[@"TERM"   ] = @"ansi";
33
+    environment[@"COLUMNS"] = @"40";
34
+    environment[@"PAGER"  ] = @"/bin/cat";
35
+
36
+    // We will perform asynchronous tasks on the default background queue.
37
+    queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
38
+
39
+    // Set up the dictionary to keep records of our tasks.
40
+    tasks = [[NSMutableDictionary alloc] init];
41
+
42
+    return self;
43
+}
44
+
45
+- (void)launchScript:(NSDictionary *)script forUUID:(NSString *)UUID handler:(XPCHandler)handler;
46
+{
47
+//    NSURL *programURL = [NSURL URLWithString:script[TodayScriptProgramKey]];
48
+//    NSUserUnixTask *task = [[NSUserUnixTask alloc] initWithURL:programURL error:nil];
49
+
50
+    // Create a new task object and add it to our dictionary.
51
+    NSTask *task = [[NSTask alloc] init];
52
+    tasks[UUID] = task;
53
+
54
+    // Set the task's program to the user's shell.
55
+    task.launchPath = script[TodayScriptProgramKey];
56
+    // Set the task's environment as previously determined.
57
+    task.environment = environment;
58
+    // Set our current directory to the user's home directory.
59
+    task.currentDirectoryPath = environment[@"HOME"];
60
+
61
+    // Open a 40-character wide pseudo-TTY for this script, getting its file
62
+    // descriptors.
63
+    int master, slave;
64
+    struct winsize size = { .ws_col = 40 };
65
+    openpty(&master, &slave, NULL, NULL, &size);
66
+
67
+    // Set up handles for the TTY's file descriptors, such that they close them
68
+    // once we're done.
69
+    NSFileHandle *masterHandle = NSFileHandle.alloc, *slaveHandle = NSFileHandle.alloc;
70
+    masterHandle = [masterHandle initWithFileDescriptor:master closeOnDealloc:YES];
71
+    slaveHandle  = [slaveHandle  initWithFileDescriptor:slave  closeOnDealloc:YES];
72
+
73
+    // The script's output will be sent to the pseudo-TTY.
74
+    task.standardOutput = task.standardError = slaveHandle;
75
+
76
+    // If we were provided a script, convert it to UTF-8 to pass to the program.
77
+    NSString *scriptString = script[TodayScriptScriptKey];
78
+    if (scriptString.length) {
79
+        // We will be piping the script to the program via its standard input.
80
+        // Write the script to the pipe so that it is ready for the interpreter.
81
+        NSPipe *pipe = NSPipe.pipe;
82
+        task.standardInput = pipe.fileHandleForReading;
83
+        [pipe.fileHandleForWriting writeData:[scriptString dataUsingEncoding:NSUTF8StringEncoding]];
84
+        [pipe.fileHandleForWriting closeFile];
85
+    }
86
+
87
+    // Create a data object which can be modified by the TTY reader block, as
88
+    // well as a semaphore which it can use to signal the termination block.
89
+    __block NSData *outputData = nil;
90
+    dispatch_semaphore_t outputSemaphore = dispatch_semaphore_create(0);
91
+
92
+    dispatch_async(queue, ^
93
+    {
94
+        // Read the master handle in the background until the TTY is closed.
95
+        outputData = [masterHandle readDataToEndOfFile];
96
+        // After we've set the data object, we may signal the termination block
97
+        // that it is ready.
98
+        dispatch_semaphore_signal(outputSemaphore);
99
+    });
100
+
101
+    task.terminationHandler = ^(NSTask *task)
102
+    {
103
+        // If the task did not complete on its own volition, we we be returning
104
+        // an invalid exit status and output.
105
+        NSString *output = nil;
106
+        int status = -1;
107
+
108
+        // Close the TTY such that the reader thread stops waiting on it.
109
+        [slaveHandle closeFile];
110
+
111
+        // If the task ran to completion on its own volition, we will need to
112
+        // process its results.
113
+        if (task.terminationReason == NSTaskTerminationReasonExit)
114
+        {
115
+            // Wait for the reader thread to set the data object and signal us.
116
+            dispatch_semaphore_wait(outputSemaphore, DISPATCH_TIME_FOREVER);
117
+            // We will return the actual exit status of the process.
118
+            status = task.terminationStatus;
119
+            // Convert the UTF-8 output from the reader block to a string.
120
+            output = [[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding];
121
+        }
122
+
123
+        // Return the results to the widget.
124
+        handler(status, output);
125
+
126
+        // Remove the task from our records.
127
+        [tasks removeObjectForKey:UUID];
128
+    };
129
+
130
+    // Launch the task.
131
+    [task launch];
132
+}
133
+
134
+- (void)terminateScriptForUUID:(NSString *)UUID {
135
+    [(NSTask *)tasks[UUID] terminate];
136
+}
137
+
138
+@end
0 139
new file mode 100644
... ...
@@ -0,0 +1,22 @@
1
+//
2
+//  XPCMain.h
3
+//  Today Scripts
4
+//
5
+//  Created by Sam Rothenberg on 8/14/14.
6
+//  Copyright (c) 2014 Sam Rothenberg. All rights reserved.
7
+//
8
+
9
+#import <Cocoa/Cocoa.h>
10
+#import "TodayScripts.h"
11
+
12
+@interface XPCListenerDelegate : NSObject <NSXPCListenerDelegate> @end
13
+XPCListenerDelegate *listenerDelegate;
14
+
15
+NSXPCListener *listener;
16
+NSXPCConnection *connection;
17
+
18
+// This object implements the protocol which we have defined. It provides the
19
+// actual behavior for the service. It is 'exported' by the service to make it
20
+// available to the process hosting the service over an NSXPCConnection.
21
+@interface XPCHelper : NSObject <XPCHelping> @end
22
+XPCHelper *helper;
0 23
new file mode 100644
... ...
@@ -0,0 +1,49 @@
1
+//
2
+//  XPCMain.m
3
+//  xpc
4
+//
5
+//  Created by Sam Rothenberg on 8/10/14.
6
+//  Copyright (c) 2014 Sam Rothenberg. All rights reserved.
7
+//
8
+
9
+#import "XPCMain.h"
10
+
11
+@implementation XPCListenerDelegate
12
+
13
+- (BOOL)listener:(NSXPCListener *)aListener shouldAcceptNewConnection:(NSXPCConnection *)aConnection
14
+{
15
+    helper = [XPCHelper.alloc init];
16
+
17
+    // This method is where the NSXPCListener configures, accepts, and resumes a new incoming NSXPCConnection.
18
+    connection = aConnection;
19
+
20
+    // Configure the connection.
21
+    // First, set the interface that the exported object implements.
22
+    connection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCHelping)];
23
+    
24
+    // Next, set the object that the connection exports. All messages sent on the connection to this service will be sent to the exported object to handle. The connection retains the exported object.
25
+    connection.exportedObject = helper;
26
+    
27
+    // Resuming the connection allows the system to deliver more incoming messages.
28
+    [connection resume];
29
+
30
+    // Returning YES from this method tells the system that you have accepted this connection. If you want to reject the connection for some reason, call -invalidate on the connection and return NO.
31
+    return YES;
32
+}
33
+
34
+@end
35
+
36
+int main()
37
+{
38
+    // Set up the one NSXPCListener for this service. It will handle all incoming connections.
39
+    listener = NSXPCListener.serviceListener;
40
+
41
+    // Create the delegate for the service.
42
+    listenerDelegate = [XPCListenerDelegate.alloc init];
43
+    listener.delegate = listenerDelegate;
44
+    
45
+    // Resuming the serviceListener starts this service. This method does not return.
46
+    [listener resume];
47
+
48
+    return 0;
49
+}