Header File: BJGTextIOHandler.h

//
// BJGTextIOHandler.h
// Escape
//
// Created by Brent Gulanowski on 11/27/05.
// Copyright 2005 Bored Astronaut Software. All rights reserved.
//
// An interface to text-based input and output; primarily for handling stdio.

#import <Foundation/Foundation.h>

@interface BJGTextIOHandler : NSObject {

 NSFileHandle *inputFH;
 NSFileHandle *outputFH;
 id delegate;
}

/* designated initializer */
-(id)initWithInputFile:(NSString *)inFile outputFile:(NSString *)outFile;

/* set inFile to nil for stdin, outFile to nil for stdout */
-(void)setInputFile:(NSString *)inFile;
-(void)setOutputFile:(NSString *)outFile;

/* if there’s no delegate, nothing happens */
-(id)delegate;
-(void)setDelegate:(id)del;
-(void)write:(NSString *)line;

@end

Source code: BJGTextIOHandler.h

Implementation File: BJGTextIOHandler.m

//
// BJGTextIOHandler.m
// Escape
//
// Created by Brent Gulanowski on 11/27/05.
// Copyright 2005 Bored Astronaut Software. All rights reserved.
//

#import “BJGTextIOHandler.h”

@interface BJGTextIOHandler (BJGTextIOHandler_private)
-(void)receive;
@end

@implementation BJGTextIOHandler

/** inheritance tree overrides **/
#pragma mark NSObject
#if 0 /* remove the guards as necessary */
+(void)initialize {
}
#endif

-(id)init {
 return [self initWithInputFile:nil outputFile:nil];
}

/** new methods **/
#pragma mark BJGTextIOHandler
-(id)initWithInputFile:(NSString *)inFile outputFile:(NSString *)outFile {
 [self setInputFile:inFile];
 [self setOutputFile:outFile];

return self;
}

-(void)write:(NSString *)line {
 if(line) {
  [outputFH writeData:[line dataUsingEncoding:NSUTF8StringEncoding]];
 }
}

-(void)setInputFile:(NSString *)inFile {
 inputFH = [[NSFileHandle fileHandleForReadingAtPath:inFile] retain];
 if(nil == inputFH) {
  inputFH = [NSFileHandle fileHandleWithStandardInput];
 }
}

-(void)setOutputFile:(NSString *)outFile {
 outputFH = [[NSFileHandle fileHandleForWritingAtPath:outFile] retain];
 if(nil == outputFH) {
  outputFH = [NSFileHandle fileHandleWithStandardOutput];
 }
}

-(id)delegate {
 return delegate;
}

-(void)setDelegate:(id)del {
 if(del) {
  if(NO == [del respondsToSelector:NSSelectorFromString(@”processInput:”)]) {
   NSLog(@”Delegate must implement -processInput: method.”);
   delegate = nil;
  }
  else if (!delegate) {
   [[NSNotificationCenter defaultCenter] addObserver:self
              selector:NSSelectorFromString(@”receive”)
               name:NSFileHandleDataAvailableNotification
              object:inputFH];
  }
 }
 delegate = del;
 if(delegate) {
  [inputFH waitForDataInBackgroundAndNotify];
 }
 else {
  [[NSNotificationCenter defaultCenter] removeObserver:self];
 }
}

#pragma mark BJGTextIOHandler_private
-(void)receive {

 NSData *data = [inputFH availableData];

  /* should always be true if “-receive” is called */
 if(delegate) {
  
  NSString *read;
  
  if(!data || [data length] == 0) {
   [delegate processInput:nil];
  }
  else {
   read = [NSString stringWithCString:[data bytes] encoding:NSUTF8StringEncoding];
   
   /* Workaround to clean up garbage attached to single-character input. Why is this happening? */
   if(’\n’ == [read characterAtIndex:[read length] - 3]) {
    read = [read substringToIndex:[read length] - 2];
   }
   
   read = [read stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
   [self write:[delegate processInput:read]];
  }
 }
 if(delegate) {
  [inputFH waitForDataInBackgroundAndNotify];
 }
}

@end

Source code: BJGTextIOHandler.m