From cbde79ee3d24796113823b5e917d0d51c333963a Mon Sep 17 00:00:00 2001 From: JamesConlan96 <10931523+JamesConlan96@users.noreply.github.com> Date: Tue, 17 Jan 2023 15:07:04 +0000 Subject: [PATCH 1/2] Fixed issue JakeWharton#182 --- pidcat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pidcat.py b/pidcat.py index 6a23786..4a2b42f 100755 --- a/pidcat.py +++ b/pidcat.py @@ -359,4 +359,4 @@ def tag_in_tags_regex(tag, tags): message = matcher.sub(replace, message) linebuf += indent_wrap(message) - print(linebuf.encode('utf-8')) + print(linebuf) From a97c87516b24ec4906494010ae35cfeb2339cd0d Mon Sep 17 00:00:00 2001 From: Consultant <10931523+JamesConlan96@users.noreply.github.com> Date: Tue, 20 Jun 2023 18:52:24 +0100 Subject: [PATCH 2/2] Added setup.py --- pidcat.py | 638 +++++++++++++++++++++++++++--------------------------- setup.py | 21 ++ 2 files changed, 343 insertions(+), 316 deletions(-) create mode 100644 setup.py diff --git a/pidcat.py b/pidcat.py index 4a2b42f..27a5fc8 100755 --- a/pidcat.py +++ b/pidcat.py @@ -29,334 +29,340 @@ __version__ = '2.1.0' -LOG_LEVELS = 'VDIWEF' -LOG_LEVELS_MAP = dict([(LOG_LEVELS[i], i) for i in range(len(LOG_LEVELS))]) -parser = argparse.ArgumentParser(description='Filter logcat by package name') -parser.add_argument('package', nargs='*', help='Application package name(s)') -parser.add_argument('-w', '--tag-width', metavar='N', dest='tag_width', type=int, default=23, help='Width of log tag') -parser.add_argument('-l', '--min-level', dest='min_level', type=str, choices=LOG_LEVELS+LOG_LEVELS.lower(), default='V', help='Minimum level to be displayed') -parser.add_argument('--color-gc', dest='color_gc', action='store_true', help='Color garbage collection') -parser.add_argument('--always-display-tags', dest='always_tags', action='store_true',help='Always display the tag name') -parser.add_argument('--current', dest='current_app', action='store_true',help='Filter logcat by current running app') -parser.add_argument('-s', '--serial', dest='device_serial', help='Device serial number (adb -s option)') -parser.add_argument('-d', '--device', dest='use_device', action='store_true', help='Use first device for log input (adb -d option)') -parser.add_argument('-e', '--emulator', dest='use_emulator', action='store_true', help='Use first emulator for log input (adb -e option)') -parser.add_argument('-c', '--clear', dest='clear_logcat', action='store_true', help='Clear the entire log before running') -parser.add_argument('-t', '--tag', dest='tag', action='append', help='Filter output by specified tag(s)') -parser.add_argument('-i', '--ignore-tag', dest='ignored_tag', action='append', help='Filter output by ignoring specified tag(s)') -parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + __version__, help='Print the version number and exit') -parser.add_argument('-a', '--all', dest='all', action='store_true', default=False, help='Print all log messages') - -args = parser.parse_args() -min_level = LOG_LEVELS_MAP[args.min_level.upper()] - -package = args.package - -base_adb_command = ['adb'] -if args.device_serial: - base_adb_command.extend(['-s', args.device_serial]) -if args.use_device: - base_adb_command.append('-d') -if args.use_emulator: - base_adb_command.append('-e') - -if args.current_app: - system_dump_command = base_adb_command + ["shell", "dumpsys", "activity", "activities"] - system_dump = subprocess.Popen(system_dump_command, stdout=PIPE, stderr=PIPE).communicate()[0] - running_package_name = re.search(".*TaskRecord.*A[= ]([^ ^}]*)", str(system_dump)).group(1) - package.append(running_package_name) - -if len(package) == 0: - args.all = True - -# Store the names of packages for which to match all processes. -catchall_package = list(filter(lambda package: package.find(":") == -1, package)) -# Store the name of processes to match exactly. -named_processes = list(filter(lambda package: package.find(":") != -1, package)) -# Convert default process names from : (cli notation) to (android notation) in the exact names match group. -named_processes = map(lambda package: package if package.find(":") != len(package) - 1 else package[:-1], named_processes) - -header_size = args.tag_width + 1 + 3 + 1 # space, level, space - -stdout_isatty = sys.stdout.isatty() - -width = -1 -try: - # Get the current terminal width - import fcntl, termios, struct - h, width = struct.unpack('hh', fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack('hh', 0, 0))) -except: - pass - -BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) - -RESET = '\033[0m' - -def termcolor(fg=None, bg=None): - codes = [] - if fg is not None: codes.append('3%d' % fg) - if bg is not None: codes.append('10%d' % bg) - return '\033[%sm' % ';'.join(codes) if codes else '' - -def colorize(message, fg=None, bg=None): - return termcolor(fg, bg) + message + RESET if stdout_isatty else message - -def indent_wrap(message): - if width == -1: - return message - message = message.replace('\t', ' ') - wrap_area = width - header_size - messagebuf = '' - current = 0 - while current < len(message): - next = min(current + wrap_area, len(message)) - messagebuf += message[current:next] - if next < len(message): - messagebuf += '\n' - messagebuf += ' ' * header_size - current = next - return messagebuf - - -LAST_USED = [RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN] -KNOWN_TAGS = { - 'dalvikvm': WHITE, - 'Process': WHITE, - 'ActivityManager': WHITE, - 'ActivityThread': WHITE, - 'AndroidRuntime': CYAN, - 'jdwp': WHITE, - 'StrictMode': WHITE, - 'DEBUG': YELLOW, -} - -def allocate_color(tag): - # this will allocate a unique format for the given tag - # since we dont have very many colors, we always keep track of the LRU - if tag not in KNOWN_TAGS: - KNOWN_TAGS[tag] = LAST_USED[0] - color = KNOWN_TAGS[tag] - if color in LAST_USED: - LAST_USED.remove(color) - LAST_USED.append(color) - return color - - -RULES = { - # StrictMode policy violation; ~duration=319 ms: android.os.StrictMode$StrictModeDiskWriteViolation: policy=31 violation=1 - re.compile(r'^(StrictMode policy violation)(; ~duration=)(\d+ ms)') - : r'%s\1%s\2%s\3%s' % (termcolor(RED), RESET, termcolor(YELLOW), RESET), -} - -# Only enable GC coloring if the user opted-in -if args.color_gc: - # GC_CONCURRENT freed 3617K, 29% free 20525K/28648K, paused 4ms+5ms, total 85ms - key = re.compile(r'^(GC_(?:CONCURRENT|FOR_M?ALLOC|EXTERNAL_ALLOC|EXPLICIT) )(freed >>>> ([a-zA-Z0-9._:]+) \[ userId:0 \| appId:(\d+) \]$') -PID_KILL = re.compile(r'^Killing (\d+):([a-zA-Z0-9._:]+)/[^:]+: (.*)$') -PID_LEAVE = re.compile(r'^No longer want ([a-zA-Z0-9._:]+) \(pid (\d+)\): .*$') -PID_DEATH = re.compile(r'^Process ([a-zA-Z0-9._:]+) \(pid (\d+)\) has died.?$') -LOG_LINE = re.compile(r'^([A-Z])/(.+?)\( *(\d+)\): (.*?)$') -BUG_LINE = re.compile(r'.*nativeGetEnabledTags.*') -BACKTRACE_LINE = re.compile(r'^#(.*?)pc\s(.*?)$') - -adb_command = base_adb_command[:] -adb_command.append('logcat') -adb_command.extend(['-v', 'brief']) - -# Clear log before starting logcat -if args.clear_logcat: - adb_clear_command = list(adb_command) - adb_clear_command.append('-c') - adb_clear = subprocess.Popen(adb_clear_command) - - while adb_clear.poll() is None: - pass +def main(): + """Main method for setup.py""" + + LOG_LEVELS = 'VDIWEF' + LOG_LEVELS_MAP = dict([(LOG_LEVELS[i], i) for i in range(len(LOG_LEVELS))]) + parser = argparse.ArgumentParser(description='Filter logcat by package name') + parser.add_argument('package', nargs='*', help='Application package name(s)') + parser.add_argument('-w', '--tag-width', metavar='N', dest='tag_width', type=int, default=23, help='Width of log tag') + parser.add_argument('-l', '--min-level', dest='min_level', type=str, choices=LOG_LEVELS+LOG_LEVELS.lower(), default='V', help='Minimum level to be displayed') + parser.add_argument('--color-gc', dest='color_gc', action='store_true', help='Color garbage collection') + parser.add_argument('--always-display-tags', dest='always_tags', action='store_true',help='Always display the tag name') + parser.add_argument('--current', dest='current_app', action='store_true',help='Filter logcat by current running app') + parser.add_argument('-s', '--serial', dest='device_serial', help='Device serial number (adb -s option)') + parser.add_argument('-d', '--device', dest='use_device', action='store_true', help='Use first device for log input (adb -d option)') + parser.add_argument('-e', '--emulator', dest='use_emulator', action='store_true', help='Use first emulator for log input (adb -e option)') + parser.add_argument('-c', '--clear', dest='clear_logcat', action='store_true', help='Clear the entire log before running') + parser.add_argument('-t', '--tag', dest='tag', action='append', help='Filter output by specified tag(s)') + parser.add_argument('-i', '--ignore-tag', dest='ignored_tag', action='append', help='Filter output by ignoring specified tag(s)') + parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + __version__, help='Print the version number and exit') + parser.add_argument('-a', '--all', dest='all', action='store_true', default=False, help='Print all log messages') + + args = parser.parse_args() + min_level = LOG_LEVELS_MAP[args.min_level.upper()] + + package = args.package + + base_adb_command = ['adb'] + if args.device_serial: + base_adb_command.extend(['-s', args.device_serial]) + if args.use_device: + base_adb_command.append('-d') + if args.use_emulator: + base_adb_command.append('-e') + + if args.current_app: + system_dump_command = base_adb_command + ["shell", "dumpsys", "activity", "activities"] + system_dump = subprocess.Popen(system_dump_command, stdout=PIPE, stderr=PIPE).communicate()[0] + running_package_name = re.search(".*TaskRecord.*A[= ]([^ ^}]*)", str(system_dump)).group(1) + package.append(running_package_name) -# This is a ducktype of the subprocess.Popen object -class FakeStdinProcess(): - def __init__(self): - self.stdout = sys.stdin - def poll(self): - return None + if len(package) == 0: + args.all = True -if sys.stdin.isatty(): - adb = subprocess.Popen(adb_command, stdin=PIPE, stdout=PIPE) -else: - adb = FakeStdinProcess() -pids = set() -last_tag = None -app_pid = None + # Store the names of packages for which to match all processes. + catchall_package = list(filter(lambda package: package.find(":") == -1, package)) + # Store the name of processes to match exactly. + named_processes = list(filter(lambda package: package.find(":") != -1, package)) + # Convert default process names from : (cli notation) to (android notation) in the exact names match group. + named_processes = map(lambda package: package if package.find(":") != len(package) - 1 else package[:-1], named_processes) -def match_packages(token): - if len(package) == 0: - return True - if token in named_processes: - return True - index = token.find(':') - return (token in catchall_package) if index == -1 else (token[:index] in catchall_package) - -def parse_death(tag, message): - if tag != 'ActivityManager': - return None, None - kill = PID_KILL.match(message) - if kill: - pid = kill.group(1) - package_line = kill.group(2) - if match_packages(package_line) and pid in pids: - return pid, package_line - leave = PID_LEAVE.match(message) - if leave: - pid = leave.group(2) - package_line = leave.group(1) - if match_packages(package_line) and pid in pids: - return pid, package_line - death = PID_DEATH.match(message) - if death: - pid = death.group(2) - package_line = death.group(1) - if match_packages(package_line) and pid in pids: - return pid, package_line - return None, None - -def parse_start_proc(line): - start = PID_START_5_1.match(line) - if start is not None: - line_pid, line_package, target = start.groups() - return line_package, target, line_pid, '', '' - start = PID_START.match(line) - if start is not None: - line_package, target, line_pid, line_uid, line_gids = start.groups() - return line_package, target, line_pid, line_uid, line_gids - start = PID_START_DALVIK.match(line) - if start is not None: - line_pid, line_package, line_uid = start.groups() - return line_package, '', line_pid, line_uid, '' - return None - -def tag_in_tags_regex(tag, tags): - return any(re.match(r'^' + t + r'$', tag) for t in map(str.strip, tags)) - -ps_command = base_adb_command + ['shell', 'ps'] -ps_pid = subprocess.Popen(ps_command, stdin=PIPE, stdout=PIPE, stderr=PIPE) -while True: - try: - line = ps_pid.stdout.readline().decode('utf-8', 'replace').strip() - except KeyboardInterrupt: - break - if len(line) == 0: - break - - pid_match = PID_LINE.match(line) - if pid_match is not None: - pid = pid_match.group(1) - proc = pid_match.group(2) - if proc in catchall_package: - seen_pids = True - pids.add(pid) - -while adb.poll() is None: + header_size = args.tag_width + 1 + 3 + 1 # space, level, space + + stdout_isatty = sys.stdout.isatty() + + width = -1 try: - line = adb.stdout.readline().decode('utf-8', 'replace').strip() - except KeyboardInterrupt: - break - if len(line) == 0: - break - - bug_line = BUG_LINE.match(line) - if bug_line is not None: - continue - - log_line = LOG_LINE.match(line) - if log_line is None: - continue - - level, tag, owner, message = log_line.groups() - tag = tag.strip() - start = parse_start_proc(line) - if start: - line_package, target, line_pid, line_uid, line_gids = start - if match_packages(line_package): - pids.add(line_pid) - - app_pid = line_pid + # Get the current terminal width + import fcntl, termios, struct + h, width = struct.unpack('hh', fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack('hh', 0, 0))) + except: + pass + BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) + + RESET = '\033[0m' + + def termcolor(fg=None, bg=None): + codes = [] + if fg is not None: codes.append('3%d' % fg) + if bg is not None: codes.append('10%d' % bg) + return '\033[%sm' % ';'.join(codes) if codes else '' + + def colorize(message, fg=None, bg=None): + return termcolor(fg, bg) + message + RESET if stdout_isatty else message + + def indent_wrap(message): + if width == -1: + return message + message = message.replace('\t', ' ') + wrap_area = width - header_size + messagebuf = '' + current = 0 + while current < len(message): + next = min(current + wrap_area, len(message)) + messagebuf += message[current:next] + if next < len(message): + messagebuf += '\n' + messagebuf += ' ' * header_size + current = next + return messagebuf + + + LAST_USED = [RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN] + KNOWN_TAGS = { + 'dalvikvm': WHITE, + 'Process': WHITE, + 'ActivityManager': WHITE, + 'ActivityThread': WHITE, + 'AndroidRuntime': CYAN, + 'jdwp': WHITE, + 'StrictMode': WHITE, + 'DEBUG': YELLOW, + } + + def allocate_color(tag): + # this will allocate a unique format for the given tag + # since we dont have very many colors, we always keep track of the LRU + if tag not in KNOWN_TAGS: + KNOWN_TAGS[tag] = LAST_USED[0] + color = KNOWN_TAGS[tag] + if color in LAST_USED: + LAST_USED.remove(color) + LAST_USED.append(color) + return color + + + RULES = { + # StrictMode policy violation; ~duration=319 ms: android.os.StrictMode$StrictModeDiskWriteViolation: policy=31 violation=1 + re.compile(r'^(StrictMode policy violation)(; ~duration=)(\d+ ms)') + : r'%s\1%s\2%s\3%s' % (termcolor(RED), RESET, termcolor(YELLOW), RESET), + } + + # Only enable GC coloring if the user opted-in + if args.color_gc: + # GC_CONCURRENT freed 3617K, 29% free 20525K/28648K, paused 4ms+5ms, total 85ms + key = re.compile(r'^(GC_(?:CONCURRENT|FOR_M?ALLOC|EXTERNAL_ALLOC|EXPLICIT) )(freed >>>> ([a-zA-Z0-9._:]+) \[ userId:0 \| appId:(\d+) \]$') + PID_KILL = re.compile(r'^Killing (\d+):([a-zA-Z0-9._:]+)/[^:]+: (.*)$') + PID_LEAVE = re.compile(r'^No longer want ([a-zA-Z0-9._:]+) \(pid (\d+)\): .*$') + PID_DEATH = re.compile(r'^Process ([a-zA-Z0-9._:]+) \(pid (\d+)\) has died.?$') + LOG_LINE = re.compile(r'^([A-Z])/(.+?)\( *(\d+)\): (.*?)$') + BUG_LINE = re.compile(r'.*nativeGetEnabledTags.*') + BACKTRACE_LINE = re.compile(r'^#(.*?)pc\s(.*?)$') + + adb_command = base_adb_command[:] + adb_command.append('logcat') + adb_command.extend(['-v', 'brief']) + + # Clear log before starting logcat + if args.clear_logcat: + adb_clear_command = list(adb_command) + adb_clear_command.append('-c') + adb_clear = subprocess.Popen(adb_clear_command) + + while adb_clear.poll() is None: + pass + + # This is a ducktype of the subprocess.Popen object + class FakeStdinProcess(): + def __init__(self): + self.stdout = sys.stdin + def poll(self): + return None + + if sys.stdin.isatty(): + adb = subprocess.Popen(adb_command, stdin=PIPE, stdout=PIPE) + else: + adb = FakeStdinProcess() + pids = set() + last_tag = None + app_pid = None + + def match_packages(token): + if len(package) == 0: + return True + if token in named_processes: + return True + index = token.find(':') + return (token in catchall_package) if index == -1 else (token[:index] in catchall_package) + + def parse_death(tag, message): + if tag != 'ActivityManager': + return None, None + kill = PID_KILL.match(message) + if kill: + pid = kill.group(1) + package_line = kill.group(2) + if match_packages(package_line) and pid in pids: + return pid, package_line + leave = PID_LEAVE.match(message) + if leave: + pid = leave.group(2) + package_line = leave.group(1) + if match_packages(package_line) and pid in pids: + return pid, package_line + death = PID_DEATH.match(message) + if death: + pid = death.group(2) + package_line = death.group(1) + if match_packages(package_line) and pid in pids: + return pid, package_line + return None, None + + def parse_start_proc(line): + start = PID_START_5_1.match(line) + if start is not None: + line_pid, line_package, target = start.groups() + return line_package, target, line_pid, '', '' + start = PID_START.match(line) + if start is not None: + line_package, target, line_pid, line_uid, line_gids = start.groups() + return line_package, target, line_pid, line_uid, line_gids + start = PID_START_DALVIK.match(line) + if start is not None: + line_pid, line_package, line_uid = start.groups() + return line_package, '', line_pid, line_uid, '' + return None + + def tag_in_tags_regex(tag, tags): + return any(re.match(r'^' + t + r'$', tag) for t in map(str.strip, tags)) + + ps_command = base_adb_command + ['shell', 'ps'] + ps_pid = subprocess.Popen(ps_command, stdin=PIPE, stdout=PIPE, stderr=PIPE) + while True: + try: + line = ps_pid.stdout.readline().decode('utf-8', 'replace').strip() + except KeyboardInterrupt: + break + if len(line) == 0: + break + + pid_match = PID_LINE.match(line) + if pid_match is not None: + pid = pid_match.group(1) + proc = pid_match.group(2) + if proc in catchall_package: + seen_pids = True + pids.add(pid) + + while adb.poll() is None: + try: + line = adb.stdout.readline().decode('utf-8', 'replace').strip() + except KeyboardInterrupt: + break + if len(line) == 0: + break + + bug_line = BUG_LINE.match(line) + if bug_line is not None: + continue + + log_line = LOG_LINE.match(line) + if log_line is None: + continue + + level, tag, owner, message = log_line.groups() + tag = tag.strip() + start = parse_start_proc(line) + if start: + line_package, target, line_pid, line_uid, line_gids = start + if match_packages(line_package): + pids.add(line_pid) + + app_pid = line_pid + + linebuf = '\n' + linebuf += colorize(' ' * (header_size - 1), bg=WHITE) + linebuf += indent_wrap(' Process %s created for %s\n' % (line_package, target)) + linebuf += colorize(' ' * (header_size - 1), bg=WHITE) + linebuf += ' PID: %s UID: %s GIDs: %s' % (line_pid, line_uid, line_gids) + linebuf += '\n' + print(linebuf) + last_tag = None # Ensure next log gets a tag printed + + dead_pid, dead_pname = parse_death(tag, message) + if dead_pid: + pids.remove(dead_pid) linebuf = '\n' - linebuf += colorize(' ' * (header_size - 1), bg=WHITE) - linebuf += indent_wrap(' Process %s created for %s\n' % (line_package, target)) - linebuf += colorize(' ' * (header_size - 1), bg=WHITE) - linebuf += ' PID: %s UID: %s GIDs: %s' % (line_pid, line_uid, line_gids) + linebuf += colorize(' ' * (header_size - 1), bg=RED) + linebuf += ' Process %s (PID: %s) ended' % (dead_pname, dead_pid) linebuf += '\n' print(linebuf) last_tag = None # Ensure next log gets a tag printed - dead_pid, dead_pname = parse_death(tag, message) - if dead_pid: - pids.remove(dead_pid) - linebuf = '\n' - linebuf += colorize(' ' * (header_size - 1), bg=RED) - linebuf += ' Process %s (PID: %s) ended' % (dead_pname, dead_pid) - linebuf += '\n' - print(linebuf) - last_tag = None # Ensure next log gets a tag printed - - # Make sure the backtrace is printed after a native crash - if tag == 'DEBUG': - bt_line = BACKTRACE_LINE.match(message.lstrip()) - if bt_line is not None: - message = message.lstrip() - owner = app_pid - - if not args.all and owner not in pids: - continue - if level in LOG_LEVELS_MAP and LOG_LEVELS_MAP[level] < min_level: - continue - if args.ignored_tag and tag_in_tags_regex(tag, args.ignored_tag): - continue - if args.tag and not tag_in_tags_regex(tag, args.tag): - continue - - linebuf = '' - - if args.tag_width > 0: - # right-align tag title and allocate color if needed - if tag != last_tag or args.always_tags: - last_tag = tag - color = allocate_color(tag) - tag = tag[-args.tag_width:].rjust(args.tag_width) - linebuf += colorize(tag, fg=color) + # Make sure the backtrace is printed after a native crash + if tag == 'DEBUG': + bt_line = BACKTRACE_LINE.match(message.lstrip()) + if bt_line is not None: + message = message.lstrip() + owner = app_pid + + if not args.all and owner not in pids: + continue + if level in LOG_LEVELS_MAP and LOG_LEVELS_MAP[level] < min_level: + continue + if args.ignored_tag and tag_in_tags_regex(tag, args.ignored_tag): + continue + if args.tag and not tag_in_tags_regex(tag, args.tag): + continue + + linebuf = '' + + if args.tag_width > 0: + # right-align tag title and allocate color if needed + if tag != last_tag or args.always_tags: + last_tag = tag + color = allocate_color(tag) + tag = tag[-args.tag_width:].rjust(args.tag_width) + linebuf += colorize(tag, fg=color) + else: + linebuf += ' ' * args.tag_width + linebuf += ' ' + + # write out level colored edge + if level in TAGTYPES: + linebuf += TAGTYPES[level] else: - linebuf += ' ' * args.tag_width + linebuf += ' ' + level + ' ' linebuf += ' ' - # write out level colored edge - if level in TAGTYPES: - linebuf += TAGTYPES[level] - else: - linebuf += ' ' + level + ' ' - linebuf += ' ' + # format tag message using rules + for matcher in RULES: + replace = RULES[matcher] + message = matcher.sub(replace, message) - # format tag message using rules - for matcher in RULES: - replace = RULES[matcher] - message = matcher.sub(replace, message) + linebuf += indent_wrap(message) + print(linebuf) - linebuf += indent_wrap(message) - print(linebuf) +if __name__ == "__main__": + main() diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..6529ade --- /dev/null +++ b/setup.py @@ -0,0 +1,21 @@ +from setuptools import setup + +from pidcat import __version__ + +setup( + name='pidcat', + version=__version__ , + description='Colored logcat script which only shows log entries for a specific application package.', + author='JakeWharton', + url='https://github.com/JamesConlan96/pidcat', + license='Apache-2.0', + py_modules=[ + 'pidcat' + ], + python_requires='>=3.0.0', + entry_points={ + 'console_scripts': [ + 'pidcat = pidcat:main' + ] + } +) \ No newline at end of file