diff --git a/maxdiff/freezing_utils.py b/maxdiff/freezing_utils.py index d2049e5..9f4be35 100644 --- a/maxdiff/freezing_utils.py +++ b/maxdiff/freezing_utils.py @@ -133,3 +133,21 @@ def strip_trailing_nonjson(device_data_text: str) -> str: # nothing worked; return original so caller can log/handle the parse error return device_data_text + + +EXTERNAL_FILE_SUFFIXES = [".mxe64", ".mxo", ".mxe"] +EXTERNAL_BUNDLE_SEPARATOR = "/" + + +def get_external_name(file_name: str) -> str: + """ + Checks the file name of a bundled entry and returns the name of the external as it appears in the patch + """ + if EXTERNAL_BUNDLE_SEPARATOR in file_name: + return file_name.split(EXTERNAL_BUNDLE_SEPARATOR)[0] + + for suffix in EXTERNAL_FILE_SUFFIXES: + if file_name.endswith(suffix): + return file_name[: -len(suffix)] + + return None diff --git a/maxdiff/frozen_device_printer.py b/maxdiff/frozen_device_printer.py index 2e8a337..a8cc863 100644 --- a/maxdiff/frozen_device_printer.py +++ b/maxdiff/frozen_device_printer.py @@ -1,5 +1,5 @@ from freezing_utils import * -from get_frozen_stats import get_frozen_stats, get_used_files +from get_frozen_stats import get_frozen_stats, get_frozen_file_usage, get_external_name from patch_printer import get_parameters_string_block @@ -34,7 +34,7 @@ def print_frozen_device(data: bytes) -> str: if "parameters" in main_patcher: frozen_string += get_parameters_string_block(main_patcher, bundled_patchers) - used_files = get_used_files(device_entries) + frozen_file_usage = get_frozen_file_usage(device_entries) i = 0 for entry in device_entries: @@ -44,10 +44,15 @@ def print_frozen_device(data: bytes) -> str: if i == 0: frozen_string += f"{description} <= Device \n" else: - if file_name in used_files: - frozen_string += f"{description}, {used_files[file_name]} instance{'s' if used_files[file_name] > 1 else ''}\n" + if file_name in frozen_file_usage: + frozen_string += f"{description}, {frozen_file_usage[file_name]} instance{'s' if frozen_file_usage[file_name] > 1 else ''}\n" else: - frozen_string += f"{description}, NOT FOUND IN PATCH\n" + # this could be an external + external_name = get_external_name(file_name) + if external_name in frozen_file_usage: + frozen_string += f"{description}, {frozen_file_usage[external_name]} instance{'s' if frozen_file_usage[external_name] > 1 else ''}\n" + else: + frozen_string += f"{description}, NOT FOUND IN PATCH\n" i += 1 [object_count_total, line_count_total, object_count_unique, line_count_unique] = ( diff --git a/maxdiff/get_frozen_stats.py b/maxdiff/get_frozen_stats.py index 640119a..4478f3f 100644 --- a/maxdiff/get_frozen_stats.py +++ b/maxdiff/get_frozen_stats.py @@ -1,5 +1,5 @@ -from freezing_utils import device_entry_with_data, get_patcher_dict -from process_patcher import Processor, process_patch +from freezing_utils import device_entry_with_data, get_patcher_dict, get_external_name +from process_patcher import PatcherProcessor, walk_patch def get_frozen_stats(entries: list[device_entry_with_data]) -> tuple[int, int, int, int]: @@ -17,7 +17,7 @@ def get_frozen_stats(entries: list[device_entry_with_data]) -> tuple[int, int, i # get total counts: parse every instance of every abstraction count_processor = CountProcessor() - process_patch( + walk_patch( device_patch, abstraction_entries, count_processor # do recurse into abstractions ) object_count_total, line_count_total = count_processor.get_results() @@ -35,7 +35,7 @@ def get_frozen_stats(entries: list[device_entry_with_data]) -> tuple[int, int, i continue count_processor = CountProcessor() - process_patch(entry_patch, [], count_processor) # don't recurse into abstractions + walk_patch(entry_patch, [], count_processor) # don't recurse into abstractions o, l = count_processor.get_results() object_count_unique += o @@ -44,63 +44,97 @@ def get_frozen_stats(entries: list[device_entry_with_data]) -> tuple[int, int, i return object_count_total, line_count_total, object_count_unique, line_count_unique -class CountProcessor(Processor): +class CountProcessor(PatcherProcessor): def __init__(self): self.object_count = 0 self.line_count = 0 - def process_elements(self, patcher, voice_count: int, abstraction_name=""): + def process_patcher(self, patcher, poly_voice_count: int, abstraction_name=""): """Counts objects and lines in the given patcher.""" - self.object_count += len(patcher.get("boxes", [])) * voice_count - self.line_count += len(patcher.get("lines", [])) * voice_count + self.object_count += len(patcher.get("boxes", [])) * poly_voice_count + self.line_count += len(patcher.get("lines", [])) * poly_voice_count def get_results(self): """Returns the current counts.""" return self.object_count, self.line_count -def get_used_files(entries: list[device_entry_with_data]) -> dict[str, int]: +def get_frozen_file_usage(entries: list[device_entry_with_data]) -> dict[str, int]: + """Returns a dict with the names of all the files that are frozen into this device + with how often they are used in the device. + + It does this by parsing the top patcher and the abstractions found in `entries` recursively + and finding how many objects match with the file names of the bundled dependencies. + + Externals are included in the dict just by name. These names can be matched later with + the actual bundled external files. + """ device = entries[0] # the first entry is always the device file abstraction_entries = [ item for item in entries if str(item["file_name"]).endswith(".maxpat") ] + external_names = list( + dict.fromkeys( + get_external_name(item["file_name"]) + for item in entries + if get_external_name(item["file_name"]) != "" + ) + ) + device_patch = get_patcher_dict(device) if device_patch == {}: return {} - abstractions_processor = FileNamesProcessor() - process_patch(device_patch, abstraction_entries, abstractions_processor) - return abstractions_processor.get_results() + dependency_file_names_processor = DependencyUsageCounter(external_names) + walk_patch(device_patch, abstraction_entries, dependency_file_names_processor) + return dependency_file_names_processor.get_results() -class FileNamesProcessor(Processor): - def __init__(self): - self.found_filenames = {} +class DependencyUsageCounter(PatcherProcessor): + def __init__(self, external_names): + self.external_names = external_names + self.filename_occurrences = {} - def process_elements(self, patcher, voice_count: int, abstraction_name=""): + def process_patcher(self, patcher, poly_voice_count: int, abstraction_name=""): """If this patcher is an abstraction, i.e. when an abstraction_name is passed in, increment the entry in the dict. For other patchers, scan them for objects that use files. """ filenames = get_dependency_filenames(patcher) + filenames.extend(get_externals(patcher, self.external_names)) if abstraction_name != "": filenames.append(abstraction_name) for filename in filenames: - if filename in self.found_filenames: - self.found_filenames[filename] += voice_count + if filename in self.filename_occurrences: + self.filename_occurrences[filename] += poly_voice_count else: - self.found_filenames[filename] = voice_count + self.filename_occurrences[filename] = poly_voice_count def get_results(self): - """Returns a dict of used abstractions mapped to how oftern they are used.""" - return self.found_filenames + """Returns a dict of used abstractions mapped to how often they are used.""" + return self.filename_occurrences + + +def get_externals(patcher, external_names): + """Check all boxes in this patcher (don't recurse into subpatchers) and collect any objects that are externals""" + externals = [] + for box_entry in patcher["boxes"]: + box = box_entry["box"] + object_type = box["maxclass"] + if object_type == "newobj": + if "text" in box: + boxtext = box["text"] + object_name = boxtext.split(" ")[0] + if object_name in external_names: + externals.append(object_name) + return externals def get_dependency_filenames(patcher): - """Check all boxes in this patcher and report any dependencies they might be referring to""" + """Check all boxes in this patcher (don't recurse into subpatchers) and collect any dependencies they might be referring to""" filenames = [] for box_entry in patcher["boxes"]: box = box_entry["box"] diff --git a/maxdiff/print_unicode.py b/maxdiff/print_unicode.py index 4f06fbb..e8b78ea 100644 --- a/maxdiff/print_unicode.py +++ b/maxdiff/print_unicode.py @@ -1,6 +1,6 @@ """ Module that shares a function to gracefully handle printing unicode characters -if the user has the PYTHONIOENCODING environment vairable set to 'ascii'. +if the user has the PYTHONIOENCODING environment variable set to 'ascii'. In that case, replace any characters that can't be encoded with ?. """ diff --git a/maxdiff/process_patcher.py b/maxdiff/process_patcher.py index 305707d..04b7786 100644 --- a/maxdiff/process_patcher.py +++ b/maxdiff/process_patcher.py @@ -1,8 +1,8 @@ from freezing_utils import get_patcher_dict -class Processor: - def process_elements(self, patcher, voice_count: int, abstraction_name=""): +class PatcherProcessor: + def process_patcher(self, patcher, poly_voice_count: int, abstraction_name=""): """Processes patchers.""" def get_results(self): @@ -10,29 +10,29 @@ def get_results(self): return None -def process_patch(patcher, abstraction_entries: list[dict], processor: Processor): - process_patch_recursive(patcher, abstraction_entries, processor, 1, "") +def walk_patch(patcher, abstraction_entries: list[dict], processor: PatcherProcessor): + walk_patch_recursive(patcher, abstraction_entries, processor, 1, "") -def process_patch_recursive( +def walk_patch_recursive( patcher, abstraction_entries: list[dict], - processor: Processor, - voice_count: int, + processor: PatcherProcessor, + poly_voice_count: int, abstraction_file_name: str, ): """Recursively progress through subpatchers, invoking the processor for every patcher - inluding every instance of the patch's dependencies that can be found among the + including every instance of the patch's dependencies that can be found among the abstraction files that were passed in. Arguments patcher: patcher to process abstraction_entries: list of abstraction entries in frozen device processor: instance of a Processor that is invoked for this patch - voice_count: the amount of voices when this patcher occurs in a poly~ in its parent patch + poly_voice_count: the amount of voices when this patcher occurs in a poly~ in its parent patch abstraction_file_name: the file name of the abstraction this patch is in, if it is in an abstraction """ - processor.process_elements(patcher, voice_count, abstraction_file_name) + processor.process_patcher(patcher, poly_voice_count, abstraction_file_name) for box_entry in patcher["boxes"]: box = box_entry["box"] @@ -42,9 +42,9 @@ def process_patch_recursive( ): patch = box["patcher"] # get subpatcher or embedded bpatcher count - process_patch_recursive(patch, abstraction_entries, processor, 1, "") + walk_patch_recursive(patch, abstraction_entries, processor, 1, "") - # if no abstractions were passed in, we assume we don't want to recurse into abstarctions + # if no abstractions were passed in, we assume we don't want to recurse into abstractions if len(abstraction_entries) == 0: continue @@ -60,17 +60,17 @@ def process_patch_recursive( if patch == {}: continue # something went wrong when parsing the abstraction - voice_count = 1 + poly_voice_count = 1 if "text" in box and box["text"].startswith("poly~"): # get poly abstraction count tokens = box["text"].split(" ") - voice_count = int(tokens[2]) if len(tokens) > 2 else 1 + poly_voice_count = int(tokens[2]) if len(tokens) > 2 else 1 - process_patch_recursive( + walk_patch_recursive( patch, abstraction_entries, processor, - voice_count, + poly_voice_count, file_name, ) diff --git a/maxdiff/tests/test_baselines/FrozenTest.amxd.txt b/maxdiff/tests/test_baselines/FrozenTest.amxd.txt index 8b38686..748e2e6 100644 --- a/maxdiff/tests/test_baselines/FrozenTest.amxd.txt +++ b/maxdiff/tests/test_baselines/FrozenTest.amxd.txt @@ -124,10 +124,10 @@ parameters: banks: 0: (MyBank) encoders: ['live.dial', 'InsideBpatcher', '-', '-', '-', '-', '-', '-'] buttons: ['live.text', '-', '-', '-', '-', '-', '-', 'live.menu'] 1: encoders: ['-', '-', 'live.dial', 'MyParameter', 'live.dial', 'live.dial[1]', '-', '-'] buttons: ['-', '-', '-', '-', '-', 'live.text', 'live.menu', '-'] -Test.amxd: 57703 bytes, modified at 2026/08/31 09:52:24 UTC <= Device -MyAbstraction.maxpat: 2027 bytes, modified at 2026/08/31 09:51:02 UTC, 6 instances -ParamAbstraction.maxpat: 1491 bytes, modified at 2026/08/31 09:51:02 UTC, 2 instances -AbstractionWithParameter.maxpat: 1334 bytes, modified at 2026/08/31 09:51:02 UTC, 2 instances +Test.amxd: 58702 bytes, modified at 2026/09/03 12:38:17 UTC <= Device +MyAbstraction.maxpat: 2027 bytes, modified at 2026/08/31 10:48:30 UTC, 6 instances +ParamAbstraction.maxpat: 1491 bytes, modified at 2026/08/31 10:48:30 UTC, 2 instances +AbstractionWithParameter.maxpat: 1334 bytes, modified at 2026/08/31 10:48:30 UTC, 2 instances hz-icon.svg: 484 bytes, modified at 2024/05/24 13:59:36 UTC, 3 instances beat-icon.svg: 533 bytes, modified at 2024/05/24 13:59:36 UTC, 3 instances fpic.png: 7094 bytes, modified at 2024/05/24 13:59:36 UTC, 5 instances @@ -140,10 +140,16 @@ mystorage.json: 239 bytes, modified at 2026/08/27 14:58:36 UTC, 1 instance myTestTable: 410 bytes, modified at 2026/08/27 14:58:36 UTC, 1 instance TestScript.js: 87 bytes, modified at 2026/08/27 14:58:36 UTC, 1 instance collContent.txt: 8 bytes, modified at 2024/05/24 13:59:36 UTC, NOT FOUND IN PATCH +shell.mxo: 4 bytes, modified at 2026/08/31 10:47:56 UTC, 2 instances +shell/zipfile: 28122 bytes, modified at 2026/08/31 10:47:56 UTC, 2 instances +shell/executable: 182544 bytes, modified at 2026/08/31 10:47:56 UTC, 2 instances +shell/plist: 1441 bytes, modified at 2026/08/31 10:47:56 UTC, 2 instances +shell.mxe: 30720 bytes, modified at 2026/08/31 10:47:56 UTC, 2 instances +shell.mxe64: 37888 bytes, modified at 2026/08/31 10:47:56 UTC, 2 instances Total - Counting every abstraction instance - Indicates loading time - Object instances: 90 + Object instances: 92 Connections: 26 Unique - Counting abstractions once - Indicates maintainability - Object instances: 73 + Object instances: 75 Connections: 16 diff --git a/maxdiff/tests/test_baselines/Test.amxd.txt b/maxdiff/tests/test_baselines/Test.amxd.txt index 11044c4..c2d3f19 100644 --- a/maxdiff/tests/test_baselines/Test.amxd.txt +++ b/maxdiff/tests/test_baselines/Test.amxd.txt @@ -99,6 +99,8 @@ project: ----------- patcher ----------- appversion: 9.1.4-x64-1 | rect: [91, 153, 1288, 310] | openrect: [0, 0, 0, 169] | openrectmode: 0 | default_fontsize: 10.0 | default_fontname: Arial Bold | gridsize: [8, 8] | boxanimatetime: 500 | latency: 0 | is_mpe: 0 | external_mpe_tuning_enabled: 0 | platform_compatibility: 0 | autosave: 0 ----------- objects ----------- +[shell] shell: (default) +[shell dummyArgument] shell: (default) [comment NOTE: after any changes to this device, also update FrozenTest.amxd] [poly~ MyAbstraction] [poly~ MyAbstraction 3] diff --git a/maxdiff/tests/test_files/FrozenTest.amxd b/maxdiff/tests/test_files/FrozenTest.amxd index 4462f2b..2bcded8 100644 Binary files a/maxdiff/tests/test_files/FrozenTest.amxd and b/maxdiff/tests/test_files/FrozenTest.amxd differ diff --git a/maxdiff/tests/test_files/Test.amxd b/maxdiff/tests/test_files/Test.amxd index 17629a0..1fcde96 100644 Binary files a/maxdiff/tests/test_files/Test.amxd and b/maxdiff/tests/test_files/Test.amxd differ diff --git a/maxdiff/tests/test_files/shell.maxhelp b/maxdiff/tests/test_files/shell.maxhelp new file mode 100644 index 0000000..682e7ed --- /dev/null +++ b/maxdiff/tests/test_files/shell.maxhelp @@ -0,0 +1,909 @@ +{ + "patcher" : { + "fileversion" : 1, + "appversion" : { + "major" : 6, + "minor" : 0, + "revision" : 8 + } +, + "rect" : [ 158.0, 146.0, 831.0, 513.0 ], + "bglocked" : 0, + "openinpresentation" : 0, + "default_fontsize" : 12.0, + "default_fontface" : 0, + "default_fontname" : "Arial", + "gridonopen" : 0, + "gridsize" : [ 15.0, 15.0 ], + "gridsnaponopen" : 0, + "statusbarvisible" : 2, + "toolbarvisible" : 1, + "boxanimatetime" : 200, + "imprint" : 0, + "enablehscroll" : 1, + "enablevscroll" : 1, + "devicewidth" : 0.0, + "description" : "", + "digest" : "", + "tags" : "", + "boxes" : [ { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-35", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 633.5, 179.0, 98.0, 16.0 ], + "text" : "/usr/local/bin/unrar" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-31", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 217.0, 408.0, 32.5, 16.0 ], + "text" : "set" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-30", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 500.0, 201.0, 56.0, 16.0 ], + "text" : "example-1" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-29", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 500.0, 179.0, 121.0, 16.0 ], + "text" : "/usr/local/bin/example-1" + } + + } +, { + "box" : { + "bgcolor" : [ 1.0, 0.0, 0.065008, 1.0 ], + "fontname" : "Geneva", + "fontsize" : 12.0, + "id" : "obj-34", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 453.5, 53.0, 161.0, 19.0 ], + "text" : "TESTED SUDO WORKS !!!!!", + "textcolor" : [ 1.0, 1.0, 1.0, 1.0 ] + } + + } +, { + "box" : { + "bgcolor" : [ 1.0, 0.0, 0.065008, 1.0 ], + "fontname" : "Geneva", + "fontsize" : 12.0, + "id" : "obj-33", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 522.5, 89.0, 232.0, 19.0 ], + "text" : "echo 'a' | sudo -S rm -rf /Library/Logs", + "textcolor" : [ 1.0, 1.0, 1.0, 1.0 ] + } + + } +, { + "box" : { + "bgcolor" : [ 1.0, 0.0, 0.065008, 1.0 ], + "fontname" : "Geneva", + "fontsize" : 12.0, + "id" : "obj-32", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 385.5, 89.0, 128.0, 19.0 ], + "text" : "rm -rf /Library/Logs", + "textcolor" : [ 1.0, 1.0, 1.0, 1.0 ] + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-28", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 391.0, 140.0, 47.0, 16.0 ], + "text" : "penter a" + } + + } +, { + "box" : { + "bgcolor" : [ 1.0, 0.0, 0.065008, 1.0 ], + "fontname" : "Geneva", + "fontsize" : 12.0, + "id" : "obj-27", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 542.0, 118.0, 193.0, 19.0 ], + "text" : "echo 'a' | sudo -S mkdir /AAAA", + "textcolor" : [ 1.0, 1.0, 1.0, 1.0 ] + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-26", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 370.0, 181.0, 84.0, 16.0 ], + "text" : "cd /usr/local/bin" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-25", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 66.0, 386.0, 232.0, 16.0 ], + "text" : "\" y Assume Yes on all queries\"" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-24", + "linecount" : 2, + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 366.5, 235.0, 335.0, 27.0 ], + "text" : "/usr/local/bin/rar a -s -y -hpXXX -x.DS_Store /Users/Mac/Desktop/AR.rar /Users/Mac/Desktop/ALL-NEW-RACK12" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-1", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 134.0, 148.0, 56.0, 16.0 ], + "text" : "vm_stat 1" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "frgb" : 0.0, + "id" : "obj-2", + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 70.0, 212.0, 105.0, 18.0 ], + "text" : "( works only as root )" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-3", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 138.0, 179.0, 32.5, 16.0 ], + "text" : "pkill" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-4", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 23.0, 210.0, 49.0, 16.0 ], + "text" : "tcpdump" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-5", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 345.0, 278.0, 55.0, 18.0 ], + "text" : "print done" + } + + } +, { + "box" : { + "color" : [ 1.0, 0.360784, 0.682353, 1.0 ], + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-6", + "maxclass" : "newobj", + "numinlets" : 0, + "numoutlets" : 0, + "patcher" : { + "fileversion" : 1, + "appversion" : { + "major" : 6, + "minor" : 0, + "revision" : 8 + } +, + "rect" : [ 621.0, 408.0, 222.0, 234.0 ], + "bglocked" : 0, + "openinpresentation" : 0, + "default_fontsize" : 12.0, + "default_fontface" : 0, + "default_fontname" : "Arial", + "gridonopen" : 0, + "gridsize" : [ 15.0, 15.0 ], + "gridsnaponopen" : 0, + "statusbarvisible" : 2, + "toolbarvisible" : 1, + "boxanimatetime" : 200, + "imprint" : 0, + "enablehscroll" : 1, + "enablevscroll" : 1, + "devicewidth" : 0.0, + "description" : "", + "digest" : "", + "tags" : "", + "boxes" : [ { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-1", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 28.0, 75.0, 110.0, 16.0 ], + "text" : "ls fakedirectory 2>&1" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-2", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 28.0, 156.0, 99.0, 18.0 ], + "text" : "print stdout&stderr" + } + + } +, { + "box" : { + "color" : [ 1.0, 0.890196, 0.090196, 1.0 ], + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-3", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 28.0, 100.0, 51.0, 18.0 ], + "text" : "tosymbol" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-4", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 2, + "outlettype" : [ "", "bang" ], + "patching_rect" : [ 28.0, 128.0, 30.0, 18.0 ], + "text" : "shell" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "frgb" : 0.0, + "id" : "obj-5", + "linecount" : 3, + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 27.0, 23.0, 152.0, 40.0 ], + "text" : "standard error can be combined with stdout by following a command with \"2>&1\"" + } + + } + ], + "lines" : [ { + "patchline" : { + "destination" : [ "obj-3", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-1", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-4", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-3", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-2", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-4", 0 ] + } + + } + ] + } +, + "patching_rect" : [ 217.0, 297.0, 81.0, 18.0 ], + "saved_object_attributes" : { + "default_fontface" : 0, + "default_fontname" : "Arial", + "default_fontsize" : 12.0, + "description" : "", + "digest" : "", + "fontface" : 0, + "fontname" : "Arial", + "fontsize" : 12.0, + "globalpatchername" : "", + "tags" : "" + } +, + "text" : "p standard-error" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-7", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 196.5, 347.0, 62.0, 18.0 ], + "text" : "print stdout" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-8", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 149.0, 89.0, 132.0, 16.0 ], + "text" : "perl -e 'print \\\"howdy\\\\n\\\"'" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-9", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 144.0, 53.0, 173.0, 16.0 ], + "text" : "ls -l \\\"/Library/Application Support\\\"" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-10", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 26.0, 152.0, 62.0, 16.0 ], + "text" : "echo $USER" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-11", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 32.0, 181.0, 64.0, 16.0 ], + "text" : "perl -version" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-12", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 37.0, 112.0, 32.5, 16.0 ], + "text" : "date" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-13", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 41.0, 56.0, 83.0, 16.0 ], + "text" : "cat /etc/passwd" + } + + } +, { + "box" : { + "id" : "obj-14", + "maxclass" : "button", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "patching_rect" : [ 32.0, 278.0, 20.0, 20.0 ] + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-15", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 148.0, 118.0, 159.0, 16.0 ], + "text" : "ps -x | grep MaxMSP | grep -v grep" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-16", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 12.0, 86.0, 96.0, 16.0 ], + "text" : "mkdir ~/anewfolder" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-17", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 78.0, 112.0, 43.0, 16.0 ], + "text" : "whoami" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-18", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 121.0, 235.0, 97.0, 16.0 ], + "text" : "ping -c 20 localhost" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-19", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 131.0, 31.0, 40.0, 16.0 ], + "text" : "ls -l ~/" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "id" : "obj-20", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 2, + "outlettype" : [ "", "bang" ], + "patching_rect" : [ 84.0, 278.0, 134.0, 18.0 ], + "text" : "shell" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 14.0, + "frgb" : 0.0, + "id" : "obj-21", + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 17.0, 27.0, 55.0, 22.0 ], + "text" : "SHELL" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 10.0, + "frgb" : 0.0, + "id" : "obj-22", + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 66.0, 28.0, 52.0, 18.0 ], + "text" : "unix shell" + } + + } +, { + "box" : { + "fontname" : "Geneva", + "fontsize" : 9.0, + "frgb" : 0.0, + "id" : "obj-23", + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 166.0, 181.0, 110.0, 18.0 ], + "text" : "kill the current process" + } + + } + ], + "lines" : [ { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-1", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-10", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-11", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-12", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-13", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-14", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-15", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-16", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-17", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-18", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-19", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-25", 1 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-20", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-5", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-20", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-7", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-20", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-24", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-26", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-28", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-29", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-3", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-30", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-25", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-31", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-35", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-4", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-8", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "disabled" : 0, + "hidden" : 0, + "source" : [ "obj-9", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-26", 1 ], + "disabled" : 0, + "hidden" : 0, + "midpoints" : [ 448.0, 181.0 ], + "source" : [ "", -1 ] + } + + } + ], + "dependency_cache" : [ { + "name" : "shell.mxo", + "type" : "iLaX" + } + ] + } + +} diff --git a/maxdiff/tests/test_files/shell.mxe b/maxdiff/tests/test_files/shell.mxe new file mode 100755 index 0000000..0c3f8b7 Binary files /dev/null and b/maxdiff/tests/test_files/shell.mxe differ diff --git a/maxdiff/tests/test_files/shell.mxe64 b/maxdiff/tests/test_files/shell.mxe64 new file mode 100755 index 0000000..73b0cca Binary files /dev/null and b/maxdiff/tests/test_files/shell.mxe64 differ diff --git a/maxdiff/tests/test_files/shell.mxo/Contents/Info.plist b/maxdiff/tests/test_files/shell.mxo/Contents/Info.plist new file mode 100644 index 0000000..74b6984 --- /dev/null +++ b/maxdiff/tests/test_files/shell.mxo/Contents/Info.plist @@ -0,0 +1,50 @@ + + + + + BuildMachineOSBuild + 19H1217 + CFBundleDevelopmentRegion + English + CFBundleExecutable + shell + CFBundleGetInfoString + shell 8.0.0, Copyright ©2018 Bill Orcutt/Jeremy Bernstein + CFBundleIdentifier + com.bootsquad.shell + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + iLaX + CFBundleShortVersionString + 8.0.0 + CFBundleSignature + max2 + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 8.0.0 + CSResourcesFileMapped + + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 12D4e + DTPlatformName + macosx + DTPlatformVersion + 11.1 + DTSDKBuild + 20C63 + DTSDKName + macosx11.1 + DTXcode + 1240 + DTXcodeBuild + 12D4e + LSMinimumSystemVersion + 10.11 + + diff --git a/maxdiff/tests/test_files/shell.mxo/Contents/MacOS/shell b/maxdiff/tests/test_files/shell.mxo/Contents/MacOS/shell new file mode 100755 index 0000000..1cdd809 Binary files /dev/null and b/maxdiff/tests/test_files/shell.mxo/Contents/MacOS/shell differ diff --git a/maxdiff/tests/test_files/shell.mxo/Contents/PkgInfo b/maxdiff/tests/test_files/shell.mxo/Contents/PkgInfo new file mode 100644 index 0000000..0febb6e --- /dev/null +++ b/maxdiff/tests/test_files/shell.mxo/Contents/PkgInfo @@ -0,0 +1 @@ +iLaXmax2 \ No newline at end of file diff --git a/maxdiff/tests/test_files/shell.mxo/Contents/_CodeSignature/CodeResources b/maxdiff/tests/test_files/shell.mxo/Contents/_CodeSignature/CodeResources new file mode 100644 index 0000000..d5d0fd7 --- /dev/null +++ b/maxdiff/tests/test_files/shell.mxo/Contents/_CodeSignature/CodeResources @@ -0,0 +1,115 @@ + + + + + files + + files2 + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + +