mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-12 15:27:14 +03:00
[2051] Scripts Update
This commit is contained in:
parent
62cf621b20
commit
c7c6d5eed7
@ -1,4 +1,6 @@
|
|||||||
"""Search all given paths recursively for localised string calls."""
|
"""Search all given paths recursively for localised string calls."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import ast
|
import ast
|
||||||
import dataclasses
|
import dataclasses
|
||||||
@ -6,9 +8,6 @@ import json
|
|||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
# spell-checker: words dedupe deduping deduped
|
|
||||||
|
|
||||||
|
|
||||||
def get_func_name(thing: ast.AST) -> str:
|
def get_func_name(thing: ast.AST) -> str:
|
||||||
@ -16,11 +15,9 @@ def get_func_name(thing: ast.AST) -> str:
|
|||||||
if isinstance(thing, ast.Name):
|
if isinstance(thing, ast.Name):
|
||||||
return thing.id
|
return thing.id
|
||||||
|
|
||||||
elif isinstance(thing, ast.Attribute):
|
if isinstance(thing, ast.Attribute):
|
||||||
return get_func_name(thing.value)
|
return get_func_name(thing.value)
|
||||||
|
return ''
|
||||||
else:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
def get_arg(call: ast.Call) -> str:
|
def get_arg(call: ast.Call) -> str:
|
||||||
@ -31,10 +28,9 @@ def get_arg(call: ast.Call) -> str:
|
|||||||
arg = call.args[0]
|
arg = call.args[0]
|
||||||
if isinstance(arg, ast.Constant):
|
if isinstance(arg, ast.Constant):
|
||||||
return arg.value
|
return arg.value
|
||||||
elif isinstance(arg, ast.Name):
|
if isinstance(arg, ast.Name):
|
||||||
return f'VARIABLE! CHECK CODE! {arg.id}'
|
return f'VARIABLE! CHECK CODE! {arg.id}'
|
||||||
else:
|
return f'Unknown! {type(arg)=} {ast.dump(arg)} ||| {ast.unparse(arg)}' # type: ignore
|
||||||
return f'Unknown! {type(arg)=} {ast.dump(arg)} ||| {ast.unparse(arg)}'
|
|
||||||
|
|
||||||
|
|
||||||
def find_calls_in_stmt(statement: ast.AST) -> list[ast.Call]:
|
def find_calls_in_stmt(statement: ast.AST) -> list[ast.Call]:
|
||||||
@ -62,7 +58,7 @@ COMMENT_SAME_LINE_RE = re.compile(r'^.*?(#.*)$')
|
|||||||
COMMENT_OWN_LINE_RE = re.compile(r'^\s*?(#.*)$')
|
COMMENT_OWN_LINE_RE = re.compile(r'^\s*?(#.*)$')
|
||||||
|
|
||||||
|
|
||||||
def extract_comments(call: ast.Call, lines: list[str], file: pathlib.Path) -> Optional[str]: # noqa: CCR001
|
def extract_comments(call: ast.Call, lines: list[str], file: pathlib.Path) -> str | None:
|
||||||
"""
|
"""
|
||||||
Extract comments from source code based on the given call.
|
Extract comments from source code based on the given call.
|
||||||
|
|
||||||
@ -74,51 +70,32 @@ def extract_comments(call: ast.Call, lines: list[str], file: pathlib.Path) -> Op
|
|||||||
:param file: The path to the file this call node came from
|
:param file: The path to the file this call node came from
|
||||||
:return: The first comment that matches the rules, or None
|
:return: The first comment that matches the rules, or None
|
||||||
"""
|
"""
|
||||||
out: Optional[str] = None
|
above_line_number = call.lineno - 2
|
||||||
above = call.lineno - 2
|
current_line_number = call.lineno - 1
|
||||||
current = call.lineno - 1
|
|
||||||
|
|
||||||
above_line = lines[above].strip() if len(lines) >= above else None
|
def extract_lang_comment(line: str) -> str | None:
|
||||||
above_comment: Optional[str] = None
|
"""
|
||||||
current_line = lines[current].strip()
|
Extract a language comment from a given line.
|
||||||
current_comment: Optional[str] = None
|
|
||||||
|
|
||||||
bad_comment: Optional[str] = None
|
:param line: The line to extract the language comment from.
|
||||||
if above_line is not None:
|
:return: The extracted language comment, or None if no valid comment is found.
|
||||||
match = COMMENT_OWN_LINE_RE.match(above_line)
|
"""
|
||||||
if match:
|
match = COMMENT_OWN_LINE_RE.match(line)
|
||||||
above_comment = match.group(1).strip()
|
if match and match.group(1).startswith('# LANG:'):
|
||||||
if not above_comment.startswith('# LANG:'):
|
return match.group(1).replace('# LANG:', '').strip()
|
||||||
bad_comment = f'Unknown comment for {file}:{call.lineno} {above_line}'
|
return None
|
||||||
above_comment = None
|
|
||||||
|
|
||||||
else:
|
above_comment = extract_lang_comment(lines[above_line_number]) if len(lines) >= above_line_number else None
|
||||||
above_comment = above_comment.replace('# LANG:', '').strip()
|
current_comment = extract_lang_comment(lines[current_line_number])
|
||||||
|
|
||||||
if current_line is not None:
|
if current_comment is None:
|
||||||
match = COMMENT_SAME_LINE_RE.match(current_line)
|
current_comment = above_comment
|
||||||
if match:
|
|
||||||
current_comment = match.group(1).strip()
|
|
||||||
if not current_comment.startswith('# LANG:'):
|
|
||||||
bad_comment = f'Unknown comment for {file}:{call.lineno} {current_line}'
|
|
||||||
current_comment = None
|
|
||||||
|
|
||||||
else:
|
if current_comment is None:
|
||||||
current_comment = current_comment.replace('# LANG:', '').strip()
|
print(f'No comment for {file}:{call.lineno} {lines[current_line_number]}', file=sys.stderr)
|
||||||
|
return None
|
||||||
|
|
||||||
if current_comment is not None:
|
return current_comment
|
||||||
out = current_comment
|
|
||||||
|
|
||||||
elif above_comment is not None:
|
|
||||||
out = above_comment
|
|
||||||
|
|
||||||
elif bad_comment is not None:
|
|
||||||
print(bad_comment, file=sys.stderr)
|
|
||||||
|
|
||||||
if out is None:
|
|
||||||
print(f'No comment for {file}:{call.lineno} {current_line}', file=sys.stderr)
|
|
||||||
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
def scan_file(path: pathlib.Path) -> list[ast.Call]:
|
def scan_file(path: pathlib.Path) -> list[ast.Call]:
|
||||||
@ -126,17 +103,19 @@ def scan_file(path: pathlib.Path) -> list[ast.Call]:
|
|||||||
data = path.read_text(encoding='utf-8')
|
data = path.read_text(encoding='utf-8')
|
||||||
lines = data.splitlines()
|
lines = data.splitlines()
|
||||||
parsed = ast.parse(data)
|
parsed = ast.parse(data)
|
||||||
out: list[ast.Call] = []
|
calls: list[ast.Call] = []
|
||||||
|
|
||||||
for statement in parsed.body:
|
for statement in parsed.body:
|
||||||
out.extend(find_calls_in_stmt(statement))
|
calls.extend(find_calls_in_stmt(statement))
|
||||||
|
|
||||||
# see if we can extract any comments
|
# Extract and assign comments to each call
|
||||||
for call in out:
|
for call in calls:
|
||||||
setattr(call, 'comment', extract_comments(call, lines, path))
|
call.comment = extract_comments(call, lines, path) # type: ignore
|
||||||
|
|
||||||
out.sort(key=lambda c: c.lineno)
|
# Sort the calls by line number
|
||||||
return out
|
calls.sort(key=lambda c: c.lineno)
|
||||||
|
|
||||||
|
return calls
|
||||||
|
|
||||||
|
|
||||||
def scan_directory(path: pathlib.Path, skip: list[pathlib.Path] | None = None) -> dict[pathlib.Path, list[ast.Call]]:
|
def scan_directory(path: pathlib.Path, skip: list[pathlib.Path] | None = None) -> dict[pathlib.Path, list[ast.Call]]:
|
||||||
@ -150,40 +129,34 @@ def scan_directory(path: pathlib.Path, skip: list[pathlib.Path] | None = None) -
|
|||||||
for thing in path.iterdir():
|
for thing in path.iterdir():
|
||||||
if skip is not None and any(s.name == thing.name for s in skip):
|
if skip is not None and any(s.name == thing.name for s in skip):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if thing.is_file():
|
if thing.is_file():
|
||||||
if not thing.name.endswith('.py'):
|
if not thing.name.endswith('.py'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
out[thing] = scan_file(thing)
|
out[thing] = scan_file(thing)
|
||||||
|
|
||||||
elif thing.is_dir():
|
elif thing.is_dir():
|
||||||
out |= scan_directory(thing)
|
out |= scan_directory(thing) # type: ignore
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(type(thing), thing)
|
raise ValueError(type(thing), thing)
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def parse_template(path) -> set[str]:
|
def parse_template(path: pathlib.Path) -> set[str]:
|
||||||
"""
|
"""
|
||||||
Parse a lang.template file.
|
Parse a lang.template file.
|
||||||
|
|
||||||
The regexp this uses was extracted from l10n.py.
|
The regular expression used here was extracted from l10n.py.
|
||||||
|
|
||||||
:param path: The path to the lang file
|
:param path: The path to the lang file
|
||||||
"""
|
"""
|
||||||
lang_re = re.compile(r'\s*"((?:[^"]|(?:\"))+)"\s*=\s*"((?:[^"]|(?:\"))+)"\s*;\s*$')
|
lang_re = re.compile(r'\s*"([^"]+)"\s*=\s*"([^"]+)"\s*;\s*$')
|
||||||
out = set()
|
result = set()
|
||||||
|
|
||||||
for line in pathlib.Path(path).read_text(encoding='utf-8').splitlines():
|
for line in pathlib.Path(path).read_text(encoding='utf-8').splitlines():
|
||||||
match = lang_re.match(line)
|
match = lang_re.match(line)
|
||||||
if not match:
|
if match and match.group(1) != '!Language':
|
||||||
continue
|
result.add(match.group(1))
|
||||||
if match.group(1) != '!Language':
|
|
||||||
out.add(match.group(1))
|
|
||||||
|
|
||||||
return out
|
return result
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
@ -193,8 +166,8 @@ class FileLocation:
|
|||||||
path: pathlib.Path
|
path: pathlib.Path
|
||||||
line_start: int
|
line_start: int
|
||||||
line_start_col: int
|
line_start_col: int
|
||||||
line_end: Optional[int]
|
line_end: int | None
|
||||||
line_end_col: Optional[int]
|
line_end_col: int | None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_call(path: pathlib.Path, c: ast.Call) -> 'FileLocation':
|
def from_call(path: pathlib.Path, c: ast.Call) -> 'FileLocation':
|
||||||
@ -213,50 +186,46 @@ class LangEntry:
|
|||||||
|
|
||||||
locations: list[FileLocation]
|
locations: list[FileLocation]
|
||||||
string: str
|
string: str
|
||||||
comments: list[Optional[str]]
|
comments: list[str | None]
|
||||||
|
|
||||||
def files(self) -> str:
|
def files(self) -> str:
|
||||||
"""Return a string representation of all the files this LangEntry is in, and its location therein."""
|
"""Return a string representation of all the files this LangEntry is in, and its location therein."""
|
||||||
out = ''
|
file_locations = [
|
||||||
for loc in self.locations:
|
f'{loc.path.name}:{loc.line_start}' +
|
||||||
start = loc.line_start
|
(f':{loc.line_end}' if loc.line_end is not None and loc.line_end != loc.line_start else '')
|
||||||
end = loc.line_end
|
for loc in self.locations
|
||||||
end_str = f':{end}' if end is not None and end != start else ''
|
]
|
||||||
out += f'{loc.path.name}:{start}{end_str}; '
|
|
||||||
|
|
||||||
return out
|
return '; '.join(file_locations)
|
||||||
|
|
||||||
|
|
||||||
def dedupe_lang_entries(entries: list[LangEntry]) -> list[LangEntry]:
|
def dedupe_lang_entries(entries: list[LangEntry]) -> list[LangEntry]:
|
||||||
"""
|
"""
|
||||||
Deduplicate a list of lang entries.
|
Deduplicate a list of lang entries.
|
||||||
|
|
||||||
This will coalesce LangEntries that have that same string but differing files and comments into a single
|
This will coalesce LangEntries that have the same string but differing files and comments into a single
|
||||||
LangEntry that cotains all comments and FileLocations
|
LangEntry that contains all comments and FileLocations.
|
||||||
|
|
||||||
:param entries: The list to deduplicate
|
:param entries: The list to deduplicate
|
||||||
:return: The deduplicated list
|
:return: The deduplicated list
|
||||||
"""
|
"""
|
||||||
deduped: list[LangEntry] = []
|
deduped: dict[str, LangEntry] = {}
|
||||||
|
|
||||||
for e in entries:
|
for e in entries:
|
||||||
cont = False
|
existing = deduped.get(e.string)
|
||||||
for d in deduped:
|
if existing:
|
||||||
if d.string == e.string:
|
existing.locations.extend(e.locations)
|
||||||
cont = True
|
existing.comments.extend(e.comments)
|
||||||
d.locations.append(e.locations[0])
|
else:
|
||||||
d.comments.extend(e.comments)
|
deduped[e.string] = LangEntry(locations=e.locations[:], string=e.string, comments=e.comments[:])
|
||||||
|
|
||||||
if cont:
|
return list(deduped.values())
|
||||||
continue
|
|
||||||
|
|
||||||
deduped.append(e)
|
|
||||||
|
|
||||||
return deduped
|
|
||||||
|
|
||||||
|
|
||||||
def generate_lang_template(data: dict[pathlib.Path, list[ast.Call]]) -> str:
|
def generate_lang_template(data: dict[pathlib.Path, list[ast.Call]]) -> str:
|
||||||
"""Generate a full en.template from the given data."""
|
"""Generate a full en.template from the given data."""
|
||||||
entries: list[LangEntry] = []
|
entries: list[LangEntry] = []
|
||||||
|
|
||||||
for path, calls in data.items():
|
for path, calls in data.items():
|
||||||
for c in calls:
|
for c in calls:
|
||||||
entries.append(LangEntry([FileLocation.from_call(path, c)], get_arg(c), [getattr(c, 'comment')]))
|
entries.append(LangEntry([FileLocation.from_call(path, c)], get_arg(c), [getattr(c, 'comment')]))
|
||||||
@ -267,30 +236,72 @@ def generate_lang_template(data: dict[pathlib.Path, list[ast.Call]]) -> str:
|
|||||||
|
|
||||||
'''
|
'''
|
||||||
print(f'Done Deduping entries {len(entries)=} {len(deduped)=}', file=sys.stderr)
|
print(f'Done Deduping entries {len(entries)=} {len(deduped)=}', file=sys.stderr)
|
||||||
|
|
||||||
for entry in deduped:
|
for entry in deduped:
|
||||||
assert len(entry.comments) == len(entry.locations)
|
assert len(entry.comments) == len(entry.locations)
|
||||||
comment = ''
|
comment_parts = []
|
||||||
files = 'In files: ' + entry.files()
|
|
||||||
string = f'"{entry.string}"'
|
string = f'"{entry.string}"'
|
||||||
|
|
||||||
for i in range(len(entry.comments)):
|
for i, comment_text in enumerate(entry.comments):
|
||||||
if entry.comments[i] is None:
|
if comment_text is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
loc = entry.locations[i]
|
loc = entry.locations[i]
|
||||||
to_append = f'{loc.path.name}: {entry.comments[i]}; '
|
comment_parts.append(f'{loc.path.name}: {comment_text};')
|
||||||
if to_append not in comment:
|
|
||||||
comment += to_append
|
|
||||||
|
|
||||||
header = f'{comment.strip()} {files}'.strip()
|
if comment_parts:
|
||||||
out += f'/* {header} */\n'
|
header = ' '.join(comment_parts)
|
||||||
out += f'{string} = {string};\n'
|
out += f'/* {header} */\n'
|
||||||
out += '\n'
|
|
||||||
|
out += f'{string} = {string};\n\n'
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
def compare_lang_with_template(template: set[str], res: dict[pathlib.Path, list[ast.Call]]) -> None:
|
||||||
|
"""
|
||||||
|
Compare language entries in source code with a given language template.
|
||||||
|
|
||||||
|
:param template: A set of language entries from a language template.
|
||||||
|
:param res: A dictionary containing source code paths as keys and lists of ast.Call objects as values.
|
||||||
|
"""
|
||||||
|
seen = set()
|
||||||
|
|
||||||
|
for file, calls in res.items():
|
||||||
|
for c in calls:
|
||||||
|
arg = get_arg(c)
|
||||||
|
if arg in template:
|
||||||
|
seen.add(arg)
|
||||||
|
else:
|
||||||
|
print(f'NEW! {file}:{c.lineno}: {arg!r}')
|
||||||
|
|
||||||
|
for old in set(template) ^ seen:
|
||||||
|
print(f'No longer used: {old}')
|
||||||
|
|
||||||
|
|
||||||
|
def print_json_output(res: dict[pathlib.Path, list[ast.Call]]) -> None:
|
||||||
|
"""
|
||||||
|
Print JSON output of extracted language entries.
|
||||||
|
|
||||||
|
:param res: A dictionary containing source code paths as keys and lists of ast.Call objects as values.
|
||||||
|
"""
|
||||||
|
to_print_data = [
|
||||||
|
{
|
||||||
|
'path': str(path),
|
||||||
|
'string': get_arg(c),
|
||||||
|
'reconstructed': ast.unparse(c), # type: ignore
|
||||||
|
'start_line': c.lineno,
|
||||||
|
'start_offset': c.col_offset,
|
||||||
|
'end_line': c.end_lineno,
|
||||||
|
'end_offset': c.end_col_offset,
|
||||||
|
'comment': getattr(c, 'comment', None)
|
||||||
|
} for (path, calls) in res.items() for c in calls
|
||||||
|
]
|
||||||
|
|
||||||
|
print(json.dumps(to_print_data, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('--directory', help='Directory to search from', default='.')
|
parser.add_argument('--directory', help='Directory to search from', default='.')
|
||||||
parser.add_argument('--ignore', action='append', help='directories to ignore', default=['venv', '.venv', '.git'])
|
parser.add_argument('--ignore', action='append', help='directories to ignore', default=['venv', '.venv', '.git'])
|
||||||
@ -302,56 +313,32 @@ if __name__ == '__main__':
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
directory = pathlib.Path(args.directory)
|
directory = pathlib.Path(args.directory)
|
||||||
res = scan_directory(directory, [pathlib.Path(p) for p in args.ignore])
|
skip = [pathlib.Path(p) for p in args.ignore]
|
||||||
|
res = scan_directory(directory, skip)
|
||||||
|
|
||||||
if args.compare_lang is not None and len(args.compare_lang) > 0:
|
if args.compare_lang:
|
||||||
seen = set()
|
|
||||||
template = parse_template(args.compare_lang)
|
template = parse_template(args.compare_lang)
|
||||||
|
compare_lang_with_template(template, res)
|
||||||
for file, calls in res.items():
|
|
||||||
for c in calls:
|
|
||||||
arg = get_arg(c)
|
|
||||||
if arg in template:
|
|
||||||
seen.add(arg)
|
|
||||||
else:
|
|
||||||
print(f'NEW! {file}:{c.lineno}: {arg!r}')
|
|
||||||
|
|
||||||
for old in set(template) ^ seen:
|
|
||||||
print(f'No longer used: {old}')
|
|
||||||
|
|
||||||
elif args.json:
|
elif args.json:
|
||||||
to_print_data = [
|
print_json_output(res)
|
||||||
{
|
|
||||||
'path': str(path),
|
|
||||||
'string': get_arg(c),
|
|
||||||
'reconstructed': ast.unparse(c),
|
|
||||||
'start_line': c.lineno,
|
|
||||||
'start_offset': c.col_offset,
|
|
||||||
'end_line': c.end_lineno,
|
|
||||||
'end_offset': c.end_col_offset,
|
|
||||||
'comment': getattr(c, 'comment', None)
|
|
||||||
} for (path, calls) in res.items() for c in calls
|
|
||||||
]
|
|
||||||
|
|
||||||
print(json.dumps(to_print_data, indent=2))
|
|
||||||
|
|
||||||
elif args.lang:
|
elif args.lang:
|
||||||
if args.lang == '-':
|
if args.lang == '-':
|
||||||
print(generate_lang_template(res))
|
print(generate_lang_template(res))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
with open(args.lang, mode='w+', newline='\n') as langfile:
|
with open(args.lang, mode='w+', newline='\n') as langfile:
|
||||||
langfile.writelines(generate_lang_template(res))
|
langfile.writelines(generate_lang_template(res))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for path, calls in res.items():
|
for path, calls in res.items():
|
||||||
if len(calls) == 0:
|
if not calls:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
print(path)
|
print(path)
|
||||||
for c in calls:
|
for c in calls:
|
||||||
print(
|
print(
|
||||||
f' {c.lineno:4d}({c.col_offset:3d}):{c.end_lineno:4d}({c.end_col_offset:3d})\t', ast.unparse(c)
|
f' {c.lineno:4d}({c.col_offset:3d}):{c.end_lineno:4d}('
|
||||||
|
f'{c.end_col_offset:3d})\t', ast.unparse(c) # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
print()
|
print()
|
||||||
|
@ -116,9 +116,14 @@ if __name__ == '__main__':
|
|||||||
if file_name == '-':
|
if file_name == '-':
|
||||||
file = sys.stdin
|
file = sys.stdin
|
||||||
else:
|
else:
|
||||||
file = open(file_name)
|
try:
|
||||||
|
with open(file_name) as file:
|
||||||
res = json.load(file)
|
res = json.load(file)
|
||||||
file.close()
|
except FileNotFoundError:
|
||||||
|
print(f"File '{file_name}' not found.")
|
||||||
|
sys.exit(1)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print(f"Error decoding JSON in '{file_name}'.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
show_killswitch_set_info(KillSwitchSet(parse_kill_switches(res)))
|
show_killswitch_set_info(KillSwitchSet(parse_kill_switches(res)))
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
#!/usr/bin/env python
|
"""Search for dependencies given a package."""
|
||||||
"""Find the reverse dependencies of a package according to pip."""
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
|
||||||
|
|
||||||
def find_reverse_deps(package_name: str):
|
def find_reverse_deps(package_name: str) -> list[str]:
|
||||||
"""
|
"""
|
||||||
Find the packages that depend on the named one.
|
Find the packages that depend on the named one.
|
||||||
|
|
||||||
@ -19,4 +18,16 @@ def find_reverse_deps(package_name: str):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print(find_reverse_deps(sys.argv[1]))
|
if len(sys.argv) != 2:
|
||||||
|
print("Usage: python reverse_deps.py <package_name>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
package_name = sys.argv[1]
|
||||||
|
reverse_deps = find_reverse_deps(package_name)
|
||||||
|
|
||||||
|
if reverse_deps:
|
||||||
|
print(f"Reverse dependencies of '{package_name}':")
|
||||||
|
for dep in reverse_deps:
|
||||||
|
print(dep)
|
||||||
|
else:
|
||||||
|
print(f"No reverse dependencies found for '{package_name}'.")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user