diff --git a/checkpkg.py b/checkpkg.py index 5f6f933..3488111 100755 --- a/checkpkg.py +++ b/checkpkg.py @@ -5,24 +5,33 @@ import json import signal import re import shutil +import textwrap from concurrent.futures import ThreadPoolExecutor, as_completed from urllib.error import HTTPError, URLError -# Updated color codes with simpler separator +# Color codes COLORS = { 'reset': '\033[0m', 'bold': '\033[1m', 'red': '\033[91m', 'green': '\033[92m', 'yellow': '\033[93m', - 'header': '\033[94m' # Simpler header color + 'header': '\033[94m' } def colorize(text, color): + """Add ANSI color codes if output is a terminal""" if not hasattr(colorize, 'is_tty'): colorize.is_tty = __import__('sys').stdout.isatty() return f"{COLORS[color]}{text}{COLORS['reset']}" if colorize.is_tty else text +# def get_terminal_width(default=80): +# """Get terminal width with fallback""" +# try: +# return shutil.get_terminal_size().columns +# except: +# return default + signal.signal(signal.SIGINT, lambda s, f: exit(1)) REQUEST_HEADERS = { @@ -84,6 +93,148 @@ def fetch_package_data(package): except Exception: return None +def print_table(output, headers, selected_pms, args): + """Print formatted table with consistent alignment""" + # Terminal dimensions + terminal_width = shutil.get_terminal_size().columns + min_col_width = 10 + padding = 1 # Space on each side of the column content + + # Calculate initial column widths based on content + pkg_col_width = max( + len(headers[0]), + max(len(pkg) for pkg in args.packages) if args.packages else min_col_width + ) + (padding * 2) + + # Allocate remaining space proportionally to other columns + remaining_width = terminal_width - pkg_col_width - (3 * len(selected_pms)) # 3 chars for " | " + version_col_width = max(min_col_width, remaining_width // len(selected_pms)) + + # Define column widths + col_widths = [pkg_col_width] + [version_col_width] * len(selected_pms) + + # Adjust to fit terminal if needed + while sum(col_widths) + (3 * (len(col_widths) - 1)) > terminal_width and min(col_widths) > min_col_width: + max_idx = col_widths.index(max(col_widths)) + col_widths[max_idx] -= 1 + + # Calculate total table width for separators + total_width = sum(col_widths) + (3 * (len(col_widths) - 1)) + + # Print headers (centered) + header_row = [] + for idx, header in enumerate(headers): + header_text = header.center(col_widths[idx] - (padding * 2)) + padded_header = " " * padding + colorize(header_text, 'header') + " " * padding + header_row.append(padded_header.ljust(col_widths[idx])) + + print(" | ".join(header_row)) + + # Print main separator line + print("-" * total_width) + + # Process and print each package row + for pkg_index, pkg in enumerate(args.packages): + versions = output.get(pkg, {pm: 'Not found' for pm in selected_pms}) + + # Prepare content for each cell + cells_content = [] + + # Package name (first column) + cells_content.append(colorize(pkg, 'bold')) + + # Version information (remaining columns) + for pm in selected_pms: + version = versions.get(pm, 'Not found') + color = 'green' if version != 'Not found' else 'red' + if 'AUR' in version: color = 'yellow' + # Apply color to the entire string at once + cells_content.append(colorize(version, color)) + + # Apply wrapping to each cell based on column width + wrapped_cells = [] + for idx, content in enumerate(cells_content): + # Use raw text for wrapping calculation, but keep the colored text for display + raw_text = content + if colorize.is_tty: + # Remove ANSI color codes for length calculation + raw_text = re.sub(r'\033\[\d+m', '', content) + + avail_width = col_widths[idx] - (padding * 2) + + # Wrap text without color codes + wrapped_lines = textwrap.wrap( + raw_text, + width=avail_width, + break_long_words=False, + break_on_hyphens=True + ) or [''] + + # If we have color codes, we need to re-apply coloring to each wrapped line + if colorize.is_tty: + color = None + if idx == 0: # Package name + color = 'bold' + else: # Version info + if 'Not found' in raw_text: + color = 'red' + elif 'AUR' in raw_text: + color = 'yellow' + else: + color = 'green' + + # Apply the same color to each wrapped line + wrapped_lines = [colorize(line, color) for line in wrapped_lines] + + wrapped_cells.append(wrapped_lines) + + # Determine how many lines this row needs + max_lines = max(len(cell) for cell in wrapped_cells) + + # Print each line of the row + for line_idx in range(max_lines): + row_parts = [] + for col_idx, cell_lines in enumerate(wrapped_cells): + if line_idx < len(cell_lines): + content = cell_lines[line_idx] + if line_idx > 0: # For continuation lines + # Indent continuation lines for better readability + # We can't simply add characters to colored text, need to handle differently + if colorize.is_tty: + # Need to extract the color codes and apply them after indent + plain_content = re.sub(r'\033\[\d+m', '', content) + color_prefix = content[:content.find(plain_content)] + color_suffix = COLORS['reset'] + content = color_prefix + " " + plain_content + color_suffix + else: + content = " " + content + + # Calculate proper padding with color codes + if colorize.is_tty: + plain_content = re.sub(r'\033\[\d+m', '', content) + color_parts = content.split(plain_content) + padded = " " * padding + content + " " * padding + + # For colored text, we need to calculate padding differently + plain_padded = " " * padding + plain_content + " " * padding + padding_needed = col_widths[col_idx] - len(plain_padded) + if padding_needed > 0: + padded += " " * padding_needed + else: + padded = (" " * padding + content + " " * padding).ljust(col_widths[col_idx]) + + row_parts.append(padded) + else: + # Empty space for this cell in this line + row_parts.append(" " * col_widths[col_idx]) + + print(" | ".join(row_parts)) + + # Add a separator between packages (but not after the last one) + if pkg_index < len(args.packages) - 1: + # Use a lighter separator for between packages + print("ยท" * total_width) + def main(): parser = argparse.ArgumentParser(description='Package search tool') parser.add_argument('--all', action='store_true') @@ -151,69 +302,7 @@ def main(): else: output[pkg][pm] = 'Not found' - headers = ['Package'] + selected_pms - terminal_width = get_terminal_width() - - # Calculate initial column widths - col_widths = [len(h) for h in headers] - content_widths = {0: max(len(pkg) for pkg in args.packages)} - - for pkg in args.packages: - versions = output.get(pkg, {}) - for i, pm in enumerate(selected_pms, 1): - content_widths[i] = max(content_widths.get(i, 0), len(versions.get(pm, ''))) - - # Set initial widths - for i in range(len(headers)): - col_widths[i] = max(len(headers[i]), content_widths.get(i, 0)) - - # Adjust for terminal width - total_width = sum(col_widths) + 3 * (len(headers) - 1) # 3 chars per separator - max_reductions = 5 # Max characters to reduce per column - - if total_width > terminal_width: - excess = total_width - terminal_width - reducible = [i for i in range(1, len(headers)) if col_widths[i] > 10] - - while excess > 0 and reducible: - per_col = max(1, min(max_reductions, excess // len(reducible))) - for i in reducible: - reduction = min(per_col, col_widths[i] - 10, excess) - col_widths[i] -= reduction - excess -= reduction - if excess <= 0: - break - - # Truncate text with ellipsis if needed - def truncate(text, width): - if len(text) > width and width > 3: - return text[:width-3] + '...' - return text.ljust(width) - - # Print header - header_line = ' | '.join( - truncate(colorize(h, 'header'), w) - for h, w in zip(headers, col_widths) - ) - print(header_line) - - # Separator line - print('-' * min(sum(col_widths) + 3*(len(headers)-1), terminal_width)) - - # Print rows - for pkg in args.packages: - row = [truncate(colorize(pkg, 'bold'), col_widths[0])] - versions = output.get(pkg, {pm: 'Not found' for pm in selected_pms}) - - for i, pm in enumerate(selected_pms, 1): - version = versions.get(pm, 'Not found') - color = 'green' if version != 'Not found' else 'red' - if 'AUR' in version: - color = 'yellow' - # Fixed line with proper parenthesis closure - row.append(truncate(colorize(version, color), col_widths[i])) - - print(' | '.join(row)) + print_table(output, ['Package'] + selected_pms, selected_pms, args) if __name__ == '__main__': main()