CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-gnureadline

The standard Python readline extension statically linked against the GNU readline library.

Pending
Overview
Eval results
Files

history.mddocs/

History Management

Comprehensive history management with programmatic control over command history storage, retrieval, and persistence. The gnureadline history system maintains compatibility with GNU Readline's indexing conventions.

Capabilities

History Buffer Operations

Basic operations for adding, retrieving, and manipulating history entries in memory.

def add_history(line: str):
    """
    Add a line to the history buffer.
    
    Parameters:
    - line: Command line to add to history
    """

def clear_history():
    """Clear the entire history list."""

def get_history_item(index: int) -> str:
    """
    Get history item by index (1-based).
    
    Parameters:
    - index: History item index (1-based, 0 returns None)
    
    Returns:
    str or None: History item or None if index is 0 or out of range
    """

def get_current_history_length() -> int:
    """
    Get the current number of history entries.
    
    Returns:
    int: Number of history entries
    """

Usage Examples

import gnureadline

# Build a command history
commands = ["ls -la", "cd /home", "python script.py", "git status"]
for cmd in commands:
    gnureadline.add_history(cmd)

print(f"History length: {gnureadline.get_current_history_length()}")  # 4

# Retrieve history items (1-based indexing)
first_cmd = gnureadline.get_history_item(1)  # "ls -la"
last_cmd = gnureadline.get_history_item(4)   # "git status"
invalid = gnureadline.get_history_item(0)    # None

# Clear all history
gnureadline.clear_history()
print(f"After clear: {gnureadline.get_current_history_length()}")  # 0

History Modification

Advanced operations for modifying existing history entries using 0-based indexing.

def remove_history_item(index: int) -> str:
    """
    Remove history entry by index (0-based).
    
    Parameters:
    - index: History item index (0-based)
    
    Returns:
    str: Removed history item
    """

def replace_history_item(index: int, line: str) -> str:
    """
    Replace history entry at index (0-based).
    
    Parameters:
    - index: History item index (0-based)
    - line: New history line
    
    Returns:
    str: Previous history item at that index
    """

Usage Examples

import gnureadline

# Set up some history
gnureadline.add_history("command1")
gnureadline.add_history("command2")  
gnureadline.add_history("command3")

# Replace second command (0-based index 1)
old_cmd = gnureadline.replace_history_item(1, "modified_command2")
print(f"Replaced '{old_cmd}' with 'modified_command2'")

# Remove first command (0-based index 0)  
removed = gnureadline.remove_history_item(0)
print(f"Removed: '{removed}'")

# Check final state
for i in range(gnureadline.get_current_history_length()):
    print(f"History[{i}]: {gnureadline.get_history_item(i+1)}")  # +1 for 1-based get

History Configuration

Control automatic history behavior and size limits.

def set_history_length(length: int):
    """
    Set maximum history length.
    
    Parameters:
    - length: Maximum number of history entries (-1 for unlimited)
    """

def get_history_length() -> int:
    """
    Get maximum history length setting.
    
    Returns:
    int: Maximum history length (-1 if unlimited)
    """

def set_auto_history(enabled: bool):
    """
    Enable or disable automatic history addition.
    
    Parameters:
    - enabled: Whether to automatically add lines to history
    """

Usage Examples

import gnureadline

# Set history limit
gnureadline.set_history_length(100)
print(f"History limit: {gnureadline.get_history_length()}")  # 100

# Disable automatic history (manual control)
gnureadline.set_auto_history(False)

# Unlimited history
gnureadline.set_history_length(-1)
print(f"Unlimited history: {gnureadline.get_history_length()}")  # -1

File Persistence

History File Operations

Save and load history to/from files for persistence across sessions.

def read_history_file(filename: str = None):
    """
    Load a readline history file.
    
    Parameters:
    - filename: History file path (default: ~/.history)
    """

def write_history_file(filename: str = None):
    """
    Save the history to a file.
    
    Parameters:
    - filename: History file path (default: ~/.history)
    """

def append_history_file(nelements: int, filename: str = None):
    """
    Append recent history entries to a file.
    
    Parameters:
    - nelements: Number of recent entries to append
    - filename: History file path (default: ~/.history)
    
    Note: Only available if HAVE_RL_APPEND_HISTORY is defined
    """

Usage Examples

import gnureadline
import os

# Set up custom history file location
history_file = os.path.expanduser("~/.my_app_history")

