1
0
mirror of https://github.com/kellyjonbrazil/jc.git synced 2025-06-17 00:07:37 +02:00

add nextstep plist support. using the pbPlist library

This commit is contained in:
Kelly Brazil
2022-12-30 10:27:03 -08:00
parent ba89092e12
commit b1358a7eca
17 changed files with 1809 additions and 8 deletions

View File

@ -1,10 +1,11 @@
jc changelog
20221229 v1.23.4
20221229 v1.22.4
- Add `iwconfig` command parser
- Add NeXTSTEP format support to the PLIST file parser
- Fix `proc` parser magic signature detection for `/proc/pid/stat` hacks
- Fix `x509-cert` parser for string serial numbers
- Add category tags to parser metadata
- Add category tags to parser metadata: generic, standard, file, string, binary, command
- Add "list parsers by category" view to help
- Fix python 3.6-related issues
- Add python 3.6 to automated tests

View File

@ -5,7 +5,7 @@
jc - JSON Convert PLIST file parser
Converts binary and XML PLIST files.
Converts binary, XML, and NeXTSTEP PLIST files.
Binary values are converted into an ASCII hex representation.
@ -74,4 +74,4 @@ Returns:
### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Version 1.0 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.1 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@ -0,0 +1,366 @@
# Copyright (c) 2016, Samantha Marshall (http://pewpewthespells.com)
# All rights reserved.
#
# https://github.com/samdmarshall/pbPlist
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
#
# 3. Neither the name of Samantha Marshall nor the names of its contributors may
# be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
import sys
import string
if sys.version_info >= (3, 0):
def unichr(character): # pylint: disable=redefined-builtin
return chr(character)
def ConvertNEXTSTEPToUnicode(hex_digits):
# taken from http://ftp.unicode.org/Public/MAPPINGS/VENDORS/NEXT/NEXTSTEP.TXT
conversion = {
"80": "a0", # NO-BREAK SPACE
"81": "c0", # LATIN CAPITAL LETTER A WITH GRAVE
"82": "c1", # LATIN CAPITAL LETTER A WITH ACUTE
"83": "c2", # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
"84": "c3", # LATIN CAPITAL LETTER A WITH TILDE
"85": "c4", # LATIN CAPITAL LETTER A WITH DIAERESIS
"86": "c5", # LATIN CAPITAL LETTER A WITH RING
"87": "c7", # LATIN CAPITAL LETTER C WITH CEDILLA
"88": "c8", # LATIN CAPITAL LETTER E WITH GRAVE
"89": "c9", # LATIN CAPITAL LETTER E WITH ACUTE
"8a": "ca", # LATIN CAPITAL LETTER E WITH CIRCUMFLEX
"8b": "cb", # LATIN CAPITAL LETTER E WITH DIAERESIS
"8c": "cc", # LATIN CAPITAL LETTER I WITH GRAVE
"8d": "cd", # LATIN CAPITAL LETTER I WITH ACUTE
"8e": "ce", # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
"8f": "cf", # LATIN CAPITAL LETTER I WITH DIAERESIS
"90": "d0", # LATIN CAPITAL LETTER ETH
"91": "d1", # LATIN CAPITAL LETTER N WITH TILDE
"92": "d2", # LATIN CAPITAL LETTER O WITH GRAVE
"93": "d3", # LATIN CAPITAL LETTER O WITH ACUTE
"94": "d4", # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
"95": "d5", # LATIN CAPITAL LETTER O WITH TILDE
"96": "d6", # LATIN CAPITAL LETTER O WITH DIAERESIS
"97": "d9", # LATIN CAPITAL LETTER U WITH GRAVE
"98": "da", # LATIN CAPITAL LETTER U WITH ACUTE
"99": "db", # LATIN CAPITAL LETTER U WITH CIRCUMFLEX
"9a": "dc", # LATIN CAPITAL LETTER U WITH DIAERESIS
"9b": "dd", # LATIN CAPITAL LETTER Y WITH ACUTE
"9c": "de", # LATIN CAPITAL LETTER THORN
"9d": "b5", # MICRO SIGN
"9e": "d7", # MULTIPLICATION SIGN
"9f": "f7", # DIVISION SIGN
"a0": "a9", # COPYRIGHT SIGN
"a1": "a1", # INVERTED EXCLAMATION MARK
"a2": "a2", # CENT SIGN
"a3": "a3", # POUND SIGN
"a4": "44", # FRACTION SLASH
"a5": "a5", # YEN SIGN
"a6": "92", # LATIN SMALL LETTER F WITH HOOK
"a7": "a7", # SECTION SIGN
"a8": "a4", # CURRENCY SIGN
"a9": "19", # RIGHT SINGLE QUOTATION MARK
"aa": "1c", # LEFT DOUBLE QUOTATION MARK
"ab": "ab", # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
"ac": "39", # SINGLE LEFT-POINTING ANGLE QUOTATION MARK
"ad": "3a", # SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
"ae": "01", # LATIN SMALL LIGATURE FI
"af": "02", # LATIN SMALL LIGATURE FL
"b0": "ae", # REGISTERED SIGN
"b1": "13", # EN DASH
"b2": "20", # DAGGER
"b3": "21", # DOUBLE DAGGER
"b4": "b7", # MIDDLE DOT
"b5": "a6", # BROKEN BAR
"b6": "b6", # PILCROW SIGN
"b7": "22", # BULLET
"b8": "1a", # SINGLE LOW-9 QUOTATION MARK
"b9": "1e", # DOUBLE LOW-9 QUOTATION MARK
"ba": "1d", # RIGHT DOUBLE QUOTATION MARK
"bb": "bb", # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
"bc": "26", # HORIZONTAL ELLIPSIS
"bd": "30", # PER MILLE SIGN
"be": "ac", # NOT SIGN
"bf": "bf", # INVERTED QUESTION MARK
"c0": "b9", # SUPERSCRIPT ONE
"c1": "cb", # MODIFIER LETTER GRAVE ACCENT
"c2": "b4", # ACUTE ACCENT
"c3": "c6", # MODIFIER LETTER CIRCUMFLEX ACCENT
"c4": "dc", # SMALL TILDE
"c5": "af", # MACRON
"c6": "d8", # BREVE
"c7": "d9", # DOT ABOVE
"c8": "a8", # DIAERESIS
"c9": "b2", # SUPERSCRIPT TWO
"ca": "da", # RING ABOVE
"cb": "b8", # CEDILLA
"cc": "b3", # SUPERSCRIPT THREE
"cd": "dd", # DOUBLE ACUTE ACCENT
"ce": "db", # OGONEK
"cf": "c7", # CARON
"d0": "14", # EM DASH
"d1": "b1", # PLUS-MINUS SIGN
"d2": "bc", # VULGAR FRACTION ONE QUARTER
"d3": "bd", # VULGAR FRACTION ONE HALF
"d4": "be", # VULGAR FRACTION THREE QUARTERS
"d5": "e0", # LATIN SMALL LETTER A WITH GRAVE
"d6": "e1", # LATIN SMALL LETTER A WITH ACUTE
"d7": "e2", # LATIN SMALL LETTER A WITH CIRCUMFLEX
"d8": "e3", # LATIN SMALL LETTER A WITH TILDE
"d9": "e4", # LATIN SMALL LETTER A WITH DIAERESIS
"da": "e5", # LATIN SMALL LETTER A WITH RING ABOVE
"db": "e7", # LATIN SMALL LETTER C WITH CEDILLA
"dc": "e8", # LATIN SMALL LETTER E WITH GRAVE
"dd": "e9", # LATIN SMALL LETTER E WITH ACUTE
"de": "ea", # LATIN SMALL LETTER E WITH CIRCUMFLEX
"df": "eb", # LATIN SMALL LETTER E WITH DIAERESIS
"e0": "ec", # LATIN SMALL LETTER I WITH GRAVE
"e1": "c6", # LATIN CAPITAL LETTER AE
"e2": "ed", # LATIN SMALL LETTER I WITH ACUTE
"e3": "aa", # FEMININE ORDINAL INDICATOR
"e4": "ee", # LATIN SMALL LETTER I WITH CIRCUMFLEX
"e5": "ef", # LATIN SMALL LETTER I WITH DIAERESIS
"e6": "f0", # LATIN SMALL LETTER ETH
"e7": "f1", # LATIN SMALL LETTER N WITH TILDE
"e8": "41", # LATIN CAPITAL LETTER L WITH STROKE
"e9": "d8", # LATIN CAPITAL LETTER O WITH STROKE
"ea": "52", # LATIN CAPITAL LIGATURE OE
"eb": "ba", # MASCULINE ORDINAL INDICATOR
"ec": "f2", # LATIN SMALL LETTER O WITH GRAVE
"ed": "f3", # LATIN SMALL LETTER O WITH ACUTE
"ee": "f4", # LATIN SMALL LETTER O WITH CIRCUMFLEX
"ef": "f5", # LATIN SMALL LETTER O WITH TILDE
"f0": "f6", # LATIN SMALL LETTER O WITH DIAERESIS
"f1": "e6", # LATIN SMALL LETTER AE
"f2": "f9", # LATIN SMALL LETTER U WITH GRAVE
"f3": "fa", # LATIN SMALL LETTER U WITH ACUTE
"f4": "fb", # LATIN SMALL LETTER U WITH CIRCUMFLEX
"f5": "31", # LATIN SMALL LETTER DOTLESS I
"f6": "fc", # LATIN SMALL LETTER U WITH DIAERESIS
"f7": "fd", # LATIN SMALL LETTER Y WITH ACUTE
"f8": "42", # LATIN SMALL LETTER L WITH STROKE
"f9": "f8", # LATIN SMALL LETTER O WITH STROKE
"fa": "53", # LATIN SMALL LIGATURE OE
"fb": "df", # LATIN SMALL LETTER SHARP S
"fc": "fe", # LATIN SMALL LETTER THORN
"fd": "ff", # LATIN SMALL LETTER Y WITH DIAERESIS
"fe": "fd", # .notdef, REPLACEMENT CHARACTER
"ff": "fd", # .notdef, REPLACEMENT CHARACTER
}
return conversion[hex_digits]
def IsOctalNumber(character):
oct_digits = set(string.octdigits)
return set(character).issubset(oct_digits)
def IsHexNumber(character):
hex_digits = set(string.hexdigits)
return set(character).issubset(hex_digits)
def SanitizeCharacter(character):
char = character
escaped_characters = {
'\a': '\\a',
'\b': '\\b',
'\f': '\\f',
'\n': '\\n',
'\r': '\\r',
'\t': '\\t',
'\v': '\\v',
'\"': '\\"',
}
if character in escaped_characters.keys():
char = escaped_characters[character]
return char
# http://www.opensource.apple.com/source/CF/CF-744.19/CFOldStylePList.c See `getSlashedChar()`
def UnQuotifyString(string_data, start_index, end_index): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
formatted_string = ''
extracted_string = string_data[start_index:end_index]
string_length = len(extracted_string)
all_cases = ['0', '1', '2', '3', '4', '5', '6', '7', 'a', 'b', 'f', 'n', 'r', 't', 'v', '\"', '\n', 'U']
index = 0
while index < string_length: # pylint: disable=too-many-nested-blocks
current_char = extracted_string[index]
if current_char == '\\':
next_char = extracted_string[index+1]
if next_char in all_cases:
index += 1
if next_char == 'a':
formatted_string += '\a'
if next_char == 'b':
formatted_string += '\b'
if next_char == 'f':
formatted_string += '\f'
if next_char == 'n':
formatted_string += '\n'
if next_char == 'r':
formatted_string += '\r'
if next_char == 't':
formatted_string += '\t'
if next_char == 'v':
formatted_string += '\v'
if next_char == '"':
formatted_string += '\"'
if next_char == '\n':
formatted_string += '\n'
if next_char == 'U':
starting_index = index + 1
ending_index = starting_index + 4
unicode_numbers = extracted_string[starting_index:ending_index]
for number in unicode_numbers:
index += 1
if IsHexNumber(number) is False: # pragma: no cover
message = 'Invalid unicode sequence on line '+str(LineNumberForIndex(string_data, start_index+index))
raise Exception(message)
formatted_string += unichr(int(unicode_numbers, 16))
if IsOctalNumber(next_char) is True: # https://twitter.com/Catfish_Man/status/658014170055507968
starting_index = index
ending_index = starting_index + 1
for oct_index in range(3):
test_index = starting_index + oct_index
test_oct = extracted_string[test_index]
if IsOctalNumber(test_oct) is True:
ending_index += 1
octal_numbers = extracted_string[starting_index:ending_index]
hex_number = int(octal_numbers, 8)
hex_str = format(hex_number, 'x')
if hex_number >= 0x80:
hex_str = ConvertNEXTSTEPToUnicode(hex_str)
formatted_string += unichr(int('00'+hex_str, 16))
else:
formatted_string += current_char
index += 1
formatted_string += next_char
else:
formatted_string += current_char
index += 1
return formatted_string
def LineNumberForIndex(string_data, current_index):
line_number = 1
index = 0
string_length = len(string_data)
while (index < current_index) and (index < string_length):
current_char = string_data[index]
if IsNewline(current_char) is True:
line_number += 1
index += 1
return line_number
def IsValidUnquotedStringCharacter(character):
if len(character) == 1:
valid_characters = set(string.ascii_letters+string.digits+'_$/:.-')
return set(character).issubset(valid_characters)
else: # pragma: no cover
message = 'The function "IsValidUnquotedStringCharacter()" can only take single characters!'
raise ValueError(message)
def IsSpecialWhitespace(character):
value = ord(character)
result = (value >= 9 and value <= 13) # tab, newline, vt, form feed, carriage return
return result
def IsUnicodeSeparator(character):
value = ord(character)
result = (value == 8232 or value == 8233)
return result
def IsRegularWhitespace(character):
value = ord(character)
result = (value == 32 or IsUnicodeSeparator(character)) # space and Unicode line sep, para sep
return result
def IsDataFormattingWhitespace(character):
value = ord(character)
result = (IsNewline(character) or IsRegularWhitespace(character) or value == 9)
return result
def IsNewline(character):
value = ord(character)
result = (value == 13 or value == 10)
return result
def IsEndOfLine(character):
result = (IsNewline(character) or IsUnicodeSeparator(character))
return result
def IndexOfNextNonSpace(string_data, current_index): # pylint: disable=too-many-branches,too-many-statements
successful = False
found_index = current_index
string_length = len(string_data)
annotation_string = ''
while found_index < string_length: # pylint: disable=too-many-nested-blocks
current_char = string_data[found_index]
if IsSpecialWhitespace(current_char) is True:
found_index += 1
continue
if IsRegularWhitespace(current_char) is True:
found_index += 1
continue
if current_char == '/':
next_index = found_index + 1
if next_index >= string_length:
successful = True
break
else:
next_character = string_data[next_index]
if next_character == '/': # found a line comment "//"
found_index += 1
next_index = found_index
first_pass = True
while next_index < string_length:
test_char = string_data[next_index]
if IsEndOfLine(test_char) is True:
break
else:
if first_pass is False:
annotation_string += test_char
else:
first_pass = False
next_index += 1
found_index = next_index
elif next_character == '*': # found a block comment "/* ... */"
found_index += 1
next_index = found_index
first_pass = True
while next_index < string_length:
test_char = string_data[next_index]
if test_char == '*' and (next_index+1 < string_length) and string_data[next_index+1] == '/':
next_index += 2
break
else:
if first_pass != True:
annotation_string += test_char
else:
first_pass = False
next_index += 1
found_index = next_index
else:
successful = True
break
else:
successful = True
break
result = (successful, found_index, annotation_string)
return result

