talonhub / community

Voice command set for Talon, community-supported.
MIT License
637 stars 782 forks source link

replacements for system_command and system_command_nb #945

Open lunixbochs opened 2 years ago

lunixbochs commented 2 years ago

Right now system_command and system_command_nb use os.system and subprocess.Popen(shell=True) respectively.

These both can have some undesirable behavior:

I propose improving these in a couple of ways:

Please test this on each platform before merging.

from talon import Module, system
import shlex
import subprocess

mod = Module()
@mod.action_class
class Actions:
    def system_command(cmd: str) -> None:
        """Run a command and wait for it to finish"""
        args = shlex.split(cmd)
        subprocess.run(args, check=True)

    def system_command_nb(cmd: str) -> None:
        """Run a command without waiting for it"""
        args = shlex.split(cmd)
        cmd, args = args[0], args[1:]
        system.launch(path=cmd, args=args)
splondike commented 1 year ago

Had a go at this on the branch mentioned above. The implementation is the same as lunixbochs' with an attempt to allow ~ to expand to the user's home directory. There are some issues with it though relative to the current implementation:

The first issue I think requires a different parser to shlex.split. This would probably also let us fix the second (more minor) issue.

The following were the set of test cases (with per-platform variants) I was using to verify the behaviour:

auscompgeek commented 1 year ago

FWIW, there only two files that use system_command, which could well use a rewrite for the affected commands anyway:

splondike commented 1 year ago

OK, I ended up writing my own lexer/parser. I've heard of enough people using this action in their scripts that it seems worth implementing. It was also fun to write it ;).

lunixbochs commented 1 year ago

Ok, let me think about this and review it.

splondike commented 1 year ago

Listing out some options for how to proceed with this issue:

  1. Change the action to def system_command(cmd: str, arg1: str="", arg2: str="", arg3: str="", arg4: str=""):. You could then call it from .talon like user.system_command("echo") or user.system_command("echo", "foo") etc. up to four arguments. We could deprecate the existing system by checking if there was a space in the first argument and using the existing os.system path if so.
  2. Don't change the implementation. lunixbochs has identified some theoretical issues with it, but I haven't heard of it being too problematic so far.
  3. lunixbochs makes a 1st party implementation in Talon.
  4. Use my implementation in #1140. Hopefully it's pretty much a set and forget situation; I doubt we'd want to change the behaviour again (unless there are bugs). I am doing some model based testing against shlex.split, so hopefully there aren't many issues.
  5. Deprecate then remove this API, as identified in this comment it's only used in a couple of places in this repo. I think it is used by a fair few knausj users for custom commands though, so we'd be making them all do some work.
  6. Use shlex.split for Unix and a .dll call on Windows.
  7. Try and find a third party pure Python implementation with compatible licensing and copy paste it in to knausj. This doesn't seem like an improvement on #1140 though.

I've sorted them in my order of preference. The fixed args count thing seems kind of neat, and if people want more they probably want to drop in to Python anyway.

rntz commented 1 year ago

One thing we could try is to to replace all of knausj's uses with some other, safer API/implementation, and keep but deprecate the existing actions.