From 0a6d27abcddd70a4c1d12c27b5ccf579982f2b92 Mon Sep 17 00:00:00 2001 From: phluxjr Date: Sun, 9 Nov 2025 18:18:51 -0600 Subject: [PATCH] initial commit: confy v1.0 --- main.py | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100755 main.py diff --git a/main.py b/main.py new file mode 100755 index 0000000..fa97fda --- /dev/null +++ b/main.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python3 + +import curses +import json +import os +import subprocess +from pathlib import Path +from datetime import datetime + +CONFIG_DIR = Path.home() / ".config" / "confy" +TRACKED_FILE = CONFIG_DIR / "tracked.json" + +class Confy: + def __init__(self): + self.files = [] + self.selected = 0 + self.page = 0 + self.items_per_page = 10 + self.command_mode = False + self.command_buffer = "" + self.last_opened = None + self.config_dir = str(Path.home() / ".config") + self.load_data() + + def load_data(self): + if TRACKED_FILE.exists(): + with open(TRACKED_FILE, 'r') as f: + data = json.load(f) + self.files = data.get('files', []) + self.last_opened = data.get('last_opened') + + def save_data(self): + CONFIG_DIR.mkdir(parents=True, exist_ok=True) + with open(TRACKED_FILE, 'w') as f: + json.dump({ + 'files': self.files, + 'last_opened': self.last_opened + }, f, indent=2) + + def get_file_info(self, filepath): + try: + stat = os.stat(filepath) + mtime = datetime.fromtimestamp(stat.st_mtime).strftime('%Y-%m-%d %H:%M') + size = self.format_size(stat.st_size) + return mtime, size + except: + return "unknown", "unknown" + + def format_size(self, size): + for unit in ['B', 'KB', 'MB', 'GB']: + if size < 1024: + return f"{size:.1f}{unit}" + size /= 1024 + return f"{size:.1f}TB" + + def add_config(self): + curses.endwin() + try: + result = subprocess.run(['ranger', '--choosefile=/tmp/confy_pick', self.config_dir]) + if os.path.exists('/tmp/confy_pick'): + with open('/tmp/confy_pick', 'r') as f: + filepath = f.read().strip() + if filepath and filepath not in self.files: + self.files.append(filepath) + self.save_data() + os.remove('/tmp/confy_pick') + except Exception as e: + pass + curses.doupdate() + + def remove_config(self): + if self.files and 0 <= self.selected < len(self.files): + del self.files[self.selected] + if self.selected >= len(self.files) and self.files: + self.selected = len(self.files) - 1 + self.save_data() + + def open_file(self, filepath): + editor = os.environ.get('EDITOR', 'nano') + curses.endwin() + try: + subprocess.run([editor, filepath]) + except FileNotFoundError: + input(f"error: editor '{editor}' not found. press enter to continue...") + except Exception as e: + input(f"error opening file: {e}. press enter to continue...") + curses.doupdate() + self.last_opened = filepath + self.save_data() + + def draw(self, stdscr): + height, width = stdscr.getmaxyx() + stdscr.clear() + + # top bar + stdscr.addstr(0, 1, "confy") + last_text = f"previous: {{{Path(self.last_opened).name if self.last_opened else 'none'}}}" + stdscr.addstr(1, 1, last_text) + config_text = f"config dir is {self.config_dir}" + stdscr.addstr(1, width - len(config_text) - 2, config_text) + stdscr.addstr(2, 0, "═" * width) + + # file list + start_idx = self.page * self.items_per_page + end_idx = min(start_idx + self.items_per_page, len(self.files)) + + for i in range(start_idx, end_idx): + y = 4 + (i - start_idx) + filepath = self.files[i] + filename = Path(filepath).name + directory = str(Path(filepath).parent) + mtime, size = self.get_file_info(filepath) + + line = f"{filename:<20} | {directory:<30} | {mtime:<16} | {size:<10}" + if i == self.selected: + line = f"{line} <" + stdscr.attron(curses.A_REVERSE) + stdscr.addstr(y, 1, line[:width-2]) + if i == self.selected: + stdscr.attroff(curses.A_REVERSE) + + # bottom bar + total_pages = (len(self.files) + self.items_per_page - 1) // self.items_per_page + if total_pages == 0: + total_pages = 1 + bottom_y = height - 2 + stdscr.addstr(bottom_y, 0, "═" * width) + if self.command_mode: + page_text = f"page {self.page + 1}/{total_pages} ▌ :{self.command_buffer}" + else: + page_text = f"page {self.page + 1}/{total_pages} ▌" + stdscr.addstr(bottom_y + 1, 1, page_text[:width-2]) + + stdscr.refresh() + + def handle_command(self): + cmd = self.command_buffer.strip() + if cmd == "q": + return False + elif cmd == "ac": + self.add_config() + elif cmd == "rm": + self.remove_config() + elif cmd == "l": + if self.last_opened and os.path.exists(self.last_opened): + self.open_file(self.last_opened) + self.command_buffer = "" + self.command_mode = False + return True + + def run(self, stdscr): + curses.curs_set(0) + stdscr.timeout(100) + + while True: + self.draw(stdscr) + + try: + key = stdscr.getch() + except: + continue + + if self.command_mode: + if key == ord('\n'): + if not self.handle_command(): + break + elif key == 27: # ESC + self.command_mode = False + self.command_buffer = "" + elif key in (curses.KEY_BACKSPACE, 127, 8): + self.command_buffer = self.command_buffer[:-1] + elif 32 <= key <= 126: + self.command_buffer += chr(key) + else: + if key == ord(':'): + self.command_mode = True + self.command_buffer = "" + elif key in (ord('j'), curses.KEY_DOWN): + if self.selected < len(self.files) - 1: + self.selected += 1 + if self.selected >= (self.page + 1) * self.items_per_page: + self.page += 1 + elif key in (ord('k'), curses.KEY_UP): + if self.selected > 0: + self.selected -= 1 + if self.selected < self.page * self.items_per_page: + self.page -= 1 + elif key == ord('\n'): + if self.files and 0 <= self.selected < len(self.files): + self.open_file(self.files[self.selected]) + elif key == ord('q'): + break + +def main(): + app = Confy() + curses.wrapper(app.run) + +if __name__ == "__main__": + main()