View File

@ -0,0 +1,50 @@
# Copyright (c) 2016, Samantha Marshall (http://pewpewthespells.com)
# All rights reserved.
#
# https://github.com/samdmarshall/pylocalizer
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
#
# 3. Neither the name of Samantha Marshall nor the names of its contributors may
# be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
# Original code taken from http://code.activestate.com/recipes/410692/
class Switch(object):
def __init__(self, value):
self.value = value
self.fall = False
def __iter__(self):
"""Return the match method once, then stop"""
yield self.match
def match(self, *args):
"""Indicate whether or not to enter a case suite"""
result = False
if self.fall or not args:
result = True
elif self.value in args: # changed for v1.5, see below
self.fall = True
result = True
return result

View File

@ -0,0 +1,32 @@
# Copyright (c) 2016, Samantha Marshall (http://pewpewthespells.com)
# All rights reserved.
#
# https://github.com/samdmarshall/pbPlist
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
#
# 3. Neither the name of Samantha Marshall nor the names of its contributors may
# be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
from . import pbPlist
__version__ = '1.0.4'

View File

@ -0,0 +1,262 @@
# Copyright (c) 2016, Samantha Marshall (http://pewpewthespells.com)
# All rights reserved.
#
# https://github.com/samdmarshall/pbPlist
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
#
# 3. Neither the name of Samantha Marshall nor the names of its contributors may
# be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
from . import StrParse
def PushIndent(indent_level):
return indent_level + 1
def PopIndent(indent_level):
return indent_level - 1
def WriteIndent(level=0):
output = ''
for _index in range(level):
output += '\t'
return output
def WriteNewline(level=0, indent=True):
output = '\n'
if indent:
output += WriteIndent(level)
return output
class pbItem(object):
def __init__(self, value=None, type_name=None, annotation=None):
if value != None and type_name != None:
self.value = value
if type_name not in KnownTypes.keys(): # pragma: no cover
message = 'Unknown type "'+type_name+'" passed to '+self.__class__.__name__+' initializer!'
raise TypeError(message)
self.type_name = type_name
self.annotation = annotation
else: # pragma: no cover
message = 'The class "'+self.__class__.__name__+'" must be initialized with a non-None value'
raise ValueError(message)
def __eq__(self, other):
is_equal = False
if isinstance(other, pbItem):
other = other.value
if type(other) is type(self.value):
is_equal = self.value.__eq__(other)
return is_equal
def __hash__(self):
return self.value.__hash__()
def __repr__(self):
return self.value.__repr__()
def __iter__(self):
return self.value.__iter__()
def __getattr__(self, attrib):
return self.value.__getattr__(attrib)
def __str__(self):
return self.writeStringRep(0, False)[0]
def __getitem__(self, key):
return self.value.__getitem__(key)
def __setitem__(self, key, value):
self.value.__setitem__(key, value)
def __len__(self):
return self.value.__len__()
def __contains__(self, item):
return self.value.__contains__(item)
def __get__(self, obj, objtype):
return self.value.__get__(obj, objtype)
def writeStringRep(self, indent_level=0, pretty=True):
return self.writeString(indent_level, pretty)
def writeString(self, indent_level=0, pretty=True): # pylint: disable=no-self-use,unused-variable,unused-argument ; # pragma: no cover
message = 'This is a base class, it cannot write!'
raise Exception(message)
def nativeType(self):
return self.value
def writeAnnotation(self):
output_string = ''
if self.annotation != None and len(self.annotation) > 0:
output_string += ' '
output_string += '/*'
output_string += self.annotation
output_string += '*/'
return output_string
class pbString(pbItem):
def writeString(self, indent_level=0, pretty=True):
string_string = ''
string_string += self.value
if pretty is True:
string_string += self.writeAnnotation()
return (string_string, indent_level)
class pbQString(pbItem):
def writeStringRep(self, indent_level=0, pretty=True):
qstring_string = ''
for character in self.value:
qstring_string += StrParse.SanitizeCharacter(character)
return (qstring_string, indent_level)
def writeString(self, indent_level=0, pretty=True):
qstring_string = ''
qstring_string += '"'
string_rep, indent_level = self.writeStringRep(indent_level, pretty)
qstring_string += string_rep
qstring_string += '"'
if pretty is True:
qstring_string += self.writeAnnotation()
return (qstring_string, indent_level)
class pbData(pbItem):
def writeString(self, indent_level=0, pretty=True):
data_string = ''
indent_level = PushIndent(indent_level)
data_string += '<'
grouping_byte_counter = 0
grouping_line_counter = 0
for hex_byte in map(ord, self.value.decode()):
data_string += format(hex_byte, 'x')
grouping_byte_counter += 1
# write a space every 4th byte
if grouping_byte_counter == 4:
data_string += ' '
grouping_byte_counter = 0
grouping_line_counter += 1
# write a newline every 4th grouping of bytes
if grouping_line_counter == 4:
data_string += WriteNewline(indent_level)
data_string += ' ' # indent an additional space to make the byte groupings line up
grouping_line_counter = 0
data_string += '>'
if pretty is True:
data_string += self.writeAnnotation()
indent_level = PopIndent(indent_level)
return (data_string, indent_level)
class pbDictionary(pbItem):
def nativeType(self):
new_value = dict()
for key in self.keys():
value = self[key]
new_value[str(key)] = value.nativeType()
return new_value
def writeString(self, indent_level=0, pretty=True):
dictionary_string = ''
dictionary_string += '{'
has_sorted_keys, keys_array = self.value.sortedKeys()
dictionary_string += WriteNewline(indent_level, not has_sorted_keys)
indent_level = PushIndent(indent_level)
previous_value_type = None
if len(keys_array) == 0:
indent_level = PopIndent(indent_level)
else:
if not has_sorted_keys:
dictionary_string += '\t'
for key in keys_array:
if has_sorted_keys:
current_value_type = str(self.value[key]['isa'])
if previous_value_type != current_value_type:
if previous_value_type != None:
dictionary_string += '/* End '+previous_value_type+' section */'
dictionary_string += WriteNewline(indent_level, False)
previous_value_type = current_value_type
dictionary_string += '\n/* Begin '+current_value_type+' section */'
dictionary_string += WriteNewline(indent_level)
else:
dictionary_string += WriteIndent(indent_level)
write_string, indent_level = key.writeString(indent_level, pretty)
dictionary_string += write_string
dictionary_string += ' = '
write_string, indent_level = self.value[key].writeString(indent_level, pretty)
dictionary_string += write_string
dictionary_string += ';'
should_indent = True
is_last_key = (key == keys_array[-1])
if is_last_key:
if has_sorted_keys:
dictionary_string += WriteNewline(indent_level, False)
dictionary_string += '/* End '+previous_value_type+' section */'
indent_level = PopIndent(indent_level)
else:
if has_sorted_keys:
should_indent = False
dictionary_string += WriteNewline(indent_level, should_indent)
dictionary_string += '}'
return (dictionary_string, indent_level)
class pbArray(pbItem):
def nativeType(self):
new_value = [item.nativeType() for item in self.value]
return new_value
def writeString(self, indent_level=0, pretty=True):
array_string = ''
array_string += '('
array_string += WriteNewline(indent_level)
indent_level = PushIndent(indent_level)
values_array = list(self.value)
if len(values_array) == 0:
indent_level = PopIndent(indent_level)
else:
array_string += '\t'
for value in values_array:
write_string, indent_level = value.writeString(indent_level, pretty)
array_string += write_string
if value != values_array[-1]:
array_string += ','
else:
indent_level = PopIndent(indent_level)
array_string += WriteNewline(indent_level)
array_string += ')'
return (array_string, indent_level)
KnownTypes = {
'string': pbString,
'qstring': pbQString,
'data': pbData,
'dictionary': pbDictionary,
'array': pbArray,
}
def pbItemResolver(obj, type_name):
initializer = KnownTypes[type_name]
if initializer:
return initializer(obj, type_name)
else: # pragma: no cover
message = 'Unknown type "'+type_name+'" passed to pbItemResolver!'
raise TypeError(message)

