oneaiguru / GenAICodeUpdater

0 stars 0 forks source link

split file selector #8

Open oneaiguru opened 4 weeks ago

oneaiguru commented 4 weeks ago

Implementation Plan: Modular File Selection System

1. Project Structure

project/
├── src/
│   ├── __init__.py
│   ├── config.py           # Project configuration and feature toggles
│   ├── storage.py          # Persistence layer for settings and selections
│   ├── models.py           # Core data models
│   ├── displays/
│   │   ├── __init__.py
│   │   ├── base.py        # Base display components
│   │   ├── files.py       # File selection table
│   │   ├── folders.py     # Folder selection table
│   │   ├── tags.py        # Tag selection table
│   │   └── console.py     # Console utilities
│   ├── selectors/
│   │   ├── __init__.py
│   │   ├── base.py        # Base selector functionality
│   │   ├── files.py       # File selection logic
│   │   ├── folders.py     # Folder selection logic
│   │   └── tags.py        # Tag selection logic
│   └── utils/
│       ├── __init__.py
│       ├── paths.py       # Path handling utilities
│       └── keyboard.py    # Keyboard input handling
└── tests/
    ├── __init__.py
    ├── test_config.py
    ├── test_storage.py
    ├── test_models.py
    ├── test_displays/
    └── test_selectors/

2. Core Components

2.1 Configuration (config.py)

2.2 Storage (storage.py)

2.3 Models (models.py)

3. Display Components

3.1 Base Display (displays/base.py)

3.2 Files Display (displays/files.py)

3.3 Folders Display (displays/folders.py)

3.4 Tags Display (displays/tags.py)

4. Selector Components

4.1 Base Selector (selectors/base.py)

4.2 File Selector (selectors/files.py)

4.3 Folder Selector (selectors/folders.py)

4.4 Tag Selector (selectors/tags.py)

5. Main Selection Manager

class SelectionManager:
    def __init__(self, root_dir):
        self.config = ProjectConfig(root_dir)
        self.storage = SelectionStorage(root_dir)

        # Initialize active selectors based on config
        self.selectors = {}
        if self.config.is_feature_enabled('files'):
            self.selectors['files'] = FileSelector(root_dir)
        if self.config.is_feature_enabled('folders'):
            self.selectors['folders'] = FolderSelector(root_dir)
        if self.config.is_feature_enabled('tags'):
            self.selectors['tags'] = TagSelector(root_dir)

        self.key_mappings = {
            'files': KeyMapping(),
            'folders': KeyMapping(),
            'tags': KeyMapping()
        }

    def run(self):
        """Main selection loop"""
        while True:
            self.display_tables()
            key = self.handle_input()
            if key == '\n':
                break
            self.process_key(key)

    def display_tables(self):
        """Display active tables based on enabled features"""
        console = Console()
        console.clear()

        if self.config.is_feature_enabled('tags'):
            TagTable().display_tags(
                self.selectors['tags'].list_tags(),
                self.selectors['tags'].get_selection(),
                self.key_mappings['tags']
            )

        if self.config.is_feature_enabled('folders'):
            FolderTable().display_folders(
                self.selectors['folders'].list_folders(),
                self.selectors['folders'].get_selection(),
                self.key_mappings['folders']
            )

        FileTable().display_files(
            self.selectors['files'].list_files(),
            self.selectors['files'].get_selection(),
            self.key_mappings['files']
        )

6. Implementation Notes

  1. Feature Toggle Storage:

    • Store in .fileselect directory in project root
    • JSON format for configuration
    • Separate files for features, selections, and tags
  2. Key Mapping Priorities:

    • Tags: 1-9
    • Folders: a-z
    • Files: remaining alphanumeric
  3. UI Layout:

    • Each table clearly separated
    • Tag table (if enabled): top
    • Folder table (if enabled): middle
    • File table: bottom
    • Clear visual hierarchy
  4. Selection Behavior:

    • Tag selection applies to current file selection
    • Folder selection cascades to contained files
    • File selection independent unless part of folder
  5. Performance Considerations:

    • Lazy loading of disabled features
    • Caching of file/folder lists
    • Efficient path calculations
  6. Testing Strategy:

    • Unit tests for each component
    • Integration tests for feature combinations
    • Mock file system for consistent testing
oneaiguru commented 4 weeks ago

code currently generated probably nbot fully is in scr - # src/config.py from pathlib import Path import json

