linux-command 0.2.3__tar.gz → 0.3.1__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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: linux-command
3
- Version: 0.2.3
3
+ Version: 0.3.1
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.3
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,18 +26,23 @@ 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.2.3"
38
+ VERSION = "0.3.1"
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`.',
@@ -47,6 +52,7 @@ commands = {
47
52
  'ls-human': 'List in human-readable format (for file sizes)',
48
53
  'ls-long': 'Long format listing',
49
54
  'ls-size': 'Sort files by size',
55
+ 'ls-recursive': 'Recursively list files in directories and subdirectories.',
50
56
  'ls-recursive-size': 'List all files and directories recursively, with sizes in human-readable format',
51
57
  'ls-bs': 'Display the size of each file in specified block size (e.g., K, M, G).',
52
58
  'ls-block-size': 'Display the size of each file in specified block size (e.g., K, M, G).',
@@ -64,12 +70,13 @@ commands = {
64
70
  'rm': 'Remove file, directory, or multiple files by patterns (e.g., *.txt)',
65
71
  'grep': 'Search for a pattern in files or output.',
66
72
  'tar': 'Pack into .tar or .tar.gz file.',
67
- 'tar-comress': 'Pack into .tar or .tar.gz file.',
73
+ 'tar-compress': 'Pack into .tar or .tar.gz file.',
68
74
  'untar': 'Unpack .tar or .tar.gz file, or batch process in a directory.',
69
75
  'tar-extract': 'Unpack .tar or .tar.gz file, or batch process in a directory.',
70
76
  'tar-list': 'List all contents in a tar file.',
71
77
  'tar-add': 'Add a file to a tar file.',
72
78
  'zip': 'Pack a folder to a .zip file.',
79
+ 'zip-compress': 'Pack a folder to a .zip file.',
73
80
  'zip-all': 'Pack a folder to a .zip file.',
74
81
  'unzip': 'Unpack all .zip files in a directory to another.',
75
82
  'unzip-all': 'Unpack all .zip files in a directory to another.',
@@ -92,6 +99,130 @@ def confirm_action(message):
92
99
  return confirmation in ['yes', 'y']
93
100
 
94
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
+
95
226
  def main():
96
227
  # Set up argparse
97
228
  parser = argparse.ArgumentParser(
@@ -104,11 +235,17 @@ def main():
104
235
  parser.add_argument('-V', '--version', action='store_true', help='Show program\'s version number and exit')
105
236
 
106
237
  # Main command and subcommands
107
- parser.add_argument('command', nargs='?', help='Command to execute')
108
- parser.add_argument('extra', nargs='*', help='Additional arguments for the command')
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')
109
242
 
110
243
  # Parse the arguments
111
- args = parser.parse_args()
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)
112
249
 
113
250
  if args.help:
114
251
  custom_help()
@@ -117,140 +254,107 @@ def main():
117
254
  if args.version:
118
255
  print(f'linux-command {VERSION}')
119
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
+ }
120
281
 
121
282
  # `ls` commands
122
- if args.command == 'ls':
123
- if len(args.extra) == 0:
124
- # List current directory
125
- os.system('ls')
126
- else:
127
- options = ' '.join(args.extra)
128
- os.system(f'ls {options}')
129
-
130
- # Advanced `ls` commands
283
+ if args.command in ls_simple:
284
+ run_cmd(['ls', *ls_simple[args.command], *expand_globs(args.extra)])
131
285
 
286
+ # Advanced `ls` commands
132
287
  elif args.command == 'ls-dir' or args.command == 'lsd':
133
288
  # Count the number of directories
134
- os.system('ls -lR | grep "^d" | wc -l')
289
+ print(count_dirs('.'))
135
290
 
136
291
  elif args.command == 'ls-file' or args.command == 'lsf':
137
292
  if len(args.extra) == 0:
138
293
  # Count the number of all files
139
- os.system('ls -l | grep "^-" | wc -l')
294
+ print(count_files('.'))
140
295
  else:
141
296
  # Count files of a specific type based on provided extension or pattern
142
297
  pattern = args.extra[0] # First extra argument as file pattern
143
- os.system(f'ls -l | grep "^-" | grep "{pattern}" | wc -l')
144
-
145
- elif args.command == 'ls-reverse':
146
- # List files and directories in reverse order
147
- if len(args.extra) == 0:
148
- os.system('ls -r')
149
- else:
150
- options = ' '.join(args.extra)
151
- os.system(f'ls -r {options}')
152
-
153
- elif args.command == 'ls-time':
154
- # Sort by modification time, newest first
155
- if len(args.extra) == 0:
156
- os.system('ls -lt')
157
- else:
158
- options = ' '.join(args.extra)
159
- os.system(f'ls -lt {options}')
160
-
161
- elif args.command == 'ls-long':
162
- # Long format listing
163
- if len(args.extra) == 0:
164
- os.system('ls -l')
165
- else:
166
- options = ' '.join(args.extra)
167
- os.system(f'ls -l {options}')
168
-
169
- elif args.command == 'ls-human':
170
- # List in human-readable format (for file sizes)
171
- if len(args.extra) == 0:
172
- os.system('ls -lh')
173
- else:
174
- options = ' '.join(args.extra)
175
- os.system(f'ls -lh {options}')
176
-
177
- elif args.command == 'ls-recursive-size':
178
- # List all files and directories recursively, with sizes in human-readable format
179
- if len(args.extra) == 0:
180
- os.system('ls -lRh')
181
- else:
182
- options = ' '.join(args.extra)
183
- os.system(f'ls -lRh {options}')
184
-
185
- elif args.command == 'ls-size':
186
- # Sort files by size
187
- if len(args.extra) == 0:
188
- os.system('ls -lS')
189
- else:
190
- options = ' '.join(args.extra)
191
- os.system(f'ls -lS {options}')
298
+ print(count_files('.', pattern=pattern))
192
299
 
193
300
  elif args.command == 'ls-block-size' or args.command == 'ls-bs':
194
301
  # Display the size of each file in specified block size
195
302
  if len(args.extra) == 1:
196
303
  block_size = args.extra[0]
197
- os.system(f'ls --block-size={block_size}')
304
+ run_cmd(['ls', f'--block-size={block_size}'])
198
305
  else:
199
306
  print('Please provide a valid block size (e.g., K, M, G).')
200
307
 
201
308
  # `ps` commands
202
- elif args.command == 'ps':
203
- # Basic process list
204
- os.system('ps')
205
-
206
- elif args.command == 'ps-all':
207
- # Show all processes
208
- os.system('ps -A')
309
+ elif args.command in ps_simple:
310
+ run_cmd(['ps', *ps_simple[args.command]])
209
311
 
210
312
  elif args.command == 'ps-user':
211
313
  # Show processes for a specific user
212
314
  if len(args.extra) > 0:
213
315
  user = args.extra[0]
214
- os.system(f'ps -u {user}')
316
+ run_cmd(['ps', '-u', user])
215
317
  else:
216
318
  print('Please provide a username to show processes for that user')
217
319
 
218
- elif args.command == 'ps-aux':
219
- # Show detailed information about all processes
220
- os.system('ps aux')
221
-
222
- elif args.command == 'ps-sort-memory':
223
- # Sort processes by memory usage
224
- os.system('ps aux --sort=-%mem')
225
-
226
- elif args.command == 'ps-sort-cpu':
227
- # Sort processes by CPU usage
228
- os.system('ps aux --sort=-%cpu')
229
-
230
320
  elif args.command == 'ps-grep':
231
321
  # Search for a specific process by name or keyword
232
322
  if len(args.extra) > 0:
233
323
  keyword = args.extra[0]
234
- os.system(f'ps aux | grep {keyword}')
324
+ matches = filter_ps(keyword)
325
+ for line in matches:
326
+ print(line)
235
327
  else:
236
328
  print('Please provide a keyword to search for in process list')
237
329
 
238
330
  # `kill` command
239
331
  elif args.command == 'kill':
240
- if len(args.extra) > 0:
241
- os.system(f'ps -ef | grep {args.extra[0]} | grep -v grep | cut -c 9-16 | xargs kill -9')
242
- else:
332
+ if len(args.extra) == 0:
243
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)
244
348
 
245
349
  # Disk usage and free space commands
246
350
  elif args.command == 'df':
247
- os.system('df -h')
351
+ run_cmd(['df', '-h'])
248
352
 
249
353
  elif args.command == 'du' or args.command == 'disk':
250
354
  if len(args.extra) > 0:
251
- os.system(f'du -sh {args.extra[0]}')
355
+ run_cmd(['du', '-sh', args.extra[0]])
252
356
  else:
253
- print('Please provide a valid path for du command')
357
+ run_cmd(['du', '-sh', '.'])
254
358
 
255
359
  # Remove files or directories with confirmation and support for bulk removal
256
360
  elif args.command == 'rm':
@@ -258,9 +362,12 @@ def main():
258
362
  target = args.extra[0]
259
363
  if confirm_action(f"Are you sure you want to remove '{target}'?"):
260
364
  if os.path.isdir(target):
261
- os.system(f'rm -rf {target}')
365
+ shutil.rmtree(target)
366
+ elif os.path.isfile(target):
367
+ os.remove(target)
262
368
  else:
263
- os.system(f'rm {target}')
369
+ print(f"Path not found: {target}")
370
+ return
264
371
  print(f"'{target}' has been removed.")
265
372
  else:
266
373
  print(f"Operation canceled. '{target}' was not removed.")
@@ -273,7 +380,10 @@ def main():
273
380
  print(f"Found {len(files_to_remove)} files to remove: {files_to_remove}")
274
381
  if confirm_action(f"Are you sure you want to remove these files?"):
275
382
  for file in files_to_remove:
276
- os.system(f'rm {file}')
383
+ if os.path.isdir(file):
384
+ shutil.rmtree(file)
385
+ elif os.path.isfile(file):
386
+ os.remove(file)
277
387
  print(f"Removed files matching {pattern}.")
278
388
  else:
279
389
  print(f"Operation canceled for files matching {pattern}.")
@@ -283,7 +393,7 @@ def main():
283
393
  # Search for a pattern in files or output
284
394
  elif args.command == 'grep':
285
395
  if len(args.extra) == 2:
286
- os.system(f'grep "{args.extra[0]}" {args.extra[1]}')
396
+ run_cmd(['grep', args.extra[0], args.extra[1]])
287
397
  else:
288
398
  print('Please provide a keyword and file path for grep command')
289
399
 
@@ -292,12 +402,11 @@ def main():
292
402
  if len(args.extra) >= 2:
293
403
  source = args.extra[0]
294
404
  output = args.extra[1]
295
- exclude = args.extra[2:] # Additional arguments as exclude patterns
296
- exclude_params = ' '.join(f'--exclude={x}' for x in exclude)
405
+ exclude = parse_excludes(args.extra[2:]) # Additional arguments as exclude patterns
297
406
  if output.endswith('.tar.gz'):
298
- os.system(f'tar -czvf {output} {exclude_params} {source}')
407
+ run_cmd(['tar', '-czvf', output, *[f'--exclude={x}' for x in exclude], source])
299
408
  elif output.endswith('.tar'):
300
- os.system(f'tar -cvf {output} {exclude_params} {source}')
409
+ run_cmd(['tar', '-cvf', output, *[f'--exclude={x}' for x in exclude], source])
301
410
  else:
302
411
  print('Unsupported output format. Please provide .tar or .tar.gz as the output file extension.')
303
412
  else:
@@ -319,14 +428,14 @@ def main():
319
428
 
320
429
  for tar_file in tar_files:
321
430
  if tar_file.endswith('.tar'):
322
- os.system(f'tar -xvf {tar_file} -C {destination}')
431
+ run_cmd(['tar', '-xvf', tar_file, '-C', destination])
323
432
  elif tar_file.endswith('.tar.gz'):
324
- os.system(f'tar -xzvf {tar_file} -C {destination}')
433
+ run_cmd(['tar', '-xzvf', tar_file, '-C', destination])
325
434
  # Extract a single tar file
326
435
  elif source.endswith('.tar.gz'):
327
- os.system(f'tar -xzvf {source} -C {destination}')
436
+ run_cmd(['tar', '-xzvf', source, '-C', destination])
328
437
  elif source.endswith('.tar'):
329
- os.system(f'tar -xvf {source} -C {destination}')
438
+ run_cmd(['tar', '-xvf', source, '-C', destination])
330
439
  else:
331
440
  print('Please provide a valid .tar or .tar.gz file, or a directory containing such files.')
332
441
 
@@ -334,13 +443,13 @@ def main():
334
443
 
335
444
  elif args.command == 'tar-list':
336
445
  if len(args.extra) > 0:
337
- os.system(f'tar -tvf {args.extra[0]}')
446
+ run_cmd(['tar', '-tvf', args.extra[0]])
338
447
  else:
339
448
  print('Please provide a tar file to list contents')
340
449
 
341
450
  elif args.command == 'tar-add':
342
451
  if len(args.extra) == 2:
343
- os.system(f'tar -rvf {args.extra[1]} {args.extra[0]}')
452
+ run_cmd(['tar', '-rvf', args.extra[1], args.extra[0]])
344
453
  else:
345
454
  print('Please provide a file to add and the target tar file')
346
455
 
@@ -349,17 +458,19 @@ def main():
349
458
  if len(args.extra) == 2:
350
459
  source_dir = args.extra[0]
351
460
  target_dir = args.extra[1]
352
- os.system(f'find {source_dir} -name "*.zip" -exec unzip {{}} -d {target_dir} \;')
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])
353
465
  else:
354
466
  print('Please provide both a source and a target directory.')
355
467
 
356
468
  # Zip compress
357
- elif args.command == 'zip-all' or args.command == 'zip':
469
+ elif args.command == 'zip-all' or args.command == 'zip' or args.command == 'zip-compress':
358
470
  if len(args.extra) >= 2:
359
471
  output = args.extra[0]
360
- sources = args.extra[1:] # All other arguments as sources to be zipped
361
- source_params = ' '.join(sources)
362
- 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])
363
474
  else:
364
475
  print('Please provide an output file name and at least one source to compress.')
365
476
 
@@ -371,17 +482,17 @@ def main():
371
482
 
372
483
  if '*' in source:
373
484
  # Handle wildcard batch conversion
374
- source_files = glob.glob(source)
485
+ source_files = sorted(glob.glob(source))
375
486
  destination_dir = os.path.dirname(destination)
376
487
  file_extension = destination.split('.')[-1]
377
488
 
378
489
  for src_file in source_files:
379
490
  basename = os.path.splitext(os.path.basename(src_file))[0]
380
491
  dest_file = os.path.join(destination_dir, f'{basename}.{file_extension}')
381
- os.system(f'ffmpeg -i "{src_file}" -c copy "{dest_file}" -y')
492
+ run_cmd(['ffmpeg', '-i', src_file, '-c', 'copy', dest_file, '-y'])
382
493
  else:
383
494
  # Handle single file conversion
384
- os.system(f'ffmpeg -i "{source}" -c copy "{destination}" -y')
495
+ run_cmd(['ffmpeg', '-i', source, '-c', 'copy', destination, '-y'])
385
496
  else:
386
497
  print('Usage: convert-video [source file or pattern] [destination file or pattern]')
387
498
 
@@ -1,3 +1,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: linux-command
3
+ Version: 0.3.1
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.
@@ -1,5 +1,6 @@
1
1
  LICENSE
2
2
  README.md
3
+ pyproject.toml
3
4
  setup.py
4
5
  linux_command/__init__.py
5
6
  linux_command/linux_command.py
@@ -7,4 +8,5 @@ linux_command.egg-info/PKG-INFO
7
8
  linux_command.egg-info/SOURCES.txt
8
9
  linux_command.egg-info/dependency_links.txt
9
10
  linux_command.egg-info/entry_points.txt
10
- linux_command.egg-info/top_level.txt
11
+ linux_command.egg-info/top_level.txt
12
+ tests/test_linux_command.py
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=40.8.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,46 @@
1
+ from pathlib import Path
2
+ from setuptools import setup, find_packages
3
+
4
+
5
+ def read_metadata():
6
+ content = Path(__file__).parent.joinpath("linux_command", "linux_command.py").read_text(encoding="utf-8")
7
+ version = None
8
+ project_url = None
9
+ for line in content.splitlines():
10
+ if line.startswith("VERSION ="):
11
+ version = line.split("=", 1)[1].strip().strip('"').strip("'")
12
+ if line.startswith("PROJECT_URL ="):
13
+ project_url = line.split("=", 1)[1].strip().strip('"').strip("'")
14
+ if version is None:
15
+ raise RuntimeError("VERSION not found in linux_command.py")
16
+ if project_url is None:
17
+ raise RuntimeError("PROJECT_URL not found in linux_command.py")
18
+ return version, project_url
19
+
20
+
21
+ VERSION, PROJECT_URL = read_metadata()
22
+
23
+
24
+ setup(
25
+ name='linux-command',
26
+ version=VERSION,
27
+ packages=find_packages(),
28
+ entry_points={
29
+ 'console_scripts': [
30
+ 'cmd=linux_command.linux_command:main',
31
+ ],
32
+ },
33
+ install_requires=[],
34
+ author='Mouxiao Huang',
35
+ author_email='huangmouxiao@gmail.com',
36
+ description='A command line tool to perform custom tasks.',
37
+ long_description=open('README.md').read(),
38
+ long_description_content_type='text/markdown',
39
+ url=PROJECT_URL,
40
+ classifiers=[
41
+ 'Programming Language :: Python :: 3',
42
+ 'License :: OSI Approved :: MIT License',
43
+ 'Operating System :: OS Independent',
44
+ ],
45
+ python_requires='>=3.6',
46
+ )
@@ -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"]
@@ -1,27 +0,0 @@
1
- from setuptools import setup, find_packages
2
- from linux_command.linux_command import VERSION, PROJECT_URL
3
-
4
-
5
- setup(
6
- name='linux-command',
7
- version=VERSION,
8
- packages=find_packages(),
9
- entry_points={
10
- 'console_scripts': [
11
- 'cmd=linux_command.linux_command:main',
12
- ],
13
- },
14
- install_requires=[],
15
- author='Mouxiao Huang',
16
- author_email='huangmouxiao@gmail.com',
17
- description='A command line tool to perform custom tasks.',
18
- long_description=open('README.md').read(),
19
- long_description_content_type='text/markdown',
20
- url=PROJECT_URL,
21
- classifiers=[
22
- 'Programming Language :: Python :: 3',
23
- 'License :: OSI Approved :: MIT License',
24
- 'Operating System :: OS Independent',
25
- ],
26
- python_requires='>=3.6',
27
- )
File without changes
File without changes