View File

@ -0,0 +1,291 @@
# Copyright (c) 2016, Samantha Marshall (http://pewpewthespells.com)
# All rights reserved.
#
# https://github.com/samdmarshall/pbPlist
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
#
# 3. Neither the name of Samantha Marshall nor the names of its contributors may
# be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import print_function
import os
import sys
import codecs
from . import StrParse
from . import pbRoot
from . import pbItem
from .Switch import Switch
def GetFileEncoding(path):
encoding = 'utf-8-sig'
size = os.path.getsize(path)
if size > 2:
file_descriptor = OpenFile(path)
first_two_bytes = file_descriptor.read(2)
file_descriptor.close()
for case in Switch(first_two_bytes):
if case(codecs.BOM_UTF16):
encoding = 'utf-16'
break
if case(codecs.BOM_UTF16_LE):
encoding = 'utf-16-le'
break
if case(codecs.BOM_UTF16_BE):
encoding = 'utf-16-be'
break
if case():
break # pragma: no cover
return encoding
def OpenFileWithEncoding(file_path, encoding):
return codecs.open(file_path, 'r', encoding=encoding, errors='ignore')
if sys.version_info < (3, 0):
def OpenFile(file_path):
return open(file_path, 'rb')
else:
def OpenFile(file_path):
return open(file_path, 'br')
class PBParser(object):
def __init__(self, file_path=None):
self.index = 0
self.string_encoding = None
self.file_path = file_path
self.file_type = None
try:
encoding = GetFileEncoding(self.file_path)
file_descriptor = OpenFileWithEncoding(self.file_path, encoding)
self.data = file_descriptor.read()
if self.file_path.endswith('.strings'):
self.data = '{'+self.data+'}'
file_descriptor.close()
except IOError as exception: # pragma: no cover
print('I/O error({0}): {1}'.format(exception.errno, exception.strerror))
except: # pragma: no cover
print('Unexpected error:'+str(sys.exc_info()[0]))
raise
def read(self):
parsed_plist = None
prefix = self.data[0:6]
for case in Switch(prefix):
if case('bplist'):
self.file_type = 'binary'
import biplist
parsed_plist = biplist.readPlist(self.file_path)
break
if case('<?xml '):
self.file_type = 'xml'
import plistlib
parsed_plist = plistlib.readPlist(self.file_path)
break
if case():
self.file_type = 'ascii'
# test for encoding hint
if self.data[0:2] == '//':
# this is to try to see if we can locate the desired string encoding of the file
import re
result = re.search('^// !\$\*(.+?)\*\$!', self.data) # pylint: disable=anomalous-backslash-in-string
if result:
self.string_encoding = result.group(1)
#now return the parse
parsed_plist = self.__readTest(True)
break
return parsed_plist
def __readTest(self, requires_object=True):
read_result = None
# can we parse this?
can_parse, self.index, _annotation = StrParse.IndexOfNextNonSpace(self.data, self.index)
# we can ignore the annotation value here
if not can_parse:
if self.index != len(self.data):
if requires_object is True: # pragma: no cover
message = 'Invalid plist file!'
raise Exception(message)
else:
read_result = self.__parse(requires_object)
return read_result
def __parse(self, requires_object=True):
parsed_item = None
starting_character = self.data[self.index]
for case in Switch(starting_character):
if case('{'):
# parse dictionary
parsed_item = pbItem.pbItemResolver(self.__parseDict(), 'dictionary') # pylint: disable=redefined-variable-type
break
if case('('):
# parse array
parsed_item = pbItem.pbItemResolver(self.__parseArray(), 'array') # pylint: disable=redefined-variable-type
break
if case('<'):
# parse data
parsed_item = pbItem.pbItemResolver(self.__parseData(), 'data') # pylint: disable=redefined-variable-type
break
if case('\''):
pass
if case('\"'):
# parse quoted string
parsed_item = pbItem.pbItemResolver(self.__parseQuotedString(), 'qstring') # pylint: disable=redefined-variable-type
break
if case():
if StrParse.IsValidUnquotedStringCharacter(starting_character) is True:
# parse unquoted string
parsed_item = pbItem.pbItemResolver(self.__parseUnquotedString(), 'string') # pylint: disable=redefined-variable-type
else:
if requires_object is True: # pragma: no cover
message = 'Unexpected character "0x%s" at line %i of file %s' % (str(format(ord(starting_character), 'x')), StrParse.LineNumberForIndex(self.data, self.index), self.file_path)
raise Exception(message)
return parsed_item
def __parseUnquotedString(self):
string_length = len(self.data)
start_index = self.index
while self.index < string_length:
current_char = self.data[self.index]
if StrParse.IsValidUnquotedStringCharacter(current_char) is True:
self.index += 1
else:
break
if start_index != self.index:
return self.data[start_index:self.index]
else: # pragma: no cover
message = 'Unexpected EOF in file %s' % self.file_path
raise Exception(message)
def __parseQuotedString(self):
quote = self.data[self.index]
string_length = len(self.data)
self.index += 1 # skip over the first quote
start_index = self.index
while self.index < string_length:
current_char = self.data[self.index]
if current_char == quote:
break
if current_char == '\\':
self.index += 2
else:
self.index += 1
if self.index >= string_length: # pragma: no cover
message = 'Unterminated quoted string starting on line %s in file %s' % (str(StrParse.LineNumberForIndex(self.data, start_index)), self.file_path)
raise Exception(message)
else:
string_without_quotes = StrParse.UnQuotifyString(self.data, start_index, self.index)
self.index += 1 # advance past quote character
return string_without_quotes
def __parseData(self):
string_length = len(self.data)
self.index += 1 # skip over "<"
start_index = self.index
end_index = 0
byte_stream = ''
while self.index < string_length:
current_char = self.data[self.index]
if current_char == '>':
self.index += 1 # move past the ">"
end_index = self.index
break
if StrParse.IsHexNumber(current_char) is True:
byte_stream += current_char
else:
if not StrParse.IsDataFormattingWhitespace(current_char): # pragma: no cover
message = 'Malformed data byte group (invalid hex) at line %s in file %s' % (str(StrParse.LineNumberForIndex(self.data, start_index)), self.file_path)
raise Exception(message)
self.index += 1
if (len(byte_stream) % 2) == 1: # pragma: no cover
message = 'Malformed data byte group (uneven length) at line %s in file %s' % (str(StrParse.LineNumberForIndex(self.data, start_index)), self.file_path)
raise Exception(message)
if end_index == 0: # pragma: no cover
message = 'Expected terminating >" for data at line %s in file %s' % (str(StrParse.LineNumberForIndex(self.data, start_index)), self.file_path)
raise Exception(message)
data_object = bytearray.fromhex(byte_stream)
return data_object
def __parseArray(self):
array_objects = list()
self.index += 1 # move past the "("
start_index = self.index
new_object = self.__readTest(False)
while new_object is not None:
can_parse, self.index, new_object.annotation = StrParse.IndexOfNextNonSpace(self.data, self.index)
_can_parse = can_parse # pylint: disable=unused-variable
array_objects.append(new_object)
current_char = self.data[self.index]
if current_char == ',':
self.index += 1
new_object = self.__readTest(False)
current_char = self.data[self.index]
if current_char != ')': # pragma: no cover
message = 'Expected terminating ")" for array at line %s in file %s' % (str(StrParse.LineNumberForIndex(self.data, start_index)), self.file_path)
raise Exception(message)
self.index += 1 # skip over ending ")"
return array_objects
def __parseDict(self):
dictionary = pbRoot.pbRoot()
self.index += 1 # move past the "{"
start_index = self.index
new_object = self.__readTest(False)
while new_object is not None:
can_parse, self.index, new_object.annotation = StrParse.IndexOfNextNonSpace(self.data, self.index)
_can_parse = can_parse # pylint: disable=unused-variable
key_object = new_object
current_char = self.data[self.index]
value_object = None
for case in Switch(current_char):
if case('='):
self.index += 1
value_object = self.__readTest(True)
break
if case(';'):
# this is for strings files where the key and the value may be the same thing
self.index += 1
value_object = pbItem.pbItemResolver(new_object.value, new_object.type_name)
value_object.annotation = new_object.annotation
break
if case(): # pragma: no cover
message = 'Missing ";" or "=" on line %s in file %s' % (str(StrParse.LineNumberForIndex(self.data, start_index)), self.file_path)
raise Exception(message)
can_parse, self.index, annotation = StrParse.IndexOfNextNonSpace(self.data, self.index)
_can_parse = can_parse # pylint: disable=unused-variable
if value_object.annotation is None: # this is to prevent losing the annotation of the key when parsing strings dicts
value_object.annotation = annotation
dictionary[key_object] = value_object
current_char = self.data[self.index]
if current_char == ';':
self.index += 1 # advancing to the next key
new_object = self.__readTest(False)
current_char = self.data[self.index]
if current_char != '}': # pragma: no cover
message = 'Expected terminating "}" for dictionary at line %s in file %s' % (str(StrParse.LineNumberForIndex(self.data, start_index)), self.file_path)
raise Exception(message)
self.index += 1 # skip over ending "}"
return dictionary

