Dup Goto 📝

Cman

PT2/lang/python/clipboard 02-23 12:35:33
To Pop
157 lines, 580 words, 5040 chars Monday 2026-02-23 12:35:33

Get and Put

Trivial one line convenience wrappers

#!/usr/bin/env bash
cman get "$@"
#!/usr/bin/env bash
cman put "$@"

Cman

#!/usr/bin/env python3

# pip install pyperclip

prompt = r"""
Python wrapper for aclient and cclient, called get and put.
Use main() and argparse.

get <name> <name> <name>
does the same as cat name, unless name starts with . or ,.
If name = ., then paste from the clipboard.
If name = .clipname, then take the output of cclient get clipname
If name = ,clipname, then take the output of aclient get clipname.
If any result other than the last doesn't end with a \n, then append a \n.
Then concatenate the results and send to output.
Output is stdout by default, unless -o is also specified. If -p is also
specified, then print anyway (as well as outputting). The argument to -o is
similar: . is the clipboard, .clipname pipes output into cclient put clipname,
and ,clipname pipes output to aclient put clipname.

put is the reverse, but can reference multiple clips. By default put takes
input from stdin. Inputs are specified by the -i argument, multiple are
possible. Again . .clipname and ,clipname get from cclient get and aclient get
and the clipboard. - gets from stdin, and if specified multiple times, stdin is
cached and repeated. Other arguments are output names, following the same .
.clipname ,clipname convention as before.
"""

import argparse
import subprocess
import sys
import pyperclip
import os

def run_cmd(args, input_data=None):
    """Helper to run external client commands."""
    try:
        process = subprocess.Popen(
            args, 
            stdin=subprocess.PIPE, 
            stdout=subprocess.PIPE, 
            stderr=subprocess.PIPE, 
            text=True
        )
        stdout, stderr = process.communicate(input=input_data)
        if process.returncode != 0:
            print(f"Error running {' '.join(args)}: {stderr}", file=sys.stderr)
            return ""
        return stdout
    except FileNotFoundError:
        print(f"Error: Command '{args[0]}' not found.", file=sys.stderr)
        return ""

def get_content(name, stdin_cache=None):
    """Fetches content based on the naming convention."""
    if name == ".":
        return pyperclip.paste()
    elif name.startswith("."):
        return run_cmd(["cclient", "get", name[1:]])
    elif name.startswith(","):
        return run_cmd(["aclient", "get", name[1:]])
    elif name == "-":
        return stdin_cache if stdin_cache is not None else sys.stdin.read()
    else:
        # Standard file 'cat' behavior
        if os.path.exists(name):
            with open(name, 'r') as f:
                return f.read()
        return ""

def put_content(name, content):
    """Sends content to the specified target."""
    if name == ".":
        pyperclip.copy(content)
    elif name.startswith("."):
        run_cmd(["cclient", "put", name[1:]], input_data=content)
    elif name.startswith(","):
        run_cmd(["aclient", "put", name[1:]], input_data=content)
    else:
        with open(name, 'w') as f:
            f.write(content)

def handle_get(args):
    results = []
    for i, name in enumerate(args.names):
        content = get_content(name)

        # Append newline if not the last item and missing a trailing newline
        if i < len(args.names) - 1 and content and not content.endswith('\n'):
            content += '\n'
        results.append(content)

    final_output = "".join(results)

    # Handle output logic
    if args.o:
        put_content(args.o, final_output)

    if not args.o or args.p:
        sys.stdout.write(final_output)

def handle_put(args):
    stdin_cache = None
    input_contents = []

    # If no inputs specified, default to stdin
    inputs = args.i if args.i else ["-"]

    for item in inputs:
        if item == "-" and stdin_cache is None:
            stdin_cache = sys.stdin.read()
        input_contents.append(get_content(item, stdin_cache))

    combined_input = "".join(input_contents)

    for target in args.targets:
        put_content(target, combined_input)

def main():
    parser = argparse.ArgumentParser(description="Wrapper for aclient and cclient")
    subparsers = parser.add_subparsers(dest="command", required=True)

    # GET parser
    get_parser = subparsers.add_parser("get")
    get_parser.add_argument("names", nargs="+", help="Names of clips or files")
    get_parser.add_argument("-o", help="Output target (., .clip, ,clip, or file)")
    get_parser.add_argument("-p", action="store_true", help="Print to stdout even if -o is used")

    # PUT parser
    put_parser = subparsers.add_parser("put")
    put_parser.add_argument("-i", action="append", help="Input sources (can be multiple)")
    put_parser.add_argument("targets", nargs="+", help="Output targets (., .clip, ,clip, or file)")

    args = parser.parse_args()

    if args.command == "get":
        handle_get(args)
    elif args.command == "put":
        handle_put(args)

if __name__ == "__main__":
    main()