class ProjectConfig: def init(self, project_path): self.project_path = Path(project_path) self.config_path = self.project_path / '.fileselect' / 'config.json' self.config = self._load_config()

def _load_config(self):
    if self.config_path.exists():
        with open(self.config_path) as f:
            return json.load(f)
    return {
        'features': {
            'files': True,  # Files are always enabled
            'folders': False,
            'tags': False
        },
        'key_mappings': {
            'tags': '123456789',
            'folders': 'abcdefghijklmnopqrstuvwxyz',
            'files': '0!@#$%^&*()'
        }
    }

def save_config(self):
    self.config_path.parent.mkdir(parents=True, exist_ok=True)
    with open(self.config_path, 'w') as f:
        json.dump(self.config, f, indent=2)

def is_feature_enabled(self, feature_name):
    return self.config['features'].get(feature_name, False)

def toggle_feature(self, feature_name):
    if feature_name in self.config['features']:
        self.config['features'][feature_name] = not self.config['features'][feature_name]
        self.save_config()

def get_key_mapping(self, feature_name):
    return self.config['key_mappings'].get(feature_name, '')

src/models.py

class Selection: def init(self): self.selected = set()

def toggle(self, item):
    if item in self.selected:
        self.selected.remove(item)
        return False
    else:
        self.selected.add(item)
        return True

def is_selected(self, item):
    return item in self.selected

def clear(self):
    self.selected.clear()

def add_all(self, items):
    self.selected.update(items)

def remove_all(self, items):
    self.selected.difference_update(items)

def get_selected(self):
    return list(self.selected)

class KeyMapping: def init(self): self.key_to_item = {} self.item_to_key = {}

def assign_key(self, item, key):
    if key in self.key_to_item or item in self.item_to_key:
        self.clear_key(key)
        self.clear_item(item)
    self.key_to_item[key] = item
    self.item_to_key[item] = key

def get_key(self, item):
    return self.item_to_key.get(item)

def get_item(self, key):
    return self.key_to_item.get(key)

def clear_key(self, key):
    if key in self.key_to_item:
        item = self.key_to_item[key]
        del self.key_to_item[key]
        if item in self.item_to_key:
            del self.item_to_key[item]

def clear_item(self, item):
    if item in self.item_to_key:
        key = self.item_to_key[item]
        del self.item_to_key[item]
        if key in self.key_to_item:
            del self.key_to_item[key]

src/displays/base.py

from rich.console import Console from rich.table import Table

class BaseTable: def init(self): self.console = Console() self.table = None

def create_table(self):
    self.table = Table(show_header=True, header_style="bold magenta")
    self.add_header()
    return self.table

def add_header(self):
    raise NotImplementedError("Subclasses must implement add_header")

def add_row(self, *args, **kwargs):
    if self.table is None:
        self.create_table()
    self.table.add_row(*args, **kwargs)

def render(self):
    if self.table is None:
        self.create_table()
    self.console.print(self.table)

src/displays/files.py

from .base import BaseTable from pathlib import Path

class FileTable(BaseTable): def add_header(self): self.table.add_column("S", width=1) self.table.add_column("Key", width=1) self.table.add_column("Path", style="cyan") self.table.add_column("Extension", style="yellow")

def display_files(self, files, selection, key_mapping):
    self.create_table()
    for file in files:
        key = key_mapping.get_key(file)
        if key:
            selected_mark = '[bright_white]✔[/bright_white]' if selection.is_selected(file) else ''
            relative_path = file.relative_to(file.parent)
            name, ext = relative_path.stem, relative_path.suffix
            self.add_row(
                selected_mark,
                key,
                f"  {name}",
                ext
            )
    self.render()

src/displays/folders.py

from .base import BaseTable from pathlib import Path

class FolderTable(BaseTable): def add_header(self): self.table.add_column("S", width=1) self.table.add_column("Key", width=1) self.table.add_column("Folder", style="bright_blue") self.table.add_column("Files", justify="right")

def display_folders(self, folders, selection, key_mapping):
    self.create_table()
    folder_colors = self._get_folder_colors()

    for i, folder in enumerate(sorted(folders)):
        key = key_mapping.get_key(folder)
        if key:
            folder_files = folders[folder]
            selected_count = sum(1 for f in folder_files if selection.is_selected(f))
            total_count = len(folder_files)

            if selected_count == total_count and total_count > 0:
                selected_mark = '[bright_white]✔[/bright_white]'
            elif selected_count > 0:
                selected_mark = '[bright_white]◐[/bright_white]'
            else:
                selected_mark = ''

            color = folder_colors[i % len(folder_colors)]
            self.add_row(
                selected_mark,
                key,
                f"[{color}]{folder.name}[/{color}]",
                f"{selected_count}/{total_count}"
            )
    self.render()

