diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..5ace414 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -3,4 +3,4 @@ - \ No newline at end of file + diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..4fb5a96 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.4.0 + hooks: + - id: check-added-large-files + args: ['--maxkb=100'] + - id: check-ast + - id: check-byte-order-marker + - id: check-case-conflict + - id: check-docstring-first + - id: check-executables-have-shebangs + - id: check-merge-conflict + - id: end-of-file-fixer + - id: fix-encoding-pragma + - id: mixed-line-ending + - id: trailing-whitespace diff --git a/README.md b/README.md index 19fd34c..99c2dde 100644 --- a/README.md +++ b/README.md @@ -33,12 +33,12 @@ Usage: yaml-plist.py [] **Notes:** -1. With `plistyamlplist.py`, if you do not specify an `output-file` value, the script determines if the `input-file` is - a plist or yaml file. If a plist file, the `input-file` name will be appended with `.yaml` for the output file. If a +1. With `plistyamlplist.py`, if you do not specify an `output-file` value, the script determines if the `input-file` is + a plist or yaml file. If a plist file, the `input-file` name will be appended with `.yaml` for the output file. If a yaml file, the output file name will be the `input-file` name with `.yaml` removed. -2. With `plist_yaml.py`, if you do not specify an `output-file` value, the `input-file` name will be appended with +2. With `plist_yaml.py`, if you do not specify an `output-file` value, the `input-file` name will be appended with `.yaml` for the output file. -3. With `yaml_plist.py`, if you do not specify an `output-file` value, and the `input-file` name ends with `.yaml`, +3. With `yaml_plist.py`, if you do not specify an `output-file` value, and the `input-file` name ends with `.yaml`, the output file name will be the `input-file` name with `.yaml` removed. 4. With `plist_yaml.py`, you may have to first convert a binary plist to text format using `plutil`. @@ -69,15 +69,15 @@ $ ./plistyamlplist.py ~/Downloads/com.something.plist.yaml ## YAML folder -If you have a folder named `YAML` in your path, and you do not supply a destination, the script will determine if a +If you have a folder named `YAML` in your path, and you do not supply a destination, the script will determine if a corresponding folder exists in the path without 'YAML'. For example, consider the following file: /Users/myuser/gitrepo/YAML/product/com.something.plist.yaml - -If the folder `/Users/myuser/gitrepo/product` exists, the converted file will be created/overwritten at: + +If the folder `/Users/myuser/gitrepo/product` exists, the converted file will be created/overwritten at: /Users/myuser/gitrepo/product/com.something.plist - + If there is no `YAML` folder in the path, the converted file will be placed in the same folder # Credits diff --git a/plist_yaml.py b/plist_yaml.py index 4ebcc68..dc63b38 100755 --- a/plist_yaml.py +++ b/plist_yaml.py @@ -1,9 +1,9 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- -""" -If this script is run directly, it takes an input file and an output file from -the command line. The input file must be in PLIST format. -The output file will be in YAML format: +"""If this script is run directly, it takes an input file and an output file +from the command line. The input file must be in PLIST format. The output file +will be in YAML format: plist_yaml.py @@ -12,50 +12,92 @@ """ import sys +from collections import OrderedDict + +try: + from plistlib import Data # Python 3 + from plistlib import load as load_plist +except ImportError: + from plistlib import Data # Python 2 + from plistlib import readPlist as load_plist + import yaml -from os import path -from plistlib import readPlist, Data -def normalize_types(input): - """ - This allows YAML and JSON to store Data fields as strings. However, this - operation is irreversible. Only use if read-only access to the plist is - required. +def represent_ordereddict(dumper, data): + value = [] + + for item_key, item_value in data.items(): + node_key = dumper.represent_data(item_key) + node_value = dumper.represent_data(item_value) + + value.append((node_key, node_value)) + + return yaml.nodes.MappingNode(u"tag:yaml.org,2002:map", value) + + +def normalize_types(input_data): + """This allows YAML and JSON to store Data fields as strings. + + However, this operation is irreversible. Only use if read-only + access to the plist is required. """ - if isinstance(input, Data): return input.data - if isinstance(input, list): + if isinstance(input_data, Data): + return input_data.data + if isinstance(input_data, list): retval = [] - for child in input: + for child in input_data: retval.append(normalize_types(child)) return retval - if isinstance(input, dict): + if isinstance(input_data, dict): retval = {} - for key, child in input.iteritems(): - retval[key] = normalize_types(child) + for key in input_data: + retval[key] = normalize_types(input_data[key]) return retval - return input + return input_data + + +def sort_autopkg_processes(recipe): + """If input is an AutoPkg recipe, adjust the processor dictionaries such + that the Arguments key is ordered last. + + This usually puts the Processor key first, which makes the process + list more human-readable. + """ + if "Process" in recipe: + process = recipe["Process"] + new_process = [] + for processor in process: + if "Arguments" in processor: + processor = OrderedDict(processor) + processor.move_to_end("Arguments") + new_process.append(processor) + recipe["Process"] = new_process + return recipe def convert(xml): - """Do the conversion""" - return yaml.dump(xml, width=float('inf'), default_flow_style=False) + """Do the conversion.""" + yaml.add_representer(OrderedDict, represent_ordereddict) + return yaml.dump(xml, width=float("inf"), default_flow_style=False) def plist_yaml(in_path, out_path): - """Convert plist to yaml""" - in_file = open(in_path, 'r') - input = readPlist(in_file) + """Convert plist to yaml.""" + with open(in_path, "rb") as in_file: + input_data = load_plist(in_file) - normalized = normalize_types(input) + normalized = normalize_types(input_data) + if sys.version_info.major == 3 and in_path.endswith((".recipe", ".recipe.plist")): + normalized = sort_autopkg_processes(normalized) output = convert(normalized) - out_file = open(out_path, 'w') + out_file = open(out_path, "w") out_file.writelines(output) def main(): - """Get the command line inputs if running this script directly""" + """Get the command line inputs if running this script directly.""" if len(sys.argv) < 2: print("Usage: plist_yaml.py ") sys.exit(1) @@ -72,5 +114,5 @@ def main(): plist_yaml(in_path, out_path) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plistyamlplist.py b/plistyamlplist.py index cf6d507..f65ad24 100755 --- a/plistyamlplist.py +++ b/plistyamlplist.py @@ -1,8 +1,8 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- -""" -If this script is run directly, it takes an input file and an output file from -the command line. It detects if the input file is a YAML or a PLIST file, +"""If this script is run directly, it takes an input file and an output file +from the command line. It detects if the input file is a YAML or a PLIST file, and converts to the other format: plistyamlplist.py @@ -13,27 +13,32 @@ import sys from os import path + from plist_yaml import plist_yaml from yaml_plist import yaml_plist def usage(): - """print help""" + """print help.""" print("Usage: plistyamlplist.py \n") - print("If is a PLIST and is omitted,\n" - " is converted to .yaml\n") - print("If ends in .yaml or .yml and is omitted,\n" - ".yaml is converted to PLIST format with name \n") + print( + "If is a PLIST and is omitted,\n" + " is converted to .yaml\n" + ) + print( + "If ends in .yaml or .yml and is omitted,\n" + ".yaml is converted to PLIST format with name \n" + ) def check_if_plist(in_path): - """rather than restrict by filename, check if the file is a plist by reading - the second line of the file for the PLIST declaration""" + """rather than restrict by filename, check if the file is a plist by + reading the second line of the file for the PLIST declaration.""" with open(in_path) as fp: for i, line in enumerate(fp): if i == 1: # print line - if line.find('PLIST 1.0') == -1: + if line.find("PLIST 1.0") == -1: is_plist = False else: is_plist = True @@ -43,26 +48,29 @@ def check_if_plist(in_path): def check_for_yaml_folder(check_path): - """Check folder hierarchy for a YAML folder. Output to same folder structure outwith YAML - folder if it exists, - e.g. /path/to/YAML/folder/subfolder/my.plist.yaml ==> /path/to/folder/subfolder/my.plist - Note there is no reverse option at this time""" + """Check folder hierarchy for a YAML folder. + + Output to same folder structure outwith YAML folder if it exists, + e.g. /path/to/YAML/folder/subfolder/my.plist.yaml ==> + /path/to/folder/subfolder/my.plist Note there is no reverse option + at this time + """ check_abspath = path.abspath(check_path) - if 'YAML' in check_abspath: - print('YAML folder exists : {}'.format(check_abspath)) - top_path, base_path = check_abspath.split('YAML/') + if "YAML" in check_abspath: + print("YAML folder exists : {}".format(check_abspath)) + top_path, base_path = check_abspath.split("YAML/") out_path = path.dirname(path.join(top_path, base_path)) if path.exists(out_path): - print('Path exists : {}'.format(out_path)) + print("Path exists : {}".format(out_path)) return out_path else: - print('Path does not exist : {}'.format(out_path)) - print('Please create this folder and try again') + print("Path does not exist : {}".format(out_path)) + print("Please create this folder and try again") exit(1) def main(): - """get the command line inputs if running this script directly""" + """get the command line inputs if running this script directly.""" if len(sys.argv) < 2: usage() exit(1) @@ -71,7 +79,7 @@ def main(): try: sys.argv[2] except IndexError: - if in_path.endswith('.yaml') or in_path.endswith('.yml'): + if in_path.endswith(".yaml") or in_path.endswith(".yml"): out_dir = check_for_yaml_folder(in_path) if out_dir: filename, file_extension = path.splitext(path.basename(in_path)) @@ -90,7 +98,7 @@ def main(): out_path = sys.argv[2] # auto-determine which direction the conversion should go - if in_path.endswith('.yaml'): + if in_path.endswith(".yaml"): yaml_plist(in_path, out_path) else: if check_if_plist(in_path): @@ -99,8 +107,8 @@ def main(): print("\nERROR: Input File is neither PLIST nor YAML format.\n") usage() exit(1) - print('Wrote to : {}'.format(out_path)) + print("Wrote to : {}".format(out_path)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/yaml_plist.py b/yaml_plist.py index 2aa45bb..12b7b37 100755 --- a/yaml_plist.py +++ b/yaml_plist.py @@ -1,9 +1,9 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- -""" -If this script is run directly, it takes an input file and an output file from -the command lineThe input file must be in YAML format. -The output file will be in PLIST format: +"""If this script is run directly, it takes an input file and an output file +from the command lineThe input file must be in YAML format. The output file +will be in PLIST format: yaml_plist.py @@ -14,31 +14,32 @@ """ import sys -import yaml from os import path from plistlib import writePlistToString +import yaml + -def convert(yaml): - """Do the conversion""" - lines = writePlistToString(yaml).splitlines() - lines.append('') +def convert(data): + """Do the conversion.""" + lines = writePlistToString(data).splitlines() + lines.append("") return "\n".join(lines) def yaml_plist(in_path, out_path): - """Convert yaml to plist""" - in_file = open(in_path, 'r') - out_file = open(out_path, 'w') + """Convert yaml to plist.""" + in_file = open(in_path, "r") + out_file = open(out_path, "w") - input = yaml.safe_load(in_file) - output = convert(input) + input_data = yaml.safe_load(in_file) + output = convert(input_data) out_file.writelines(output) def main(): - """Get the command line inputs if running this script directly""" + """Get the command line inputs if running this script directly.""" if len(sys.argv) < 2: print("Usage: yaml_plist.py ") sys.exit(1) @@ -47,7 +48,7 @@ def main(): try: sys.argv[2] except Exception as e: - if in_path.endswith('.yaml'): + if in_path.endswith(".yaml"): filename, file_extension = path.splitext(in_path) out_path = filename else: @@ -59,5 +60,5 @@ def main(): yaml_plist(in_path, out_path) -if __name__ == '__main__': +if __name__ == "__main__": main()