View File

@ -0,0 +1,55 @@
# Copyright (c) 2016, Samantha Marshall (http://pewpewthespells.com)
# All rights reserved.
#
# https://github.com/samdmarshall/pbPlist
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
#
# 3. Neither the name of Samantha Marshall nor the names of its contributors may
# be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
import os
from .pbParser import PBParser
from .pbSerializer import PBSerializer
class PBPlist(object):
def __init__(self, file_path):
self.root = None
if self.__checkFile(file_path) is True:
parser = PBParser(self.file_path)
self.root = parser.read()
self.string_encoding = parser.string_encoding
self.file_type = parser.file_type
def write(self, file_path=None):
if file_path is None:
file_path = self.file_path
serializer = PBSerializer(file_path, self.string_encoding, self.file_type)
serializer.write(self.root)
def __checkFile(self, file_path):
can_access_file = os.path.exists(file_path)
if can_access_file is True:
self.file_path = file_path
return can_access_file

View File

@ -0,0 +1,110 @@
# Copyright (c) 2016, Samantha Marshall (http://pewpewthespells.com)
# All rights reserved.
#
# https://github.com/samdmarshall/pbPlist
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
#
# 3. Neither the name of Samantha Marshall nor the names of its contributors may
# be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
from functools import cmp_to_key
import collections
from . import pbItem
def StringCmp(obj1, obj2):
result = -1
if obj1 > obj2:
result = 1
elif obj1 == obj2:
result = 0
return result
def KeySorter(obj1, obj2):
result = 0
if str(obj1) == 'isa':
result = -1
elif str(obj2) == 'isa':
result = 1
else:
result = StringCmp(str(obj1), str(obj2))
return result
class pbRoot(collections.MutableMapping):
def __init__(self, *args, **kwargs):
self.store = dict()
self.key_storage = list()
self.update(dict(*args, **kwargs)) # use the free update to set keys
def __internalKeyCheck(self, key): # pylint: disable=no-self-use
safe_key = key
if isinstance(safe_key, str):
safe_key = pbItem.pbItemResolver(safe_key, 'qstring')
return safe_key
def __getitem__(self, key):
return self.store[key]
def __setitem__(self, key, value):
if key not in self.key_storage:
self.key_storage.append(self.__internalKeyCheck(key))
self.store[key] = value
def __delitem__(self, key):
if key in self.key_storage:
self.key_storage.remove(key)
del self.store[key]
def __iter__(self):
return self.key_storage.__iter__()
def __len__(self):
return self.key_storage.__len__()
def __str__(self):
return self.store.__str__()
def __contains__(self, item):
return item in self.key_storage
def __getattr__(self, attrib):
return getattr(self.store, attrib)
def __keytransform__(self, key): # pylint: disable=no-self-use
result = key
if isinstance(key, pbItem.pbItem):
result = key.value
return result
def sortedKeys(self):
unsorted_keys = self.key_storage
sorted_keys = sorted(unsorted_keys, key=cmp_to_key(KeySorter))
can_sort = False
if len(sorted_keys) > 0:
all_dictionaries = all((isinstance(self[key].value, dict) or isinstance(self[key].value, pbRoot)) for key in unsorted_keys)
if all_dictionaries:
can_sort = all(self[key].get('isa', None) is not None for key in unsorted_keys)
if can_sort:
sorted_keys = sorted(unsorted_keys, key=lambda k: str(self[k]['isa']))
return (can_sort, sorted_keys)

