Header File: BJGOptionInterpreter.h

//
// BJGOptionInterpreter.h
// Escape
//
// Created by Brent Gulanowski on 11/26/05.
// Copyright 2005 Bored Astronaut Software. All rights reserved.
//
// Parses options which follow the “-<option_letter> [option_arg] [-<option_letter> …] [other_arg …]” style

#import <Foundation/Foundation.h>

@interface BJGOptionInterpreter : NSObject {

 NSString *optionList;
 NSString *executable;
 NSDictionary *options;
 NSArray *arguments;
}

/* for format of opts, see “man 3 getopt”; args is an array of strings: launch arguments */
-(id)initWithOptionList:(NSString *)opts rawArguments:(NSArray *)args;

-(NSString *)optionList;
-(NSString *)executable;
-(NSDictionary *)options;
-(NSArray *)arguments;

@end

Source: BJGOptionInterpreter.h

Implementation File: BJGOptionInterpreter.m

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

#import “BJGOptionInterpreter.h”

#import <unistd.h>

@interface BJGOptionInterpreter (BJGOptionInterpreter_private)
+(NSArray *)parseOptions:(NSString *)opts;
@end

@implementation BJGOptionInterpreter

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

-(id)init {
 [self release];
 return nil;
}

/** new methods **/
#pragma mark BJGOptionInterpreter

-(id)initWithOptionList:(NSString *)optList rawArguments:(NSArray *)args {

 NSArray *allOpts = [BJGOptionInterpreter parseOptions:optList];

 NSMutableArray *opts = [NSMutableArray array];
 NSMutableArray *optsArgs = [NSMutableArray array]; /* “opt_s_Args” plural: array of arrays of args */
 NSMutableArray *otherArgs = [NSMutableArray array];

 NSEnumerator *argsEnumerator;
 id arg;
 id nextArg;

 if(nil == args) {
  args = [[NSProcessInfo processInfo] arguments];
 }
 argsEnumerator = [args objectEnumerator];

 executable = [argsEnumerator nextObject];
 arg = [argsEnumerator nextObject];
 nextArg = [argsEnumerator nextObject];

 while(arg) {
  
  id option;
  id optionWithArgs;
  
  /* If the current arg doesn’t look like an option… */
  if(’-’ != [arg characterAtIndex:0] /*|| 2 != [arg length]*/) {
    /* it isn’t … */
   [otherArgs addObject:arg];
   /* … and none of the rest are either */
   if(nextArg) {
    do {
     [otherArgs addObject:nextArg];
    } while(nextArg = [argsEnumerator nextObject]);
   }
   break;
  }

  /* It may look like an option, but we have to confirm it’s legitimate */
  option = [arg substringWithRange:NSMakeRange(1,1)];

  optionWithArgs = [option stringByAppendingString:@”:”];
  
  /* a legit option without arguments */
  if(YES == [allOpts containsObject:option]) {
   int argLength = [arg length];
   int pos = 2;
   if(NO == [opts containsObject:option]) {
    [opts addObject:option];
    [optsArgs addObject:[NSArray array]];
   }
   while(argLength - pos > 0 && YES == [allOpts containsObject:option]) {
    option = [arg substringWithRange:NSMakeRange(pos++,1)];
    if(NO == [opts containsObject:option]) {
     [opts addObject:option];
     [optsArgs addObject:[NSArray array]];
    }
   }
  }
  
  else if(YES == [allOpts containsObject:optionWithArgs]) {
   /* a legit option with arguments */
   
   NSMutableArray *optionArgs = [NSMutableArray array]; /* array of args */
   
   if([arg length] > 2) {
    [optionArgs addObject:[arg substringFromIndex:2]];
   }
   
   /* collect the arguments */
   while(nextArg && ‘-’ != [nextArg characterAtIndex:0]) {
    [optionArgs addObject:nextArg];
    nextArg = [argsEnumerator nextObject];
   }
   /* opts that require args and don’t have ‘em mean an error */
   if(0 == [optionArgs count]) {
    [self release];
    self = nil;
    break;
   }
   /* have we seen this option already? if so, coalesce arguments */
   if(YES == [opts containsObject:option]) {
    int originalOptionIndex = [opts indexOfObject:option];
    [optionArgs addObjectsFromArray:[optsArgs objectAtIndex:originalOptionIndex]];
    [optsArgs replaceObjectAtIndex:originalOptionIndex withObject:optionArgs];
   }
   else {
    [opts addObject:option];    
    [optsArgs addObject:optionArgs];
   }
  }
  
  /* bad option; release self and return nothing; app should print usage message */
  else {
   [self release];
   self = nil;
   break;
  }
  
  arg = nextArg;
  nextArg = [argsEnumerator nextObject];
 }

 /* if we got here and didn’t commit suicide, collect a nice arg dictionary */
 if(self) {
  optionList = [optList retain];
  arguments = [[NSArray arrayWithArray:otherArgs] retain];
  options = [[NSDictionary dictionaryWithObjects:optsArgs forKeys:opts] retain];
 }

 return self;
}

-(NSString *)optionList {
 return [[optionList copy] autorelease];
}

-(NSString *)executable {
 return [[executable copy] autorelease];
}

-(NSDictionary *)options {
 return [[options copy] autorelease];
}

-(NSArray *)arguments {
 return [[arguments copy] autorelease];
}

#pragma mark BJGOptionInterpreter_private
+(NSArray *)parseOptions:(NSString *)opts {
 int i;
 int length =[opts length];
 NSMutableArray *options = [NSMutableArray arrayWithCapacity:length];
 NSCharacterSet *lower = [NSCharacterSet lowercaseLetterCharacterSet];

 for(i = 0; i   char current = [opts characterAtIndex:i];
  char next;
  
  if(i < length - 1) {
   next = [opts characterAtIndex:i+1];
  }
  else {
   next = '\0';
  }
  
  if(NO == [lower characterIsMember:current]) {
   continue;
  }
  
  NSString *option = [NSString stringWithCString:&current length:1];
  if(':' == next) {
   option = [option stringByAppendingString:@":"];
   i++;
  }
  
  if(NO == [options containsObject:option]) {
   [options addObject:option];
  }
 }

 return [NSArray arrayWithArray:options];
}

@end

Source Code: BJGOptionInterpreter.m