Merge pull request #102 from Artoria2e5/pluti

Add plutil utility
This commit is contained in:
rfm 2020-11-06 09:12:33 +00:00 committed by GitHub
commit 8ccd6e2675
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 882 additions and 11 deletions

77
.clang-format Normal file
View file

@ -0,0 +1,77 @@
# Copyright (C) 2015 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Modified from gcc's clang-format config.
# clang-format 7.0.1 is required
#
# To utilize the tool to lines just touched by a patch, use
# clang-format-diff script that is usually also packaged with clang-format.
#
# Example of usage:
# git diff -U0 --no-color | clang-format-diff -p1
# (here the tool will generate a patch)
# git diff -U0 --no-color | clang-format-diff -p1 -i
# (modifications are applied)
---
AccessModifierOffset: -2
AlwaysBreakAfterReturnType: TopLevel
AlignConsecutiveDeclarations: true
BinPackArguments: true
BinPackParameters: true
BreakBeforeBinaryOperators: All
BreakBeforeBraces: Custom
# Newer clang-format has BS_GNU
BraceWrapping:
AfterClass: true
AfterControlStatement: true
AfterEnum: true
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: true
AfterStruct: true
AfterUnion: true
BeforeCatch: true
BeforeElse: true
IndentBraces: true
SplitEmptyFunction: false
BreakBeforeTernaryOperators: true
ColumnLimit: 80
ConstructorInitializerIndentWidth: 2
ContinuationIndentWidth: 2
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
ForEachMacros: []
IndentCaseLabels: false
NamespaceIndentation: None
PenaltyBreakBeforeFirstCallParameter: 100
DerivePointerAlignment: false
PointerAlignment: Right
SortIncludes: false
SpaceAfterCStyleCast: true
SpaceBeforeParens: ControlStatements
SpacesBeforeTrailingComments: 1
UseTab: Always
AlignEscapedNewlines: Right
AlignTrailingComments: true
AllowShortFunctionsOnASingleLine: All
AlwaysBreakTemplateDeclarations: MultiLine
KeepEmptyLinesAtTheStartOfBlocks: false
# TODO
# MacroBlockBegin:
# MacroBlockEnd:

View file

