Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PyDMShellCommand bash option #1045

Merged
merged 13 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from 11 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
5 changes: 5 additions & 0 deletions examples/shell_command/example_cmd.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/sh

# This script can be called by a PyDMShellCommand widget,
# allowing it to make use of command chaining and other shell features.
echo "Hello World!" && echo "Hello Again!"
266 changes: 266 additions & 0 deletions examples/shell_command/shell_command_full_shell.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>458</width>
<height>386</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The PyDMShellCommand button with run your command through a full shell if you enable the 'runCmdsInFullShell' option. This allows you to use some additional features such as shell syntax ('|', '&amp;amp;', ';', etc), environment variables ($VAR), glob expansion ('*', '?', etc), and some other features.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="PyDMShellCommand" name="PyDMShellCommand">
<property name="toolTip">
<string/>
</property>
<property name="whatsThis">
<string/>
</property>
<property name="text">
<string>runCmdsInFullShell Option Enabled</string>
nstelter-slac marked this conversation as resolved.
Show resolved Hide resolved
</property>
<property name="alarmSensitiveContent" stdset="0">
<bool>false</bool>
</property>
<property name="alarmSensitiveBorder" stdset="0">
<bool>true</bool>
</property>
<property name="PyDMToolTip" stdset="0">
<string/>
</property>
<property name="channel" stdset="0">
<string/>
</property>
<property name="showConfirmDialog" stdset="0">
<bool>false</bool>
</property>
<property name="confirmMessage" stdset="0">
<string>Are you sure you want to proceed?</string>
</property>
<property name="environmentVariables" stdset="0">
<string/>
</property>
<property name="showIcon" stdset="0">
<bool>true</bool>
</property>
<property name="redirectCommandOutput" stdset="0">
<bool>true</bool>
</property>
<property name="allowMultipleExecutions" stdset="0">
<bool>false</bool>
</property>
<property name="titles" stdset="0">
<stringlist/>
</property>
<property name="commands" stdset="0">
<stringlist>
<string>echo Fist; echo Second</string>
nstelter-slac marked this conversation as resolved.
Show resolved Hide resolved
</stringlist>
</property>
<property name="passwordProtected" stdset="0">
<bool>false</bool>
</property>
<property name="password" stdset="0">
<string/>
</property>
<property name="protectedPassword" stdset="0">
<string/>
</property>
<property name="runCommandsInBash" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>You can run through a Bash shell (without needing to enable any options) by specifying &quot;bash -c&quot; at the start of your command. </string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="PyDMShellCommand" name="PyDMShellCommand_3">
<property name="toolTip">
<string/>
</property>
<property name="whatsThis">
<string/>
</property>
<property name="text">
<string>Using &quot;-c bash&quot;</string>
</property>
<property name="alarmSensitiveContent" stdset="0">
<bool>false</bool>
</property>
<property name="alarmSensitiveBorder" stdset="0">
<bool>true</bool>
</property>
<property name="PyDMToolTip" stdset="0">
<string/>
</property>
<property name="channel" stdset="0">
<string/>
</property>
<property name="showConfirmDialog" stdset="0">
<bool>false</bool>
</property>
<property name="confirmMessage" stdset="0">
<string>Are you sure you want to proceed?</string>
</property>
<property name="environmentVariables" stdset="0">
<string/>
</property>
<property name="showIcon" stdset="0">
<bool>true</bool>
</property>
<property name="redirectCommandOutput" stdset="0">
<bool>true</bool>
</property>
<property name="allowMultipleExecutions" stdset="0">
<bool>false</bool>
</property>
<property name="titles" stdset="0">
<stringlist/>
</property>
<property name="commands" stdset="0">
<stringlist>
<string>bash -c &quot;echo 'Hello One'; echo 'Hello two'&quot;</string>
</stringlist>
</property>
<property name="passwordProtected" stdset="0">
<bool>false</bool>
</property>
<property name="password" stdset="0">
<string/>
</property>
<property name="protectedPassword" stdset="0">
<string/>
</property>
<property name="runCommandsInBash" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>You can also call a shell script. For this button to work correctly, run pydm from dir 'examples/shell_command' so it can find the script file.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="PyDMShellCommand" name="PyDMShellCommand_2">
<property name="toolTip">
<string/>
</property>
<property name="text">
<string>Calling external script</string>
</property>
<property name="alarmSensitiveContent" stdset="0">
<bool>false</bool>
</property>
<property name="alarmSensitiveBorder" stdset="0">
<bool>true</bool>
</property>
<property name="PyDMToolTip" stdset="0">
<string/>
</property>
<property name="channel" stdset="0">
<string/>
</property>
<property name="showConfirmDialog" stdset="0">
<bool>false</bool>
</property>
<property name="confirmMessage" stdset="0">
<string>Are you sure you want to proceed?</string>
</property>
<property name="environmentVariables" stdset="0">
<string/>
</property>
<property name="showIcon" stdset="0">
<bool>true</bool>
</property>
<property name="redirectCommandOutput" stdset="0">
<bool>true</bool>
</property>
<property name="allowMultipleExecutions" stdset="0">
<bool>false</bool>
</property>
<property name="titles" stdset="0">
<stringlist>
<string>Print &quot;Hello, World!&quot; to terminal</string>
<string>Print current working directory to terminal</string>
</stringlist>
</property>
<property name="commands" stdset="0">
<stringlist>
<string>./example_cmd.sh</string>
</stringlist>
</property>
<property name="passwordProtected" stdset="0">
<bool>false</bool>
</property>
<property name="password" stdset="0">
<string/>
</property>
<property name="protectedPassword" stdset="0">
<string/>
</property>
<property name="runCommandsInBash" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>PyDMShellCommand</class>
<extends>QPushButton</extends>
<header>pydm.widgets.shell_command</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
38 changes: 35 additions & 3 deletions pydm/widgets/shell_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ def __init__(
self.process = None
self._show_icon = True
self._redirect_output = False
# shell allows for more options such as command chaining ("cmd1;cmd2", "cmd1 && cmd2", etc ...),
# use of environment variables, glob expansion ('ls *.txt'), etc...
self._run_cmds_in_full_shell = False

self._password_protected = False
self._password = ""
Expand Down Expand Up @@ -106,7 +109,7 @@ def confirmDialog(self) -> bool:
@Property(bool)
def showConfirmDialog(self) -> bool:
"""
Wether or not to display a confirmation dialog.
Whether or not to display a confirmation dialog.

Returns
-------
Expand All @@ -117,7 +120,7 @@ def showConfirmDialog(self) -> bool:
@showConfirmDialog.setter
def showConfirmDialog(self, value: bool) -> None:
"""
Wether or not to display a confirmation dialog.
Whether or not to display a confirmation dialog.

Parameters
----------
Expand All @@ -126,6 +129,29 @@ def showConfirmDialog(self, value: bool) -> None:
if self._show_confirm_dialog != value:
self._show_confirm_dialog = value

@Property(bool)
def runCmdsInFullShell(self) -> bool:
nstelter-slac marked this conversation as resolved.
Show resolved Hide resolved
"""
Whether or not to run cmds with Popen's option for running them through a shell subprocess.

Returns
-------
bool
"""
return self._run_cmds_in_full_shell

@runCmdsInFullShell.setter
def runCmdsInFullShell(self, value: bool) -> None:
"""
Whether or not to run cmds with Popen's option for running them through a shell subprocess.

Parameters
----------
value : bool
"""
if self._run_cmds_in_full_shell != value:
self._run_cmds_in_full_shell = value

@Property(str)
def confirmMessage(self) -> str:
"""
Expand Down Expand Up @@ -526,6 +552,9 @@ def execute_command(self, command: str) -> None:
if (self.process is None or self.process.poll() is not None) or self._allow_multiple:
cmd = os.path.expanduser(os.path.expandvars(command))
args = shlex.split(cmd, posix="win" not in sys.platform)
# when shell enabled, Popen should take the cmds as a single string (not list)
if self._run_cmds_in_full_shell:
args = cmd
try:
logger.debug("Launching process: %s", repr(args))
stdout = subprocess.PIPE
Expand All @@ -537,7 +566,10 @@ def execute_command(self, command: str) -> None:

if self._redirect_output:
stdout = None
self.process = subprocess.Popen(args, stdout=stdout, stderr=subprocess.PIPE, env=env_var)
self.process = subprocess.Popen(
args, stdout=stdout, stderr=subprocess.PIPE, env=env_var, shell=self._run_cmds_in_full_shell
)

except Exception as exc:
self.show_warning_icon()
logger.error("Error in shell command: %s", exc)
Expand Down
Loading