def _get_folder_colors(self):
    return [
        "bright_blue", "bright_green", "bright_yellow",
        "bright_magenta", "bright_cyan", "orange1",
        "deep_sky_blue1", "spring_green1"
    ]

src/displays/tags.py

from .base import BaseTable

class TagTable(BaseTable): def add_header(self): self.table.add_column("S", width=1) self.table.add_column("Key", width=1) self.table.add_column("Tag", style="bright_yellow") self.table.add_column("Files", justify="right")

def display_tags(self, tags, selection, key_mapping):
    self.create_table()
    for tag_name, tag_files in sorted(tags.items()):
        key = key_mapping.get_key(tag_name)
        if key:
            selected_mark = '[bright_white]✔[/bright_white]' if selection.is_selected(tag_name) else ''
            self.add_row(
                selected_mark,
                key,
                tag_name,
                str(len(tag_files))
            )
    self.render()

src/main.py

from pathlib import Path import readchar from rich.console import Console from config import ProjectConfig from storage import SelectionStorage from selectors.files import FileSelector from selectors.folders import FolderSelector from selectors.tags import TagSelector from displays.files import FileTable from displays.folders import FolderTable from displays.tags import TagTable

class SelectionManager: def init(self, root_dir): self.root_dir = Path(root_dir) self.config = ProjectConfig(root_dir) self.storage = SelectionStorage(root_dir) self.console = Console()

    # Initialize selectors based on enabled features
    self.selectors = {}
    self.displays = {}

    # Files are always enabled
    self.selectors['files'] = FileSelector(self.root_dir)
    self.displays['files'] = FileTable()

    if self.config.is_feature_enabled('folders'):
        self.selectors['folders'] = FolderSelector(self.root_dir)
        self.displays['folders'] = FolderTable()

    if self.config.is_feature_enabled('tags'):
        self.selectors['tags'] = TagSelector(self.root_dir)
        self.displays['tags'] = TagTable()

def run(self):
    while True:
        self.display_tables()
        key = readchar.readkey()

        if key == '\n':  # Enter key
            self.save_selection()
            break
        elif key == '\x1b':  # Escape key
            break
        else:
            self.handle_key(key)

def display_tables(self):
    self.console.clear()

    # Display tables in order: tags -> folders -> files
    if self.config.is_feature_enabled('tags'):
        self.displays['tags'].display_tags(
            self.selectors['tags'].list_tags(),
            self.selectors['tags'].selection,
            self.selectors['tags'].key_mapping
        )

    if self.config.is_feature_enabled('folders'):
        self.displays['folders'].display_folders(
            self.selectors['folders'].list_folders(),
            self.selectors['files'].selection,  # Use file selection for folder status
            self.selectors['folders'].key_mapping
        )

    self.displays['files'].display_files(
        self.selectors['files'].list_files(),
        self.selectors['files'].selection,
        self.selectors['files'].key_mapping
    )

def handle_key(self, key):
    handled = False

    # Try tags first
    if self.config.is_feature_enabled('tags'):
        handled = self.selectors['tags'].handle_tag_key(key)
        if handled:
            tag = self.selectors['tags'].key_mapping.get_item(key)
            if tag:
                self.selectors['tags'].apply_tag_selection(tag)
            return

    # Then try folders
    if not handled and self.config.is_feature_enabled('folders'):
        handled = self.selectors['folders'].handle_folder_key(key)
        if handled:
            folder = self.selectors['folders'].key_mapping.get_item(key)
            if folder:
                self.selectors['folders'].toggle_folder_files(folder)
            return

    # Finally try files
    if not handled:
        self.selectors['files'].handle_file_key(key)

def save_selection(self):
    # Save the current selection state
    self.storage.save_selection(
        self.selectors['files'].get_selection(),
        'files'
    )

def main(): import argparse parser = argparse.ArgumentParser(description="Interactive file selection tool") parser.add_argument("directory", help="Directory to select files from") args = parser.parse_args()

manager = SelectionManager(args.directory)
manager.run()

if name == "main": main()