First commit
This commit is contained in:
269
bagheerasearch.py
Executable file
269
bagheerasearch.py
Executable file
@@ -0,0 +1,269 @@
|
||||
#!/usr/bin/env python3
|
||||
# flake8: noqa: E501
|
||||
"""
|
||||
Bagheera Search Tool - CLI Client
|
||||
"""
|
||||
|
||||
__appname__ = "BagheeraSearch"
|
||||
__version__ = "1.0"
|
||||
__author__ = "Ignacio Serantes"
|
||||
__email__ = "kde@aynoa.net"
|
||||
__license__ = "LGPL"
|
||||
__status__ = "Production"
|
||||
# "Prototype, Development, Alpha, Beta, Production, Stable, Deprecated"
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import signal
|
||||
import sys
|
||||
from pathlib import Path
|
||||
# from baloo_tools import get_resolution
|
||||
# from date_query_parser import parse_date
|
||||
from bagheera_search_lib import BagheeraSearcher
|
||||
|
||||
# --- CONFIGURATION ---
|
||||
PROG_NAME = "Bagheera Search Tool"
|
||||
PROG_ID = "bagheerasearch"
|
||||
PROG_VERSION = "1.0"
|
||||
PROG_BY = "Ignacio Serantes"
|
||||
PROG_DATE = "2026-03-19"
|
||||
|
||||
CONFIG_DIR = Path.home() / ".config" / PROG_ID
|
||||
CONFIG_FILE = CONFIG_DIR / "config.json"
|
||||
|
||||
|
||||
def load_config() -> dict:
|
||||
"""Loads user configuration from disk."""
|
||||
if CONFIG_FILE.exists():
|
||||
try:
|
||||
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except (json.JSONDecodeError, OSError) as e:
|
||||
print(f"Warning: Could not load config file: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
def save_config(config: dict) -> None:
|
||||
"""Saves user configuration to disk."""
|
||||
try:
|
||||
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
||||
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
|
||||
json.dump(config, f, indent=4)
|
||||
except OSError as e:
|
||||
print(f"Warning: Could not save config file: {e}")
|
||||
|
||||
|
||||
def print_help_query() -> None:
|
||||
"""Prints the detailed help for query syntax."""
|
||||
help_query = f"""Help updated to 2025-01-01.
|
||||
|
||||
Baloo offers a rich syntax for searching through your files. Certain attributes of a file can be searched through.
|
||||
|
||||
For example 'type' can be used to filter for files based on their general type:
|
||||
|
||||
type:Audio or type:Document
|
||||
|
||||
The following comparison operators are supported, but note that 'not equal' operator is not available.
|
||||
· : - contains (only for text comparison)
|
||||
· = - equal
|
||||
· > - greater than
|
||||
· >= - greater than or equal to
|
||||
· < - less than
|
||||
· <= - less than or equal to
|
||||
|
||||
Currently the following types are supported:
|
||||
|
||||
· Archive
|
||||
· Folder
|
||||
· Audio
|
||||
· Video
|
||||
· Image
|
||||
· Document
|
||||
· Spreadsheet
|
||||
· Presentation
|
||||
· Text
|
||||
|
||||
These expressions can be combined using AND or OR and additional parenthesis, but note that 'NOT' logical operator is not available.
|
||||
|
||||
[... omitted for brevity, but includes the full list of searchable properties as in your original script ...]
|
||||
|
||||
{PROG_NAME} recognizes some natural language sentences in English, as long as they are capitalized, and transforms them into queries that can be interpreted by the search engine.
|
||||
|
||||
Supported natural language sentences and patterns for queries are:
|
||||
· MODIFIED TODAY
|
||||
· MODIFIED YESTERDAY
|
||||
· MODIFIED THIS [ DAY | WEEK | MONTH | YEAR ]
|
||||
· LAST <NUMBER> [ DAYS | WEEKS | MONTHS | YEARS ]
|
||||
· <NUMBER> [ DAYS | WEEKS | MONTHS | YEARS ] AGO
|
||||
|
||||
<NUMBER> can be any number or a number text from ONE to TWENTY.
|
||||
|
||||
Remarks: LAST DAY, if used, is interpreted as YESTERDAY.
|
||||
|
||||
Supported expressions for --exclude and --recursive-exclude are:
|
||||
· width<CMP_OP>height - only if file has width and height properties
|
||||
· height<CMP_OP>width - only if file has width and height properties
|
||||
· PORTRAIT - only if file width is greater or equal to height
|
||||
· LANDSCAPE - only if file height is greater or equal to width
|
||||
· SQUARE - only if file width equals to height
|
||||
|
||||
<CMP_OP> can be: != | >= | <= | = | > | <"""
|
||||
print(help_query)
|
||||
|
||||
|
||||
def print_version() -> None:
|
||||
"""Prints version information."""
|
||||
print(f"{PROG_NAME} v{PROG_VERSION} - {PROG_DATE}")
|
||||
print(
|
||||
f"Copyright (C) {PROG_DATE[:4]} by {PROG_BY} and, mostly, "
|
||||
"the good people at KDE"
|
||||
)
|
||||
|
||||
|
||||
def signal_handler(sig, frame) -> None:
|
||||
"""Handles Ctrl+C gracefully."""
|
||||
print("\nSearch canceled at user request.")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="An improved search tool for Baloo"
|
||||
)
|
||||
parser.add_argument("query", nargs="?", help="list of words to query for")
|
||||
parser.add_argument("-d", "--directory", help="limit search to specified directory")
|
||||
parser.add_argument("-e", "--exclude", help="Search exclude pattern")
|
||||
parser.add_argument("-i", "--id", action="store_true", help="show document IDs")
|
||||
parser.add_argument("-k", "--konsole", action="store_true", help="show files using file:/ and quotes")
|
||||
parser.add_argument("-l", "--limit", type=int, help="the maximum number of results")
|
||||
parser.add_argument("-o", "--offset", type=int, help="offset from which to start the search")
|
||||
parser.add_argument("-r", "--recursive", nargs="?", const="", default=None, help="enable recurse with or without a query")
|
||||
parser.add_argument("-n", "--recursive-indent", help="recursive indent character")
|
||||
parser.add_argument("-x", "--recursive-exclude", help="recursion exclude pattern")
|
||||
parser.add_argument("-s", "--sort", help="sorting criteria <auto|none>")
|
||||
parser.add_argument("-t", "--type", help="type of Baloo data to be searched")
|
||||
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose mode")
|
||||
|
||||
parser.add_argument("--day", type=int, help="day fixed filter, --month is required")
|
||||
parser.add_argument("--month", type=int, help="month fixed filter, --year is required")
|
||||
parser.add_argument("--year", type=int, help="year filter fixed filter")
|
||||
|
||||
parser.add_argument("--help-query", action="store_true", help="show query syntax help")
|
||||
parser.add_argument("--version", action="store_true", help="show version information")
|
||||
|
||||
args, unknown_args = parser.parse_known_args()
|
||||
|
||||
query_parts = [args.query] if args.query else []
|
||||
if unknown_args:
|
||||
query_parts.extend(unknown_args)
|
||||
|
||||
query_text = " ".join(query_parts)
|
||||
|
||||
if args.day is not None and args.month is None:
|
||||
raise ValueError("Missing --month (required when --day is used)")
|
||||
|
||||
if args.month is not None and args.year is None:
|
||||
raise ValueError("Missing --year (requered when --month is used)")
|
||||
|
||||
if args.help_query:
|
||||
print_help_query()
|
||||
return
|
||||
|
||||
if args.version:
|
||||
print_version()
|
||||
return
|
||||
|
||||
if not query_text and not args.recursive and not args.type and not args.directory:
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
# Configuration and Sort restoring
|
||||
user_config = load_config()
|
||||
if args.sort:
|
||||
user_config["last_sort_order"] = args.sort
|
||||
save_config(user_config)
|
||||
elif "last_sort_order" in user_config:
|
||||
args.sort = user_config["last_sort_order"]
|
||||
|
||||
# Build options dictionary
|
||||
main_options = {}
|
||||
if args.recursive is not None:
|
||||
main_options["type"] = "folder"
|
||||
else:
|
||||
if args.limit is not None:
|
||||
main_options["limit"] = args.limit
|
||||
if args.offset is not None:
|
||||
main_options["offset"] = args.offset
|
||||
if args.type:
|
||||
main_options["type"] = args.type
|
||||
|
||||
if args.directory:
|
||||
main_options["directory"] = args.directory
|
||||
if args.year is not None:
|
||||
main_options["year"] = args.year
|
||||
if args.month is not None:
|
||||
main_options["month"] = args.month
|
||||
if args.day is not None:
|
||||
main_options["day"] = args.day
|
||||
if args.sort:
|
||||
main_options["sort"] = args.sort
|
||||
|
||||
other_options = {
|
||||
"exclude": args.exclude,
|
||||
"id": args.id,
|
||||
"konsole": args.konsole,
|
||||
"limit": args.limit if args.limit and args.recursive is not None else 99999999999,
|
||||
"offset": args.offset if args.offset and args.recursive is not None else 0,
|
||||
"recursive": args.recursive,
|
||||
"recursive_indent": args.recursive_indent or "",
|
||||
"recursive_exclude": args.recursive_exclude,
|
||||
"sort": args.sort,
|
||||
"type": args.type if args.recursive is not None else None,
|
||||
"verbose": args.verbose,
|
||||
}
|
||||
|
||||
if other_options["verbose"]:
|
||||
print(f"Query: '{query_text}'")
|
||||
print(f"Main Options: {main_options}")
|
||||
print(f"Other Options: {other_options}")
|
||||
print("-" * 30)
|
||||
|
||||
try:
|
||||
searcher = BagheeraSearcher()
|
||||
files_count = 0
|
||||
|
||||
# Consumir el generador de la librería
|
||||
for item in searcher.search(query_text, main_options, other_options):
|
||||
if other_options["konsole"]:
|
||||
output = f"file:/'{item['path']}'"
|
||||
else:
|
||||
output = item["path"]
|
||||
|
||||
if other_options["id"]:
|
||||
output += f" [ID: {item['id']}]"
|
||||
|
||||
print(output)
|
||||
files_count += 1
|
||||
|
||||
if other_options["verbose"]:
|
||||
if files_count == 0:
|
||||
print("No results found.")
|
||||
else:
|
||||
print(f"Total: {files_count} files found.")
|
||||
|
||||
except FileNotFoundError as e:
|
||||
print(e)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Error executing search: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
try:
|
||||
main()
|
||||
except Exception as e:
|
||||
print(f"Critical error: {e}")
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user