mirror of
https://github.com/MacPaw/XADMaster.git
synced 2025-08-29 11:33:49 +02:00
855 lines
24 KiB
Objective-C
855 lines
24 KiB
Objective-C
/*
|
|
* CSCommandLineParser.m
|
|
*
|
|
* Copyright (c) 2017-present, MacPaw Inc. All rights reserved.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301 USA
|
|
*/
|
|
#import "CSCommandLineParser.h"
|
|
#import "NSStringPrinting.h"
|
|
|
|
#ifdef __MINGW32__
|
|
#import <windows.h>
|
|
#endif
|
|
|
|
|
|
|
|
static NSString *NamesKey=@"NamesKey";
|
|
static NSString *AllowedValuesKey=@"AllowedValuesKey";
|
|
static NSString *DefaultValueKey=@"DefaultValueKey";
|
|
static NSString *OptionTypeKey=@"OptionType";
|
|
static NSString *DescriptionKey=@"DescriptionKey";
|
|
static NSString *ArgumentDescriptionKey=@"ArgumentDescriptionKey";
|
|
static NSString *AliasTargetKey=@"AliasTargetKey";
|
|
static NSString *RequiredOptionsKey=@"RequiredOptionsKey";
|
|
|
|
static NSString *NumberValueKey=@"NumberValue";
|
|
static NSString *StringValueKey=@"StringValue";
|
|
static NSString *ArrayValueKey=@"ArrayValue";
|
|
|
|
static NSString *StringOptionType=@"StringOptionType";
|
|
static NSString *MultipleChoiceOptionType=@"MultipleChoiceOptionType";
|
|
static NSString *IntegerOptionType=@"IntegerOptionType";
|
|
static NSString *FloatingPointOptionType=@"FloatingPointOptionType";
|
|
static NSString *SwitchOptionType=@"SwitchOptionType";
|
|
static NSString *HelpOptionType=@"HelpOptionType";
|
|
static NSString *VersionOptionType=@"VersionOptionType";
|
|
static NSString *AliasOptionType=@"AliasOptionType";
|
|
|
|
#if MAC_OS_X_VERSION_MAX_ALLOWED<1050
|
|
@interface NSScanner (BuildKludge)
|
|
-(BOOL)scanHexLongLong:(unsigned long long *)val;
|
|
-(BOOL)scanHexDouble:(double *)val;
|
|
@end
|
|
#endif
|
|
|
|
@implementation CSCommandLineParser
|
|
|
|
-(id)init
|
|
{
|
|
if((self=[super init]))
|
|
{
|
|
options=[NSMutableDictionary new];
|
|
optionordering=[NSMutableArray new];
|
|
alwaysrequiredoptions=[NSMutableArray new];
|
|
|
|
remainingargumentarray=nil;
|
|
|
|
programname=nil;
|
|
usageheader=nil;
|
|
usagefooter=nil;
|
|
programversion=nil;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
-(void)dealloc
|
|
{
|
|
[options release];
|
|
[optionordering release];
|
|
[alwaysrequiredoptions release];
|
|
|
|
[remainingargumentarray release];
|
|
|
|
[programname release];
|
|
[usageheader release];
|
|
[usagefooter release];
|
|
[programversion release];
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
|
|
|
|
|
|
-(void)setProgramName:(NSString *)name
|
|
{
|
|
[programname autorelease];
|
|
programname=[name retain];
|
|
}
|
|
|
|
-(void)setUsageHeader:(NSString *)header
|
|
{
|
|
[usageheader autorelease];
|
|
usageheader=[header retain];
|
|
}
|
|
|
|
-(void)setUsageFooter:(NSString *)footer
|
|
{
|
|
[usagefooter autorelease];
|
|
usagefooter=[footer retain];
|
|
}
|
|
|
|
-(void)setProgramVersion:(NSString *)version
|
|
{
|
|
[programversion autorelease];
|
|
programversion=[version retain];
|
|
}
|
|
|
|
|
|
|
|
-(void)addStringOption:(NSString *)option
|
|
description:(NSString *)description
|
|
{
|
|
[self addStringOption:option defaultValue:nil description:description argumentDescription:@"string"];
|
|
}
|
|
|
|
-(void)addStringOption:(NSString *)option defaultValue:(NSString *)defaultvalue
|
|
description:(NSString *)description
|
|
{
|
|
[self addStringOption:option defaultValue:defaultvalue description:description argumentDescription:@"string"];
|
|
}
|
|
|
|
-(void)addStringOption:(NSString *)option
|
|
description:(NSString *)description argumentDescription:(NSString *)argdescription
|
|
{
|
|
[self addStringOption:option defaultValue:nil description:description argumentDescription:argdescription];
|
|
}
|
|
|
|
-(void)addStringOption:(NSString *)option defaultValue:(NSString *)defaultvalue
|
|
description:(NSString *)description argumentDescription:(NSString *)argdescription
|
|
{
|
|
[self _assertOptionNameIsUnique:option];
|
|
|
|
NSMutableDictionary *dict=[NSMutableDictionary dictionaryWithObjectsAndKeys:
|
|
[NSMutableArray arrayWithObject:option],NamesKey,
|
|
StringOptionType,OptionTypeKey,
|
|
description,DescriptionKey,
|
|
argdescription,ArgumentDescriptionKey,
|
|
nil];
|
|
if(defaultvalue) [dict setObject:defaultvalue forKey:DefaultValueKey];
|
|
|
|
[options setObject:dict forKey:option];
|
|
[optionordering addObject:option];
|
|
}
|
|
|
|
|
|
|
|
|
|
-(void)addMultipleChoiceOption:(NSString *)option allowedValues:(NSArray *)allowedvalues
|
|
description:(NSString *)description
|
|
{
|
|
[self addMultipleChoiceOption:option allowedValues:allowedvalues defaultValue:nil
|
|
description:description argumentDescription:[allowedvalues componentsJoinedByString:@"|"]];
|
|
}
|
|
|
|
-(void)addMultipleChoiceOption:(NSString *)option allowedValues:(NSArray *)allowedvalues defaultValue:(NSString *)defaultvalue
|
|
description:(NSString *)description
|
|
{
|
|
[self addMultipleChoiceOption:option allowedValues:allowedvalues defaultValue:defaultvalue
|
|
description:description argumentDescription:[allowedvalues componentsJoinedByString:@"|"]];
|
|
}
|
|
|
|
-(void)addMultipleChoiceOption:(NSString *)option allowedValues:(NSArray *)allowedvalues
|
|
description:(NSString *)description argumentDescription:(NSString *)argdescription
|
|
{
|
|
[self addMultipleChoiceOption:option allowedValues:allowedvalues defaultValue:nil
|
|
description:description argumentDescription:argdescription];
|
|
}
|
|
|
|
-(void)addMultipleChoiceOption:(NSString *)option allowedValues:(NSArray *)allowedvalues defaultValue:(NSString *)defaultvalue
|
|
description:(NSString *)description argumentDescription:(NSString *)argdescription
|
|
{
|
|
[self _assertOptionNameIsUnique:option];
|
|
|
|
NSMutableDictionary *dict=[NSMutableDictionary dictionaryWithObjectsAndKeys:
|
|
[NSMutableArray arrayWithObject:option],NamesKey,
|
|
allowedvalues,AllowedValuesKey,
|
|
MultipleChoiceOptionType,OptionTypeKey,
|
|
description,DescriptionKey,
|
|
argdescription,ArgumentDescriptionKey,
|
|
nil];
|
|
if(defaultvalue)
|
|
{
|
|
NSUInteger index=[allowedvalues indexOfObject:[defaultvalue lowercaseString]];
|
|
if(index==NSNotFound) [NSException raise:NSInvalidArgumentException format:
|
|
@"Default value \"%@\" is not in the array of allowed values.",defaultvalue];
|
|
[dict setObject:[NSNumber numberWithUnsignedInt:index] forKey:DefaultValueKey];
|
|
}
|
|
|
|
[options setObject:dict forKey:option];
|
|
[optionordering addObject:option];
|
|
}
|
|
|
|
|
|
|
|
|
|
-(void)addIntegerOption:(NSString *)option
|
|
description:(NSString *)description
|
|
{
|
|
[self addIntegerOption:option
|
|
description:description argumentDescription:@"integer"];
|
|
}
|
|
|
|
-(void)addIntegerOption:(NSString *)option
|
|
description:(NSString *)description argumentDescription:(NSString *)argdescription
|
|
{
|
|
[self _assertOptionNameIsUnique:option];
|
|
|
|
NSMutableDictionary *dict=[NSMutableDictionary dictionaryWithObjectsAndKeys:
|
|
[NSMutableArray arrayWithObject:option],NamesKey,
|
|
IntegerOptionType,OptionTypeKey,
|
|
description,DescriptionKey,
|
|
argdescription,ArgumentDescriptionKey,
|
|
nil];
|
|
|
|
[options setObject:dict forKey:option];
|
|
[optionordering addObject:option];
|
|
}
|
|
|
|
-(void)addIntegerOption:(NSString *)option defaultValue:(int)defaultvalue
|
|
description:(NSString *)description
|
|
{
|
|
[self addIntegerOption:option defaultValue:defaultvalue
|
|
description:description argumentDescription:@"integer"];
|
|
}
|
|
|
|
-(void)addIntegerOption:(NSString *)option defaultValue:(int)defaultvalue
|
|
description:(NSString *)description argumentDescription:(NSString *)argdescription
|
|
{
|
|
[self _assertOptionNameIsUnique:option];
|
|
|
|
NSMutableDictionary *dict=[NSMutableDictionary dictionaryWithObjectsAndKeys:
|
|
[NSMutableArray arrayWithObject:option],NamesKey,
|
|
[NSNumber numberWithInt:defaultvalue],DefaultValueKey,
|
|
IntegerOptionType,OptionTypeKey,
|
|
description,DescriptionKey,
|
|
argdescription,ArgumentDescriptionKey,
|
|
nil];
|
|
|
|
[options setObject:dict forKey:option];
|
|
[optionordering addObject:option];
|
|
}
|
|
|
|
// Int options with range?
|
|
|
|
|
|
|
|
|
|
-(void)addFloatingPointOption:(NSString *)option
|
|
description:(NSString *)description
|
|
{
|
|
[self addFloatingPointOption:option
|
|
description:description argumentDescription:@"number"];
|
|
}
|
|
|
|
-(void)addFloatingPointOption:(NSString *)option
|
|
description:(NSString *)description argumentDescription:(NSString *)argdescription
|
|
{
|
|
[self _assertOptionNameIsUnique:option];
|
|
|
|
NSMutableDictionary *dict=[NSMutableDictionary dictionaryWithObjectsAndKeys:
|
|
[NSMutableArray arrayWithObject:option],NamesKey,
|
|
FloatingPointOptionType,OptionTypeKey,
|
|
description,DescriptionKey,
|
|
argdescription,ArgumentDescriptionKey,
|
|
nil];
|
|
|
|
[options setObject:dict forKey:option];
|
|
[optionordering addObject:option];
|
|
}
|
|
|
|
-(void)addFloatingPointOption:(NSString *)option defaultValue:(double)defaultvalue
|
|
description:(NSString *)description
|
|
{
|
|
[self addFloatingPointOption:option defaultValue:defaultvalue
|
|
description:description argumentDescription:@"number"];
|
|
}
|
|
|
|
-(void)addFloatingPointOption:(NSString *)option defaultValue:(double)defaultvalue
|
|
description:(NSString *)description argumentDescription:(NSString *)argdescription
|
|
{
|
|
[self _assertOptionNameIsUnique:option];
|
|
|
|
NSMutableDictionary *dict=[NSMutableDictionary dictionaryWithObjectsAndKeys:
|
|
[NSMutableArray arrayWithObject:option],NamesKey,
|
|
[NSNumber numberWithDouble:defaultvalue],DefaultValueKey,
|
|
FloatingPointOptionType,OptionTypeKey,
|
|
description,DescriptionKey,
|
|
argdescription,ArgumentDescriptionKey,
|
|
nil];
|
|
|
|
[options setObject:dict forKey:option];
|
|
[optionordering addObject:option];
|
|
}
|
|
|
|
|
|
|
|
|
|
-(void)addSwitchOption:(NSString *)option description:(NSString *)description
|
|
{
|
|
[self _assertOptionNameIsUnique:option];
|
|
[options setObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
|
|
[NSMutableArray arrayWithObject:option],NamesKey,
|
|
SwitchOptionType,OptionTypeKey,
|
|
description,DescriptionKey,
|
|
nil] forKey:option];
|
|
[optionordering addObject:option];
|
|
}
|
|
|
|
|
|
|
|
|
|
-(void)addHelpOption
|
|
{
|
|
[self addHelpOptionNamed:@"help" description:@"Display this information."];
|
|
[self addAlias:@"h" forOption:@"help"];
|
|
}
|
|
|
|
-(void)addHelpOptionNamed:(NSString *)helpoption description:(NSString *)description
|
|
{
|
|
[options setObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
|
|
[NSMutableArray arrayWithObject:helpoption],NamesKey,
|
|
HelpOptionType,OptionTypeKey,
|
|
description,DescriptionKey,
|
|
nil] forKey:helpoption];
|
|
[optionordering addObject:helpoption];
|
|
}
|
|
|
|
|
|
|
|
|
|
-(void)addVersionOption
|
|
{
|
|
[self addVersionOptionNamed:@"version" description:@"Print version and exit."];
|
|
[self addAlias:@"v" forOption:@"version"];
|
|
}
|
|
|
|
-(void)addVersionOptionNamed:(NSString *)versionoption description:(NSString *)description
|
|
{
|
|
[options setObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
|
|
[NSMutableArray arrayWithObject:versionoption],NamesKey,
|
|
VersionOptionType,OptionTypeKey,
|
|
description,DescriptionKey,
|
|
nil] forKey:versionoption];
|
|
[optionordering addObject:versionoption];
|
|
}
|
|
|
|
|
|
|
|
|
|
-(void)addAlias:(NSString *)alias forOption:(NSString *)option
|
|
{
|
|
[self _assertOptionNameIsUnique:alias];
|
|
|
|
NSMutableDictionary *dict=[options objectForKey:option];
|
|
if(!dict) [self _raiseUnknownOption:option];
|
|
|
|
[[dict objectForKey:NamesKey] addObject:alias];
|
|
|
|
[options setObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
|
|
option,AliasTargetKey,
|
|
AliasOptionType,OptionTypeKey,
|
|
nil] forKey:alias];
|
|
}
|
|
|
|
|
|
|
|
|
|
-(void)addRequiredOption:(NSString *)requiredoption
|
|
{
|
|
if(![options objectForKey:requiredoption]) [self _raiseUnknownOption:requiredoption];
|
|
|
|
[alwaysrequiredoptions addObject:requiredoption];
|
|
}
|
|
|
|
-(void)addRequiredOptionsArray:(NSArray *)requiredoptions
|
|
{
|
|
NSEnumerator *enumerator=[requiredoptions objectEnumerator];
|
|
NSString *requiredoption;
|
|
while((requiredoption=[enumerator nextObject])) [self addRequiredOption:requiredoption];
|
|
}
|
|
|
|
-(void)addRequiredOption:(NSString *)requiredoption forOption:(NSString *)option
|
|
{
|
|
NSMutableDictionary *dict=[options objectForKey:option];
|
|
if(!dict) [self _raiseUnknownOption:option];
|
|
|
|
NSMutableArray *requiredoptions=[dict objectForKey:RequiredOptionsKey];
|
|
if(requiredoptions) [requiredoptions addObject:requiredoption];
|
|
else
|
|
{
|
|
requiredoptions=[NSMutableArray arrayWithObject:requiredoption];
|
|
[dict setObject:requiredoptions forKey:RequiredOptionsKey];
|
|
}
|
|
}
|
|
|
|
-(void)addRequiredOptionsArray:(NSArray *)requiredoptions forOption:(NSString *)option
|
|
{
|
|
NSEnumerator *enumerator=[requiredoptions objectEnumerator];
|
|
NSString *requiredoption;
|
|
while((requiredoption=[enumerator nextObject])) [self addRequiredOption:requiredoption forOption:option];
|
|
}
|
|
|
|
|
|
|
|
|
|
-(BOOL)parseCommandLineWithArgc:(int)argc argv:(const char **)argv
|
|
{
|
|
NSMutableArray *arguments=[NSMutableArray array];
|
|
|
|
#ifdef __MINGW32__
|
|
int wargc;
|
|
wchar_t **wargv=CommandLineToArgvW(GetCommandLineW(),&wargc);
|
|
for(int i=1;i<wargc;i++) [arguments addObject:[NSString stringWithCharacters:wargv[i] length:wcslen(wargv[i])]];
|
|
if(!programname) [self setProgramName:[NSString stringWithCharacters:wargv[0] length:wcslen(wargv[0])]];
|
|
#else
|
|
for(int i=1;i<argc;i++) [arguments addObject:[NSString stringWithUTF8String:argv[i]]];
|
|
if(!programname) [self setProgramName:[NSString stringWithUTF8String:argv[0]]];
|
|
#endif
|
|
|
|
return [self parseArgumentArray:arguments];
|
|
}
|
|
|
|
-(BOOL)parseArgumentArray:(NSArray *)arguments
|
|
{
|
|
NSMutableArray *remainingarguments=[NSMutableArray array];
|
|
NSMutableArray *errors=[NSMutableArray array];
|
|
|
|
[self _parseArguments:arguments remainingArguments:remainingarguments errors:errors];
|
|
[self _setDefaultValues];
|
|
[self _parseRemainingArguments:remainingarguments errors:errors];
|
|
[self _enforceRequirementsWithErrors:errors];
|
|
|
|
if([errors count])
|
|
{
|
|
[self _reportErrors:errors];
|
|
return NO;
|
|
}
|
|
else return YES;
|
|
}
|
|
|
|
-(void)_parseArguments:(NSArray *)arguments remainingArguments:(NSMutableArray *)remainingarguments
|
|
errors:(NSMutableArray *)errors
|
|
{
|
|
NSEnumerator *enumerator=[arguments objectEnumerator];
|
|
NSString *argument;
|
|
BOOL stillparsing=YES;
|
|
while((argument=[enumerator nextObject]))
|
|
{
|
|
// Check for options, unless we have seen a stop marker.
|
|
if(stillparsing && [argument length]>1 && [argument characterAtIndex:0]=='-')
|
|
{
|
|
// Check for a stop marker.
|
|
if([argument isEqual:@"--"])
|
|
{
|
|
stillparsing=NO;
|
|
continue;
|
|
}
|
|
|
|
// See if option starts with one or two dashes (treated the same for now).
|
|
int firstchar=1;
|
|
if([argument characterAtIndex:1]=='-') firstchar=2;
|
|
|
|
// See if the option is of the form -option=value, and extract the name and value.
|
|
// Otherwise, just extract the name.
|
|
NSString *option,*value;
|
|
NSRange equalsign=[argument rangeOfString:@"="];
|
|
if(equalsign.location!=NSNotFound)
|
|
{
|
|
option=[argument substringWithRange:NSMakeRange(firstchar,equalsign.location-firstchar)];
|
|
value=[argument substringFromIndex:equalsign.location+1];
|
|
}
|
|
else
|
|
{
|
|
option=[argument substringFromIndex:firstchar];
|
|
value=nil; // Find the value later.
|
|
}
|
|
|
|
// Find option dictionary, or produce an error if the option is not known.
|
|
NSMutableDictionary *dict=[options objectForKey:option];
|
|
if(!dict)
|
|
{
|
|
[errors addObject:[NSString stringWithFormat:@"Unknown option %@.",argument]];
|
|
continue;
|
|
}
|
|
|
|
NSString *type=[dict objectForKey:OptionTypeKey];
|
|
|
|
// Resolve aliases.
|
|
while(type==AliasOptionType)
|
|
{
|
|
dict=[options objectForKey:[dict objectForKey:AliasTargetKey]];
|
|
type=[dict objectForKey:OptionTypeKey];
|
|
}
|
|
|
|
// Handle help options.
|
|
if(type==HelpOptionType)
|
|
{
|
|
[self printUsage];
|
|
exit(0);
|
|
}
|
|
|
|
// Handle version option
|
|
if(type==VersionOptionType)
|
|
{
|
|
[programversion print];
|
|
[@"\n" print];
|
|
exit(0);
|
|
}
|
|
|
|
// Find value for options of the form -option value, if needed.
|
|
if(!value)
|
|
if(type==StringOptionType||type==MultipleChoiceOptionType||
|
|
type==IntegerOptionType||type==FloatingPointOptionType)
|
|
{
|
|
value=[enumerator nextObject];
|
|
if(!value)
|
|
{
|
|
[errors addObject:[NSString stringWithFormat:@"The option -%@ requires a value.",option]];
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Actually parse value and type
|
|
[self _parseOptionWithDictionary:dict type:type name:option value:value errors:errors];
|
|
}
|
|
else
|
|
{
|
|
[remainingarguments addObject:argument];
|
|
}
|
|
}
|
|
}
|
|
|
|
-(void)_parseOptionWithDictionary:(NSMutableDictionary *)dict type:(NSString *)type
|
|
name:(NSString *)option value:(NSString *)value errors:(NSMutableArray *)errors
|
|
{
|
|
if(type==StringOptionType)
|
|
{
|
|
[dict setObject:value forKey:StringValueKey];
|
|
}
|
|
else if(type==MultipleChoiceOptionType)
|
|
{
|
|
NSArray *allowedvalues=[dict objectForKey:AllowedValuesKey];
|
|
NSUInteger index=[allowedvalues indexOfObject:[value lowercaseString]];
|
|
if(index==NSNotFound)
|
|
{
|
|
[errors addObject:[NSString stringWithFormat:@"\"%@\" is not a valid "
|
|
@"value for option \"%@\". (Valid values are: %@.)",value,option,
|
|
[allowedvalues componentsJoinedByString:@", "]]];
|
|
return;
|
|
}
|
|
|
|
[dict setObject:[allowedvalues objectAtIndex:index] forKey:StringValueKey];
|
|
[dict setObject:[NSNumber numberWithUnsignedInt:index] forKey:NumberValueKey];
|
|
}
|
|
else if(type==IntegerOptionType)
|
|
{
|
|
NSScanner *scanner=[NSScanner scannerWithString:value];
|
|
BOOL success;
|
|
if([value hasPrefix:@"0x"]||[value hasPrefix:@"0X"])
|
|
{
|
|
unsigned long long intval;
|
|
success=[scanner scanHexLongLong:&intval];
|
|
if(success) [dict setObject:[NSNumber numberWithUnsignedLongLong:intval] forKey:NumberValueKey];
|
|
}
|
|
else
|
|
{
|
|
long long intval;
|
|
success=[scanner scanLongLong:&intval];
|
|
if(success) [dict setObject:[NSNumber numberWithLongLong:intval] forKey:NumberValueKey];
|
|
}
|
|
|
|
if(!success)
|
|
{
|
|
[errors addObject:[NSString stringWithFormat:@"The option -%@ requires an "
|
|
@"integer number value.",option]];
|
|
return;
|
|
}
|
|
}
|
|
else if(type==FloatingPointOptionType)
|
|
{
|
|
NSScanner *scanner=[NSScanner scannerWithString:value];
|
|
double floatval;
|
|
BOOL success;
|
|
|
|
#ifndef __COCOTRON__
|
|
if([value hasPrefix:@"0x"]||[value hasPrefix:@"0X"]) success=[scanner scanHexDouble:&floatval];
|
|
else success=[scanner scanDouble:&floatval];
|
|
#else
|
|
success=[scanner scanDouble:&floatval];
|
|
#endif
|
|
|
|
if(!success)
|
|
{
|
|
[errors addObject:[NSString stringWithFormat:@"The option -%@ requires a "
|
|
@"floating-point number value.",option]];
|
|
return;
|
|
}
|
|
|
|
[dict setObject:[NSNumber numberWithDouble:floatval] forKey:NumberValueKey];
|
|
}
|
|
else if(type==SwitchOptionType)
|
|
{
|
|
[dict setObject:[NSNumber numberWithBool:YES] forKey:NumberValueKey];
|
|
}
|
|
}
|
|
|
|
-(void)_setDefaultValues
|
|
{
|
|
NSEnumerator *enumerator=[options objectEnumerator];
|
|
NSMutableDictionary *dict;
|
|
while((dict=[enumerator nextObject]))
|
|
{
|
|
id defaultvalue=[dict objectForKey:DefaultValueKey];
|
|
if(!defaultvalue) continue;
|
|
|
|
NSString *type=[dict objectForKey:OptionTypeKey];
|
|
|
|
if(type==StringOptionType)
|
|
{
|
|
if(![dict objectForKey:StringValueKey]) [dict setObject:defaultvalue forKey:StringValueKey];
|
|
}
|
|
else if(type==MultipleChoiceOptionType)
|
|
{
|
|
if(![dict objectForKey:NumberValueKey])
|
|
{
|
|
int index=[defaultvalue unsignedIntValue];
|
|
NSArray *allowedvalues=[dict objectForKey:AllowedValuesKey];
|
|
[dict setObject:[allowedvalues objectAtIndex:index] forKey:StringValueKey];
|
|
[dict setObject:defaultvalue forKey:NumberValueKey];
|
|
}
|
|
}
|
|
else if(type==IntegerOptionType||type==FloatingPointOptionType)
|
|
{
|
|
if(![dict objectForKey:NumberValueKey]) [dict setObject:defaultvalue forKey:NumberValueKey];
|
|
}
|
|
}
|
|
}
|
|
|
|
-(void)_parseRemainingArguments:(NSArray *)remainingarguments errors:(NSMutableArray *)errors
|
|
{
|
|
[remainingargumentarray autorelease];
|
|
remainingargumentarray=[remainingarguments retain];
|
|
}
|
|
|
|
-(void)_enforceRequirementsWithErrors:(NSMutableArray *)errors
|
|
{
|
|
if([alwaysrequiredoptions count]) [self _requireOptionsInArray:alwaysrequiredoptions when:@"" errors:errors];
|
|
|
|
NSEnumerator *enumerator=[options objectEnumerator];
|
|
NSDictionary *dict;
|
|
while((dict=[enumerator nextObject]))
|
|
{
|
|
NSArray *names=[dict objectForKey:NamesKey];
|
|
NSString *name=[names objectAtIndex:0];
|
|
NSArray *requiredoptions=[dict objectForKey:RequiredOptionsKey];
|
|
|
|
if(requiredoptions)
|
|
if([self _isOptionDefined:name])
|
|
{
|
|
[self _requireOptionsInArray:requiredoptions
|
|
when:[NSString stringWithFormat:@" when the option %@ is used",[self _describeOption:name]]
|
|
errors:errors];
|
|
}
|
|
}
|
|
}
|
|
|
|
-(void)_requireOptionsInArray:(NSArray *)requiredoptions when:(NSString *)when errors:(NSMutableArray *)errors
|
|
{
|
|
NSMutableSet *set=[NSMutableSet set];
|
|
|
|
NSEnumerator *enumerator=[requiredoptions objectEnumerator];
|
|
NSString *requiredoption;
|
|
while((requiredoption=[enumerator nextObject]))
|
|
{
|
|
if(![self _isOptionDefined:requiredoption]) [set addObject:requiredoption];
|
|
}
|
|
|
|
if([set count]==0) return;
|
|
|
|
NSMutableArray *array=[NSMutableArray array];
|
|
|
|
enumerator=[optionordering objectEnumerator];
|
|
NSString *option;
|
|
while((option=[enumerator nextObject]))
|
|
{
|
|
if([set containsObject:option]) [array addObject:[self _describeOption:option]];
|
|
}
|
|
|
|
if([array count]==1)
|
|
{
|
|
[errors addObject:[NSString stringWithFormat:@"The option %@ is required%@.",[array objectAtIndex:0],when]];
|
|
}
|
|
else
|
|
{
|
|
[errors addObject:[NSString stringWithFormat:@"The options %@ and %@ are required%@.",[array objectAtIndex:0],
|
|
[[array subarrayWithRange:NSMakeRange(0,[array count]-1)] componentsJoinedByString:@", "],when]];
|
|
}
|
|
}
|
|
|
|
-(BOOL)_isOptionDefined:(NSString *)option
|
|
{
|
|
NSDictionary *dict=[options objectForKey:option];
|
|
return [dict objectForKey:StringValueKey]||[dict objectForKey:NumberValueKey]||[dict objectForKey:ArrayValueKey];
|
|
}
|
|
|
|
-(NSString *)_describeOption:(NSString *)name
|
|
{
|
|
NSDictionary *dict=[options objectForKey:name];
|
|
NSArray *names=[dict objectForKey:NamesKey];
|
|
if([names count]==1) return [names objectAtIndex:0];
|
|
else return [NSString stringWithFormat:@"-%@ (-%@)",[names objectAtIndex:0],
|
|
[[names subarrayWithRange:NSMakeRange(1,[names count]-1)] componentsJoinedByString:@", -"]];
|
|
}
|
|
|
|
-(NSString *)_describeOptionAndArgument:(NSString *)name
|
|
{
|
|
NSDictionary *dict=[options objectForKey:name];
|
|
NSString *argdescription=[dict objectForKey:ArgumentDescriptionKey];
|
|
|
|
if(argdescription) return [NSString stringWithFormat:@"%@ <%@>",[self _describeOption:name],argdescription];
|
|
else return [self _describeOption:name];
|
|
}
|
|
|
|
-(void)_reportErrors:(NSArray *)errors
|
|
{
|
|
NSEnumerator *enumerator=[errors objectEnumerator];
|
|
NSString *error;
|
|
while((error=[enumerator nextObject]))
|
|
{
|
|
[error print];
|
|
[@"\n" print];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-(void)printUsage
|
|
{
|
|
[usageheader print];
|
|
|
|
int terminalwidth=[NSString terminalWidth];
|
|
int count=[optionordering count];
|
|
int maxlength=0;
|
|
|
|
NSMutableArray *optiondescriptions=[NSMutableArray array];
|
|
|
|
for(int i=0;i<count;i++)
|
|
{
|
|
NSString *option=[optionordering objectAtIndex:i];
|
|
|
|
NSString *description=[self _describeOptionAndArgument:option];
|
|
|
|
[optiondescriptions addObject:description];
|
|
|
|
int length=[description length];
|
|
if(length>maxlength) maxlength=length;
|
|
}
|
|
|
|
int columnwidth1=maxlength+2;
|
|
int columnwidth2=terminalwidth-columnwidth1;
|
|
|
|
for(int i=0;i<count;i++)
|
|
{
|
|
NSString *option=[optionordering objectAtIndex:i];
|
|
NSString *optiondescription=[optiondescriptions objectAtIndex:i];
|
|
NSDictionary *dict=[options objectForKey:option];
|
|
|
|
[optiondescription print];
|
|
|
|
for(int i=[optiondescription length];i<columnwidth1;i++)
|
|
[@" " print];
|
|
|
|
NSArray *lines=[[dict objectForKey:DescriptionKey] linesWrappedToWidth:columnwidth2];
|
|
|
|
int numlines=[lines count];
|
|
for(int i=0;i<numlines;i++)
|
|
{
|
|
if(i!=0) for(int j=0;j<columnwidth1;j++) [@" " print];
|
|
[[lines objectAtIndex:i] print];
|
|
[@"\n" print];
|
|
}
|
|
}
|
|
|
|
[usagefooter print];
|
|
}
|
|
|
|
|
|
|
|
-(NSString *)stringValueForOption:(NSString *)option
|
|
{
|
|
return [[options objectForKey:option] objectForKey:StringValueKey];
|
|
}
|
|
|
|
-(NSArray *)stringArrayValueForOption:(NSString *)option
|
|
{
|
|
return [[options objectForKey:option] objectForKey:ArrayValueKey];
|
|
}
|
|
|
|
-(int)intValueForOption:(NSString *)option
|
|
{
|
|
return [[[options objectForKey:option] objectForKey:NumberValueKey] intValue];
|
|
}
|
|
|
|
-(float)floatValueForOption:(NSString *)option
|
|
{
|
|
return [[[options objectForKey:option] objectForKey:NumberValueKey] floatValue];
|
|
}
|
|
|
|
-(double)doubleValueForOption:(NSString *)option
|
|
{
|
|
return [[[options objectForKey:option] objectForKey:NumberValueKey] doubleValue];
|
|
}
|
|
|
|
-(BOOL)boolValueForOption:(NSString *)option
|
|
{
|
|
return [[[options objectForKey:option] objectForKey:NumberValueKey] boolValue];
|
|
}
|
|
|
|
|
|
|
|
|
|
-(NSArray *)remainingArguments
|
|
{
|
|
return remainingargumentarray;
|
|
}
|
|
|
|
|
|
|
|
|
|
-(void)_assertOptionNameIsUnique:(NSString *)option
|
|
{
|
|
if([options objectForKey:option])
|
|
[NSException raise:NSInvalidArgumentException format:@"Attempted to add duplicate option \"%@\".",option];
|
|
}
|
|
|
|
-(void)_raiseUnknownOption:(NSString *)option
|
|
{
|
|
[NSException raise:NSInvalidArgumentException format:@"Unknown option \"%@\".",option];
|
|
}
|
|
|
|
@end
|