@ -345,7 +345,7 @@ foundIgnorableWhitespace: (NSString *)string
}
else if ([elementName isEqualToString: @"real"])
{
ASSIGN(plist, [NSNumber numberWithDouble: [value doubleValue]]);
ASSIGN(plist, [NSNumber numberWithDouble: strtod([value cString], NULL)]);
}
else if ([elementName isEqualToString: @"true"])
{
@ -1135,7 +1135,7 @@ static id parsePlItem(pldata* pld)
else
{
result = [[NSNumber alloc]
initWithUnsignedLongLong: strtoull(buf, 0, 10)];
initWithUnsignedLongLong: strtoull(buf, NULL, 10)];
}
}
else if (type == 'B')
@ -1169,12 +1169,12 @@ static id parsePlItem(pldata* pld)
}
else if (type == 'R')
{
unichar buf[len];
double d = 0.0;
char buf[len+1];
for (i = 0; i < len; i++) buf[i] = ptr[i];
GSScanDouble(buf, len, &d);
result = [[NSNumber alloc] initWithDouble: d];
buf[len] = '\0';
result = [[NSNumber alloc]
initWithDouble: strtod(buf, NULL)];
}
else
{
@ -2481,6 +2481,18 @@ static BOOL classInitialized = NO;
return dest;
}
/**
* <p>Make <var>obj</var> into a plist in <var>str</var>, using the locale <var>loc</var>.</p>
*
* <p>If <var>*str</var> is <code>nil</code>, create a <ref>GSMutableString</ref>.
* Otherwise <var>*str</var> must be a GSMutableString.</p>
*
* <p>Options:</p><ul>
* <li><var>step</var> is the indent level.</li>
* <li><var>forDescription</var> enables OpenStep formatting.</li>
* <li><var>xml</var> enables XML formatting.</li>
* </ul>
*/
void
GSPropertyListMake(id obj, NSDictionary *loc, BOOL xml,
BOOL forDescription, unsigned step, id *str)
@ -2757,7 +2769,7 @@ GSPropertyListMake(id obj, NSDictionary *loc, BOOL xml,
// not the other way round,
NSData *data = [self dataWithPropertyList: aPropertyList
format: aFormat
options: 0
options: anOption
error: error];
return [stream write: [data bytes] maxLength: [data length]];

View file

@ -72,7 +72,7 @@ MAN8_PAGES = gdomap.8
ifeq ($(add),yes)
TOOL_NAME = autogsdoc cvtenc plmerge sfparse xmlparse
else
TOOL_NAME = autogsdoc cvtenc gdnc gspath defaults pl plmerge \
TOOL_NAME = autogsdoc cvtenc gdnc gspath defaults pl plmerge plutil \
plparse sfparse pldes plget plser pl2link xmlparse HTMLLinker
CTOOL_NAME = gdomap
@ -97,6 +97,7 @@ plget_OBJC_FILES = plget.m
plser_OBJC_FILES = plser.m
plmerge_OBJC_FILES = plmerge.m
plparse_OBJC_FILES = plparse.m
plutil_OBJC_FILES = NSPropertyList+PLUtil.m plutil.m
sfparse_OBJC_FILES = sfparse.m
pl2link_OBJC_FILES = pl2link.m
locale_alias_OBJC_FILES = locale_alias.m

View file

@ -0,0 +1,47 @@
/** Permit handling JSON as plists, and writing Objective-C literals.
Copyright (C) 2020 Free Software Foundation, Inc.
Written by: Mingye Wang
Created: feb 2020
This file is part of the GNUstep Objective-C Library.
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 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
Library 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 02111 USA.
*/
#import "Foundation/NSPropertyList.h"
/** Extra types supported by plutil. */
enum _PLUExtentedFormats
{
NSPropertyListJSONFormat = NSPropertyListBinaryFormat_v1_0 + 100,
/** https://clang.llvm.org/docs/ObjectiveCLiterals.html */
NSPropertyListObjectiveCFormat,
/** https://docs.swift.org/swift-book/ReferenceManual/zzSummaryOfTheGrammar.html */
NSPropertyListSwiftFormat,
};
@interface NSPropertyListSerialization (PLUtilAdditions)
+ (NSData *)_pdataFromPropertyList:(id)aPropertyList
format:(NSPropertyListFormat)aFormat
errorDescription:(NSString **)anErrorString;
+ (id)_ppropertyListWithData:(NSData *)data
options:(NSPropertyListReadOptions)anOption
format:(NSPropertyListFormat *)aFormat
error:(out NSError **)error;
+ (void)load;
@end

View file

@ -0,0 +1,122 @@
/** Permit handling JSON as plists, and writing Objective-C literals.
Copyright (C) 2020 Free Software Foundation, Inc.
Written by: Mingye Wang
Created: feb 2020
This file is part of the GNUstep Objective-C Library.
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 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
Library 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 02111 USA.
*/
#import "NSPropertyList+PLUtil.h"
#import "GNUstepBase/GSObjCRuntime.h"
#import "Foundation/NSData.h"
#import "Foundation/NSUserDefaults.h"
#import "Foundation/NSJSONSerialization.h"
static IMP originalRead = 0;
static IMP originalWrite = 0;
@implementation NSPropertyListSerialization (PLUtilAdditions)
+ (NSData *)_pdataFromPropertyList:(id)aPropertyList
format:(NSPropertyListFormat)aFormat
errorDescription:(NSString **)anErrorString
{
NSError * myError = nil;
NSData * dest;
NSDictionary *loc;
loc = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation];
switch (aFormat)
{
case NSPropertyListJSONFormat:
dest = [NSJSONSerialization
dataWithJSONObject:aPropertyList
options:loc != nil ? NSJSONWritingPrettyPrinted : 0
error:&myError];
if (myError != nil && anErrorString != NULL)
{
*anErrorString = [myError description];
}
return dest;
case NSPropertyListObjectiveCFormat:
case NSPropertyListSwiftFormat:
*anErrorString = @"Not implemented";
return nil;
default:
return (*originalWrite)(self, _cmd, aPropertyList, aFormat, anErrorString);
}
}
+ (id)_ppropertyListWithData:(NSData *)data
options:(NSPropertyListReadOptions)anOption
format:(NSPropertyListFormat *)aFormat
error:(out NSError **)error;
{
NSError * myError = nil;
NSPropertyListFormat format;
NSJSONReadingOptions jsonOptions = NSJSONReadingAllowFragments;
id prop = (*originalRead)(self, _cmd, data, anOption, &format, &myError);
if (prop == nil)
if (format == NSPropertyListOpenStepFormat
|| format == NSPropertyListGNUstepFormat)
// rescue as json when we know it is not anything else
{
switch (anOption)
{
case NSPropertyListMutableContainersAndLeaves:
jsonOptions |= NSJSONReadingMutableLeaves;
/* FALLTHROUGH */
case NSPropertyListMutableContainers:
jsonOptions |= NSJSONReadingMutableContainers;
}
format = NSPropertyListJSONFormat;
prop = [NSJSONSerialization JSONObjectWithData:data
options:jsonOptions
error:&myError];
}
if (error != NULL)
*error = myError;
if (*aFormat != NULL)
*aFormat = format;
return prop;
}
+ (void)load
{
Method replacementRead;
Method replacementWrite;
replacementRead
= class_getClassMethod(self, @selector
(_ppropertyListWithData:options:format:error:));
replacementWrite
= class_getClassMethod(self, @selector
(_pdataFromPropertyList:format:errorDescription:));
originalRead
= class_replaceMethod(object_getClass(self),
@selector(propertyListWithData:options:format:error:),
method_getImplementation(replacementRead),
method_getTypeEncoding(replacementRead));
originalWrite
= class_replaceMethod(object_getClass(self),
@selector(dataFromPropertyList:
format:errorDescription:),
method_getImplementation(replacementWrite),
method_getTypeEncoding(replacementWrite));
}
@end

