1
0
Fork 0
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

180 lines
4.6 KiB

#!/usr/bin/env python3
"""Bangs in DDG style without using DDG but just Rofi.
Load a JSON config file to have the bangs available. Rofi should return, using
its dmenu option, the handle followed by your query, e.g. "w burger" if you
want a Wikipedia article for "burger", or at least a much needed
disambiguation…
Config file example:
{
"bangs": [
{
"handle": "w",
"name": "Wikipedia",
"url": "https://en.wikipedia.org/wiki/Special:Search?search={}&go=Go"
}
]
}
Optionally, a bang object can contain a "raw" key with a boolean value: if
true, arguments are passed without being URL-encoded.
By default this scripts attempts to load your config file from
`~/.config/bangs.json`, but you can specify the BANGS_CONFIG_PATH
environment variable or pass the path through the -c command-line option.
"""
import argparse
import json
import os
import subprocess
import urllib.parse
import webbrowser
def load_config(config_path=None):
if config_path is None:
config_path = (
os.environ.get("BANGS_CONFIG_PATH")
or os.path.expanduser("~/.config/bangs.json")
)
try:
with open(config_path, "rt") as bangs_file:
return json.load(bangs_file)
except OSError:
return None
def list_bangs(config):
for item in config["bangs"]:
name = item["name"]
handle = item["handle"]
print(f"- {handle}: {name}")
def run_rofi(config, input_text="", title="bang", no_lines=False):
rofi_path = config.get("rofi_path", "rofi")
args = ["-dmenu", "-p", title]
if no_lines:
args += ["-l", "0"]
completed_process = subprocess.run(
[rofi_path] + args,
text=True,
capture_output=True,
input=input_text,
)
output = completed_process.stdout
if not output:
exit("Empty Rofi output.")
return output
def open_bang(config, handle, query):
try:
bang = next(
bang for bang in config["bangs"]
if handle == bang["handle"]
)
except StopIteration:
print("Unknown handle.")
return
query = query.strip()
if not bang.get("raw", False):
query = urllib.parse.quote(query)
url = bang["url"].format(query)
webbrowser.open_new_tab(url)
def main():
ap = argparse.ArgumentParser()
ap.add_argument(
"-c",
"--config",
help="path to JSON config file"
)
ap.add_argument(
"-l",
"--list",
action="store_true",
help="show available bangs"
)
ap.add_argument(
"-b",
"--bang",
nargs="+",
help="launch with this bang already set"
)
ap.add_argument(
"-f",
"--queries-file",
help="file with one bang argument per line"
)
ap.add_argument(
"-d",
"--default",
help="handle to use if the first word is not a known handle"
)
args = ap.parse_args()
config = load_config()
if config is None:
exit("Can't load config file.")
if args.list:
list_bangs(config)
return
queries = []
if listfile := args.queries_file:
try:
with open(listfile, "rt") as file:
queries = [line.rstrip() for line in file.readlines()]
except OSError:
exit("Can't load queries file.")
handles = [
bang["handle"]
for bang in sorted(config["bangs"], key=lambda bang: bang["handle"])
]
# If a bang is specified on the command line, use it,
# optionally with its args.
if bang_args := args.bang:
handle = bang_args[0]
if bang_args[1:]:
queries.append(" ".join(bang_args[1:]))
# Else show a Rofi with the list of available bangs.
else:
process_input = "\n".join(handles) + "\n"
output = run_rofi(config, input_text=process_input)
parts = output.split(maxsplit=1)
if len(parts) == 0:
exit("Bad Rofi output.")
handle = parts[0]
query = None
if handle not in handles and (default_handle := args.default):
handle = default_handle
if len(parts) == 1:
query = parts[0]
else:
query = output
elif len(parts) == 2:
query = parts[1]
if query is not None:
queries.append(query)
# If no queries were obtained during options parsing,
# show Rofi now to get a single query.
if not queries:
queries.append(run_rofi(config, title=handle, no_lines=True))
for query in queries:
open_bang(config, handle, query)
if __name__ == "__main__":
main()