linux-command 0.2.2__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {linux-command-0.2.2/linux_command.egg-info → linux_command-0.3.0}/PKG-INFO +43 -3
- linux-command-0.2.2/PKG-INFO → linux_command-0.3.0/README.md +32 -15
- {linux-command-0.2.2 → linux_command-0.3.0}/linux_command/linux_command.py +235 -93
- linux-command-0.2.2/README.md → linux_command-0.3.0/linux_command.egg-info/PKG-INFO +55 -1
- {linux-command-0.2.2 → linux_command-0.3.0}/linux_command.egg-info/SOURCES.txt +2 -1
- linux_command-0.3.0/tests/test_linux_command.py +308 -0
- {linux-command-0.2.2 → linux_command-0.3.0}/LICENSE +0 -0
- {linux-command-0.2.2 → linux_command-0.3.0}/linux_command/__init__.py +0 -0
- {linux-command-0.2.2 → linux_command-0.3.0}/linux_command.egg-info/dependency_links.txt +0 -0
- {linux-command-0.2.2 → linux_command-0.3.0}/linux_command.egg-info/entry_points.txt +0 -0
- {linux-command-0.2.2 → linux_command-0.3.0}/linux_command.egg-info/top_level.txt +0 -0
- {linux-command-0.2.2 → linux_command-0.3.0}/setup.cfg +0 -0
- {linux-command-0.2.2 → linux_command-0.3.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: linux-command
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A command line tool to perform custom tasks.
|
|
5
5
|
Home-page: https://github.com/MouxiaoHuang/linux-command
|
|
6
6
|
Author: Mouxiao Huang
|
|
@@ -11,6 +11,15 @@ Classifier: Operating System :: OS Independent
|
|
|
11
11
|
Requires-Python: >=3.6
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
+
Dynamic: author
|
|
15
|
+
Dynamic: author-email
|
|
16
|
+
Dynamic: classifier
|
|
17
|
+
Dynamic: description
|
|
18
|
+
Dynamic: description-content-type
|
|
19
|
+
Dynamic: home-page
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
Dynamic: requires-python
|
|
22
|
+
Dynamic: summary
|
|
14
23
|
|
|
15
24
|
|
|
16
25
|
# linux-command
|
|
@@ -25,6 +34,16 @@ To install the package, run the following command:
|
|
|
25
34
|
pip install linux-command
|
|
26
35
|
```
|
|
27
36
|
|
|
37
|
+
## Install From Source (Development)
|
|
38
|
+
|
|
39
|
+
If you want to develop or modify the tool locally:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
git clone https://github.com/MouxiaoHuang/linux-command.git
|
|
43
|
+
cd linux-command
|
|
44
|
+
pip install -e .
|
|
45
|
+
```
|
|
46
|
+
|
|
28
47
|
## Usage
|
|
29
48
|
|
|
30
49
|
Once installed, you can access all commands using `cmd` followed by the specific command name. You can use `cmd -h` or `cmd --help` to see the supported commands. Below is a list of supported commands along with examples for each.
|
|
@@ -155,6 +174,12 @@ cmd ps-grep python
|
|
|
155
174
|
cmd kill process_name
|
|
156
175
|
```
|
|
157
176
|
|
|
177
|
+
To force kill:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
cmd kill --force process_name
|
|
181
|
+
```
|
|
182
|
+
|
|
158
183
|
---
|
|
159
184
|
|
|
160
185
|
### 21. `df` - Show disk usage in human-readable format
|
|
@@ -163,7 +188,7 @@ cmd kill process_name
|
|
|
163
188
|
cmd df
|
|
164
189
|
```
|
|
165
190
|
|
|
166
|
-
### 22. `du [path]` - Show disk usage for a specific file or directory
|
|
191
|
+
### 22. `du [path]` - Show disk usage for a specific file or directory (default: current directory)
|
|
167
192
|
|
|
168
193
|
```bash
|
|
169
194
|
cmd du /path/to/directory
|
|
@@ -256,6 +281,21 @@ This command will create a `.zip` file at `/path/to/output.zip` that contains ev
|
|
|
256
281
|
|
|
257
282
|
---
|
|
258
283
|
|
|
284
|
+
## Aliases
|
|
285
|
+
|
|
286
|
+
These commands are aliases for the same behavior:
|
|
287
|
+
|
|
288
|
+
- `lsf` = `ls-file`
|
|
289
|
+
- `lsd` = `ls-dir`
|
|
290
|
+
- `ls-bs` = `ls-block-size`
|
|
291
|
+
- `disk` = `du`
|
|
292
|
+
- `tar` = `tar-compress`
|
|
293
|
+
- `untar` = `tar-extract`
|
|
294
|
+
- `zip` = `zip-compress`
|
|
295
|
+
- `zip-all` = `zip-compress`
|
|
296
|
+
- `unzip` = `unzip-all`
|
|
297
|
+
- `convert-vid` = `convert-video`
|
|
298
|
+
|
|
259
299
|
## Contributing
|
|
260
300
|
|
|
261
301
|
We welcome contributions from the community! If you'd like to help improve `linux-command`, feel free to report issues or submit pull requests.
|
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: linux-command
|
|
3
|
-
Version: 0.2.2
|
|
4
|
-
Summary: A command line tool to perform custom tasks.
|
|
5
|
-
Home-page: https://github.com/MouxiaoHuang/linux-command
|
|
6
|
-
Author: Mouxiao Huang
|
|
7
|
-
Author-email: huangmouxiao@gmail.com
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Operating System :: OS Independent
|
|
11
|
-
Requires-Python: >=3.6
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
License-File: LICENSE
|
|
14
|
-
|
|
15
1
|
|
|
16
2
|
# linux-command
|
|
17
3
|
|
|
@@ -25,6 +11,16 @@ To install the package, run the following command:
|
|
|
25
11
|
pip install linux-command
|
|
26
12
|
```
|
|
27
13
|
|
|
14
|
+
## Install From Source (Development)
|
|
15
|
+
|
|
16
|
+
If you want to develop or modify the tool locally:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
git clone https://github.com/MouxiaoHuang/linux-command.git
|
|
20
|
+
cd linux-command
|
|
21
|
+
pip install -e .
|
|
22
|
+
```
|
|
23
|
+
|
|
28
24
|
## Usage
|
|
29
25
|
|
|
30
26
|
Once installed, you can access all commands using `cmd` followed by the specific command name. You can use `cmd -h` or `cmd --help` to see the supported commands. Below is a list of supported commands along with examples for each.
|
|
@@ -155,6 +151,12 @@ cmd ps-grep python
|
|
|
155
151
|
cmd kill process_name
|
|
156
152
|
```
|
|
157
153
|
|
|
154
|
+
To force kill:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
cmd kill --force process_name
|
|
158
|
+
```
|
|
159
|
+
|
|
158
160
|
---
|
|
159
161
|
|
|
160
162
|
### 21. `df` - Show disk usage in human-readable format
|
|
@@ -163,7 +165,7 @@ cmd kill process_name
|
|
|
163
165
|
cmd df
|
|
164
166
|
```
|
|
165
167
|
|
|
166
|
-
### 22. `du [path]` - Show disk usage for a specific file or directory
|
|
168
|
+
### 22. `du [path]` - Show disk usage for a specific file or directory (default: current directory)
|
|
167
169
|
|
|
168
170
|
```bash
|
|
169
171
|
cmd du /path/to/directory
|
|
@@ -256,6 +258,21 @@ This command will create a `.zip` file at `/path/to/output.zip` that contains ev
|
|
|
256
258
|
|
|
257
259
|
---
|
|
258
260
|
|
|
261
|
+
## Aliases
|
|
262
|
+
|
|
263
|
+
These commands are aliases for the same behavior:
|
|
264
|
+
|
|
265
|
+
- `lsf` = `ls-file`
|
|
266
|
+
- `lsd` = `ls-dir`
|
|
267
|
+
- `ls-bs` = `ls-block-size`
|
|
268
|
+
- `disk` = `du`
|
|
269
|
+
- `tar` = `tar-compress`
|
|
270
|
+
- `untar` = `tar-extract`
|
|
271
|
+
- `zip` = `zip-compress`
|
|
272
|
+
- `zip-all` = `zip-compress`
|
|
273
|
+
- `unzip` = `unzip-all`
|
|
274
|
+
- `convert-vid` = `convert-video`
|
|
275
|
+
|
|
259
276
|
## Contributing
|
|
260
277
|
|
|
261
278
|
We welcome contributions from the community! If you'd like to help improve `linux-command`, feel free to report issues or submit pull requests.
|
|
@@ -26,27 +26,35 @@ SOFTWARE.
|
|
|
26
26
|
For more information, visit the project page: https://github.com/MouxiaoHuang/linux-command
|
|
27
27
|
"""
|
|
28
28
|
import argparse
|
|
29
|
-
import os
|
|
30
29
|
import glob
|
|
30
|
+
import os
|
|
31
|
+
import shutil
|
|
32
|
+
import signal
|
|
33
|
+
import subprocess
|
|
34
|
+
from fnmatch import fnmatch
|
|
31
35
|
|
|
32
36
|
|
|
33
37
|
# Define the version
|
|
34
|
-
VERSION = "0.
|
|
38
|
+
VERSION = "0.3.0"
|
|
35
39
|
PROJECT_URL = "https://github.com/MouxiaoHuang/linux-command"
|
|
36
40
|
|
|
37
41
|
|
|
38
42
|
# Command descriptions
|
|
39
43
|
commands = {
|
|
40
44
|
'ls': 'List contents.',
|
|
45
|
+
'ls-all': 'List all files, including hidden ones.',
|
|
41
46
|
'lsf': 'Count all files or filter by a specified pattern, extension or keyword. Same as `ls-file`.',
|
|
42
47
|
'ls-file': 'Count all files or filter by a specified pattern, extension or keyword. Same as `lsf`.',
|
|
43
48
|
'lsd': 'Count all directories. Same as `ls-dir`.',
|
|
44
49
|
'ls-dir': 'Count all directories. Same as `lsd`.',
|
|
45
50
|
'ls-reverse': 'List files and directories in reverse order.',
|
|
46
51
|
'ls-time': 'List sorted by modification time, newest first.',
|
|
52
|
+
'ls-human': 'List in human-readable format (for file sizes)',
|
|
53
|
+
'ls-long': 'Long format listing',
|
|
54
|
+
'ls-size': 'Sort files by size',
|
|
55
|
+
'ls-recursive': 'Recursively list files in directories and subdirectories.',
|
|
47
56
|
'ls-recursive-size': 'List all files and directories recursively, with sizes in human-readable format',
|
|
48
57
|
'ls-bs': 'Display the size of each file in specified block size (e.g., K, M, G).',
|
|
49
|
-
'ls-size': 'Display the size of each file in specified block size (e.g., K, M, G).',
|
|
50
58
|
'ls-block-size': 'Display the size of each file in specified block size (e.g., K, M, G).',
|
|
51
59
|
'ps': 'Basic process list.',
|
|
52
60
|
'ps-all': 'Show all processes.',
|
|
@@ -62,12 +70,13 @@ commands = {
|
|
|
62
70
|
'rm': 'Remove file, directory, or multiple files by patterns (e.g., *.txt)',
|
|
63
71
|
'grep': 'Search for a pattern in files or output.',
|
|
64
72
|
'tar': 'Pack into .tar or .tar.gz file.',
|
|
65
|
-
'tar-
|
|
73
|
+
'tar-compress': 'Pack into .tar or .tar.gz file.',
|
|
66
74
|
'untar': 'Unpack .tar or .tar.gz file, or batch process in a directory.',
|
|
67
75
|
'tar-extract': 'Unpack .tar or .tar.gz file, or batch process in a directory.',
|
|
68
76
|
'tar-list': 'List all contents in a tar file.',
|
|
69
77
|
'tar-add': 'Add a file to a tar file.',
|
|
70
78
|
'zip': 'Pack a folder to a .zip file.',
|
|
79
|
+
'zip-compress': 'Pack a folder to a .zip file.',
|
|
71
80
|
'zip-all': 'Pack a folder to a .zip file.',
|
|
72
81
|
'unzip': 'Unpack all .zip files in a directory to another.',
|
|
73
82
|
'unzip-all': 'Unpack all .zip files in a directory to another.',
|
|
@@ -90,6 +99,130 @@ def confirm_action(message):
|
|
|
90
99
|
return confirmation in ['yes', 'y']
|
|
91
100
|
|
|
92
101
|
|
|
102
|
+
def run_cmd(cmd_args):
|
|
103
|
+
"""Run a command safely without invoking a shell."""
|
|
104
|
+
try:
|
|
105
|
+
subprocess.run(cmd_args, check=False)
|
|
106
|
+
except FileNotFoundError:
|
|
107
|
+
print(f"Command not found: {cmd_args[0]}")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def parse_excludes(args):
|
|
111
|
+
excludes = []
|
|
112
|
+
i = 0
|
|
113
|
+
while i < len(args):
|
|
114
|
+
arg = args[i]
|
|
115
|
+
if arg == '--exclude':
|
|
116
|
+
if i + 1 < len(args):
|
|
117
|
+
excludes.append(args[i + 1])
|
|
118
|
+
i += 2
|
|
119
|
+
else:
|
|
120
|
+
break
|
|
121
|
+
elif arg.startswith('--exclude='):
|
|
122
|
+
excludes.append(arg.split('=', 1)[1])
|
|
123
|
+
i += 1
|
|
124
|
+
else:
|
|
125
|
+
excludes.append(arg)
|
|
126
|
+
i += 1
|
|
127
|
+
return excludes
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def expand_globs(args):
|
|
131
|
+
expanded = []
|
|
132
|
+
for arg in args:
|
|
133
|
+
if any(ch in arg for ch in ['*', '?', '[']):
|
|
134
|
+
matches = glob.glob(arg)
|
|
135
|
+
if matches:
|
|
136
|
+
expanded.extend(matches)
|
|
137
|
+
else:
|
|
138
|
+
expanded.append(arg)
|
|
139
|
+
else:
|
|
140
|
+
expanded.append(arg)
|
|
141
|
+
return expanded
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def list_files_in_dir(path):
|
|
145
|
+
try:
|
|
146
|
+
return os.listdir(path)
|
|
147
|
+
except FileNotFoundError:
|
|
148
|
+
print(f"Path not found: {path}")
|
|
149
|
+
return []
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def count_dirs(root):
|
|
153
|
+
total = 0
|
|
154
|
+
for _, dirs, _ in os.walk(root):
|
|
155
|
+
total += len(dirs)
|
|
156
|
+
return total
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def count_files(root, pattern=None):
|
|
160
|
+
files = [f for f in list_files_in_dir(root) if os.path.isfile(os.path.join(root, f))]
|
|
161
|
+
if pattern is None:
|
|
162
|
+
return len(files)
|
|
163
|
+
if any(ch in pattern for ch in ['*', '?', '[']):
|
|
164
|
+
return sum(1 for f in files if fnmatch(f, pattern))
|
|
165
|
+
return sum(1 for f in files if pattern in f)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def filter_ps(keyword):
|
|
169
|
+
try:
|
|
170
|
+
result = subprocess.run(['ps', 'aux'], check=False, capture_output=True, text=True)
|
|
171
|
+
except FileNotFoundError:
|
|
172
|
+
print("Command not found: ps")
|
|
173
|
+
return []
|
|
174
|
+
lines = result.stdout.splitlines()
|
|
175
|
+
matches = []
|
|
176
|
+
for line in lines:
|
|
177
|
+
if keyword in line and 'ps aux' not in line:
|
|
178
|
+
matches.append(line)
|
|
179
|
+
return matches
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def kill_processes(target, force=False):
|
|
183
|
+
sig = signal.SIGKILL if force else signal.SIGTERM
|
|
184
|
+
if target.isdigit():
|
|
185
|
+
try:
|
|
186
|
+
os.kill(int(target), sig)
|
|
187
|
+
print(f"Killed PID {target} with signal {sig}.")
|
|
188
|
+
except ProcessLookupError:
|
|
189
|
+
print(f"No such process: {target}")
|
|
190
|
+
except PermissionError:
|
|
191
|
+
print(f"Permission denied to kill PID {target}")
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
matches = filter_ps(target)
|
|
195
|
+
if not matches:
|
|
196
|
+
print(f"No processes found matching: {target}")
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
pids = []
|
|
200
|
+
for line in matches:
|
|
201
|
+
parts = line.split()
|
|
202
|
+
if len(parts) >= 2 and parts[1].isdigit():
|
|
203
|
+
pids.append(parts[1])
|
|
204
|
+
|
|
205
|
+
if not pids:
|
|
206
|
+
print(f"No processes found matching: {target}")
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
print("Matched processes:")
|
|
210
|
+
for line in matches:
|
|
211
|
+
print(line)
|
|
212
|
+
|
|
213
|
+
if confirm_action(f"Kill {len(pids)} process(es) matching '{target}'?"):
|
|
214
|
+
for pid in pids:
|
|
215
|
+
try:
|
|
216
|
+
os.kill(int(pid), sig)
|
|
217
|
+
except ProcessLookupError:
|
|
218
|
+
print(f"No such process: {pid}")
|
|
219
|
+
except PermissionError:
|
|
220
|
+
print(f"Permission denied to kill PID {pid}")
|
|
221
|
+
print("Kill completed.")
|
|
222
|
+
else:
|
|
223
|
+
print("Operation canceled.")
|
|
224
|
+
|
|
225
|
+
|
|
93
226
|
def main():
|
|
94
227
|
# Set up argparse
|
|
95
228
|
parser = argparse.ArgumentParser(
|
|
@@ -102,11 +235,17 @@ def main():
|
|
|
102
235
|
parser.add_argument('-V', '--version', action='store_true', help='Show program\'s version number and exit')
|
|
103
236
|
|
|
104
237
|
# Main command and subcommands
|
|
105
|
-
parser.
|
|
106
|
-
|
|
238
|
+
subparsers = parser.add_subparsers(dest='command')
|
|
239
|
+
for command, description in commands.items():
|
|
240
|
+
subparser = subparsers.add_parser(command, help=description, add_help=True)
|
|
241
|
+
subparser.add_argument('extra', nargs=argparse.REMAINDER, help='Additional arguments for the command')
|
|
107
242
|
|
|
108
243
|
# Parse the arguments
|
|
109
|
-
args = parser.
|
|
244
|
+
args, unknown = parser.parse_known_args()
|
|
245
|
+
if unknown:
|
|
246
|
+
if not hasattr(args, 'extra') or args.extra is None:
|
|
247
|
+
args.extra = []
|
|
248
|
+
args.extra.extend(unknown)
|
|
110
249
|
|
|
111
250
|
if args.help:
|
|
112
251
|
custom_help()
|
|
@@ -115,116 +254,107 @@ def main():
|
|
|
115
254
|
if args.version:
|
|
116
255
|
print(f'linux-command {VERSION}')
|
|
117
256
|
return
|
|
257
|
+
|
|
258
|
+
if args.command is None:
|
|
259
|
+
custom_help()
|
|
260
|
+
return
|
|
261
|
+
|
|
262
|
+
ls_simple = {
|
|
263
|
+
'ls': [],
|
|
264
|
+
'ls-all': ['-a'],
|
|
265
|
+
'ls-reverse': ['-r'],
|
|
266
|
+
'ls-time': ['-lt'],
|
|
267
|
+
'ls-long': ['-l'],
|
|
268
|
+
'ls-human': ['-lh'],
|
|
269
|
+
'ls-recursive': ['-R'],
|
|
270
|
+
'ls-recursive-size': ['-lRh'],
|
|
271
|
+
'ls-size': ['-lS'],
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
ps_simple = {
|
|
275
|
+
'ps': [],
|
|
276
|
+
'ps-all': ['-A'],
|
|
277
|
+
'ps-aux': ['aux'],
|
|
278
|
+
'ps-sort-memory': ['aux', '--sort=-%mem'],
|
|
279
|
+
'ps-sort-cpu': ['aux', '--sort=-%cpu'],
|
|
280
|
+
}
|
|
118
281
|
|
|
119
282
|
# `ls` commands
|
|
120
|
-
if args.command
|
|
121
|
-
|
|
122
|
-
# List current directory
|
|
123
|
-
os.system('ls')
|
|
124
|
-
else:
|
|
125
|
-
options = ' '.join(args.extra)
|
|
126
|
-
os.system(f'ls {options}')
|
|
127
|
-
|
|
128
|
-
# Advanced `ls` commands
|
|
283
|
+
if args.command in ls_simple:
|
|
284
|
+
run_cmd(['ls', *ls_simple[args.command], *expand_globs(args.extra)])
|
|
129
285
|
|
|
286
|
+
# Advanced `ls` commands
|
|
130
287
|
elif args.command == 'ls-dir' or args.command == 'lsd':
|
|
131
288
|
# Count the number of directories
|
|
132
|
-
|
|
289
|
+
print(count_dirs('.'))
|
|
133
290
|
|
|
134
291
|
elif args.command == 'ls-file' or args.command == 'lsf':
|
|
135
292
|
if len(args.extra) == 0:
|
|
136
293
|
# Count the number of all files
|
|
137
|
-
|
|
294
|
+
print(count_files('.'))
|
|
138
295
|
else:
|
|
139
296
|
# Count files of a specific type based on provided extension or pattern
|
|
140
297
|
pattern = args.extra[0] # First extra argument as file pattern
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
elif args.command == 'ls-reverse':
|
|
144
|
-
# List files and directories in reverse order
|
|
145
|
-
if len(args.extra) == 0:
|
|
146
|
-
os.system('ls -r')
|
|
147
|
-
else:
|
|
148
|
-
options = ' '.join(args.extra)
|
|
149
|
-
os.system(f'ls -r {options}')
|
|
150
|
-
|
|
151
|
-
elif args.command == 'ls-time':
|
|
152
|
-
# Sort by modification time, newest first
|
|
153
|
-
if len(args.extra) == 0:
|
|
154
|
-
os.system('ls -lt')
|
|
155
|
-
else:
|
|
156
|
-
options = ' '.join(args.extra)
|
|
157
|
-
os.system(f'ls -lt {options}')
|
|
298
|
+
print(count_files('.', pattern=pattern))
|
|
158
299
|
|
|
159
|
-
elif args.command == 'ls-
|
|
160
|
-
# List all files and directories recursively, with sizes in human-readable format
|
|
161
|
-
if len(args.extra) == 0:
|
|
162
|
-
os.system('ls -lRh')
|
|
163
|
-
else:
|
|
164
|
-
options = ' '.join(args.extra)
|
|
165
|
-
os.system(f'ls -lRh {options}')
|
|
166
|
-
|
|
167
|
-
elif args.command == 'ls-block-size' or args.command == 'ls-bs' or args.command == 'ls-size':
|
|
300
|
+
elif args.command == 'ls-block-size' or args.command == 'ls-bs':
|
|
168
301
|
# Display the size of each file in specified block size
|
|
169
302
|
if len(args.extra) == 1:
|
|
170
303
|
block_size = args.extra[0]
|
|
171
|
-
|
|
304
|
+
run_cmd(['ls', f'--block-size={block_size}'])
|
|
172
305
|
else:
|
|
173
306
|
print('Please provide a valid block size (e.g., K, M, G).')
|
|
174
307
|
|
|
175
308
|
# `ps` commands
|
|
176
|
-
elif args.command
|
|
177
|
-
|
|
178
|
-
os.system('ps')
|
|
179
|
-
|
|
180
|
-
elif args.command == 'ps-all':
|
|
181
|
-
# Show all processes
|
|
182
|
-
os.system('ps -A')
|
|
309
|
+
elif args.command in ps_simple:
|
|
310
|
+
run_cmd(['ps', *ps_simple[args.command]])
|
|
183
311
|
|
|
184
312
|
elif args.command == 'ps-user':
|
|
185
313
|
# Show processes for a specific user
|
|
186
314
|
if len(args.extra) > 0:
|
|
187
315
|
user = args.extra[0]
|
|
188
|
-
|
|
316
|
+
run_cmd(['ps', '-u', user])
|
|
189
317
|
else:
|
|
190
318
|
print('Please provide a username to show processes for that user')
|
|
191
319
|
|
|
192
|
-
elif args.command == 'ps-aux':
|
|
193
|
-
# Show detailed information about all processes
|
|
194
|
-
os.system('ps aux')
|
|
195
|
-
|
|
196
|
-
elif args.command == 'ps-sort-memory':
|
|
197
|
-
# Sort processes by memory usage
|
|
198
|
-
os.system('ps aux --sort=-%mem')
|
|
199
|
-
|
|
200
|
-
elif args.command == 'ps-sort-cpu':
|
|
201
|
-
# Sort processes by CPU usage
|
|
202
|
-
os.system('ps aux --sort=-%cpu')
|
|
203
|
-
|
|
204
320
|
elif args.command == 'ps-grep':
|
|
205
321
|
# Search for a specific process by name or keyword
|
|
206
322
|
if len(args.extra) > 0:
|
|
207
323
|
keyword = args.extra[0]
|
|
208
|
-
|
|
324
|
+
matches = filter_ps(keyword)
|
|
325
|
+
for line in matches:
|
|
326
|
+
print(line)
|
|
209
327
|
else:
|
|
210
328
|
print('Please provide a keyword to search for in process list')
|
|
211
329
|
|
|
212
330
|
# `kill` command
|
|
213
331
|
elif args.command == 'kill':
|
|
214
|
-
if len(args.extra)
|
|
215
|
-
os.system(f'ps -ef | grep {args.extra[0]} | grep -v grep | cut -c 9-16 | xargs kill -9')
|
|
216
|
-
else:
|
|
332
|
+
if len(args.extra) == 0:
|
|
217
333
|
print('Please provide a process name or PID for kill command')
|
|
334
|
+
else:
|
|
335
|
+
force = False
|
|
336
|
+
extras = list(args.extra)
|
|
337
|
+
if '-9' in extras:
|
|
338
|
+
extras.remove('-9')
|
|
339
|
+
force = True
|
|
340
|
+
if '--force' in extras:
|
|
341
|
+
extras.remove('--force')
|
|
342
|
+
force = True
|
|
343
|
+
if len(extras) == 0:
|
|
344
|
+
print('Please provide a process name or PID for kill command')
|
|
345
|
+
return
|
|
346
|
+
target = extras[0]
|
|
347
|
+
kill_processes(target, force=force)
|
|
218
348
|
|
|
219
349
|
# Disk usage and free space commands
|
|
220
350
|
elif args.command == 'df':
|
|
221
|
-
|
|
351
|
+
run_cmd(['df', '-h'])
|
|
222
352
|
|
|
223
353
|
elif args.command == 'du' or args.command == 'disk':
|
|
224
354
|
if len(args.extra) > 0:
|
|
225
|
-
|
|
355
|
+
run_cmd(['du', '-sh', args.extra[0]])
|
|
226
356
|
else:
|
|
227
|
-
|
|
357
|
+
run_cmd(['du', '-sh', '.'])
|
|
228
358
|
|
|
229
359
|
# Remove files or directories with confirmation and support for bulk removal
|
|
230
360
|
elif args.command == 'rm':
|
|
@@ -232,9 +362,12 @@ def main():
|
|
|
232
362
|
target = args.extra[0]
|
|
233
363
|
if confirm_action(f"Are you sure you want to remove '{target}'?"):
|
|
234
364
|
if os.path.isdir(target):
|
|
235
|
-
|
|
365
|
+
shutil.rmtree(target)
|
|
366
|
+
elif os.path.isfile(target):
|
|
367
|
+
os.remove(target)
|
|
236
368
|
else:
|
|
237
|
-
|
|
369
|
+
print(f"Path not found: {target}")
|
|
370
|
+
return
|
|
238
371
|
print(f"'{target}' has been removed.")
|
|
239
372
|
else:
|
|
240
373
|
print(f"Operation canceled. '{target}' was not removed.")
|
|
@@ -247,7 +380,10 @@ def main():
|
|
|
247
380
|
print(f"Found {len(files_to_remove)} files to remove: {files_to_remove}")
|
|
248
381
|
if confirm_action(f"Are you sure you want to remove these files?"):
|
|
249
382
|
for file in files_to_remove:
|
|
250
|
-
os.
|
|
383
|
+
if os.path.isdir(file):
|
|
384
|
+
shutil.rmtree(file)
|
|
385
|
+
elif os.path.isfile(file):
|
|
386
|
+
os.remove(file)
|
|
251
387
|
print(f"Removed files matching {pattern}.")
|
|
252
388
|
else:
|
|
253
389
|
print(f"Operation canceled for files matching {pattern}.")
|
|
@@ -257,7 +393,7 @@ def main():
|
|
|
257
393
|
# Search for a pattern in files or output
|
|
258
394
|
elif args.command == 'grep':
|
|
259
395
|
if len(args.extra) == 2:
|
|
260
|
-
|
|
396
|
+
run_cmd(['grep', args.extra[0], args.extra[1]])
|
|
261
397
|
else:
|
|
262
398
|
print('Please provide a keyword and file path for grep command')
|
|
263
399
|
|
|
@@ -266,12 +402,11 @@ def main():
|
|
|
266
402
|
if len(args.extra) >= 2:
|
|
267
403
|
source = args.extra[0]
|
|
268
404
|
output = args.extra[1]
|
|
269
|
-
exclude = args.extra[2:] # Additional arguments as exclude patterns
|
|
270
|
-
exclude_params = ' '.join(f'--exclude={x}' for x in exclude)
|
|
405
|
+
exclude = parse_excludes(args.extra[2:]) # Additional arguments as exclude patterns
|
|
271
406
|
if output.endswith('.tar.gz'):
|
|
272
|
-
|
|
407
|
+
run_cmd(['tar', '-czvf', output, *[f'--exclude={x}' for x in exclude], source])
|
|
273
408
|
elif output.endswith('.tar'):
|
|
274
|
-
|
|
409
|
+
run_cmd(['tar', '-cvf', output, *[f'--exclude={x}' for x in exclude], source])
|
|
275
410
|
else:
|
|
276
411
|
print('Unsupported output format. Please provide .tar or .tar.gz as the output file extension.')
|
|
277
412
|
else:
|
|
@@ -292,10 +427,15 @@ def main():
|
|
|
292
427
|
tar_files = glob.glob(os.path.join(source, '*.tar')) + glob.glob(os.path.join(source, '*.tar.gz'))
|
|
293
428
|
|
|
294
429
|
for tar_file in tar_files:
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
430
|
+
if tar_file.endswith('.tar'):
|
|
431
|
+
run_cmd(['tar', '-xvf', tar_file, '-C', destination])
|
|
432
|
+
elif tar_file.endswith('.tar.gz'):
|
|
433
|
+
run_cmd(['tar', '-xzvf', tar_file, '-C', destination])
|
|
434
|
+
# Extract a single tar file
|
|
435
|
+
elif source.endswith('.tar.gz'):
|
|
436
|
+
run_cmd(['tar', '-xzvf', source, '-C', destination])
|
|
437
|
+
elif source.endswith('.tar'):
|
|
438
|
+
run_cmd(['tar', '-xvf', source, '-C', destination])
|
|
299
439
|
else:
|
|
300
440
|
print('Please provide a valid .tar or .tar.gz file, or a directory containing such files.')
|
|
301
441
|
|
|
@@ -303,13 +443,13 @@ def main():
|
|
|
303
443
|
|
|
304
444
|
elif args.command == 'tar-list':
|
|
305
445
|
if len(args.extra) > 0:
|
|
306
|
-
|
|
446
|
+
run_cmd(['tar', '-tvf', args.extra[0]])
|
|
307
447
|
else:
|
|
308
448
|
print('Please provide a tar file to list contents')
|
|
309
449
|
|
|
310
450
|
elif args.command == 'tar-add':
|
|
311
451
|
if len(args.extra) == 2:
|
|
312
|
-
|
|
452
|
+
run_cmd(['tar', '-rvf', args.extra[1], args.extra[0]])
|
|
313
453
|
else:
|
|
314
454
|
print('Please provide a file to add and the target tar file')
|
|
315
455
|
|
|
@@ -318,17 +458,19 @@ def main():
|
|
|
318
458
|
if len(args.extra) == 2:
|
|
319
459
|
source_dir = args.extra[0]
|
|
320
460
|
target_dir = args.extra[1]
|
|
321
|
-
|
|
461
|
+
for root, _, files in os.walk(source_dir):
|
|
462
|
+
for name in files:
|
|
463
|
+
if name.endswith('.zip'):
|
|
464
|
+
run_cmd(['unzip', os.path.join(root, name), '-d', target_dir])
|
|
322
465
|
else:
|
|
323
466
|
print('Please provide both a source and a target directory.')
|
|
324
467
|
|
|
325
468
|
# Zip compress
|
|
326
|
-
elif args.command == 'zip-all' or args.command == 'zip':
|
|
469
|
+
elif args.command == 'zip-all' or args.command == 'zip' or args.command == 'zip-compress':
|
|
327
470
|
if len(args.extra) >= 2:
|
|
328
471
|
output = args.extra[0]
|
|
329
|
-
sources = args.extra[1:] # All other arguments as sources to be zipped
|
|
330
|
-
|
|
331
|
-
os.system(f'zip -r {output} {source_params}')
|
|
472
|
+
sources = expand_globs(args.extra[1:]) # All other arguments as sources to be zipped
|
|
473
|
+
run_cmd(['zip', '-r', output, *sources])
|
|
332
474
|
else:
|
|
333
475
|
print('Please provide an output file name and at least one source to compress.')
|
|
334
476
|
|
|
@@ -347,10 +489,10 @@ def main():
|
|
|
347
489
|
for src_file in source_files:
|
|
348
490
|
basename = os.path.splitext(os.path.basename(src_file))[0]
|
|
349
491
|
dest_file = os.path.join(destination_dir, f'{basename}.{file_extension}')
|
|
350
|
-
|
|
492
|
+
run_cmd(['ffmpeg', '-i', src_file, '-c', 'copy', dest_file, '-y'])
|
|
351
493
|
else:
|
|
352
494
|
# Handle single file conversion
|
|
353
|
-
|
|
495
|
+
run_cmd(['ffmpeg', '-i', source, '-c', 'copy', destination, '-y'])
|
|
354
496
|
else:
|
|
355
497
|
print('Usage: convert-video [source file or pattern] [destination file or pattern]')
|
|
356
498
|
|
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: linux-command
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A command line tool to perform custom tasks.
|
|
5
|
+
Home-page: https://github.com/MouxiaoHuang/linux-command
|
|
6
|
+
Author: Mouxiao Huang
|
|
7
|
+
Author-email: huangmouxiao@gmail.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Dynamic: author
|
|
15
|
+
Dynamic: author-email
|
|
16
|
+
Dynamic: classifier
|
|
17
|
+
Dynamic: description
|
|
18
|
+
Dynamic: description-content-type
|
|
19
|
+
Dynamic: home-page
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
Dynamic: requires-python
|
|
22
|
+
Dynamic: summary
|
|
23
|
+
|
|
1
24
|
|
|
2
25
|
# linux-command
|
|
3
26
|
|
|
@@ -11,6 +34,16 @@ To install the package, run the following command:
|
|
|
11
34
|
pip install linux-command
|
|
12
35
|
```
|
|
13
36
|
|
|
37
|
+
## Install From Source (Development)
|
|
38
|
+
|
|
39
|
+
If you want to develop or modify the tool locally:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
git clone https://github.com/MouxiaoHuang/linux-command.git
|
|
43
|
+
cd linux-command
|
|
44
|
+
pip install -e .
|
|
45
|
+
```
|
|
46
|
+
|
|
14
47
|
## Usage
|
|
15
48
|
|
|
16
49
|
Once installed, you can access all commands using `cmd` followed by the specific command name. You can use `cmd -h` or `cmd --help` to see the supported commands. Below is a list of supported commands along with examples for each.
|
|
@@ -141,6 +174,12 @@ cmd ps-grep python
|
|
|
141
174
|
cmd kill process_name
|
|
142
175
|
```
|
|
143
176
|
|
|
177
|
+
To force kill:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
cmd kill --force process_name
|
|
181
|
+
```
|
|
182
|
+
|
|
144
183
|
---
|
|
145
184
|
|
|
146
185
|
### 21. `df` - Show disk usage in human-readable format
|
|
@@ -149,7 +188,7 @@ cmd kill process_name
|
|
|
149
188
|
cmd df
|
|
150
189
|
```
|
|
151
190
|
|
|
152
|
-
### 22. `du [path]` - Show disk usage for a specific file or directory
|
|
191
|
+
### 22. `du [path]` - Show disk usage for a specific file or directory (default: current directory)
|
|
153
192
|
|
|
154
193
|
```bash
|
|
155
194
|
cmd du /path/to/directory
|
|
@@ -242,6 +281,21 @@ This command will create a `.zip` file at `/path/to/output.zip` that contains ev
|
|
|
242
281
|
|
|
243
282
|
---
|
|
244
283
|
|
|
284
|
+
## Aliases
|
|
285
|
+
|
|
286
|
+
These commands are aliases for the same behavior:
|
|
287
|
+
|
|
288
|
+
- `lsf` = `ls-file`
|
|
289
|
+
- `lsd` = `ls-dir`
|
|
290
|
+
- `ls-bs` = `ls-block-size`
|
|
291
|
+
- `disk` = `du`
|
|
292
|
+
- `tar` = `tar-compress`
|
|
293
|
+
- `untar` = `tar-extract`
|
|
294
|
+
- `zip` = `zip-compress`
|
|
295
|
+
- `zip-all` = `zip-compress`
|
|
296
|
+
- `unzip` = `unzip-all`
|
|
297
|
+
- `convert-vid` = `convert-video`
|
|
298
|
+
|
|
245
299
|
## Contributing
|
|
246
300
|
|
|
247
301
|
We welcome contributions from the community! If you'd like to help improve `linux-command`, feel free to report issues or submit pull requests.
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
import signal
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
7
|
+
sys.path.insert(0, str(PROJECT_ROOT))
|
|
8
|
+
|
|
9
|
+
import linux_command.linux_command as lc
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RunSpy:
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self.calls = []
|
|
15
|
+
|
|
16
|
+
def __call__(self, cmd_args, check=False, **kwargs):
|
|
17
|
+
# Mimic subprocess.run signature used in code.
|
|
18
|
+
self.calls.append(list(cmd_args))
|
|
19
|
+
class Result:
|
|
20
|
+
stdout = ""
|
|
21
|
+
return Result()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RunCmdSpy:
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self.calls = []
|
|
27
|
+
|
|
28
|
+
def __call__(self, cmd_args):
|
|
29
|
+
self.calls.append(list(cmd_args))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def run_main_with_args(monkeypatch, argv, run_spy=None):
|
|
33
|
+
monkeypatch.setattr(sys, "argv", ["cmd", *argv])
|
|
34
|
+
if run_spy is not None:
|
|
35
|
+
monkeypatch.setattr(lc.subprocess, "run", run_spy)
|
|
36
|
+
lc.main()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_ls_and_ls_all(monkeypatch):
|
|
40
|
+
run_spy = RunSpy()
|
|
41
|
+
run_main_with_args(monkeypatch, ["ls"], run_spy)
|
|
42
|
+
run_main_with_args(monkeypatch, ["ls-all"], run_spy)
|
|
43
|
+
assert run_spy.calls[0] == ["ls"]
|
|
44
|
+
assert run_spy.calls[1] == ["ls", "-a"]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_ls_variants(monkeypatch):
|
|
48
|
+
run_cmd_spy = RunCmdSpy()
|
|
49
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
50
|
+
for cmd, expected in [
|
|
51
|
+
("ls-reverse", ["ls", "-r"]),
|
|
52
|
+
("ls-time", ["ls", "-lt"]),
|
|
53
|
+
("ls-long", ["ls", "-l"]),
|
|
54
|
+
("ls-human", ["ls", "-lh"]),
|
|
55
|
+
("ls-recursive", ["ls", "-R"]),
|
|
56
|
+
("ls-recursive-size", ["ls", "-lRh"]),
|
|
57
|
+
("ls-size", ["ls", "-lS"]),
|
|
58
|
+
]:
|
|
59
|
+
run_main_with_args(monkeypatch, [cmd])
|
|
60
|
+
assert run_cmd_spy.calls[-1] == expected
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_ls_block_size_ok(monkeypatch):
|
|
64
|
+
run_cmd_spy = RunCmdSpy()
|
|
65
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
66
|
+
run_main_with_args(monkeypatch, ["ls-block-size", "M"])
|
|
67
|
+
assert run_cmd_spy.calls[0] == ["ls", "--block-size=M"]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_ls_block_size_missing_arg(monkeypatch, capsys):
|
|
71
|
+
run_spy = RunSpy()
|
|
72
|
+
run_main_with_args(monkeypatch, ["ls-block-size"], run_spy)
|
|
73
|
+
out = capsys.readouterr().out
|
|
74
|
+
assert "Please provide a valid block size" in out
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_ps_user(monkeypatch):
|
|
78
|
+
run_spy = RunSpy()
|
|
79
|
+
run_main_with_args(monkeypatch, ["ps-user", "root"], run_spy)
|
|
80
|
+
assert run_spy.calls[0] == ["ps", "-u", "root"]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_ps_variants(monkeypatch):
|
|
84
|
+
run_cmd_spy = RunCmdSpy()
|
|
85
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
86
|
+
for cmd, expected in [
|
|
87
|
+
("ps", ["ps"]),
|
|
88
|
+
("ps-all", ["ps", "-A"]),
|
|
89
|
+
("ps-aux", ["ps", "aux"]),
|
|
90
|
+
("ps-sort-memory", ["ps", "aux", "--sort=-%mem"]),
|
|
91
|
+
("ps-sort-cpu", ["ps", "aux", "--sort=-%cpu"]),
|
|
92
|
+
]:
|
|
93
|
+
run_main_with_args(monkeypatch, [cmd])
|
|
94
|
+
assert run_cmd_spy.calls[-1] == expected
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_ps_grep_filters(monkeypatch, capsys):
|
|
98
|
+
def fake_run(cmd_args, check=False, capture_output=False, text=False):
|
|
99
|
+
class Result:
|
|
100
|
+
stdout = "root 1 0.0 0.1 ? Ss 00:00 python\nuser 2 0.0 0.1 ? Ss 00:00 bash\n"
|
|
101
|
+
return Result()
|
|
102
|
+
|
|
103
|
+
monkeypatch.setattr(lc.subprocess, "run", fake_run)
|
|
104
|
+
run_main_with_args(monkeypatch, ["ps-grep", "python"])
|
|
105
|
+
out = capsys.readouterr().out
|
|
106
|
+
assert "python" in out
|
|
107
|
+
assert "bash" not in out
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_kill_pid(monkeypatch, capsys):
|
|
111
|
+
killed = []
|
|
112
|
+
|
|
113
|
+
def fake_kill(pid, sig):
|
|
114
|
+
killed.append((pid, sig))
|
|
115
|
+
|
|
116
|
+
monkeypatch.setattr(lc.os, "kill", fake_kill)
|
|
117
|
+
run_main_with_args(monkeypatch, ["kill", "1234"])
|
|
118
|
+
assert killed == [(1234, signal.SIGTERM)]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_kill_force_pid(monkeypatch):
|
|
122
|
+
killed = []
|
|
123
|
+
|
|
124
|
+
def fake_kill(pid, sig):
|
|
125
|
+
killed.append((pid, sig))
|
|
126
|
+
|
|
127
|
+
monkeypatch.setattr(lc.os, "kill", fake_kill)
|
|
128
|
+
run_main_with_args(monkeypatch, ["kill", "--force", "999"])
|
|
129
|
+
assert killed == [(999, signal.SIGKILL)]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_kill_name_confirm(monkeypatch, capsys):
|
|
133
|
+
def fake_run(cmd_args, check=False, capture_output=False, text=False):
|
|
134
|
+
class Result:
|
|
135
|
+
stdout = "root 10 0.0 0.1 ? Ss 00:00 myproc\n"
|
|
136
|
+
return Result()
|
|
137
|
+
|
|
138
|
+
killed = []
|
|
139
|
+
|
|
140
|
+
def fake_kill(pid, sig):
|
|
141
|
+
killed.append((pid, sig))
|
|
142
|
+
|
|
143
|
+
monkeypatch.setattr(lc.subprocess, "run", fake_run)
|
|
144
|
+
monkeypatch.setattr(lc.os, "kill", fake_kill)
|
|
145
|
+
monkeypatch.setattr(builtins, "input", lambda _: "y")
|
|
146
|
+
run_main_with_args(monkeypatch, ["kill", "myproc"])
|
|
147
|
+
assert killed == [(10, signal.SIGTERM)]
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_du_default(monkeypatch):
|
|
151
|
+
run_spy = RunSpy()
|
|
152
|
+
run_main_with_args(monkeypatch, ["du"], run_spy)
|
|
153
|
+
assert run_spy.calls[0] == ["du", "-sh", "."]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_disk_alias(monkeypatch):
|
|
157
|
+
run_cmd_spy = RunCmdSpy()
|
|
158
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
159
|
+
run_main_with_args(monkeypatch, ["disk", "/tmp"])
|
|
160
|
+
assert run_cmd_spy.calls[0] == ["du", "-sh", "/tmp"]
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_rm_single_file(monkeypatch, tmp_path):
|
|
164
|
+
target = tmp_path / "a.txt"
|
|
165
|
+
target.write_text("x")
|
|
166
|
+
monkeypatch.setattr(builtins, "input", lambda _: "y")
|
|
167
|
+
run_main_with_args(monkeypatch, ["rm", str(target)])
|
|
168
|
+
assert not target.exists()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_rm_pattern(monkeypatch, tmp_path):
|
|
172
|
+
(tmp_path / "a.log").write_text("x")
|
|
173
|
+
(tmp_path / "b.log").write_text("x")
|
|
174
|
+
(tmp_path / "c.txt").write_text("x")
|
|
175
|
+
monkeypatch.setattr(builtins, "input", lambda _: "y")
|
|
176
|
+
run_main_with_args(monkeypatch, ["rm", str(tmp_path), "*.log"])
|
|
177
|
+
assert not (tmp_path / "a.log").exists()
|
|
178
|
+
assert not (tmp_path / "b.log").exists()
|
|
179
|
+
assert (tmp_path / "c.txt").exists()
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def test_expand_globs(tmp_path):
|
|
183
|
+
(tmp_path / "x.txt").write_text("x")
|
|
184
|
+
(tmp_path / "y.txt").write_text("x")
|
|
185
|
+
args = [str(tmp_path / "*.txt")]
|
|
186
|
+
expanded = lc.expand_globs(args)
|
|
187
|
+
assert len(expanded) == 2
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def test_ls_dir_and_ls_file(monkeypatch, tmp_path, capsys):
|
|
191
|
+
(tmp_path / "a.txt").write_text("x")
|
|
192
|
+
(tmp_path / "b.log").write_text("x")
|
|
193
|
+
(tmp_path / "dir1").mkdir()
|
|
194
|
+
(tmp_path / "dir2").mkdir()
|
|
195
|
+
monkeypatch.chdir(tmp_path)
|
|
196
|
+
|
|
197
|
+
run_main_with_args(monkeypatch, ["ls-dir"])
|
|
198
|
+
out = capsys.readouterr().out.strip()
|
|
199
|
+
assert out == "2"
|
|
200
|
+
|
|
201
|
+
run_main_with_args(monkeypatch, ["ls-file"])
|
|
202
|
+
out = capsys.readouterr().out.strip()
|
|
203
|
+
assert out == "2"
|
|
204
|
+
|
|
205
|
+
run_main_with_args(monkeypatch, ["lsf", "*.log"])
|
|
206
|
+
out = capsys.readouterr().out.strip()
|
|
207
|
+
assert out == "1"
|
|
208
|
+
|
|
209
|
+
run_main_with_args(monkeypatch, ["lsd"])
|
|
210
|
+
out = capsys.readouterr().out.strip()
|
|
211
|
+
assert out == "2"
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def test_grep_command(monkeypatch):
|
|
215
|
+
run_cmd_spy = RunCmdSpy()
|
|
216
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
217
|
+
run_main_with_args(monkeypatch, ["grep", "needle", "file.txt"])
|
|
218
|
+
assert run_cmd_spy.calls[0] == ["grep", "needle", "file.txt"]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def test_tar_compress(monkeypatch):
|
|
222
|
+
run_cmd_spy = RunCmdSpy()
|
|
223
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
224
|
+
run_main_with_args(monkeypatch, ["tar-compress", "src", "out.tar"])
|
|
225
|
+
run_main_with_args(monkeypatch, ["tar", "src", "out.tar.gz", "--exclude", "node_modules"])
|
|
226
|
+
assert run_cmd_spy.calls[0] == ["tar", "-cvf", "out.tar", "src"]
|
|
227
|
+
assert run_cmd_spy.calls[1] == ["tar", "-czvf", "out.tar.gz", "--exclude=node_modules", "src"]
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def test_tar_compress_bad_ext(monkeypatch, capsys):
|
|
231
|
+
run_cmd_spy = RunCmdSpy()
|
|
232
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
233
|
+
run_main_with_args(monkeypatch, ["tar-compress", "src", "out.zip"])
|
|
234
|
+
out = capsys.readouterr().out
|
|
235
|
+
assert "Unsupported output format" in out
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def test_tar_extract_single(monkeypatch):
|
|
239
|
+
run_cmd_spy = RunCmdSpy()
|
|
240
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
241
|
+
run_main_with_args(monkeypatch, ["tar-extract", "a.tar.gz", "dest"])
|
|
242
|
+
run_main_with_args(monkeypatch, ["untar", "b.tar", "dest"])
|
|
243
|
+
assert run_cmd_spy.calls[0] == ["tar", "-xzvf", "a.tar.gz", "-C", "dest"]
|
|
244
|
+
assert run_cmd_spy.calls[1] == ["tar", "-xvf", "b.tar", "-C", "dest"]
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def test_tar_extract_dir(monkeypatch, tmp_path):
|
|
248
|
+
src = tmp_path / "src"
|
|
249
|
+
src.mkdir()
|
|
250
|
+
(src / "a.tar").write_text("x")
|
|
251
|
+
(src / "b.tar.gz").write_text("x")
|
|
252
|
+
run_cmd_spy = RunCmdSpy()
|
|
253
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
254
|
+
run_main_with_args(monkeypatch, ["tar-extract", str(src), str(tmp_path / "dest"), "all"])
|
|
255
|
+
assert run_cmd_spy.calls[0] == ["tar", "-xvf", str(src / "a.tar"), "-C", str(tmp_path / "dest")]
|
|
256
|
+
assert run_cmd_spy.calls[1] == ["tar", "-xzvf", str(src / "b.tar.gz"), "-C", str(tmp_path / "dest")]
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def test_tar_list_and_add(monkeypatch):
|
|
260
|
+
run_cmd_spy = RunCmdSpy()
|
|
261
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
262
|
+
run_main_with_args(monkeypatch, ["tar-list", "a.tar"])
|
|
263
|
+
run_main_with_args(monkeypatch, ["tar-add", "file.txt", "a.tar"])
|
|
264
|
+
assert run_cmd_spy.calls[0] == ["tar", "-tvf", "a.tar"]
|
|
265
|
+
assert run_cmd_spy.calls[1] == ["tar", "-rvf", "a.tar", "file.txt"]
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def test_unzip_all(monkeypatch, tmp_path):
|
|
269
|
+
src = tmp_path / "zips"
|
|
270
|
+
src.mkdir()
|
|
271
|
+
(src / "a.zip").write_text("x")
|
|
272
|
+
(src / "b.txt").write_text("x")
|
|
273
|
+
run_cmd_spy = RunCmdSpy()
|
|
274
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
275
|
+
run_main_with_args(monkeypatch, ["unzip-all", str(src), str(tmp_path / "out")])
|
|
276
|
+
assert run_cmd_spy.calls[0] == ["unzip", str(src / "a.zip"), "-d", str(tmp_path / "out")]
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def test_zip_commands(monkeypatch, tmp_path):
|
|
280
|
+
(tmp_path / "a.txt").write_text("x")
|
|
281
|
+
(tmp_path / "b.txt").write_text("x")
|
|
282
|
+
run_cmd_spy = RunCmdSpy()
|
|
283
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
284
|
+
run_main_with_args(monkeypatch, ["zip", "out.zip", str(tmp_path / "*.txt")])
|
|
285
|
+
run_main_with_args(monkeypatch, ["zip-compress", "out.zip", str(tmp_path / "a.txt")])
|
|
286
|
+
assert run_cmd_spy.calls[0][0:3] == ["zip", "-r", "out.zip"]
|
|
287
|
+
assert run_cmd_spy.calls[1] == ["zip", "-r", "out.zip", str(tmp_path / "a.txt")]
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def test_convert_video_single(monkeypatch):
|
|
291
|
+
run_cmd_spy = RunCmdSpy()
|
|
292
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
293
|
+
run_main_with_args(monkeypatch, ["convert-video", "a.mp4", "b.mkv"])
|
|
294
|
+
assert run_cmd_spy.calls[0] == ["ffmpeg", "-i", "a.mp4", "-c", "copy", "b.mkv", "-y"]
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def test_convert_video_wildcard(monkeypatch, tmp_path):
|
|
298
|
+
src_dir = tmp_path / "src"
|
|
299
|
+
out_dir = tmp_path / "out"
|
|
300
|
+
src_dir.mkdir()
|
|
301
|
+
out_dir.mkdir()
|
|
302
|
+
(src_dir / "a.mp4").write_text("x")
|
|
303
|
+
(src_dir / "b.mp4").write_text("x")
|
|
304
|
+
run_cmd_spy = RunCmdSpy()
|
|
305
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
306
|
+
run_main_with_args(monkeypatch, ["convert-vid", str(src_dir / "*.mp4"), str(out_dir / "out.mkv")])
|
|
307
|
+
assert run_cmd_spy.calls[0] == ["ffmpeg", "-i", str(src_dir / "a.mp4"), "-c", "copy", str(out_dir / "a.mkv"), "-y"]
|
|
308
|
+
assert run_cmd_spy.calls[1] == ["ffmpeg", "-i", str(src_dir / "b.mp4"), "-c", "copy", str(out_dir / "b.mkv"), "-y"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|