View file

@ -17,7 +17,7 @@ pl, pldes, plser, plmerge, plparse, pl2link \- property list tools
.nf
.BI "plget " "key" [ more keys ]
.nf
.BI "plser " "filename(s)"
.BI "plser [ " "-format" "fmt" " ] filename(s)"
.nf
.BI "plmerge [ " "destination-file" " ] [ " "input-file(s)" " ]"
.nf
@ -49,9 +49,9 @@ Reads a text representation of a dictionary in property list format as
standard input, extracts the string value held in that dictionary with
the specified key, and writes the result to standard output.
Multiple keys may be used to extract values from nested dictionaries.
.IP "\fBplser\fR \fIfilename(s)\fR" 4
.IP "\fBplser\fR [ \fI-format format\fR ] \fIfilename(s)\fR" 4
Converts a text representation of a property list to a binary serialized
representation.
representation, or to another selected format.
.IP "\fBplmerge\fR [ \fIdestination-file\fR ] [ \fIinput-file(s)\fR ]" 4
Merges text property lists into a single property list
.IP "\fBplparse\fR \fIfilename(s)\fR" 4

612
Tools/plutil.m Normal file
View file

@ -0,0 +1,612 @@
/** Property list utility.
Copyright (C) 2020 Free Software Foundation, Inc.
Written by: Mingye Wang
Created: feb 2020
This file is part of the GNUstep Project
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
You should have received a copy of the GNU General Public
License along with this program; see the file COPYINGv3.
If not, write to the Free Software Foundation,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// #import "common.h"
#include <string.h>
#import "Foundation/NSArray.h"
#import "Foundation/NSAutoreleasePool.h"
#import "Foundation/NSData.h"
#import "Foundation/NSDictionary.h"
#import "Foundation/NSException.h"
#import "Foundation/NSFileHandle.h"
#import "Foundation/NSProcessInfo.h"
#import "Foundation/NSPropertyList.h"
#import "Foundation/NSString.h"
#import "Foundation/NSUserDefaults.h"
#import "Foundation/NSValue.h"
#import "NSPropertyList+PLUtil.h"
// From NSPropertyList.m
extern void
GSPropertyListMake(id, NSDictionary *, BOOL, BOOL, unsigned, id *);
// We don't have @[] on gcc
#define NARRAY(...) [NSArray arrayWithObjects:__VA_ARGS__, nil]
// And no @() or @123
#define NINT(Num) [NSNumber numberWithInt:Num]
// Unfortunately we have to define @() for @"" too because a macro later
#define NSTR(Str) [NSString stringWithCString:Str]
/* Bitmap of 'quotable' characters ... those characters which must be
* inside a quoted string if written to an old style property list.
* Taken from NSPropertyList.m.
*/
static const unsigned char quotables[32] = {
'\xff', '\xff', '\xff', '\xff', '\x85', '\x13', '\x00', '\x78',
'\x00', '\x00', '\x00', '\x38', '\x01', '\x00', '\x00', '\xa8',
'\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff',
'\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff',
};
#define IS_BIT_SET(a, i) ((((a) & (1 << (i)))) > 0)
#define GS_IS_QUOTABLE(X) IS_BIT_SET(quotables[(X) / 8], (X) % 8)
/**
* Indexes a NSArray or a NSDictionary.
*/
id
plIndex(id obj, NSString *key)
{
const char *ckey;
char * endptr = NULL;
NSInteger res;
if ([obj isKindOfClass:[NSDictionary class]])
{
return [(NSDictionary *) obj objectForKey:key];
}
else if ([obj isKindOfClass:[NSArray class]])
{
ckey = [key cStringUsingEncoding:[NSString defaultCStringEncoding]];
res = strtoll(ckey, &endptr, 10);
if (endptr && *endptr != '\0')
{
[NSException raise:NSInvalidArgumentException
format:@"%@ is not a valid integer", key];
}
return [(NSArray *) obj objectAtIndex:res];
}
else
{
[NSException raise:NSInvalidArgumentException
format:@"%@ is not indexible", obj];
return nil;
}
}
/**
* Mutate obj[key] to leaf.
* If leaf is nil, remove.
* Else if replace is NO, insert.
* Otherwise replace.
*
* Inserting and replacing are the same for NSMutableDictionary.
*/
void
mutate(id obj, NSString *key, id leaf, BOOL replace)
{
const char *ckey;
char * endptr = NULL;
NSInteger res;
if ([obj isKindOfClass:[NSMutableDictionary class]])
{
if (!leaf)
[(NSMutableDictionary *) obj removeObjectForKey:key];
else
[(NSMutableDictionary *) obj setObject:leaf forKey:key];
}
else if ([obj isKindOfClass:[NSMutableArray class]])
{
ckey = [key cStringUsingEncoding:[NSString defaultCStringEncoding]];
res = strtoll(ckey, &endptr, 10);
if (endptr && *endptr != '\0')
{
[NSException raise:NSInvalidArgumentException
format:@"%@ is not a valid integer", key];
}
if (!leaf)
[(NSMutableArray *) obj removeObjectAtIndex:res];
else if (replace)
[(NSMutableArray *) obj replaceObjectAtIndex:res withObject:leaf];
else
[(NSMutableArray *) obj insertObject:leaf atIndex:res];
}
else
{
[NSException raise:NSInvalidArgumentException
format:@"%@ is not indexible", obj];
}
}
#define KEYPATH_SEP '.'
NSString *
parseQuotedString(const char *, size_t *);
/**
* Parses a keypath to its component keys. ISO/IEC 14977 EBNF:
* <pre>
* keypath = '' | keypath '.' key;
* key = quotedString | unquotedStringNotAllowingPeriod;
* </pre>
* The definitions of strings follow plist conventions.
* The use of quoted strings is a GNUstep extension (I think).
*/
NSArray *
parseKeyPath(const char *keypath)
{
NSMutableArray *res = [[NSMutableArray alloc] init];
NSString * key = nil;
size_t i;
size_t j;
for (i = 0; keypath[i]; i++)
switch (keypath[i])
{
case KEYPATH_SEP:
[res addObject:key];
key = nil;
break;
case '"':
key = parseQuotedString(keypath, &i);
break;
default:
for (j = i; keypath[j] && !GS_IS_QUOTABLE(keypath[j])
&& keypath[j] != KEYPATH_SEP;
j++)
;
key = [NSString stringWithCString:&keypath[i] length:(j - i)];
i = j - 1;
break;
}
return [res copy];
}
/**
* Parse a quoted string by pretending it is a plist.
*/
NSString *
parseQuotedString(const char *keypath, size_t *i)
{
const char *begin = &keypath[*i];
const char *end;
id parsed;
// Select the part of the quoted string that looks like a plist string
for (end = begin + 1; *end && *end != '"'; end++)
if (*end == '\\')
{
if (end[1])
end += 1;
else
[NSException raise:NSInvalidArgumentException
format:@"Premature EOF in keypath"];
}
if (!end)
[NSException raise:NSInvalidArgumentException
format:@"Premature EOF in keypath"];
*i = end - keypath + 1;
parsed = [[NSString stringWithCString:begin length:end - begin] propertyList];
return (NSString *) parsed;
}
/**
* Index by keypath.
*/
id
plIndexKeypath(id obj, NSString *keypath, int depthOffset)
{
NSArray *parsedPath = parseKeyPath([keypath cString]);
int count = [parsedPath count];
int i;
for (i = 0; i < count - depthOffset; i++)
obj = plIndex(obj, [parsedPath objectAtIndex:i]);
return obj;
}
/**
* Interpret -type value options.
*
* The -plist type is a GNUStep extension.
*/
id
parseValue(NSString *type, NSString *value)
{
if ([type isEqual:@"-plist"])
return [value propertyList];
else if ([type isEqual:@"-xml"] || [type isEqual:@"-date"])
{
NSData *mydata =
[value dataUsingEncoding:[NSString defaultCStringEncoding]];
NSPropertyListFormat aFormat;
NSError * anError;
id result = [NSPropertyListSerialization
propertyListWithData:mydata
options:NSPropertyListMutableContainersAndLeaves
format:&aFormat
error:&anError];
if (result == nil)
{
GSPrintf(stderr, @"Parsing plist %@: %@\n", value, anError);
return nil;
}
else if ([type isEqual:@"-xml"]
&& aFormat != NSPropertyListXMLFormat_v1_0)
GSPrintf(stderr,
@"Warning: parsing XML plist %@: Not an XML (fmt %d)\n", value,
aFormat);
else if ([type isEqual:@"-date"]
&& ![result isKindOfClass:[NSDate class]])
GSPrintf(stderr, @"Warning: parsing date %@: Not a date (got %@)\n",
value, result);
return result;
}
else if ([type isEqual:@"-bool"])
return [NSNumber
numberWithBool:([value isEqual:@"YES"] || [value isEqual:@"true"])];
else if ([type isEqual:@"-integer"])
return [[NSNumber alloc] initWithLongLong:[value longLongValue]];
else if ([type isEqual:@"-float"])
// We do a step further than NSPropertyList.m and probably Apple,
// since notsupporting inf and nan is a bad look
// (No hex literals for now unless someone really wants it)
return [[NSNumber alloc] initWithDouble:strtod([value cString], 0)];
else if ([type isEqual:@"-data"])
return [[NSData alloc]
initWithBase64EncodedData:[value
dataUsingEncoding:[NSString
defaultCStringEncoding]]
options:NSDataBase64DecodingIgnoreUnknownCharacters];
else
GSPrintf(stderr, @"Unrecognized type %@\n", type);
return nil;
}
#define SELFMAP(Name) NINT(Name), NSTR(#Name)
/**
* Translates a string fmt to NSPropertyListFormat.
*/
NSPropertyListFormat
plFormatFromName(NSString *name)
{
// clang-format off
NSDictionary *nameMap = [NSDictionary dictionaryWithObjectsAndKeys:
NINT(NSPropertyListXMLFormat_v1_0), @"xml1",
NINT(NSPropertyListBinaryFormat_v1_0), @"binary1",
NINT(NSPropertyListOpenStepFormat), @"openstep",
NINT(NSPropertyListGNUstepFormat), @"gnustep",
NINT(NSPropertyListGNUstepBinaryFormat), @"gsbinary",
NINT(NSPropertyListJSONFormat), @"json",
SELFMAP(NSPropertyListOpenStepFormat),
SELFMAP(NSPropertyListXMLFormat_v1_0),
SELFMAP(NSPropertyListBinaryFormat_v1_0),
SELFMAP(NSPropertyListGNUstepFormat),
SELFMAP(NSPropertyListGNUstepBinaryFormat),
nil];
id res = [nameMap objectForKey:name];
// clang-format on
if (!res)
[NSException raise:NSInvalidArgumentException
format:@"Invalid fmt %@", name];
return [res intValue];
}
/**
* Dumps obj to outfile.
*/
int
dumpToFile(id obj, NSPropertyListFormat fmt, NSString *outfile)
{
NSString * errorString = nil;
NSFileHandle *fh;
NSData * outdata =
[NSPropertyListSerialization dataFromPropertyList:obj
format:fmt
options:0
errorDescription:&errorString];
if (errorString)
{
GSPrintf(stderr, @"Dumping %@ as format %@ - %@\n", obj, fmt,
errorString);
return EXIT_FAILURE;
}
if ([outfile isEqual:@"-"])
fh = [NSFileHandle fileHandleWithStandardOutput];
else
fh = [NSFileHandle fileHandleForWritingAtPath:outfile];
[fh writeData:outdata];
[fh synchronizeFile];
return EXIT_SUCCESS;
}
int
plCmdConvert(id obj, NSArray *cmdargs, NSString *outfile)
{
NSString *fmt = [cmdargs objectAtIndex:0];
return dumpToFile(obj, plFormatFromName(fmt), outfile);
}
int
plCmdExtract(id obj, NSArray *cmdargs, NSString *outfile)
{
NSString *keypath = [cmdargs objectAtIndex:0];
NSString *fmt = [cmdargs objectAtIndex:1];
obj = plIndexKeypath(obj, keypath, 0);
return dumpToFile(obj, plFormatFromName(fmt), outfile);
}
int
plCmdRemove(id obj, NSPropertyListFormat fmt, NSArray *cmdargs,
NSString *outfile)
{
NSString *keypath = [cmdargs objectAtIndex:0];
NSArray *parsedPath = parseKeyPath([keypath cString]);
id leaf = plIndexKeypath(obj, keypath, 1);
mutate(leaf, [parsedPath lastObject], nil, false);
return dumpToFile(obj, fmt, outfile);
}
int
plCmdInsert(id obj, NSPropertyListFormat fmt, NSArray *cmdargs,
NSString *outfile)
{
NSString *keypath = [cmdargs objectAtIndex:0];
id newleaf = parseValue([cmdargs objectAtIndex:1], [cmdargs objectAtIndex:2]);
NSArray *parsedPath = parseKeyPath([keypath cString]);
id leaf = plIndexKeypath(obj, keypath, 1);
if (!newleaf)
return EXIT_FAILURE;
mutate(leaf, [parsedPath lastObject], newleaf, false);
return dumpToFile(obj, fmt, outfile);
}
int
plCmdReplace(id obj, NSPropertyListFormat fmt, NSArray *cmdargs,
NSString *outfile)
{
NSString *keypath = [cmdargs objectAtIndex:0];
id newleaf = parseValue([cmdargs objectAtIndex:1], [cmdargs objectAtIndex:2]);
NSArray *parsedPath = parseKeyPath([keypath cString]);
id leaf = plIndexKeypath(obj, keypath, 1);
if (!newleaf)
return EXIT_FAILURE;
mutate(leaf, [parsedPath lastObject], newleaf, true);
return dumpToFile(obj, fmt, outfile);
}
static void
print_help(FILE *f)
{
GSPrintf(f, @"Property list utility\n");
GSPrintf(f, @"Usage: plutil [command] [options] file\n\n");
GSPrintf(f, @"Accepted commands:\n");
GSPrintf(
f, @" -p\tPrints the plists in a human-readable form (GNUstep ASCII).\n");
GSPrintf(f, @"Accepted options:\n");
}
typedef enum _Action
{
ACTION_LINT,
ACTION_PRINT,
ACTION_CONVERT,
ACTION_REPLACE,
ACTION_INSERT,
ACTION_REMOVE,
ACTION_EXTRACT,
} Action;
/** <p> Property list utility. Should act like macOS catalina plutil(1).
* </p>
*/
int
main(int argc, char **argv, char **env)
{
int status = EXIT_SUCCESS;
ENTER_POOL
NSProcessInfo *proc;
NSArray * args;
NSString * arg;
NSString * inpath;
NSArray * cmdargs = nil;
NSDictionary * commands = nil;
NSArray * command_rhs;
NSString * outpath = nil;
NSString * outext = nil;
Action action = ACTION_LINT;
int count = 0;
int i = 1;
#ifdef GS_PASS_ARGUMENTS
GSInitializeProcess(argc, argv, env);
#endif
proc = [NSProcessInfo processInfo];
if (proc == nil)
{
NSLog(@"plutil: unable to get process information.");
status = EXIT_FAILURE;
break;
}
args = [proc arguments];
if ((count = [args count]) <= 1)
{
NSLog(@"plutil: no files given.");
status = EXIT_FAILURE;
break;
}
// Parse the COMMAND.
// Maps number of args to commands.
// clang-format off
commands = [NSDictionary dictionaryWithObjectsAndKeys:
NARRAY(NINT(ACTION_PRINT), NINT(0)), @"-p",
NARRAY(NINT(ACTION_LINT), NINT(0)), @"-lint",
NARRAY(NINT(ACTION_CONVERT), NINT(1)), @"-convert",
NARRAY(NINT(ACTION_INSERT), NINT(3)), @"-insert",
NARRAY(NINT(ACTION_REPLACE), NINT(3)), @"-replace",
NARRAY(NINT(ACTION_REMOVE), NINT(1)), @"-remove",
NARRAY(NINT(ACTION_EXTRACT), NINT(2)), @"-extract",
nil];
// clang-format on
NS_DURING
{
NSData * fileData;
NSPropertyListFormat aFormat;
NSError * anError;
id result;
NSMutableString * outStr = nil;
NSDictionary * locale;
arg = [args objectAtIndex:i];
if (![arg hasPrefix:@"-"])
goto parse_file;
command_rhs = [commands objectForKey:arg];
if (command_rhs)
{
int iwant;
NSRange argrange;
action = [[command_rhs objectAtIndex:0] intValue];
iwant = [[command_rhs objectAtIndex:1] intValue];
argrange.location = i + 1;
argrange.length = iwant;
cmdargs = [args subarrayWithRange:argrange];
i += 1 + iwant;
}
// Parse options
for (; i < count; i++)
{
arg = [args objectAtIndex:i];
if (![arg hasPrefix:@"-"] || [arg isEqual:@"-"] || [arg isEqual:@"--"])
goto parse_file;
else if ([arg isEqual:@"-help"])
{
print_help(stdout);
break;
}
else if ([arg isEqual:@"-s"])
/* NOOP: What the heck is being quiet? */;
else if ([arg isEqual:@"-o"])
outpath = [args objectAtIndex:++i];
else if ([arg isEqual:@"-e"])
outext = [args objectAtIndex:++i];
else
[NSException raise:NSInvalidArgumentException
format:@"Invalid option %@", arg];
}
parse_file:
inpath = [args objectAtIndex:i];
if (!outpath && !outext && action != ACTION_EXTRACT)
outpath = inpath;
else if (outext)
{
NSRange dot = [inpath rangeOfString:@"." options:NSBackwardsSearch];
if (dot.length == 0)
dot.location = [inpath length];
outpath = [NSString
stringWithFormat:@"%@.%@", [inpath substringToIndex:dot.location],
outext];
}
// Open, read, do things.
if ([inpath isEqual:@"-"])
{
NSFileHandle *fh = [NSFileHandle fileHandleWithStandardInput];
fileData = [fh readDataToEndOfFile];
}
else
fileData = [NSData dataWithContentsOfFile:inpath];
result = [NSPropertyListSerialization
propertyListWithData:fileData
options:NSPropertyListMutableContainersAndLeaves
format:&aFormat
error:&anError];
if (result == nil)
{
GSPrintf(stderr, @"Loading '%@' - %@\n", inpath, anError);
status = EXIT_FAILURE;
break;
}
switch (action)
{
case ACTION_LINT:
break;
case ACTION_PRINT:
// Not using description because we can GS it
locale =
[[NSUserDefaults standardUserDefaults] dictionaryRepresentation];
GSPropertyListMake(result, locale, NO, NO, 2, &outStr);
GSPrintf(stdout, @"%@\n", outStr);
break;
case ACTION_CONVERT:
status = plCmdConvert(result, cmdargs, outpath);
break;
case ACTION_REPLACE:
status = plCmdReplace(result, aFormat, cmdargs, outpath);
break;
case ACTION_INSERT:
status = plCmdInsert(result, aFormat, cmdargs, outpath);
break;
case ACTION_REMOVE:
status = plCmdRemove(result, aFormat, cmdargs, outpath);
break;
case ACTION_EXTRACT:
if (!outpath)
outpath = @"-";
status = plCmdExtract(result, cmdargs, outpath);
break;
}
}
NS_HANDLER
{
NSLog(@"Problem: %@", localException);
if ([[localException name] isEqual:NSInvalidArgumentException])
print_help(stderr);
status = EXIT_FAILURE;
break;
}
NS_ENDHANDLER
LEAVE_POOL
return status;
}