diff --git a/tools/nid_names.py b/tools/nid_names.py index 2cb0d756e4..0df8c3f147 100644 --- a/tools/nid_names.py +++ b/tools/nid_names.py @@ -131,16 +131,18 @@ def option_name(argument, options): def nid_arguments(arguments, options): normalized = [] collecting = False + collecting_option = "--nid" for index, argument in enumerate(arguments): if argument == "--": normalized.extend(arguments[index:]) break name = option_name(argument, options) - if name == "--nid" and "=" not in argument: + if name in ("--nid", "--name", "--symbol") and "=" not in argument: collecting = True + collecting_option = "--name" if name in ("--name", "--symbol") else "--nid" continue if collecting and name is None: - normalized.append("--nid=" + argument) + normalized.append(collecting_option + "=" + argument) continue collecting = False normalized.append(argument) @@ -154,11 +156,24 @@ def main(argv=None): help="NID database CSV (downloaded to a cache dir by default)") parser.add_argument("--nid", action="append", default=None, help="resolve only these NIDs instead of scanning the tree") + parser.add_argument("--name", "--symbol", dest="name", action="append", default=None, + help="compute NIDs for these symbol names") parser.add_argument("--json", action="store_true", help="emit machine-readable JSON") options = [option for action in parser._actions for option in action.option_strings] arguments = sys.argv[1:] if argv is None else argv args = parser.parse_args(nid_arguments(arguments, options)) + if args.name: + if args.nid: + parser.error("--name/--symbol cannot be combined with --nid") + rows = [{"name": name, "nid": compute_nid(name)} for name in args.name] + if args.json: + print(json.dumps({"symbols": rows}, indent=1)) + else: + print("%-40s %s" % ("NAME", "NID")) + for row in rows: + print("%-40s %s" % (row["name"], row["nid"])) + return 0 requested_nids = any(option_name(argument, options) == "--nid" for argument in arguments) for nid in args.nid or []: if re.fullmatch(r"[A-Za-z0-9+\-]{11}", nid) is None: diff --git a/tools/tests/test_nid_names.py b/tools/tests/test_nid_names.py index 41a8f5118d..6dd4b20804 100644 --- a/tools/tests/test_nid_names.py +++ b/tools/tests/test_nid_names.py @@ -240,10 +240,47 @@ int APS5_VABI sceReal() { return 0; } ''') self.assertEqual(nid_names.collect_real_names(), {"sceReal"}) + def test_name_option_computes_symbol_nid(self): + output = io.StringIO() + with patch.object(sys, "argv", ["nid_names.py", "--name", "ceilf", "floorf"]), contextlib.redirect_stdout(output): + self.assertEqual(nid_names.main(), 0) + text = output.getvalue() + self.assertIn("ceilf", text) + self.assertIn("GAUuLKGhsCw", text) + self.assertIn("floorf", text) + self.assertIn("mKhVDmYciWA", text) + + def test_name_option_with_json_emits_symbols_list(self): + output = io.StringIO() + with patch.object(sys, "argv", ["nid_names.py", "--name", "ceilf", "--json"]), contextlib.redirect_stdout(output): + self.assertEqual(nid_names.main(), 0) + data = json.loads(output.getvalue()) + self.assertEqual(data, {"symbols": [{"name": "ceilf", "nid": "GAUuLKGhsCw"}]}) + + def test_symbol_alias_does_not_load_database(self): + output = io.StringIO() + with patch.object(sys, "argv", ["nid_names.py", "--symbol", "truncf"]), \ + patch.object(nid_names, "load_db", side_effect=AssertionError("unexpected db load")), \ + contextlib.redirect_stdout(output): + self.assertEqual(nid_names.main(), 0) + self.assertIn("Vo8rvWtZw3g", output.getvalue()) + + def test_name_option_rejects_nid_and_keeps_long_names(self): + with patch.object(sys, "argv", ["nid_names.py", "--name", "ceilf", "--nid", "AAAAAAAAAAA"]), \ + contextlib.redirect_stderr(io.StringIO()): + with self.assertRaises(SystemExit): + nid_names.main() + long_name = "a" * 50 + output = io.StringIO() + with patch.object(sys, "argv", ["nid_names.py", "--name", long_name]), contextlib.redirect_stdout(output): + self.assertEqual(nid_names.main(), 0) + self.assertIn(long_name, output.getvalue()) + def test_spliced_line_comment_and_macro_tokens(self): self.write_source('// continued \\\nAPS5_EXPORT("AAAAAAAAAAA", exampleUnknown00);\nAPS5_EX\\\nPORT("BBBBBBBBBBB", exampleUnknown01);\n') self.assertEqual(nid_names.collect_unknowns(), {"BBBBBBBBBBB": "core/libs/prx/libSceExample/Export.cpp:exampleUnknown01"}) + if __name__ == "__main__": unittest.main()