View File

@ -0,0 +1,75 @@
# Copyright (c) 2016, Samantha Marshall (http://pewpewthespells.com)
# All rights reserved.
#
# https://github.com/samdmarshall/pbPlist
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
#
# 3. Neither the name of Samantha Marshall nor the names of its contributors may
# be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import print_function
import sys
from .Switch import Switch
class PBSerializer(object):
def __init__(self, file_path=None, encoding=None, file_type=None):
self.string_encoding = encoding
self.file_path = file_path
self.file_type = file_type
def write(self, obj=None):
for case in Switch(self.file_type):
if case('ascii'):
try:
file_descriptor = open(self.file_path, 'w')
self.__writeObject(file_descriptor, obj)
file_descriptor.close()
except IOError as exception: # pragma: no cover
print('I/O error({0}): {1}'.format(exception.errno, exception.strerror))
except: # pragma: no cover
print('Unexpected error:'+str(sys.exc_info()[0]))
raise
break
if case('binary'):
import biplist
biplist.writePlist(obj, self.file_path)
break
if case('xml'):
import plistlib
plistlib.writePlist(obj, self.file_path)
break
if case():
break
def __writeObject(self, file_descriptor=None, obj=None):
if file_descriptor is None: # pragma: no cover
message = 'Fatal error, file descriptor is None'
raise TypeError(message)
if self.string_encoding is not None:
file_descriptor.write('// !$*'+self.string_encoding+'*$!\n')
if obj is not None:
write_string, indent_level = obj.writeString()
_ = indent_level
file_descriptor.write(write_string)

