Skip to content

Commit 02bf851

Browse files
committed
Initial commit
0 parents  commit 02bf851

File tree

8 files changed

+593
-0
lines changed

8 files changed

+593
-0
lines changed

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# scripts
2+
Optional Development scripts for deskflow
3+
4+
To use clone this repo into the main deskflow repo.

lib/cmd_utils.py

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# Deskflow -- mouse and keyboard sharing utility
2+
# Copyright (C) 2024 Symless Ltd.
3+
#
4+
# This package is free software; you can redistribute it and/or
5+
# modify it under the terms of the GNU General Public License
6+
# found in the file LICENSE that should have accompanied this file.
7+
#
8+
# This package is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
16+
import subprocess
17+
import sys
18+
import lib.env as env
19+
20+
try:
21+
import colorama # type: ignore
22+
from colorama import Fore # type: ignore
23+
24+
colorama.init()
25+
except ImportError:
26+
27+
class Fore:
28+
RESET = ""
29+
YELLOW = ""
30+
31+
32+
def has_command(command):
33+
platform = sys.platform
34+
if platform == "win32":
35+
cmd = f"where {command}"
36+
else:
37+
cmd = f"which {command}"
38+
try:
39+
subprocess.check_output(cmd, shell=True)
40+
return True
41+
except subprocess.CalledProcessError:
42+
return False
43+
44+
45+
def strip_continuation_sequences(command, strip_newlines=True):
46+
"""
47+
Remove the continuation sequences (\\) from a command.
48+
49+
To spread strings over multiple lines in YAML files, like in bash, a backslash is used at
50+
the end of each line as continuation character.
51+
"""
52+
53+
if isinstance(command, list):
54+
raise ValueError("List commands are not supported")
55+
56+
cmd_continuation = " \\"
57+
command = command.replace(cmd_continuation, "")
58+
59+
# Some versions of pyyaml will remove the newlines already, so always stripping
60+
# makes the output more consistent.
61+
if strip_newlines:
62+
command = command.replace("\n", " ")
63+
64+
return command
65+
66+
67+
def run(
68+
command,
69+
check=True, # true by default to fail fast
70+
shell=False, # false by default for security
71+
get_output=False,
72+
print_cmd=False, # false by default for security
73+
):
74+
"""
75+
Convenience wrapper around `subprocess.run` to:
76+
- print the command before running it (if `print_cmd` is True)
77+
78+
This differs to `subprocess.run` in that by default it:
79+
- checks the return code by default
80+
- prints list commands as a readable string on failure
81+
82+
This is the same as `subprocess.run` in that it:
83+
- does not use shell by default for security (shell is less secure)
84+
85+
Args:
86+
command (str or list): The command to run.
87+
check (bool): Raise an exception if the command fails.
88+
shell (bool): Run the command in a shell (false by default for security)
89+
get_output (bool): Return the output of the command.
90+
print_cmd (bool): Print the command before running it (false by default for security)
91+
"""
92+
93+
is_list_cmd = isinstance(command, list)
94+
95+
# create string version of list command, only for debugging purposes
96+
command_str = command
97+
if is_list_cmd:
98+
command_str = " ".join(command)
99+
100+
if print_cmd:
101+
print(f"Running: {command_str}")
102+
else:
103+
print("Running command...")
104+
command_str = "***"
105+
106+
# TODO: You can definitely use a list command with shell=True on Windows,
107+
# but can you use a string command with shell=False on Windows?
108+
#
109+
# The `subprocess.run` function has a little gotcha:
110+
# - a string command must be used when `shell=True`
111+
# - a list command must be used when shell isn't or `shell=False`
112+
# however, it allows you to pass a string command when shell isn't used or `shell=False`
113+
# then fails with a vague error message. same problem with list commands and `shell=True`
114+
if not env.is_windows() and is_list_cmd and shell:
115+
raise ValueError("List commands cannot be used when shell=True on Unix systems")
116+
elif not is_list_cmd and not shell:
117+
raise ValueError("String commands cannot be used when shell=False or not set")
118+
119+
# Flush the output to ensure the command is printed before the output of the command,
120+
# which seems to happen in the GitHub runner logs.
121+
sys.stdout.flush()
122+
sys.stderr.flush()
123+
124+
try:
125+
if get_output:
126+
result = subprocess.run(
127+
command,
128+
shell=shell,
129+
check=check,
130+
stdout=subprocess.PIPE,
131+
stderr=subprocess.PIPE,
132+
text=True,
133+
)
134+
else:
135+
result = subprocess.run(command, check=check, shell=shell)
136+
137+
except subprocess.CalledProcessError as e:
138+
# Take control of how failed commands are printed:
139+
# - if `print_cmd` is false, it will print `***` instead of the command
140+
# - if the command was a list, the command is printed as a readable string
141+
raise RuntimeError(
142+
f"Command exited with code {e.returncode}: {command_str}"
143+
) from None
144+
except Exception:
145+
# Take control of how failed commands are printed:
146+
# - if `print_cmd` is false, it will print `***` instead of the command
147+
# - if the command was a list, the command is printed as a readable string
148+
raise RuntimeError(f"Command failed: {command_str}")
149+
150+
if result.returncode != 0:
151+
print(
152+
f"{Fore.YELLOW}Command exited with code {result.returncode}:{Fore.RESET} {command_str}",
153+
file=sys.stderr,
154+
)
155+
156+
return result

lib/colors.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Deskflow -- mouse and keyboard sharing utility
2+
# Copyright (C) 2024 Symless Ltd.
3+
#
4+
# This package is free software; you can redistribute it and/or
5+
# modify it under the terms of the GNU General Public License
6+
# found in the file LICENSE that should have accompanied this file.
7+
#
8+
# This package is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
16+
import colorama # type: ignore
17+
from colorama import Fore # type: ignore
18+
19+
colorama.init()
20+
21+
SUCCESS_TEXT = f"{Fore.LIGHTGREEN_EX}Success:{Fore.RESET}"
22+
ERROR_TEXT = f"{Fore.LIGHTRED_EX}Error:{Fore.RESET}"
23+
WARNING_TEXT = f"{Fore.LIGHTYELLOW_EX}Warning:{Fore.RESET}"
24+
HINT_TEXT = f"{Fore.LIGHTBLUE_EX}Hint:{Fore.RESET}"

0 commit comments

Comments
 (0)