# Load existing history on startup
try:
    gnureadline.read_history_file(history_file)
    print(f"Loaded {gnureadline.get_current_history_length()} history entries")
except OSError as e:
    print(f"No existing history file: {e}")

# Add some commands during session
gnureadline.add_history("command 1")
gnureadline.add_history("command 2")

# Save complete history on exit
try:
    gnureadline.write_history_file(history_file)
    print("History saved successfully")
except OSError as e:
    print(f"Failed to save history: {e}")

# Alternatively, append only recent entries
try:
    gnureadline.append_history_file(10, history_file)  # Last 10 entries
    print("Recent history appended")
except OSError as e:
    print(f"Failed to append history: {e}")

Advanced History Management

History Filtering and Processing

import gnureadline
import re

class HistoryManager:
    def __init__(self, max_length=1000, filter_duplicates=True):
        self.max_length = max_length
        self.filter_duplicates = filter_duplicates
        gnureadline.set_history_length(max_length)
        gnureadline.set_auto_history(False)  # Manual control
    
    def add_filtered_history(self, line: str):
        """Add history with filtering logic."""
        # Skip empty lines and whitespace-only lines
        if not line.strip():
            return
            
        # Skip commands starting with space (privacy feature)
        if line.startswith(' '):
            return
            
        # Filter out sensitive commands
        sensitive_patterns = [r'password', r'secret', r'key\s*=']
        if any(re.search(pattern, line, re.I) for pattern in sensitive_patterns):
            return
            
        # Remove duplicates if enabled
        if self.filter_duplicates:
            current_length = gnureadline.get_current_history_length()
            if current_length > 0:
                last_item = gnureadline.get_history_item(current_length)
                if last_item == line:
                    return
        
        gnureadline.add_history(line)
    
    def search_history(self, pattern: str) -> list:
        """Search history for matching entries."""
        matches = []
        length = gnureadline.get_current_history_length()
        
        for i in range(1, length + 1):
            item = gnureadline.get_history_item(i)
            if item and re.search(pattern, item, re.I):
                matches.append((i, item))
        
        return matches
    
    def cleanup_history(self):
        """Remove old/redundant entries to keep history manageable."""
        length = gnureadline.get_current_history_length()
        if length <= self.max_length:
            return
            
        # Keep only the most recent entries
        keep_entries = []
        start_index = length - self.max_length + 1
        
        for i in range(start_index, length + 1):
            item = gnureadline.get_history_item(i)
            if item:
                keep_entries.append(item)
        
        # Clear and rebuild history
        gnureadline.clear_history()
        for entry in keep_entries:
            gnureadline.add_history(entry)

# Usage example
history_mgr = HistoryManager(max_length=500, filter_duplicates=True)

# Add commands with filtering
commands = [
    "ls -la",
    "cd /home", 
    "ls -la",  # Duplicate - will be filtered
    " secret_command",  # Starts with space - will be filtered
    "export PASSWORD=secret",  # Contains sensitive data - will be filtered
    "python script.py"
]

for cmd in commands:
    history_mgr.add_filtered_history(cmd)

# Search history
matches = history_mgr.search_history("python")
print(f"Found {len(matches)} matches for 'python'")

# Periodic cleanup
history_mgr.cleanup_history()

Index Compatibility Notes

Important: GNU Readline uses different indexing conventions for different operations:

  • get_history_item(): 1-based indexing (index 0 returns None)
  • remove_history_item() and replace_history_item(): 0-based indexing

This matches the GNU Readline C library behavior for compatibility with existing applications.

import gnureadline

# Add some test data
gnureadline.add_history("first")   # Will be at index 0 for remove/replace
gnureadline.add_history("second")  # Will be at index 1 for remove/replace

# Getting items (1-based)
first = gnureadline.get_history_item(1)   # "first"
second = gnureadline.get_history_item(2)  # "second"

# Modifying items (0-based) 
gnureadline.replace_history_item(0, "FIRST")   # Replace "first" with "FIRST"
gnureadline.remove_history_item(1)             # Remove "second"

# Verify the change
print(gnureadline.get_history_item(1))  # "FIRST"
print(gnureadline.get_history_item(2))  # None (removed)

Install with Tessl CLI

npx tessl i tessl/pypi-gnureadline

docs

completion.md

history.md

hooks.md

index.md

line-editing.md

utilities.md

tile.json