forked from tomaz/appledoc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Systemator.m
461 lines (397 loc) · 14.9 KB
/
Systemator.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
//
// Systemator.m
// appledoc
//
// Created by Tomaz Kragelj on 14.4.09.
// Copyright 2009 Tomaz Kragelj. All rights reserved.
//
#import "Systemator.h"
#import "LoggingProvider.h"
#import "CommandLineParser.h"
#define kTKSystemError @"TKSystemError"
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
/** Declares methods private for the @c Systemator class.
*/
@interface Systemator (ClassPrivateAPI)
/** Determines the system's shell path.
This method will first determine the kind of shell that is used by the user. Then it will
get the path from the shell.
@return Returns the shell path.
@exception NSException Thrown if shell path cannot be determined.
*/
+ (NSString*) systemShellPath;
/** Returns the array of all lines from the given string.
@param string The string to get the lines from.
@return Returns the array containing strings representing all lines from the @c string.
*/
+ (NSMutableArray*) linesFromString:(NSString*) string;
@end
//////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
//////////////////////////////////////////////////////////////////////////////////////////
@implementation Systemator
//////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Tasks and processes helpers
//////////////////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------------------
+ (void) runTask:(NSString*) command, ...
{
NSParameterAssert(command != nil);
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
BOOL isDebugLevelOn = [[self logger] isDebugEnabled];
NSMutableString* argumentsString = isDebugLevelOn ? [[NSMutableString alloc] init] : nil;
// Convert the variable list into the array.
NSMutableArray* arguments = [[NSMutableArray alloc] init];
va_list args;
va_start(args, command);
id arg = command;
while (arg)
{
id argument = va_arg(args, id);
if (!argument) break;
[arguments addObject:argument];
if (isDebugLevelOn) [argumentsString appendFormat:@"%@ ", argument];
arg++;
}
va_end(args);
logDebug(@"Running task '%@ %@'...", command, argumentsString);
// If debug output is desired, we should show task output, otherwise we should
// redirect it to a temporary pipe so that it doesn't "garbage" the output.
BOOL showOutput = [[CommandLineParser sharedInstance] emitUtilityOutput];
NSPipe* outputPipe = showOutput ? nil : [[NSPipe alloc] init];
// Setup and run the task.
NSTask* task = [[NSTask alloc] init];
if (outputPipe) [task setStandardOutput:outputPipe];
[task setLaunchPath:command];
[task setArguments:arguments];
[task launch];
[task waitUntilExit];
// Release temporary objects.
[outputPipe release];
[task release];
[arguments release];
[argumentsString release];
[pool drain];
}
//----------------------------------------------------------------------------------------
+ (void) runShellTaskWithCommand:(NSString*) command
{
NSParameterAssert(command != nil);
logDebug(@"Running shell task '%@'", command);
// Get the user's default shell. Throw exception if this fails.
NSDictionary* environment = [[NSProcessInfo processInfo] environment];
NSString* shell = [environment objectForKey:@"SHELL"];
if (!shell)
{
[self throwExceptionWithName:kTKSystemError withDescription:@"Failed retreiving system shell!"];
}
// Prepare the temporary file contents.
NSMutableArray* lines = [NSMutableArray array];
[lines addObject:[NSString stringWithFormat:@"#!%@", shell]];
[lines addObject:command];
// Prepare the temporary file name and save it.
NSString* filename = [NSTemporaryDirectory() stringByAppendingPathComponent:@"appledoc-temp-script.sh"];
[self writeLines:lines toFile:filename];
// Change the file permissions to execute, otherwise shell will reject it.
NSFileManager* manager = [NSFileManager defaultManager];
NSDictionary* attributes = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:S_IRWXU|S_IRWXG|S_IRWXO]
forKey:NSFilePosixPermissions];
NSError* error = nil;
[manager setAttributes:attributes
ofItemAtPath:filename
error:&error];
if (error)
{
[manager removeItemAtPath:filename error:nil];
[self throwExceptionWithName:kTKSystemError basedOnError:error];
}
// Prepare shell script arguments.
NSMutableArray* arguments = [NSMutableArray array];
[arguments addObject:@"-c"];
[arguments addObject:filename];
// If debug output is desired, we should show task output, otherwise we should
// redirect it to a temporary pipe so that it doesn't "garbage" the output.
BOOL showOutput = [[CommandLineParser sharedInstance] emitUtilityOutput];
NSPipe* outputPipe = showOutput ? nil : [[NSPipe alloc] init];
// Execute the shell script.
NSTask* task = [[NSTask alloc] init];
if (outputPipe) [task setStandardOutput:outputPipe];
[task setLaunchPath:shell];
[task setArguments:arguments];
[task launch];
[task waitUntilExit];
// Release temporary objects and remove the temporary file.
[outputPipe release];
[task release];
[manager removeItemAtPath:filename error:nil];
}
//////////////////////////////////////////////////////////////////////////////////////////
#pragma mark XML helpers
//////////////////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------------------
+ (id) applyXSLTFromFile:(NSString*) filename
toDocument:(NSXMLDocument*) document
error:(NSError**) error
{
return [self applyXSLTFromFile:filename
toDocument:document
arguments:nil
error:error];
}
//----------------------------------------------------------------------------------------
+ (id) applyXSLTFromFile:(NSString*) filename
toDocument:(NSXMLDocument*) document
arguments:(NSDictionary*) arguments
error:(NSError**) error
{
NSString* xsltString = [NSString stringWithContentsOfFile:filename
encoding:NSASCIIStringEncoding
error:error];
if (xsltString)
{
return [document objectByApplyingXSLTString:xsltString
arguments:arguments
error:error];
}
return nil;
}
//////////////////////////////////////////////////////////////////////////////////////////
#pragma mark File system helpers
//////////////////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------------------
+ (void) createDirectory:(NSString*) path
{
if (![[NSFileManager defaultManager] fileExistsAtPath:path])
{
NSError* error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtPath:path
withIntermediateDirectories:YES
attributes:nil
error:&error])
{
logError(@"Creating directory '%@' failed with error %@!",
path,
[error localizedDescription]);
[self throwExceptionWithName:kTKSystemError basedOnError:error];
}
}
}
//----------------------------------------------------------------------------------------
+ (void) copyItemAtPath:(NSString*) source
toPath:(NSString*) destination
{
NSError* error = nil;
NSFileManager* manager = [NSFileManager defaultManager];
if ([manager fileExistsAtPath:destination])
{
logDebug(@"- Removing existing '%@'...", destination);
if (![manager removeItemAtPath:destination error:&error])
{
logError(@"Removing existing '%@' failed with %@!",
destination,
[error localizedDescription]);
[self throwExceptionWithName:kTKSystemError basedOnError:error];
}
}
logDebug(@"- Copying '%@' to '%@'...", source, destination);
if (![manager copyItemAtPath:source
toPath:destination
error:&error])
{
logError(@"Copying '%@' to '%@' failed with error %@!",
source,
destination,
[error localizedDescription]);
[self throwExceptionWithName:kTKSystemError basedOnError:error];
}
}
//----------------------------------------------------------------------------------------
+ (void) removeItemAtPath:(NSString*) path
{
NSError* error = nil;
NSFileManager* manager = [NSFileManager defaultManager];
if ([manager fileExistsAtPath:path])
{
logDebug(@"Removing files at '%@'...", path);
if (![manager removeItemAtPath:path error:&error])
{
logError(@"Failed removing files at '%@'!", path);
[self throwExceptionWithName:kTKSystemError basedOnError:error];
}
}
}
//----------------------------------------------------------------------------------------
+ (NSMutableArray*) linesFromContentsOfFile:(NSString*) filename
{
// Read the data from the file into the string.
NSError* error = nil;
NSString* contents = [[NSString alloc] initWithContentsOfFile:filename
encoding:NSASCIIStringEncoding
error:&error];
if (!contents)
{
logError(@"Failed reading data from '%@'!", filename);
[self throwExceptionWithName:kTKSystemError basedOnError:error];
}
return [self linesFromString:contents];
}
//----------------------------------------------------------------------------------------
+ (void) writeLines:(NSArray*) lines toFile:(NSString*) filename
{
// Generate the string containing all lines.
NSMutableString* string = [NSMutableString string];
for (NSString* line in lines)
{
[string appendString:line];
[string appendString:@"\n"];
}
// Write the file.
NSError* error = nil;
if (![string writeToFile:filename
atomically:NO
encoding:NSASCIIStringEncoding
error:&error])
{
logError(@"Failed writting data to file '%@'!", filename);
[self throwExceptionWithName:kTKSystemError basedOnError:error];
}
}
//----------------------------------------------------------------------------------------
+ (id) readPropertyListFromFile:(NSString*) filename
{
// Read the plist data into NSData. Exit if this fails.
NSError* error = nil;
NSData* infoPlistData = [NSData dataWithContentsOfFile:filename
options:0
error:&error];
if (!infoPlistData)
{
logError(@"Failed reading property list data from '%@'!", filename);
[self throwExceptionWithName:kTKSystemError basedOnError:error];
}
// Convert the data into the object that will represent the property list.
NSString* errorDescription = nil;
id docsetInfo = [NSPropertyListSerialization propertyListFromData:infoPlistData
mutabilityOption:NSPropertyListImmutable
format:NULL
errorDescription:&errorDescription];
if (!docsetInfo)
{
logError(@"Failed parsing property list data from '%@'!", filename);
[self throwExceptionWithName:kTKSystemError withDescription:errorDescription];
}
return docsetInfo;
}
//----------------------------------------------------------------------------------------
+ (void) writePropertyList:(id) plist toFile:(NSString*) filename
{
// Convert the dictionary to property list.
NSString* errorDescription = nil;
NSData* infoPlistData = [NSPropertyListSerialization dataFromPropertyList:plist
format:NSPropertyListXMLFormat_v1_0
errorDescription:&errorDescription];
if (!infoPlistData)
{
logError(@"Failed extracting property list data for '%@'!", filename);
[errorDescription autorelease];
[self throwExceptionWithName:kTKSystemError withDescription:errorDescription];
}
// Save the data to the file.
NSError* error = nil;
if (![infoPlistData writeToFile:filename
options:0
error:&error])
{
logError(@"Failed writting property list data to '%@'!", filename);
[self throwExceptionWithName:kTKSystemError basedOnError:error];
}
}
//////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Exception helpers
//////////////////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------------------
+ (void) throwExceptionWithName:(NSString*) name basedOnError:(NSError*) error;
{
@throw [NSException exceptionWithName:name
reason:[error localizedDescription]
userInfo:[error userInfo]];
}
//----------------------------------------------------------------------------------------
+ (void) throwExceptionWithName:(NSString*) name withDescription:(NSString*) description
{
@throw [NSException exceptionWithName:name
reason:description
userInfo:nil];
}
//////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Class private methods
//////////////////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------------------
+ (NSString*) systemShellPath
{
NSTask* task = nil;
NSPipe* outputPipe = nil;
NSString* outputString = nil;
@try
{
// First we must determine the kind of shell that is used, then create a process that
// asks the shell about the path.
NSDictionary* environment = [[NSProcessInfo processInfo] environment];
NSString* shell = [environment objectForKey:@"SHELL"];
// Setup the pipe which will capture output from the task.
outputPipe = [[NSPipe alloc] init];
// Now create the task which will ask the shell for all environment variables.
task = [[NSTask alloc] init];
[task setLaunchPath:shell];
[task setArguments:[NSArray arrayWithObjects:@"-c", @"env", nil]];
[task setStandardOutput:outputPipe];
[task launch];
// Read the output from the task into a string.
NSData* data = [[outputPipe fileHandleForReading] readDataToEndOfFile];
outputString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
// Convert the string into individual lines. Then scan through the lines and extract
NSArray* lines = [self linesFromString:outputString];
for (NSString* line in lines)
{
NSRange pathRange = [line rangeOfString:@"PATH"];
NSRange separatorRange = [line rangeOfString:@"="];
if (pathRange.location != NSNotFound && separatorRange.location != NSNotFound)
{
return [line substringFromIndex:separatorRange.location + separatorRange.length];
}
}
}
@catch (NSException* e)
{
@throw;
}
@finally
{
[outputString release];
[outputPipe release];
[task release];
}
return nil;
}
//----------------------------------------------------------------------------------------
+ (NSMutableArray*) linesFromString:(NSString*) string
{
NSMutableArray *result = [NSMutableArray array];
NSUInteger paragraphStart = 0;
NSUInteger paragraphEnd = 0;
NSUInteger contentsEnd = 0;
NSUInteger length = [string length];
NSRange currentRange;
while (paragraphEnd < length)
{
[string getParagraphStart:¶graphStart
end:¶graphEnd
contentsEnd:&contentsEnd
forRange:NSMakeRange(paragraphEnd, 0)];
currentRange = NSMakeRange(paragraphStart, contentsEnd - paragraphStart);
[result addObject:[string substringWithRange:currentRange]];
}
return result;
}
@end