View File

@ -1,6 +1,6 @@
"""jc - JSON Convert PLIST file parser
Converts binary and XML PLIST files.
Converts binary, XML, and NeXTSTEP PLIST files.
Binary values are converted into an ASCII hex representation.
@ -48,15 +48,17 @@ from typing import Dict, Union
import plistlib
import binascii
from datetime import datetime
from jc.parsers.pbPlist.pbPlist import PBPlist
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
version = '1.1'
description = 'PLIST file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
details = 'Using the pbPlist library from https://github.com/samdmarshall/pbPlist/releases/tag/v1.0.4 for NeXTSTEP support'
compatible = ['linux', 'darwin', 'cygwin', 'win32', 'aix', 'freebsd']
tags = ['standard', 'file', 'string', 'binary']
@ -157,7 +159,20 @@ def parse(
if isinstance(data, str):
data = bytes(data, 'utf-8')
raw_output = plistlib.loads(data)
try:
raw_output = plistlib.loads(data)
except Exception:
# Try parsing as an old-style NeXTSTEP Plist format
# pbPlist library only works on file paths, not strings :(
import tempfile
with tempfile.NamedTemporaryFile(mode='w+') as plist_file:
data = data.decode()
plist_file.write(data)
plist_file.seek(0)
parsed_plist = PBPlist(plist_file.name)
raw_output = parsed_plist.root.nativeType()
raw_output = _fix_objects(raw_output)
return raw_output if raw else _process(raw_output)

View File

@ -1,4 +1,4 @@
.TH jc 1 2022-12-29 1.22.4 "JSON Convert"
.TH jc 1 2022-12-30 1.22.4 "JSON Convert"
.SH NAME
\fBjc\fP \- JSON Convert JSONifies the output of many CLI tools, file-types, and strings
.SH SYNOPSIS

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,508 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXAggregateTarget section */
15D488AC1BD3D6C700EC46B1 /* Lemon Setup */ = {
isa = PBXAggregateTarget;
buildConfigurationList = 15D488AD1BD3D6C700EC46B1 /* Build configuration list for PBXAggregateTarget "Lemon Setup" */;
buildPhases = (
15D488B01BD3D6D000EC46B1 /* make distclean ; ./autogen.sh ; ./configure */,
);
dependencies = (
);
name = "Lemon Setup";
productName = "Lemon Setup";
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
15D488BB1BD3D7A900EC46B1 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15D488BA1BD3D7A900EC46B1 /* AppKit.framework */; };
15D488BD1BD3D7A900EC46B1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15D488BC1BD3D7A900EC46B1 /* Foundation.framework */; };
15D488C41BD3D7A900EC46B1 /* CitrusPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 15D488C31BD3D7A900EC46B1 /* CitrusPlugin.m */; };
15D488C71BD3D7A900EC46B1 /* NSObject_Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = 15D488C61BD3D7A900EC46B1 /* NSObject_Extension.m */; };
15D488D01BD3D9CD00EC46B1 /* Lemon.pbfilespec in Resources */ = {isa = PBXBuildFile; fileRef = 15D488CC1BD3D9CD00EC46B1 /* Lemon.pbfilespec */; };
15D488D11BD3D9CD00EC46B1 /* Lemon.strings in Resources */ = {isa = PBXBuildFile; fileRef = 15D488CD1BD3D9CD00EC46B1 /* Lemon.strings */; };
15D488D21BD3D9CD00EC46B1 /* Lemon.xcspec in Resources */ = {isa = PBXBuildFile; fileRef = 15D488CE1BD3D9CD00EC46B1 /* Lemon.xcspec */; };
15D488D71BD3DA8800EC46B1 /* lemon in Resources */ = {isa = PBXBuildFile; fileRef = 15D488D61BD3DA8800EC46B1 /* lemon */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
15D488B11BD3D6F400EC46B1 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 15D488951BD3D59200EC46B1 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 15D488AC1BD3D6C700EC46B1;
remoteInfo = "Lemon Setup";
};
15D488D81BD3DA9C00EC46B1 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 15D488951BD3D59200EC46B1 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 15D488991BD3D59200EC46B1;
remoteInfo = Lemon;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
158E53E61BD3E6A600F75AAD /* PluginUsage.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = PluginUsage.md; sourceTree = "<group>"; };
15D488A01BD3D62C00EC46B1 /* autogen.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = autogen.sh; sourceTree = "<group>"; };
15D488A11BD3D62C00EC46B1 /* citrus.pc.in */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = citrus.pc.in; sourceTree = "<group>"; };
15D488A21BD3D62C00EC46B1 /* configure.ac */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = configure.ac; sourceTree = "<group>"; };
15D488A31BD3D62C00EC46B1 /* lemon.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = lemon.c; sourceTree = "<group>"; };
15D488A41BD3D62C00EC46B1 /* lempar.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = lempar.c; sourceTree = "<group>"; };
15D488A51BD3D62C00EC46B1 /* lempar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lempar.h; sourceTree = "<group>"; };
15D488A61BD3D62C00EC46B1 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
15D488A71BD3D62C00EC46B1 /* Makefile.am */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Makefile.am; sourceTree = "<group>"; };
15D488A81BD3D62C00EC46B1 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
15D488A91BD3D62C00EC46B1 /* VERSION */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = VERSION; sourceTree = "<group>"; };
15D488AA1BD3D62C00EC46B1 /* version.h.in */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = version.h.in; sourceTree = "<group>"; };
15D488B71BD3D7A900EC46B1 /* CitrusPlugin.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CitrusPlugin.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; };
15D488BA1BD3D7A900EC46B1 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
15D488BC1BD3D7A900EC46B1 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
15D488C01BD3D7A900EC46B1 /* CitrusPlugin.xcscheme */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = CitrusPlugin.xcscheme; path = CitrusPlugin.xcodeproj/xcshareddata/xcschemes/CitrusPlugin.xcscheme; sourceTree = SOURCE_ROOT; };
15D488C21BD3D7A900EC46B1 /* CitrusPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CitrusPlugin.h; sourceTree = "<group>"; };
15D488C31BD3D7A900EC46B1 /* CitrusPlugin.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CitrusPlugin.m; sourceTree = "<group>"; };
15D488C51BD3D7A900EC46B1 /* NSObject_Extension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NSObject_Extension.h; sourceTree = "<group>"; };
15D488C61BD3D7A900EC46B1 /* NSObject_Extension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NSObject_Extension.m; sourceTree = "<group>"; };
15D488C81BD3D7A900EC46B1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
15D488CC1BD3D9CD00EC46B1 /* Lemon.pbfilespec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.pbfilespec; path = Lemon.pbfilespec; sourceTree = "<group>"; };
15D488CD1BD3D9CD00EC46B1 /* Lemon.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = Lemon.strings; sourceTree = "<group>"; };
15D488CE1BD3D9CD00EC46B1 /* Lemon.xcspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xcspec; path = Lemon.xcspec; sourceTree = "<group>"; };
15D488D61BD3DA8800EC46B1 /* lemon */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = lemon; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
15D488B51BD3D7A900EC46B1 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
15D488BB1BD3D7A900EC46B1 /* AppKit.framework in Frameworks */,
15D488BD1BD3D7A900EC46B1 /* Foundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
15D488941BD3D59200EC46B1 = {
isa = PBXGroup;
children = (
158E53E61BD3E6A600F75AAD /* PluginUsage.md */,
15D4889F1BD3D5D500EC46B1 /* lemon */,
15D488BE1BD3D7A900EC46B1 /* CitrusPlugin */,
15D488B91BD3D7A900EC46B1 /* Frameworks */,
15D488B81BD3D7A900EC46B1 /* Products */,
15D488D61BD3DA8800EC46B1 /* lemon */,
);
sourceTree = "<group>";
};
15D4889F1BD3D5D500EC46B1 /* lemon */ = {
isa = PBXGroup;
children = (
15D488A01BD3D62C00EC46B1 /* autogen.sh */,
15D488A11BD3D62C00EC46B1 /* citrus.pc.in */,
15D488A21BD3D62C00EC46B1 /* configure.ac */,
15D488A31BD3D62C00EC46B1 /* lemon.c */,
15D488A41BD3D62C00EC46B1 /* lempar.c */,
15D488A51BD3D62C00EC46B1 /* lempar.h */,
15D488A61BD3D62C00EC46B1 /* LICENSE */,
15D488A71BD3D62C00EC46B1 /* Makefile.am */,
15D488A81BD3D62C00EC46B1 /* README.md */,
15D488A91BD3D62C00EC46B1 /* VERSION */,
15D488AA1BD3D62C00EC46B1 /* version.h.in */,
);
name = lemon;
sourceTree = "<group>";
};
15D488B81BD3D7A900EC46B1 /* Products */ = {
isa = PBXGroup;
children = (
15D488B71BD3D7A900EC46B1 /* CitrusPlugin.xcplugin */,
);
name = Products;
sourceTree = "<group>";
};
15D488B91BD3D7A900EC46B1 /* Frameworks */ = {
isa = PBXGroup;
children = (
15D488BA1BD3D7A900EC46B1 /* AppKit.framework */,
15D488BC1BD3D7A900EC46B1 /* Foundation.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
15D488BE1BD3D7A900EC46B1 /* CitrusPlugin */ = {
isa = PBXGroup;
children = (
15D488D41BD3D9D000EC46B1 /* Resources */,
15D488C21BD3D7A900EC46B1 /* CitrusPlugin.h */,
15D488C31BD3D7A900EC46B1 /* CitrusPlugin.m */,
15D488C51BD3D7A900EC46B1 /* NSObject_Extension.h */,
15D488C61BD3D7A900EC46B1 /* NSObject_Extension.m */,
15D488C81BD3D7A900EC46B1 /* Info.plist */,
15D488BF1BD3D7A900EC46B1 /* Supporting Files */,
);
path = CitrusPlugin;
sourceTree = "<group>";
};
15D488BF1BD3D7A900EC46B1 /* Supporting Files */ = {
isa = PBXGroup;
children = (
15D488C01BD3D7A900EC46B1 /* CitrusPlugin.xcscheme */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
15D488D41BD3D9D000EC46B1 /* Resources */ = {
isa = PBXGroup;
children = (
15D488CC1BD3D9CD00EC46B1 /* Lemon.pbfilespec */,
15D488CD1BD3D9CD00EC46B1 /* Lemon.strings */,
15D488CE1BD3D9CD00EC46B1 /* Lemon.xcspec */,
);
name = Resources;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXLegacyTarget section */
15D488991BD3D59200EC46B1 /* Lemon */ = {
isa = PBXLegacyTarget;
buildArgumentsString = "${ACTION}";
buildConfigurationList = 15D4889C1BD3D59200EC46B1 /* Build configuration list for PBXLegacyTarget "Lemon" */;
buildPhases = (
);
buildToolPath = /usr/bin/make;
buildWorkingDirectory = "$(PROJECT_DIR)";
dependencies = (
15D488B21BD3D6F400EC46B1 /* PBXTargetDependency */,
);
name = Lemon;
passBuildSettingsInEnvironment = 1;
productName = CitrusPlugin;
};
/* End PBXLegacyTarget section */
/* Begin PBXNativeTarget section */
15D488B61BD3D7A900EC46B1 /* CitrusPlugin */ = {
isa = PBXNativeTarget;
buildConfigurationList = 15D488C91BD3D7A900EC46B1 /* Build configuration list for PBXNativeTarget "CitrusPlugin" */;
buildPhases = (
15D488B31BD3D7A900EC46B1 /* Sources */,
15D488B41BD3D7A900EC46B1 /* Resources */,
15D488B51BD3D7A900EC46B1 /* Frameworks */,
15D488DC1BD3DBBB00EC46B1 /* Copying default template into bundle because Xcode has a bug */,
);
buildRules = (
);
dependencies = (
15D488D91BD3DA9C00EC46B1 /* PBXTargetDependency */,
);
name = CitrusPlugin;
productName = CitrusPlugin;
productReference = 15D488B71BD3D7A900EC46B1 /* CitrusPlugin.xcplugin */;
productType = "com.apple.product-type.bundle";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
15D488951BD3D59200EC46B1 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0700;
ORGANIZATIONNAME = "Samantha Marshall";
TargetAttributes = {
15D488991BD3D59200EC46B1 = {
CreatedOnToolsVersion = 7.0.1;
};
15D488AC1BD3D6C700EC46B1 = {
CreatedOnToolsVersion = 7.0.1;
};
15D488B61BD3D7A900EC46B1 = {
CreatedOnToolsVersion = 7.0.1;
};
};
};
buildConfigurationList = 15D488981BD3D59200EC46B1 /* Build configuration list for PBXProject "CitrusPlugin" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 15D488941BD3D59200EC46B1;
productRefGroup = 15D488B81BD3D7A900EC46B1 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
15D488AC1BD3D6C700EC46B1 /* Lemon Setup */,
15D488991BD3D59200EC46B1 /* Lemon */,
15D488B61BD3D7A900EC46B1 /* CitrusPlugin */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
15D488B41BD3D7A900EC46B1 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
15D488D71BD3DA8800EC46B1 /* lemon in Resources */,
15D488D01BD3D9CD00EC46B1 /* Lemon.pbfilespec in Resources */,
15D488D11BD3D9CD00EC46B1 /* Lemon.strings in Resources */,
15D488D21BD3D9CD00EC46B1 /* Lemon.xcspec in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
15D488B01BD3D6D000EC46B1 /* make distclean ; ./autogen.sh ; ./configure */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "make distclean ; ./autogen.sh ; ./configure";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "cd ${PROJECT_DIR}\n\nif [ -e Makefile ]; then\n\tmake distclean\nfi\n\n./autogen.sh\n./configure";
};
15D488DC1BD3DBBB00EC46B1 /* Copying default template into bundle because Xcode has a bug */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Copying default template into bundle because Xcode has a bug";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "cp ${PROJECT_DIR}/lempar.c ${CONFIGURATION_BUILD_DIR}/${PRODUCT_NAME}.${WRAPPER_EXTENSION}/Contents/Resources\ncp ${PROJECT_DIR}/lempar.h ${CONFIGURATION_BUILD_DIR}/${PRODUCT_NAME}.${WRAPPER_EXTENSION}/Contents/Resources";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
15D488B31BD3D7A900EC46B1 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
15D488C41BD3D7A900EC46B1 /* CitrusPlugin.m in Sources */,
15D488C71BD3D7A900EC46B1 /* NSObject_Extension.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
15D488B21BD3D6F400EC46B1 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 15D488AC1BD3D6C700EC46B1 /* Lemon Setup */;
targetProxy = 15D488B11BD3D6F400EC46B1 /* PBXContainerItemProxy */;
};
15D488D91BD3DA9C00EC46B1 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 15D488991BD3D59200EC46B1 /* Lemon */;
targetProxy = 15D488D81BD3DA9C00EC46B1 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
15D4889A1BD3D59200EC46B1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = NO;
SDKROOT = macosx;
};
name = Debug;
};
15D4889B1BD3D59200EC46B1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MTL_ENABLE_DEBUG_INFO = NO;
ONLY_ACTIVE_ARCH = NO;
SDKROOT = macosx;
};
name = Release;
};
15D4889D1BD3D59200EC46B1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
DEBUGGING_SYMBOLS = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
OTHER_CFLAGS = "";
OTHER_LDFLAGS = "";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
15D4889E1BD3D59200EC46B1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
OTHER_CFLAGS = "";
OTHER_LDFLAGS = "";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
15D488AE1BD3D6C700EC46B1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
15D488AF1BD3D6C700EC46B1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
15D488CA1BD3D7A900EC46B1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "Developer ID Application: Samantha Marshall (329DAD2G44)";
COMBINE_HIDPI_IMAGES = YES;
DEPLOYMENT_LOCATION = YES;
DSTROOT = "$(HOME)";
INFOPLIST_FILE = CitrusPlugin/Info.plist;
INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins";
MACOSX_DEPLOYMENT_TARGET = 10.9;
PRODUCT_BUNDLE_IDENTIFIER = com.samdmarshall.CitrusPlugin;
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = xcplugin;
};
name = Debug;
};
15D488CB1BD3D7A900EC46B1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "Developer ID Application: Samantha Marshall (329DAD2G44)";
COMBINE_HIDPI_IMAGES = YES;
DEPLOYMENT_LOCATION = YES;
DSTROOT = "$(HOME)";
INFOPLIST_FILE = CitrusPlugin/Info.plist;
INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins";
MACOSX_DEPLOYMENT_TARGET = 10.9;
PRODUCT_BUNDLE_IDENTIFIER = com.samdmarshall.CitrusPlugin;
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = xcplugin;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
15D488981BD3D59200EC46B1 /* Build configuration list for PBXProject "CitrusPlugin" */ = {
isa = XCConfigurationList;
buildConfigurations = (
15D4889A1BD3D59200EC46B1 /* Debug */,
15D4889B1BD3D59200EC46B1 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
15D4889C1BD3D59200EC46B1 /* Build configuration list for PBXLegacyTarget "Lemon" */ = {
isa = XCConfigurationList;
buildConfigurations = (
15D4889D1BD3D59200EC46B1 /* Debug */,
15D4889E1BD3D59200EC46B1 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
15D488AD1BD3D6C700EC46B1 /* Build configuration list for PBXAggregateTarget "Lemon Setup" */ = {
isa = XCConfigurationList;
buildConfigurations = (
15D488AE1BD3D6C700EC46B1 /* Debug */,
15D488AF1BD3D6C700EC46B1 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
15D488C91BD3D7A900EC46B1 /* Build configuration list for PBXNativeTarget "CitrusPlugin" */ = {
isa = XCConfigurationList;
buildConfigurations = (
15D488CA1BD3D7A900EC46B1 /* Debug */,
15D488CB1BD3D7A900EC46B1 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 15D488951BD3D59200EC46B1 /* Project object */;
}

View File

@ -0,0 +1 @@
{"key1":"Value1","key2":"value3","DateOfStateChange":"2022-12-18 05:51:30 +0000","Destination":"F134545","Progress":{"Percent":"0.4563777755358589"}}

View File

@ -0,0 +1,9 @@
{
key1 = Value1;
key2 = "value3";
DateOfStateChange = "2022-12-18 05:51:30 +0000";
Destination = "F134545";
Progress = {
Percent = "0.4563777755358589";
};
}

View File

@ -21,6 +21,12 @@ class MyTests(unittest.TestCase):
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/plist-alltypes-bin.plist'), 'rb') as f:
generic_alltypes_bin = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/plist-nextstep.plist'), 'rb') as f:
nextstep = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/plist-nextstep2.plist'), 'rb') as f:
nextstep2 = f.read()
# output
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/plist-garageband-info.json'), 'r', encoding='utf-8') as f:
generic_garageband_json = json.loads(f.read())
@ -34,6 +40,12 @@ class MyTests(unittest.TestCase):
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/plist-alltypes-bin.json'), 'r', encoding='utf-8') as f:
generic_alltypes_bin_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/plist-nextstep.json'), 'r', encoding='utf-8') as f:
nextstep_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/plist-nextstep2.json'), 'r', encoding='utf-8') as f:
nextstep2_json = json.loads(f.read())
def test_plist_nodata(self):
"""
@ -70,5 +82,18 @@ class MyTests(unittest.TestCase):
self.assertEqual(jc.parsers.plist.parse(self.generic_alltypes_bin, quiet=True), self.generic_alltypes_bin_json)
def test_plist_nextstep(self):
"""
Test NeXTSTEP style plist file
"""
self.assertEqual(jc.parsers.plist.parse(self.nextstep, quiet=True), self.nextstep_json)
def test_plist_nextstep2(self):
"""
Test NeXTSTEP style plist file simple
"""
self.assertEqual(jc.parsers.plist.parse(self.nextstep2, quiet=True), self.nextstep2_json)
if __name__ == '__main__':
unittest.main()