Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 64 additions & 7 deletions cheatbox/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,79 @@
from pick import pick
from pathlib import Path
import os
from difflib import SequenceMatcher

class Data:
def __init__(self):
self.base_dir = Path(__file__).parent.parent
self.data_dir = self.base_dir / "data"
self.domain: str = self.select_domain()

def fuzzy_search(self, query: str, options: list, threshold: float = 0.3) -> list:
"""
Fuzzy search implementation for matching cheats
Returns list of (score, option) tuples sorted by relevance
"""
results = []
query_lower = query.lower()

for option in options:
option_lower = option.lower()

# Check for exact substring match (highest priority)
if query_lower in option_lower:
score = 1.0
else:
# Use SequenceMatcher for fuzzy matching
score = SequenceMatcher(None, query_lower, option_lower).ratio()

if score >= threshold:
results.append((score, option))

# Sort by score descending
results.sort(reverse=True, key=lambda x: x[0])
return [option for score, option in results]

def select_domain(self) -> str:
"""Select domain to display"""
domains = [
"""Select domain to display with search option"""
domains = sorted([
file.stem for file in self.data_dir.glob("*.json")
if file.name != "ascii.json"
]
title = "CheatBox> Select the cheatsheet to display"
domain = pick(options=domains, title=title)
return str(domain[0]) # only return the name
])

# Add search option at the top
options_with_search = ["🔍 Search Cheatsheets..."] + domains

title = "CheatBox> Select the cheatsheet"
selected, index = pick(
options=options_with_search,
title=title,
indicator="→"
)

# If search option selected, prompt for query
if index == 0:
import typer
query = typer.prompt("Enter search query")
results = self.fuzzy_search(query, domains, threshold=0.3)

if not results:
typer.echo(f"❌ No cheatsheets found matching '{query}'")
# Recurse to show menu again
return self.select_domain()

if len(results) == 1:
return results[0]

# Multiple results, let user pick
selected_result, _ = pick(
options=results,
title=f"🔍 Search results for '{query}'",
indicator="→"
)
return selected_result

return str(selected) # Return the selected domain

def read_domain(self):
"""Read the content of the domain"""
Expand All @@ -26,7 +83,7 @@ def read_domain(self):
return json.load(f)
except Exception as e:
print(f"Error loading {self.domain}.json: {e}")
return {} # Return empty dict instead of None to prevent crashes
return {} # Return empty dict instead of None to prevent crashes

def run(self):
"""Runner for data fetch"""
Expand Down
39 changes: 37 additions & 2 deletions cheatbox/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,43 @@

@app.command()
def list():
domain = Data().run()
Display().display_bento(domain)
"""Browse and display cheatsheets"""
data = Data()
domain_data = data.run()
Display().display_bento(domain_data)

@app.command()
def search(query: str = typer.Argument(..., help="Search query for fuzzy search")):
"""Search through available cheatsheets using fuzzy matching"""
from pathlib import Path
base_dir = Path(__file__).parent.parent
data_dir = base_dir / "data"

domains = sorted([
file.stem for file in data_dir.glob("*.json")
if file.name != "ascii.json"
])

data = Data()
results = data.fuzzy_search(query, domains, threshold=0.2)

if not results:
typer.echo(f"❌ No cheatsheets found matching '{query}'")
return

if len(results) == 1:
data.domain = results[0]
domain_data = data.read_domain()
Display().display_bento(domain_data)
else:
selected_cheat, _ = pick(
options=results,
title=f"🔍 Search results for '{query}'",
indicator="→"
)
data.domain = selected_cheat
domain_data = data.read_domain()
Display().display_bento(domain_data)

@app.callback(invoke_without_command=True)
def main(ctx: typer.Context):
Expand Down
124 changes: 124 additions & 0 deletions data/asdf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
{
"LOGO": [
{
"ascii": [
"[blue] █████╗ ███████╗██████╗ ███████╗[/blue]",
"[blue] ██╔══██╗██╔════╝██╔══██╗██╔════╝[/blue]",
"[blue] ███████║███████╗██║ ██║█████╗ [/blue]",
"[blue] ██╔══██║╚════██║██║ ██║██╔══╝ [/blue]",
"[blue] ██║ ██║███████║██████╔╝██║ [/blue]",
"[blue] ╚═╝ ╚═╝╚══════╝╚═════╝ ╚═╝ [/blue]"
]
}
],
"TITLE": [
{
"ascii": [
" _ __ ",
" __ _ ___ __| |/ _|",
" / _` / __|/ _` | |_ ",
" | (_| \\__ \\ (_| | _|",
" \\__,_|___/\\__,_|_| "
]
}
],
"STYLE": {
"command_width": 40,
"outer_width": 140,
"primary_color": "blue"
},
"Installation": [
{
"command": "git clone https://github.com/asdf-vm/asdf.git ~/.asdf",
"description": "Clone asdf to home directory"
},
{
"command": "asdf --version",
"description": "Show asdf version"
}
],
"Plugins": [
{
"command": "asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git",
"description": "Add a plugin"
},
{
"command": "asdf plugin list",
"description": "List installed plugins"
},
{
"command": "asdf plugin list all",
"description": "List available plugins"
},
{
"command": "asdf plugin remove nodejs",
"description": "Remove a plugin"
}
],
"Version Management": [
{
"command": "asdf list",
"description": "List installed versions"
},
{
"command": "asdf list nodejs",
"description": "List installed versions of plugin"
},
{
"command": "asdf list all nodejs",
"description": "List available versions"
}
],
"Installation & Switching": [
{
"command": "asdf install nodejs 18.16.0",
"description": "Install specific version"
},
{
"command": "asdf install nodejs latest",
"description": "Install latest version"
},
{
"command": "asdf global nodejs 18.16.0",
"description": "Set global version"
},
{
"command": "asdf local nodejs 18.16.0",
"description": "Set local version (.tool-versions)"
}
],
"Versions": [
{
"command": "asdf current",
"description": "Show current versions"
},
{
"command": "asdf current nodejs",
"description": "Show current version of tool"
}
],
"Updates": [
{
"command": "asdf update",
"description": "Update asdf itself"
},
{
"command": "asdf plugin update nodejs",
"description": "Update plugin"
},
{
"command": "asdf plugin update --all",
"description": "Update all plugins"
}
],
"Cleanup": [
{
"command": "asdf uninstall nodejs 18.16.0",
"description": "Remove specific version"
},
{
"command": "asdf reshim",
"description": "Rebuild shims"
}
]
}
Loading