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:
@ -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
|
||||
|
@ -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)
|
||||
|
366
jc/parsers/pbPlist/StrParse.py
Normal file
366
jc/parsers/pbPlist/StrParse.py
Normal 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
|
50
jc/parsers/pbPlist/Switch.py
Normal file
50
jc/parsers/pbPlist/Switch.py
Normal 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
|
32
jc/parsers/pbPlist/__init__.py
Normal file
32
jc/parsers/pbPlist/__init__.py
Normal 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'
|
262
jc/parsers/pbPlist/pbItem.py
Normal file
262
jc/parsers/pbPlist/pbItem.py
Normal 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)
|
291
jc/parsers/pbPlist/pbParser.py
Normal file
291
jc/parsers/pbPlist/pbParser.py
Normal 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
|
55
jc/parsers/pbPlist/pbPlist.py
Normal file
55
jc/parsers/pbPlist/pbPlist.py
Normal 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
|
110
jc/parsers/pbPlist/pbRoot.py
Normal file
110
jc/parsers/pbPlist/pbRoot.py
Normal 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)
|
75
jc/parsers/pbPlist/pbSerializer.py
Normal file
75
jc/parsers/pbPlist/pbSerializer.py
Normal 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)
|
@ -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')
|
||||
|
||||
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)
|
||||
|
2
man/jc.1
2
man/jc.1
@ -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
|
||||
|
1
tests/fixtures/generic/plist-nextstep.json
vendored
Normal file
1
tests/fixtures/generic/plist-nextstep.json
vendored
Normal file
File diff suppressed because one or more lines are too long
508
tests/fixtures/generic/plist-nextstep.plist
vendored
Normal file
508
tests/fixtures/generic/plist-nextstep.plist
vendored
Normal 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 */;
|
||||
}
|
1
tests/fixtures/generic/plist-nextstep2.json
vendored
Normal file
1
tests/fixtures/generic/plist-nextstep2.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"key1":"Value1","key2":"value3","DateOfStateChange":"2022-12-18 05:51:30 +0000","Destination":"F134545","Progress":{"Percent":"0.4563777755358589"}}
|
9
tests/fixtures/generic/plist-nextstep2.plist
vendored
Normal file
9
tests/fixtures/generic/plist-nextstep2.plist
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
key1 = Value1;
|
||||
key2 = "value3";
|
||||
DateOfStateChange = "2022-12-18 05:51:30 +0000";
|
||||
Destination = "F134545";
|
||||
Progress = {
|
||||
Percent = "0.4563777755358589";
|
||||
};
|
||||
}
|
@ -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()
|
||||
|
Reference in New Issue
Block a user