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 jc changelog
20221229 v1.23.4 20221229 v1.22.4
- Add `iwconfig` command parser - 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 `proc` parser magic signature detection for `/proc/pid/stat` hacks
- Fix `x509-cert` parser for string serial numbers - 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 - Add "list parsers by category" view to help
- Fix python 3.6-related issues - Fix python 3.6-related issues
- Add python 3.6 to automated tests - Add python 3.6 to automated tests

View File

@ -5,7 +5,7 @@
jc - JSON Convert PLIST file parser 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. Binary values are converted into an ASCII hex representation.
@ -74,4 +74,4 @@ Returns:
### Parser Information ### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd 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 """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. Binary values are converted into an ASCII hex representation.
@ -48,15 +48,17 @@ from typing import Dict, Union
import plistlib import plistlib
import binascii import binascii
from datetime import datetime from datetime import datetime
from jc.parsers.pbPlist.pbPlist import PBPlist
import jc.utils import jc.utils
class info(): class info():
"""Provides parser metadata (version, author, etc.)""" """Provides parser metadata (version, author, etc.)"""
version = '1.0' version = '1.1'
description = 'PLIST file parser' description = 'PLIST file parser'
author = 'Kelly Brazil' author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com' 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'] compatible = ['linux', 'darwin', 'cygwin', 'win32', 'aix', 'freebsd']
tags = ['standard', 'file', 'string', 'binary'] tags = ['standard', 'file', 'string', 'binary']
@ -157,7 +159,20 @@ def parse(
if isinstance(data, str): if isinstance(data, str):
data = bytes(data, 'utf-8') 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) raw_output = _fix_objects(raw_output)
return raw_output if raw else _process(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 .SH NAME
\fBjc\fP \- JSON Convert JSONifies the output of many CLI tools, file-types, and strings \fBjc\fP \- JSON Convert JSONifies the output of many CLI tools, file-types, and strings
.SH SYNOPSIS .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: with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/plist-alltypes-bin.plist'), 'rb') as f:
generic_alltypes_bin = f.read() 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 # output
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/plist-garageband-info.json'), 'r', encoding='utf-8') as f: 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()) 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: 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()) 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): 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) 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__': if __name__ == '__main__':
unittest.main() unittest.main()