├── .gitignore
├── .idea
├── Pysh.iml
├── deployment.xml
├── dictionaries
│ └── Vence.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── misc.xml
├── modules.xml
├── vcs.xml
└── workspace.xml
├── Pipfile
├── Pipfile.lock
├── README.md
├── pysh.png
├── pysh.py
└── pysh
├── __init__.py
├── command
├── __init__.py
├── cd.py
├── echo.py
├── exit.py
├── hash.py
├── help.py
├── history.py
├── kill.py
├── ls.py
├── ps.py
└── pysh.py
├── contrib
├── LineEdit
│ ├── __init__.py
│ ├── action.py
│ └── line_edit.py
├── __init__.py
├── control.py
├── control_flow.py
├── keyword.py
├── parser.py
└── tools.py
└── manage
├── __init__.py
├── dispatch.py
├── env.py
└── middleware.py
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | *.pyc
3 | .idea
--------------------------------------------------------------------------------
/.idea/Pysh.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/deployment.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.idea/dictionaries/Vence.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | arian
5 | arianx
6 | baka
7 | bksp
8 | contexte
9 | contextlib
10 | curdir
11 | derc
12 | getch
13 | maxlen
14 | offseted
15 | pids
16 | pycache
17 | pysh
18 | readline
19 | redirector
20 | resutls
21 | stdin
22 | stdouttemp
23 | userprofile
24 |
25 |
26 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | deque
98 | dispatch
99 | |
100 | home
101 | clear_prev_char
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 | true
149 | DEFINITION_ORDER
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 | 1527828886709
325 |
326 |
327 | 1527828886709
328 |
329 |
330 | 1528195994914
331 |
332 |
333 |
334 | 1528195994914
335 |
336 |
337 | 1528197677225
338 |
339 |
340 |
341 | 1528197677225
342 |
343 |
344 | 1528201826507
345 |
346 |
347 |
348 | 1528201826507
349 |
350 |
351 | 1528471430521
352 |
353 |
354 |
355 | 1528471430521
356 |
357 |
358 | 1528554516617
359 |
360 |
361 |
362 | 1528554516618
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 | sys
500 | Python
501 | EXPRESSION
502 |
503 |
504 | Variable
505 | Python
506 | EXPRESSION
507 |
508 |
509 | self
510 | Python
511 | EXPRESSION
512 |
513 |
514 | sa
515 | Python
516 | EXPRESSION
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 |
621 |
622 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 |
641 |
642 |
643 |
644 |
645 |
646 |
647 |
648 |
649 |
650 |
651 |
652 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
747 |
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 |
757 |
758 |
759 |
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 |
772 |
773 |
774 |
775 |
776 |
777 |
778 |
779 |
780 |
781 |
782 |
783 |
784 |
785 |
786 |
787 |
788 |
789 |
790 |
791 |
792 |
--------------------------------------------------------------------------------
/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 |
3 | url = "https://pypi.python.org/simple"
4 | verify_ssl = true
5 | name = "pypi"
6 |
7 |
8 | [packages]
9 |
10 |
11 |
12 | [dev-packages]
13 |
14 |
15 |
16 | [requires]
17 |
18 | python_version = "3.6"
19 |
--------------------------------------------------------------------------------
/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "4e55147db217bb4120f6e68cb8cad7bc37011457441ce0eb9d97308315625834"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.6"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.python.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {},
19 | "develop": {}
20 | }
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### Simple interactive shell script powered by python
2 |
3 | Features:
4 | - High scalability. You can quickly extend the functions in the shell by registering to the framework.
5 | - Pure python. No external libraries are required
6 |
7 | Usage:
8 | 1. Clone this repository.
9 | 2. Enter the directory.
10 | 3. Run `python3 .\pysh.py`.
11 | 4. Type the `help` to get the help info.
12 |
--------------------------------------------------------------------------------
/pysh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Arianxx/Pysh/d44f7303bc4ce3764a300830d361115200ad3602/pysh.png
--------------------------------------------------------------------------------
/pysh.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from pysh import Pysh
4 |
5 | if __name__ == '__main__':
6 | Pysh.start()
7 |
--------------------------------------------------------------------------------
/pysh/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | 这里提供内部命令的导入,以及一些外部工具
4 | """
5 | from .command import pysh, exit, ps, history, ls, ps, cd, kill, hash, help, \
6 | echo
7 | from .manage.dispatch import dispatch
8 | from .manage.env import Application
9 |
10 |
11 | class Pysh:
12 | @classmethod
13 | def start(cls):
14 | dispatch.dispatch('pysh')
15 |
16 | class App:
17 | app = Application()
18 |
19 | register = app.register
--------------------------------------------------------------------------------
/pysh/command/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Arianxx/Pysh/d44f7303bc4ce3764a300830d361115200ad3602/pysh/command/__init__.py
--------------------------------------------------------------------------------
/pysh/command/cd.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import os
3 |
4 | from ..manage.env import Application
5 |
6 | app = Application()
7 |
8 | @app.register
9 | class cd:
10 | help = None
11 | path = None
12 | usage = """
13 | Usage:
14 | cd path:改变当前工作目录为 path
15 | cd --help:显示这个帮助信息
16 | """
17 | def __init__(self, *args):
18 | if '--help' in args:
19 | self.help = True
20 | else:
21 | try:
22 | self.path = args[0].strip('"').strip("'")
23 | except IndexError as e:
24 | print('没有提供路径')
25 |
26 | def handler(self):
27 | if self.path:
28 | try:
29 | os.chdir(self.path)
30 | except FileNotFoundError as e:
31 | print(e)
32 | return False
33 | except NotADirectoryError as e:
34 | print(e)
35 | return False
36 | elif self.help:
37 | print(self.usage)
38 |
39 | return True
40 |
--------------------------------------------------------------------------------
/pysh/command/echo.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from ..manage.env import Application
3 |
4 | app = Application()
5 |
6 |
7 | @app.register
8 | class echo:
9 | help = False
10 | usage = """
11 | Usage:
12 | echo [arg1] [arg2] ...:依次打印参数
13 | echo --help(default):打印本帮助
14 | """
15 |
16 | def __init__(self, *args):
17 | self.args = args or []
18 | if not self.args or '--help' in self.args:
19 | self.help = True
20 |
21 | def handler(self):
22 | if self.help:
23 | print(self.usage)
24 | else:
25 | for arg in self.args:
26 | if arg.startswith("'"):
27 | arg = arg.strip("'")
28 | elif arg.startswith('"'):
29 | arg = arg.strip('"')
30 |
31 | print(arg, sep=' ')
32 |
33 | return True
34 |
--------------------------------------------------------------------------------
/pysh/command/exit.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from ..manage.env import Application
3 |
4 | app = Application()
5 |
6 | class ShellExit(SystemExit):
7 | pass
8 |
9 | @app.register
10 | class exit:
11 | help = None
12 | usage = """
13 | Usage:
14 | 退出当前shell
15 | """
16 | def __init__(self, *args):
17 | self.args = args or []
18 |
19 | if '--help' in args:
20 | self.help = True
21 |
22 | def handler(self):
23 | if self.help:
24 | print(self.usage)
25 | else:
26 | raise ShellExit
--------------------------------------------------------------------------------
/pysh/command/hash.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from ..manage.env import Application, EnvVariable
3 |
4 | app = Application()
5 |
6 |
7 | @app.register
8 | class hash:
9 | clear = False
10 | show = False
11 | help = False
12 | usage = """
13 | Usage:
14 | hash -l:查看缓存的路径
15 | hash -r: 清空缓存的外部命令路径
16 | hash --help(default):显示本帮助
17 | """
18 |
19 | def __init__(self, *args):
20 | self.args = args or []
21 | if not self.args or '--help' in self.args or not self.args:
22 | self.help = True
23 | elif '-l' in self.args:
24 | self.show = True
25 | elif '-r' in self.args:
26 | self.clear = True
27 |
28 | def handler(self):
29 | rv = True
30 |
31 | if self.help:
32 | print(self.usage)
33 | elif self.show:
34 | for name, path in EnvVariable.cached.items():
35 | print(name + ' : ' + path)
36 | elif self.clear:
37 | length = len(EnvVariable.cached)
38 | if not EnvVariable.clear_cached():
39 | rv = False
40 | else:
41 | print('已经清空 {} 外部命令路径'.format(length))
42 |
43 | return rv
44 |
--------------------------------------------------------------------------------
/pysh/command/help.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from ..contrib.LineEdit import KeyMapping
3 | from ..contrib.keyword import Symbol, Keyword
4 | from ..manage.env import Application
5 |
6 | app = Application()
7 |
8 |
9 | @app.register
10 | class help:
11 | verbose_1 = False
12 | verbose_2 = False
13 | verbose_3 = False
14 | le = False
15 | help = False
16 | usage = """
17 | Usage:
18 | help(default):显示本帮助
19 | help -v:显示注册的命令
20 | help -vv:显示注册的命令,以及它们各自的用法(如果有)
21 | help -vvv:显示注册的命令、用法,以及内建的特殊符号和关键字
22 | help -le: 显示行编辑字符
23 | """
24 |
25 | def __init__(self, *args):
26 | self.args = args or []
27 | if '-v' in self.args:
28 | self.verbose_1 = True
29 | elif '-vv' in self.args:
30 | self.verbose_2 = True
31 | elif '-vvv' in self.args:
32 | self.verbose_3 = True
33 | elif '-le' in self.args:
34 | self.le = True
35 | else:
36 | self.help = True
37 |
38 | def handler(self):
39 | if self.help:
40 | print(self.usage)
41 | elif self.verbose_1:
42 | apps = Application.registered_app()
43 | print('注册的命令有:')
44 | for app in apps:
45 | print(app)
46 | elif self.verbose_2:
47 | apps = Application.registered_app()
48 | print('注册的命令有:')
49 | for name, instance in apps.items():
50 | print('\n', name)
51 | try:
52 | print('\t', instance.usage)
53 | except AttributeError:
54 | pass
55 | elif self.verbose_3:
56 | symbols = Symbol.registered_symbol()
57 | keywords = Keyword.registered_keyword()
58 | apps = Application.registered_app()
59 |
60 | print('注册的命令有:')
61 | for name, instance in apps.items():
62 | print('\n', name)
63 | try:
64 | print('\t', instance.usage)
65 | except AttributeError:
66 | pass
67 |
68 | print('\n注册的特殊符号有:')
69 | for key in symbols:
70 | print(key, ':', symbols[key].char)
71 | try:
72 | print('\tderc: ', symbols[key].derc)
73 | except AttributeError:
74 | pass
75 |
76 | print('\n注册的关键字有:')
77 | for key in keywords:
78 | print(key, ':', keywords[key].words)
79 | try:
80 | print('\tderc:', keywords[key].derc)
81 | except AttributeError:
82 | pass
83 | elif self.le:
84 | mapping = KeyMapping._get_map_dict()
85 |
86 | print('\n行编辑字符:')
87 | for key, value in mapping.items():
88 | print(key, ' : ', value.encode())
89 |
90 | return True
91 |
--------------------------------------------------------------------------------
/pysh/command/history.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from ..manage.env import Application
3 |
4 | app = Application()
5 |
6 | @app.register
7 | class history:
8 | num = None
9 | maxlen = None
10 | new_maxlen = None
11 | clear = None
12 | clear_num = None
13 | help = None
14 | usage = """
15 | Usage:
16 | history [num]:查看num条数量的历史命令,num默认为100
17 | history --maxlen [num]:如果没有num,查看能容纳的最大历史数量;如果有num,更新maxlen为num
18 | history --clear [num]:清空最近num条数量的历史记录,num默认为maxlen
19 | history --help: 显示这条帮助信息
20 | """
21 |
22 | def __init__(self, *args):
23 | self.args = args
24 |
25 | if '--help' in self.args:
26 | self.help = True
27 | elif '--maxlen' in self.args:
28 | self.maxlen = True
29 | if len(self.args) > 1:
30 | maxlen_index = self.args.index('--maxlen')
31 | self.new_maxlen = self.args[maxlen_index + 1]
32 | elif '--clear' in self.args:
33 | self.clear = True
34 | if len(self.args) > 1:
35 | clear_num_index = self.args.index('--clear')
36 | self.clear_num = self.args[clear_num_index + 1]
37 | else:
38 | if len(self.args) > 0:
39 | try:
40 | self.num = int(self.args[0])
41 | except ValueError as e:
42 | print(e)
43 | self.num = 0
44 |
45 | def handler(self):
46 | History = self.env['History']
47 |
48 | if self.help:
49 | print(self.usage)
50 | elif self.maxlen:
51 | if self.new_maxlen is not None:
52 | History.new_maxlen(self.new_maxlen)
53 | else:
54 | print(History.maxlen())
55 | elif self.clear:
56 | if self.clear_num is not None:
57 | for _ in range(self.clear_num):
58 | History.history.pop()
59 | else:
60 | History.history.clear()
61 | else:
62 | if self.num is not None:
63 | length = len(History.history)
64 | for index in range(length, length - self.num, -1):
65 | print(History.history[index-1])
66 | else:
67 | for h in History.history:
68 | print(h)
69 |
70 | return True
71 |
--------------------------------------------------------------------------------
/pysh/command/kill.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from ..manage.env import Application
3 |
4 | app = Application()
5 |
6 |
7 | @app.register
8 | class kill:
9 | help = False
10 | usage = """
11 | Usage:
12 | kill [pid1] [pid2] ...:杀死给出的进程
13 | kill --help(default):显示本帮助
14 | """
15 |
16 | def __init__(self, *args):
17 | self.args = args or []
18 | if '--help' in self.args or not self.args:
19 | self.help = True
20 |
21 | def handler(self):
22 | if self.help:
23 | print(self.usage)
24 | else:
25 | process = self.env['Processing'].get_records()
26 | ids = list(map(int, self.args))
27 | for id in ids:
28 | if id not in process.keys():
29 | print('{} 是一个无效的pid.'.format(str(id)))
30 | elif not process[id]['instance'].backend:
31 | print('{} 是一个前台命令,你不能终止它.'.format(str(id)))
32 | else:
33 | try:
34 | process[id]['instance'].terminate()
35 | except AttributeError as e:
36 | # 杀死当前阻塞的pysh进程时,会弹出AttributeError
37 | print('你不能杀死阻塞中的pysh进程。请使用 exit 退出。')
38 | else:
39 | print('pid 为 {} 的进程 {} 已经被你终止.'.format(
40 | str(id),
41 | process[id]['NAME'],
42 | ))
43 |
44 | return True
45 |
--------------------------------------------------------------------------------
/pysh/command/ls.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import os
3 |
4 | from ..manage.env import Application
5 |
6 | app = Application()
7 |
8 |
9 | @app.register
10 | class ls:
11 | almost_all = True
12 | all = None
13 | ignore_back_backups = None
14 | list = None
15 | reverse = None
16 | help = None
17 | paths = None
18 | usage = """
19 | Usage:
20 | ls [optional] [path1] [path2] ...
21 | Optional:
22 | ls -A, -almost-all: 列出除了 . 和 .. 的所有文件
23 | ls -a, -all: 列出目录下的所有文件,包括以. 开头的隐含文件
24 | ls -B, -ignore-back-backups:不列出任何以 ~ 字符结束的文件
25 | ls -l, -list:列排列输出
26 | ls -r, -reverse:以相反顺序排序
27 | ls --help:列出这份帮助说明
28 | """
29 |
30 | def __init__(self, *args):
31 | self.args = args or []
32 | if '-A' in self.args or '-almost-all' in self.args:
33 | self.almost_all = True
34 |
35 | if '-a' in self.args or '-all' in self.args:
36 | self.all = True
37 |
38 | if '-B' in self.args or '-ignore-back-backups' in self.args:
39 | self.ignore_back_backups = True
40 |
41 | if '-r' in self.args or '-reverse' in self.args:
42 | self.reverse = True
43 |
44 | if '-l' in self.args or '-list' in self.args:
45 | self.list = True
46 |
47 | if '--help' in self.args:
48 | self.help = True
49 |
50 | self.paths = []
51 | path_start = None
52 | for arg in self.args:
53 | if '-' not in arg:
54 | path_start = args.index(arg)
55 | break
56 | if path_start is not None:
57 | self.paths = self.args[path_start:]
58 |
59 | if not len(self.paths):
60 | self.paths += ['.']
61 |
62 | def handler(self):
63 | if self.help:
64 | print(self.usage)
65 | return
66 |
67 | for path in self.paths:
68 | path = path.strip('"').strip("'")
69 | try:
70 | dirs = os.listdir(path)
71 | except FileNotFoundError as e:
72 | print(e)
73 | return False
74 | except NotADirectoryError as e:
75 | print(e)
76 | return False
77 | except OSError as e:
78 | print(e)
79 | return False
80 | else:
81 | if self.ignore_back_backups:
82 | dirs = [dir for dir in dirs if not dir.endswith('~')]
83 | if self.all:
84 | dirs = ['.', '..'] + dirs
85 | if self.almost_all:
86 | dirs = [dir for dir in dirs if not dir.startswith('.')]
87 | if self.reverse:
88 | dirs.reverse()
89 |
90 | if self.list:
91 | print('\n'.join(dirs))
92 | else:
93 | print(' '.join(dirs))
94 | return True
95 |
--------------------------------------------------------------------------------
/pysh/command/ps.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from ..manage.env import Application
3 |
4 | app = Application()
5 |
6 | @app.register
7 | class ps:
8 | usage = """
9 | Usage:
10 | ps:查看所有运行中的命令。
11 | ps [name] [name] ...:查看给出名字的运行的命令。
12 | ps --help
13 | """
14 |
15 | def __init__(self, *args):
16 | self.args = args
17 |
18 | def handler(self):
19 | if '--help' in self.args:
20 | print(self.usage)
21 | return True
22 | else:
23 | process = self.env['Processing'].get_records()
24 | print('{:<10}{:<10}{:<10}{:<10}'.format('PID', 'NAME', 'BACKEND', 'TIME'))
25 | if len(self.args) > 0:
26 | pids = []
27 | for arg in self.args:
28 | pids += [pid
29 | for pid in process.keys()
30 | if process[pid]['NAME'] == arg]
31 | else:
32 | pids = [pid for pid in process.keys()]
33 |
34 | for pid in pids:
35 | print('{:<10}{:<10}{:<10}{:<10}'.format(
36 | pid,
37 | process[pid]['NAME'],
38 | str(process[pid]['instance'].backend),
39 | str(process[pid]['TIME']),
40 | ))
41 |
42 | return True
43 |
--------------------------------------------------------------------------------
/pysh/command/pysh.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | shell本身也是一个应用,可以通过各种方式访问和控制。初次启动时,直接通过dispatch模块启动shell。
4 |
5 | TODO: 将参数判断抽象出来,成为单独的一层,应用只需注册参数类型和列表即可,具体判断交由上层执行。
6 | """
7 |
8 | import os
9 | import sys
10 |
11 | from .exit import ShellExit
12 | from ..contrib.LineEdit import LineInput
13 | from ..contrib.parser import Parser
14 | from ..manage.env import Application
15 |
16 | app = Application()
17 |
18 | @app.register
19 | class pysh:
20 | line_symbol = '#'
21 | logout = 'logout'
22 | slogan = """
23 | *******************************
24 |
25 | Welcome to Pysh!
26 | Type 'help' to get the help.
27 | Copyright (c) 2018 ArianX
28 |
29 | *******************************
30 | """
31 | usage = """
32 | Usage:
33 | pysh:启动pysh shell
34 | """
35 |
36 | def __init__(self, *args):
37 | pass
38 |
39 | def handler(self):
40 | print(self.slogan)
41 |
42 | self._poll()
43 |
44 | return True
45 |
46 | def _poll(self):
47 | global input
48 |
49 | if sys.platform == 'win32':
50 | print('win下行编辑退格时有奇怪的错误,已经关闭行编辑功能')
51 | print('切换至linux平台体验行编辑(WSL也不行\n')
52 | else:
53 | input = LineInput
54 |
55 | while True:
56 | line_slogan = self.line_symbol + self.curdir + '$'
57 | try:
58 | raw_command = input(line_slogan).strip()
59 | except KeyboardInterrupt:
60 | print('\n',self.logout)
61 | break
62 |
63 | if not raw_command:
64 | continue
65 | else:
66 | parser = Parser(raw_command)
67 |
68 | try:
69 | parser.run()
70 | except EOFError:
71 | # 如果是输入流被重定向到文件,文件读尽时会弹出这个错误,代表执行完毕,退出shell
72 | raise ShellExit
73 | except ShellExit:
74 | print(self.logout)
75 | break
76 |
77 | return True
78 |
79 | @property
80 | def curdir(self):
81 | path = os.path.abspath(os.path.curdir)
82 | path = path.strip('C:').replace("\\", '/')
83 | return path
84 |
--------------------------------------------------------------------------------
/pysh/contrib/LineEdit/__init__.py:
--------------------------------------------------------------------------------
1 | from .action import *
2 | from .line_edit import LineInput, KeyMapping
3 |
--------------------------------------------------------------------------------
/pysh/contrib/LineEdit/action.py:
--------------------------------------------------------------------------------
1 | import itertools
2 | import sys
3 |
4 | from pysh.command.exit import ShellExit
5 | from pysh.manage.middleware import Completer
6 | from .line_edit import LineInput, KeyMapping, show_history, LineEndError
7 |
8 |
9 | @LineInput.add_action([KeyMapping.end1, KeyMapping.end2])
10 | def line_end(buffer):
11 | raise LineEndError()
12 |
13 |
14 | @LineInput.add_action([KeyMapping.bksp, KeyMapping.clear_prev_char])
15 | def bksp(buffer):
16 | """
17 | 退格删除
18 | """
19 | buffer.pop()
20 |
21 |
22 | @LineInput.add_action([KeyMapping.completion])
23 | def completion(buffer):
24 | """
25 | Tab键命令补全
26 | """
27 | if not buffer.content:
28 | return
29 |
30 | text = ''.join(buffer.content)
31 | results = []
32 | for num in itertools.count():
33 | result = Completer.search_symbol(text, num)
34 | if not result:
35 | break
36 | else:
37 | results.append(result)
38 |
39 | results = list(set(results))
40 |
41 | if len(results) == 1:
42 | buffer.clear()
43 | buffer.content = list(results[0])
44 | elif not results:
45 | return False
46 | else:
47 | print('\n')
48 | for result in results:
49 | print(result, end=' ')
50 | print('\n')
51 |
52 | print(buffer.slogan, ':', text, sep='', end='')
53 | sys.stdout.flush()
54 |
55 | return True
56 |
57 |
58 | @LineInput.add_action([KeyMapping.cursor_to_prev, KeyMapping.left])
59 | def left(buffer):
60 | """
61 | 左方向键,光标左移。
62 | """
63 | if buffer.offset < len(buffer.content):
64 | buffer.offset += 1
65 |
66 |
67 | @LineInput.add_action([KeyMapping.cursor_to_next, KeyMapping.right])
68 | def right(buffer):
69 | """
70 | 右方向键,光标右移
71 | """
72 | if buffer.offset > 0:
73 | buffer.offset -= 1
74 |
75 |
76 | @LineInput.add_action([KeyMapping.prev_history, KeyMapping.up])
77 | def up(buffer):
78 | """
79 | 上方向键,上一条历史记录
80 | """
81 | show_history(buffer, 1)
82 |
83 |
84 | @LineInput.add_action([KeyMapping.next_history, KeyMapping.down])
85 | def down(cls, buffer):
86 | """
87 | 下方向键,下一条历史记录
88 | """
89 | show_history(buffer, 0)
90 |
91 |
92 | @LineInput.add_action([KeyMapping.clear_prev_cursor])
93 | def clear_prev_cursor(buffer):
94 | """
95 | 清除光标之前的内容
96 | """
97 | length = len(buffer.content)
98 | buffer._prev_remove = buffer.content[:length - buffer.offset]
99 | buffer.content = buffer.content[length - buffer.offset:]
100 |
101 |
102 | @LineInput.add_action([KeyMapping.recover_content])
103 | def recover_content(buffer):
104 | """
105 | 恢复之前清除的内容
106 | """
107 | if buffer._prev_remove:
108 | buffer.content = buffer._prev_remove + buffer.content
109 |
110 |
111 | @LineInput.add_action([KeyMapping.cursor_to_top])
112 | def cursor_to_top(buffer):
113 | """
114 | 移动光标到行首
115 | """
116 | buffer.offset = len(buffer.content)
117 |
118 |
119 | @LineInput.add_action([KeyMapping.cursor_to_end])
120 | def cursor_to_end(buffer):
121 | """
122 | 移动光标到行尾
123 | """
124 | buffer.offset = 0
125 |
126 |
127 | @LineInput.add_action([KeyMapping.clear_prev_word])
128 | def clear_prev_word(buffer):
129 | """
130 | 清除光标之前的一个单词
131 | """
132 | index = len(buffer.content) - buffer.offset - 1
133 | while index >= 0:
134 | if buffer.content[index] != ' ' \
135 | and buffer.content[index] != '\t':
136 | buffer.content.pop(index)
137 | else:
138 | break
139 |
140 | index -= 1
141 |
142 |
143 | @LineInput.add_action([KeyMapping.clear_cursor_to_end])
144 | def clear_cursor_to_end(buffer):
145 | """
146 | 清除光标之后的内容
147 | """
148 | if buffer.offset:
149 | buffer.content = buffer.content[:-buffer.offset]
150 |
151 |
152 | @LineInput.add_action([KeyMapping.swap_two_char])
153 | def swap_two_char(buffer):
154 | """
155 | 交换光标之前的两个字符
156 | """
157 | index = len(buffer.content) - buffer.offset - 1
158 | buffer.content[index], buffer.content[index - 1] = buffer.content[index - 1], buffer.content[index]
159 |
160 |
161 | @LineInput.add_action([KeyMapping.exit_shell, KeyMapping.exit_program])
162 | def exit_shell(buffer):
163 | """
164 | 退出shell
165 | """
166 | print('\n')
167 | raise ShellExit
168 |
--------------------------------------------------------------------------------
/pysh/contrib/LineEdit/line_edit.py:
--------------------------------------------------------------------------------
1 | """
2 | 实现行编辑的模块。
3 | 用这个模块中的LineInput类替换标准的input函数以在想要实现可行编辑的地方使用行编辑。
4 | """
5 | import re
6 | import sys
7 | from collections import defaultdict
8 |
9 | from pysh.contrib.tools import getch, LineEndError
10 | from pysh.manage.env import History
11 |
12 |
13 | class Buffer:
14 | """
15 | 存放当前编辑行的信息
16 | """
17 |
18 | def __init__(self):
19 | # 光标位置。离末尾字符的偏移量
20 | self.offset = 0
21 | # 已经打印出来,显示在屏幕上的字符
22 | self.showed = 0
23 | # 已经打印出的光标的偏移量
24 | self.offseted = 0
25 | # 组合键缓冲区
26 | self._group_key = ''
27 | # 处于历史记录的位置
28 | self._history_index = -1
29 | # 前一次ctrl + U删除的内容
30 | self._prev_remove = []
31 | # 缓冲区的字符
32 | self.content = []
33 |
34 | def _group_key_buffer(self, key):
35 | """
36 | 部分键比较特殊,是字符组合形式。比如按一下方向键产生三个字符
37 | 所以为了识别这类键建立一个特殊的方法。
38 | """
39 | if key == KeyMapping.esc:
40 | self._group_key = KeyMapping.esc
41 | return False
42 | elif self._group_key.startswith(KeyMapping.esc):
43 | self._group_key += key
44 |
45 | if self._group_key == KeyMapping.left:
46 | self.pop()
47 | self._group_key = ''
48 | return KeyMapping.left
49 | elif self._group_key == KeyMapping.right:
50 | self.pop()
51 | self._group_key = ''
52 | return KeyMapping.right
53 | elif self._group_key == KeyMapping.up:
54 | self.pop()
55 | self._group_key = ''
56 | return KeyMapping.up
57 | elif self._group_key == KeyMapping.down:
58 | self.pop()
59 | self._group_key = ''
60 | return KeyMapping.down
61 | elif self._group_key == KeyMapping.home:
62 | self.pop()
63 | self.pop()
64 | self._group_key = ''
65 | return KeyMapping.home
66 | elif self._group_key == KeyMapping.end:
67 | self.pop()
68 | self.pop()
69 | self._group_key = ''
70 | return KeyMapping.end
71 |
72 | if len(self._group_key) >= 4:
73 | self._group_key = ''
74 |
75 | return False
76 |
77 | def flush(self):
78 | result = ''.join(self.content)
79 | self.offset = 0
80 | self.content.clear()
81 | return result
82 |
83 | def clear(self):
84 | self.pos = 0
85 | self.offset = 0
86 | self.content.clear()
87 |
88 | def add(self, value):
89 | self.content.append(value)
90 | return True
91 |
92 | def pop(self):
93 | index = len(self.content) - self.offset - 1
94 | if index >= 0:
95 | return self.content.pop(index)
96 | else:
97 | pass
98 |
99 | def insert(self, value, offset=None):
100 | if not offset:
101 | self.content.append(value)
102 | else:
103 | self.content.insert(len(self.content) - offset, value)
104 |
105 | def show(self):
106 | print(' ' * self.offseted, sep='', end='')
107 | print('\x08' * self.showed, ' ' * self.showed, '\x08' * self.showed, sep='', end='')
108 | content = ''.join(self.content)
109 | print(content, sep='', end='')
110 | print('\x08' * self.offset, sep='', end='')
111 | self.showed = len(content)
112 | self.offseted = self.offset
113 | sys.stdout.flush()
114 |
115 |
116 | class KeyMapping:
117 | end1 = '\n'
118 | end2 = '\r'
119 | bksp = '\x08' # 退格
120 | completion = '\t' # tab
121 | esc = '\x1b'
122 | left = '\x1b[D'
123 | right = '\x1b[C'
124 | up = '\x1b[A'
125 | down = '\x1b[B'
126 | clear_prev_cursor = '\x15' # ctrl + u
127 | clear_prev_char = '\x08' # ctrl + h
128 | exit_shell = '\x04' # ctrl + d
129 | exit_program = '\x03' # ctrl + c
130 | prev_history = '\x10' # ctrl + p
131 | next_history = '\x0e' # ctrl + n
132 | recover_content = '\x19' # ctrl + y
133 | cursor_to_top = '\x01' # ctrl + a
134 | cursor_to_end = '\x05' # ctrl + e
135 | clear_prev_word = '\x17' # ctrl + w
136 | clear_cursor_to_end = '\x0b' # ctrl + k
137 | swap_two_char = '\x14' # ctrl + t
138 | cursor_to_next = '\x06' # ctrl + f
139 | cursor_to_prev = '\x02' # ctrl + b
140 | home = '\x1b[1~' # home键
141 | end = '\x1b[4~' # end键
142 |
143 | direct_prev_history = '!!'
144 | prev_n_history = r'^!-(?P\d+)$'
145 | echo_prev_history = r'^!-(?P\d+):p$'
146 | last_like_history = r'^!\?(?P[a-zA-Z0-9]+)\?$'
147 |
148 | @classmethod
149 | def _get_map_dict(cls):
150 | return {name: getattr(cls, name) for name in dir(cls) \
151 | if not name.startswith('_')}
152 |
153 |
154 | class LineInput:
155 | dispatcher = defaultdict(list)
156 |
157 | @staticmethod
158 | def __new__(cls, slogan):
159 | buffer = Buffer()
160 | buffer.slogan = slogan
161 |
162 | buffer.clear()
163 |
164 | print(slogan, ':', sep='', end='')
165 | sys.stdout.flush()
166 |
167 | while True:
168 | char = getch().strip('\x00')
169 | try:
170 | if not len(char) == 1:
171 | for ch in char:
172 | cls._handler(ch, buffer)
173 | else:
174 | cls._handler(char, buffer)
175 | except LineEndError:
176 | print('\n')
177 | break
178 |
179 | text = cls._command_handler(buffer.flush(), buffer)
180 | return text
181 |
182 | @classmethod
183 | def add_action(cls, symbols=(None)):
184 | def decorator(func):
185 | for symbol in symbols:
186 | cls.dispatcher[symbol].append(func)
187 | return func
188 |
189 | return decorator
190 |
191 | @classmethod
192 | def _handler(cls, char, buffer):
193 | is_group = buffer._group_key_buffer(char)
194 |
195 | if is_group:
196 | char = is_group
197 | elif ord(char) in range(32, 127):
198 | buffer.insert(char, buffer.offset)
199 | elif char not in cls.dispatcher:
200 | char = None
201 |
202 | if char in cls.dispatcher:
203 | for func in cls.dispatcher[char]:
204 | func(buffer)
205 |
206 | buffer.show()
207 |
208 | @classmethod
209 | def _command_handler(cls, text, buffer):
210 | if text == KeyMapping.direct_prev_history:
211 | return cls.direct_prev_history(buffer)
212 |
213 | num = re.findall(KeyMapping.prev_n_history, text)
214 | if num:
215 | num = num[0]
216 | return cls.prev_n_history(num)
217 |
218 | num = re.findall(KeyMapping.echo_prev_history, text)
219 | if num:
220 | num = num[0]
221 | return cls.echo_prev_history(num)
222 |
223 | like_text = re.findall(KeyMapping.last_like_history, text)
224 | if like_text:
225 | like_text = like_text[0]
226 | return cls.last_like_history(like_text)
227 |
228 | return text
229 |
230 | @classmethod
231 | def _get_history(cls, buffer, arrow):
232 | """
233 | 得到指定的历史记录。
234 | arrow为 1 代表下一条历史记录,为 0 代表上一条历史记录
235 | """
236 | length = len(History.history) - 1
237 | arrow = 'prev' if arrow else 'next'
238 | if arrow == 'prev':
239 | if buffer._history_index < length:
240 | buffer._history_index += 1
241 | return History.history[length - buffer._history_index]
242 | else:
243 | return History.history[length]
244 | elif arrow == 'next':
245 | if buffer._history_index > 1:
246 | buffer._history_index -= 1
247 | return History.history[length - buffer._history_index]
248 | else:
249 | return History.history[0]
250 |
251 | @classmethod
252 | def direct_prev_history(cls, buffer):
253 | """
254 | 直接执行上一条历史记录
255 | """
256 | return cls._get_history(buffer, 1)
257 |
258 | @classmethod
259 | def prev_n_history(cls, n):
260 | """
261 | 执行上第n条历史记录
262 | """
263 | index = len(History.history) - int(n)
264 | return History.history[index]
265 |
266 | @classmethod
267 | def echo_prev_history(cls, n):
268 | """
269 | 打印上第n条历史记录不执行
270 | """
271 | index = len(History.history) - int(n)
272 | print(History.history[index])
273 | return ''
274 |
275 | @classmethod
276 | def last_like_history(cls, text):
277 | """
278 | 执行最近一条包含text的历史记录
279 | """
280 | index = len(History.history) - 1
281 | while index >= 0:
282 | if text in History.history[index]:
283 | print(History.history[index])
284 | return History.history[index]
285 |
286 | index -= 1
287 |
288 | return ''
289 |
290 |
291 | def show_history(buffer, arrow):
292 | """
293 | 展示历史记录,arrow代表方向
294 | """
295 | history = LineInput._get_history(buffer, arrow)
296 |
297 | buffer.content = list(history)
298 | buffer.offseted = buffer.offset = 0
299 | return True
300 |
--------------------------------------------------------------------------------
/pysh/contrib/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Arianxx/Pysh/d44f7303bc4ce3764a300830d361115200ad3602/pysh/contrib/__init__.py
--------------------------------------------------------------------------------
/pysh/contrib/control.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | 这里定义具体的各个符号和语句的行为,并由parser.Handler.run进行调用
4 | """
5 | import os
6 | import re
7 | from functools import partial
8 |
9 | from .keyword import Symbol, Keyword
10 | from .parser import Parser, Handler
11 | from ..command import pysh, exit
12 | from ..manage.env import Variable, EnvVariable
13 | from ..manage.middleware import StdoutRedirection, StdinRedirection
14 |
15 |
16 | class SymbolAction:
17 | """
18 | 注册符号行为的类。
19 | no_binding_action收集注册的、尚未绑定实例的类。
20 | """
21 | no_binding_action = {}
22 |
23 | def __init__(self, handler):
24 | """
25 | 实例化时绑定parser.Handle的实例,并将自身与收集的行为函数绑定,以便能够像原始方法那样调用。
26 | """
27 | self.handler = handler
28 | self.action = {}
29 | for name in self.no_binding_action:
30 | self.action[name] = partial(self.no_binding_action[name], self)
31 |
32 | def __getattr__(self, name):
33 | """
34 | 使访问注册的方法能够像访问原始方法一样。
35 | :param name: 注册的方法名
36 | :return:
37 | """
38 | if name in self.action:
39 | return self.action[name]
40 | else:
41 | raise AttributeError(
42 | "{cls.__name__} object has no attribute {name}".format(cls=type(self), name=name)
43 | )
44 |
45 | @classmethod
46 | def register(cls, func):
47 | name = func.__name__
48 | cls.no_binding_action.update(
49 | {
50 | name: func
51 | }
52 | )
53 |
54 |
55 | class KeywordAction:
56 | """
57 | 注册关键字行为的类。
58 | 与SymbolAction类似
59 | """
60 | no_binding_action = {}
61 |
62 | def __init__(self, handler):
63 | self.handler = handler
64 | self.action = {}
65 | for name in self.no_binding_action:
66 | self.action[name] = partial(self.no_binding_action[name], self)
67 |
68 | def __getattr__(self, name):
69 | if name in self.action:
70 | return self.action[name]
71 | else:
72 | raise AttributeError(
73 | "{cls.__name__} object has no attribute {name}".format(cls=type(self), name=name)
74 | )
75 |
76 | @classmethod
77 | def register(cls, func):
78 | name = func.__name__
79 | cls.no_binding_action.update(
80 | {
81 | name: func
82 | }
83 | )
84 |
85 |
86 | # 以下注册符号行为
87 |
88 | @SymbolAction.register
89 | def semicolon(self, tokens):
90 | """
91 | 分割命令。
92 | """
93 | if Symbol.mapping['semicolon'].char in tokens:
94 | tokens_list = Symbol.mapping['semicolon'].cls.handle(tokens)
95 | self.handler.tokens = None
96 | for tokens in tokens_list:
97 | Handler(tokens).run()
98 |
99 | return
100 |
101 |
102 | @SymbolAction.register
103 | def backend(self, tokens):
104 | """
105 | 是否后台执行
106 | """
107 | backend = False
108 | if tokens and tokens[-1] == Symbol.mapping['backend'].char:
109 | self.handler.tokens = Symbol.mapping['backend'].cls.handle(tokens)
110 | backend = True
111 |
112 | return backend
113 |
114 |
115 | @SymbolAction.register
116 | def double_and(self, tokens):
117 | """
118 | 逻辑与
119 | """
120 | if Symbol.mapping['double_and'].char in tokens:
121 | tokens_list = Symbol.mapping['double_and'].cls.handle(tokens)
122 | self.handler.tokens = None
123 | for tokens in tokens_list:
124 | if Handler(tokens).run():
125 | continue
126 | else:
127 | break
128 | return
129 |
130 |
131 | @SymbolAction.register
132 | def dollars(self, tokens):
133 | """
134 | 当前shell id
135 | """
136 | cls = Symbol.mapping['dollars'].cls
137 | self.handler.tokens = cls.handle(tokens)
138 | return
139 |
140 |
141 | @SymbolAction.register
142 | def wave_line(self, tokens):
143 | """
144 | 用户目录
145 | """
146 | cls = Symbol.mapping['wave_line'].cls
147 | self.handler.tokens = cls.handle(tokens)
148 | return
149 |
150 |
151 | @SymbolAction.register
152 | def equal(self, tokens):
153 | """
154 | 变量赋值
155 | """
156 | cls = Symbol.mapping['equal'].cls
157 | vr_mapping, self.handler.tokens = cls.handle(tokens)
158 | Variable.variable.update(vr_mapping)
159 | return
160 |
161 |
162 | @SymbolAction.register
163 | def dollar(self, tokens):
164 | """
165 | 变量展开
166 | """
167 | cls = Symbol.mapping['dollar'].cls
168 | self.handler.tokens = cls.handle(tokens)
169 | return
170 |
171 |
172 | @SymbolAction.register
173 | def right_angel(self, tokens):
174 | """
175 | 原来设想的输入输出重定向阶段,应该在分发应用那里,使用装饰器实现来着。
176 | 可是,后来感觉这样解析参数、传来传去的太麻烦,还是就这样吧……
177 | """
178 | cls = Symbol.mapping['right_angel'].cls
179 | if cls.char in tokens:
180 | file_path, self.handler.tokens = cls.handle(tokens)
181 | try:
182 | # 覆盖文件
183 | sr = StdoutRedirection(file_path, file_override=True)
184 | except FileNotFoundError as e:
185 | print(e)
186 | self.handler.tokens = None
187 | except PermissionError as e:
188 | print(e)
189 | self.handler.tokens = None
190 | else:
191 | with sr.context():
192 | Handler(self.handler.tokens).run()
193 | self.handler.tokens = None
194 | return
195 |
196 |
197 | @SymbolAction.register
198 | def double_right_angel(self, tokens):
199 | """
200 | 重定向
201 | """
202 | cls = Symbol.mapping['double_right_angel'].cls
203 | if cls.char in tokens:
204 | file_path, self.handler.tokens = cls.handle(tokens)
205 | try:
206 | # 不覆盖文件
207 | sr = StdoutRedirection(file_path, file_override=False)
208 | except FileNotFoundError as e:
209 | print(e)
210 | self.handler.tokens = None
211 | except PermissionError as e:
212 | print(e)
213 | self.handler.tokens = None
214 | else:
215 | with sr.context():
216 | Handler(self.handler.tokens).run()
217 | self.handler.tokens = None
218 | return
219 |
220 |
221 | @SymbolAction.register
222 | def left_angel(self, tokens):
223 | """
224 | 重定向输入流
225 | """
226 | cls = Symbol.mapping['left_angel'].cls
227 | if cls.char in tokens:
228 | file_path, self.handler.tokens = cls.handle(tokens)
229 | try:
230 | si = StdinRedirection(file_path=file_path)
231 | except FileNotFoundError as e:
232 | print(e)
233 | self.handler.tokens = None
234 | except PermissionError as e:
235 | print(e)
236 | self.handler.tokens = None
237 | else:
238 | try:
239 | with si.context():
240 | Handler(self.handler.tokens).run()
241 | except UnicodeDecodeError as e:
242 | # 指定一个非文本文件,读取时会弹出此错误
243 | print(e)
244 | finally:
245 | self.handler.tokens = None
246 | return
247 |
248 |
249 | @SymbolAction.register
250 | def double_left_angel(self, tokens):
251 | """
252 | 标记式重定向输入流
253 | """
254 | cls = Symbol.mapping['double_left_angel'].cls
255 | if cls.char in tokens:
256 | tag, tokens = cls.handle(tokens)
257 |
258 | if tag:
259 | pipe = []
260 | while True:
261 | token = input('...')
262 | if token == tag:
263 | break
264 | else:
265 | pipe.append(token + '\n')
266 | si = StdinRedirection(source=pipe)
267 |
268 | try:
269 | with si.context():
270 | Handler(self.handler.tokens).run()
271 | except TypeError as e:
272 | print(e)
273 | finally:
274 | self.handler.tokens = None
275 | return
276 |
277 |
278 | @SymbolAction.register
279 | def back_quote(self, tokens):
280 | """
281 | 命令替代
282 | """
283 | cls = Symbol.mapping['back_quote'].cls
284 | if cls.char in tokens:
285 | sub_tokens = cls.handle(tokens)
286 | if sub_tokens:
287 | for token in sub_tokens:
288 | sr = StdoutRedirection()
289 | with sr.context() as out:
290 | Handler(token).run()
291 |
292 | raw_token = ''.join(out.pipe)
293 | self.handler.raw_token = re.sub('`.*`',
294 | raw_token,
295 | self.handler.raw_token, 1)
296 | Parser(self.handler.raw_token).run()
297 | self.handler.tokens = None
298 | return
299 |
300 |
301 | @SymbolAction.register
302 | def pipe(self, tokens):
303 | """
304 | 前一个命令的输出作为下一个命令的输入
305 | """
306 | cls = Symbol.mapping['pipe'].cls
307 | if cls.char in tokens:
308 | tokens = cls.handle(tokens)
309 | for index, token in enumerate(tokens):
310 | if index == 0:
311 | sr = StdoutRedirection()
312 | with sr.context() as sr:
313 | Handler(token).run()
314 | elif index == len(tokens) - 1:
315 | si = StdinRedirection(source=sr.pipe)
316 | with si.context():
317 | Handler(token).run()
318 | else:
319 | si = StdinRedirection(source=sr.pipe)
320 | sr = StdoutRedirection()
321 | with si.context(), sr.context() as sr:
322 | Handler(token).run()
323 |
324 | self.handler.tokens = None
325 | return
326 |
327 |
328 | # 以下注册关键字行为
329 |
330 |
331 | @KeywordAction.register
332 | def daemon(self, tokens):
333 | """
334 | 守护模式
335 | """
336 | daemon_map = Keyword.mapping['daemon']
337 | daemon = False
338 | if tokens and tokens[-1] == daemon_map.words:
339 | self.handler.tokens = daemon_map.cls.handle(tokens)
340 | daemon = True
341 |
342 | return daemon
343 |
344 |
345 | @KeywordAction.register
346 | def join(self, tokens):
347 | """
348 | 阻塞模式
349 | """
350 | join_map = Keyword.mapping['join']
351 | join = False
352 | if join_map.words in tokens:
353 | self.handler.tokens = join_map.cls.handle(tokens)
354 | join = True
355 |
356 | return join
357 |
358 |
359 | @KeywordAction.register
360 | def export(self, tokens):
361 | """
362 | 设置环境变量
363 | """
364 | export_map = Keyword.mapping['export']
365 | if tokens and export_map.cls.words == tokens[0]:
366 | vr_dict = export_map.cls.handle(tokens)[0]
367 | self.handler.tokens = None
368 |
369 | for key, value in vr_dict.items():
370 | EnvVariable.set_env_variable(key, value)
371 |
372 | return True
373 |
374 |
375 | @KeywordAction.register
376 | def exec(self, tokens):
377 | """
378 | exec命令
379 | """
380 | cls = Keyword.mapping['exec']
381 | if tokens and tokens[0] == cls.words:
382 | tokens.pop(0)
383 | if len(tokens) >= 1:
384 | if tokens[0] == '>':
385 | tokens.pop(0)
386 | file_path = tokens[0]
387 | exec_shell(file_path)
388 | raise exit.ShellExit
389 | else:
390 | Handler(tokens).run()
391 | self.handler.tokens = None
392 | raise exit.ShellExit
393 |
394 | self.handler.tokens = tokens
395 | return
396 |
397 |
398 | # 辅助函数
399 | def exec_shell(file_path):
400 | while True:
401 | path = os.path.abspath(os.path.curdir).strip('C:').replace("\\", '/')
402 | line_slogan = pysh.pysh.line_symbol + path + '$'
403 | raw_command = input(line_slogan).strip()
404 |
405 | sr = StdoutRedirection(file_path, False)
406 |
407 | with sr.context():
408 | if not raw_command:
409 | continue
410 | else:
411 | parser = Parser(raw_command)
412 |
413 | try:
414 | parser.run()
415 | except EOFError:
416 | raise exit.ShellExit
417 | except exit.ShellExit:
418 | break
419 |
420 | return True
421 |
--------------------------------------------------------------------------------
/pysh/contrib/control_flow.py:
--------------------------------------------------------------------------------
1 | """
2 | 实现控制流的模块。
3 | 拦截待分发的命令,解析关键词,根据不同逻辑做出相应调度
4 | """
5 |
6 | class ControlFlow:
7 | storage = {}
8 |
9 | @classmethod
10 | def register(cls, control_cls):
11 | cls.storage.update({
12 | control_cls.__name__:control_cls,
13 | })
14 | return control_cls
15 |
16 |
17 | @ControlFlow.register
18 | class WhileControl:
19 | keyword = 'while'
20 |
21 | def __init__(self):
22 | pass
23 |
--------------------------------------------------------------------------------
/pysh/contrib/keyword.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """"
3 | 注册特殊符号或关键字。对tokens做一些与符号本身相关必要的处理(如清洗),具体的调用逻辑交给parser.Handle,
4 | 复杂的行为交给control.SymbolAction/control.KeywordAction。
5 |
6 | 类应该按照功能命名而非符号。然而自从开始的几个类名没留意后,惯性的力量是巨大的……
7 | """
8 | import os
9 | import re
10 | from collections import namedtuple, OrderedDict
11 |
12 | from ..manage.env import Processing, Variable, EnvVariable
13 |
14 | symbol_mapping = namedtuple('symbol', ['char', 'name', 'derc', 'cls'])
15 | keyword_mapping = namedtuple('keyword', ['words', 'name', 'derc', 'cls'])
16 |
17 | handle_state = namedtuple('state', ['state', 'tokens', 'others'])
18 |
19 |
20 | class Symbol:
21 | """
22 | 注册特殊符号
23 | """
24 | mapping = OrderedDict()
25 |
26 | @classmethod
27 | def register(cls, mapping_cls):
28 | """
29 | 注册特殊符号。
30 |
31 | :param mapping_cls: 遇到特殊符号时将要回调的类。
32 | :return: 返回原类
33 | """
34 | cls.mapping[mapping_cls.name] = symbol_mapping(
35 | mapping_cls.char,
36 | mapping_cls.name,
37 | mapping_cls.derc,
38 | mapping_cls,
39 | )
40 |
41 | return mapping_cls
42 |
43 | @classmethod
44 | def registered_symbol(cls):
45 | return OrderedDict(cls.mapping)
46 |
47 |
48 | class Keyword:
49 | """
50 | 注册关键字
51 | """
52 | mapping = OrderedDict()
53 |
54 | @classmethod
55 | def register(cls, mapping_cls):
56 | """
57 | 注册关键字。
58 |
59 | :param mapping_cls: 映射关键字行为的类。
60 | :return: 返回原类
61 | """
62 | cls.mapping[mapping_cls.name] = keyword_mapping(
63 | mapping_cls.words,
64 | mapping_cls.name,
65 | mapping_cls.derc,
66 | mapping_cls,
67 | )
68 |
69 | return mapping_cls
70 |
71 | @classmethod
72 | def registered_keyword(cls):
73 | return OrderedDict(cls.mapping)
74 |
75 |
76 | # 以下为符号注册
77 |
78 |
79 | @Symbol.register
80 | class Semicolon:
81 | """
82 | 执行多条分明的符号
83 | """
84 | char = ';'
85 | name = 'semicolon'
86 | derc = '分割多条命令'
87 |
88 | @classmethod
89 | def handle(cls, tokens):
90 | """
91 | 返回经cls.chars分割后的tokens列表。
92 |
93 | :param tokens: 等待分割的tokens
94 | :return: 分割后的列表
95 | """
96 | token_list = []
97 |
98 | def split(tokens):
99 | nonlocal token_list
100 | if cls.char in tokens:
101 | flag = tokens.index(cls.char)
102 | token_list.append(tokens[0:flag])
103 | tokens = tokens[flag + 1:]
104 | split(tokens)
105 | else:
106 | if tokens:
107 | token_list.append(tokens)
108 | return
109 |
110 | split(tokens)
111 |
112 | return token_list
113 |
114 |
115 | @Symbol.register
116 | class Backend:
117 | """
118 | 末尾有这个符号即启动子进程后台执行。
119 | """
120 | char = '&'
121 | name = 'backend'
122 | derc = '启动多进程'
123 |
124 | @classmethod
125 | def handle(cls, tokens):
126 | """
127 | 简单的去掉符号并返回,逻辑交给parser中的handler。
128 | :param tokens:
129 | :return:
130 | """
131 | tokens = tokens[:-1]
132 | return tokens
133 |
134 |
135 | @Symbol.register
136 | class DoubleAnd:
137 | """
138 | 逻辑与,只有第一个命令成功返回才执行第二个命令。
139 | """
140 | char = '&&'
141 | name = 'double_and'
142 | derc = '逻辑与'
143 |
144 | @classmethod
145 | def handle(cls, tokens):
146 | """
147 | 像semicolon那样分割列表
148 | :param tokens:
149 | :return:
150 | """
151 | tokens = [
152 | token if token != cls.char else Semicolon.char
153 | for token in tokens
154 | ]
155 |
156 | return Semicolon.handle(tokens)
157 |
158 |
159 | @Symbol.register
160 | class Dollars:
161 | """
162 | 替换为当前shell id.
163 | """
164 | char = '$$'
165 | name = 'dollars'
166 | derc = '当前shell id'
167 |
168 | @classmethod
169 | def handle(cls, tokens):
170 | symbol = cls.char
171 | shell_id = get_shell_id()
172 | tokens = list(map(
173 | lambda token: token.replace(symbol, str(shell_id))
174 | if cls.char in token
175 | else token,
176 | tokens
177 | ))
178 | return tokens
179 |
180 |
181 | @Symbol.register
182 | class WaveLine:
183 | """
184 | 替换为用户目录。
185 | """
186 | char = '~'
187 | name = 'wave_line'
188 | derc = '用户目录'
189 | # win用户目录储存在USERPROFILE环境变量里,linux为HOME
190 | catalog = os.environ.get('USERPROFILE', None) or os.environ.get('HOME', None)
191 |
192 | @classmethod
193 | def handle(cls, tokens):
194 | tokens = list(map(
195 | lambda token: token if token != cls.char
196 | else cls.catalog,
197 | tokens
198 | ))
199 | return tokens
200 |
201 |
202 | @Symbol.register
203 | class Equal:
204 | """
205 | 设置变量的符号
206 | """
207 | char = '='
208 | name = 'equal'
209 | derc = '变量赋值'
210 |
211 | @classmethod
212 | def handle(cls, tokens):
213 | re_str = r'^[a-zA-Z][a-zA-Z1-9]*=.+$'
214 | variable_mapping = {}
215 | for token in list(tokens):
216 | # 如果都是设置变量的语句,就记录
217 | if re.match(re_str, token):
218 | vr_pair = token.split('=')
219 | variable_mapping.update({vr_pair[0]: vr_pair[1]})
220 | tokens.pop(tokens.index(token))
221 | else:
222 | # 如果后面有其它语句,前面的设置变量的语句就无效
223 | # 模仿bash的行为
224 | variable_mapping.clear()
225 | break
226 | return (variable_mapping, tokens)
227 |
228 |
229 | @Symbol.register
230 | class Dollar:
231 | """
232 | 读取变量
233 | """
234 | char = '$'
235 | name = 'dollar'
236 | derc = '变量读取声明'
237 |
238 | @classmethod
239 | def handle(cls, tokens):
240 | tokens = list(map(
241 | variable_replace,
242 | tokens
243 | ))
244 | return tokens
245 |
246 |
247 | @Symbol.register
248 | class BackQuote:
249 | """
250 | 反引号。用于命令替代
251 | """
252 | char = '`'
253 | name = 'back_quote'
254 | derc = '命令替代'
255 |
256 | @classmethod
257 | def handle(cls, tokens):
258 | sub_tokens = []
259 | get_sub_tokens(sub_tokens, tokens)
260 | return sub_tokens
261 |
262 |
263 | @Symbol.register
264 | class RightAngel:
265 | """
266 | 重定向输入符号,覆盖文件
267 | """
268 | char = '>'
269 | name = 'right_angel'
270 | derc = '覆盖式重定向输出'
271 |
272 | @classmethod
273 | def handle(cls, tokens):
274 | index = tokens.index(cls.char)
275 | try:
276 | tag = tokens[index + 1]
277 | except IndexError:
278 | tag = ''
279 | tokens.pop(index)
280 | else:
281 | tokens.pop(index)
282 | tokens.pop(index)
283 |
284 | return tag, tokens
285 |
286 |
287 | @Symbol.register
288 | class DoubleRightAngel:
289 | """
290 | 重定向输出,文件存在不覆盖
291 | """
292 | char = '>>'
293 | name = 'double_right_angel'
294 | derc = '非覆盖式重定向输出'
295 |
296 | @classmethod
297 | def handle(cls, tokens):
298 | index = tokens.index(cls.char)
299 | try:
300 | tag = tokens[index + 1]
301 | except IndexError:
302 | tag = ''
303 | tokens.pop(index)
304 | else:
305 | tokens.pop(index)
306 | tokens.pop(index)
307 |
308 | return tag, tokens
309 |
310 |
311 | @Symbol.register
312 | class LeftAngel:
313 | """
314 | 将文件内容作为命令输入
315 | """
316 | char = '<'
317 | name = 'left_angel'
318 | derc = '重定向输入'
319 |
320 | @classmethod
321 | def handle(cls, tokens):
322 | index = tokens.index(cls.char)
323 | try:
324 | tag = tokens[index + 1]
325 | except IndexError:
326 | tag = ''
327 | tokens.pop(index)
328 | else:
329 | tokens.pop(index)
330 | tokens.pop(index)
331 |
332 | return tag, tokens
333 |
334 |
335 | @Symbol.register
336 | class DoubleLeftAngel:
337 | """
338 | 标记式重定向输入。
339 | 将指定标记之间的内容作为输入流。
340 | """
341 | char = '<<'
342 | name = 'double_left_angel'
343 | derc = '标记式重定向输入'
344 |
345 | @classmethod
346 | def handle(cls, tokens):
347 | index = tokens.index(cls.char)
348 | try:
349 | tag = tokens[index + 1]
350 | except IndexError:
351 | tag = ''
352 | tokens.pop(index)
353 | else:
354 | tokens.pop(index)
355 | tokens.pop(index)
356 |
357 | return tag, tokens
358 |
359 |
360 | @Symbol.register
361 | class Pipe:
362 | """
363 | 管道功能
364 | """
365 | char = '|'
366 | name = 'pipe'
367 | derc = '前一个命令的输出,作为下一个命令的输入'
368 |
369 | @classmethod
370 | def handle(cls, tokens):
371 | if cls.char in tokens:
372 | index = [index
373 | for index, value in enumerate(tokens)
374 | if value == cls.char]
375 | return split_tokens(tokens, index)
376 |
377 |
378 | # 以下为关键字注册
379 |
380 | @Keyword.register
381 | class Daemon:
382 | """
383 | 是否以守护进程方式启动子进程
384 | """
385 | words = '--daemon'
386 | name = 'daemon'
387 | derc = '进程守护模式'
388 |
389 | @classmethod
390 | def handle(cls, tokens):
391 | tokens = tokens[:-1]
392 | return tokens
393 |
394 |
395 | @Keyword.register
396 | class Join:
397 | """
398 | 子进程是否阻塞
399 | """
400 | words = '--join'
401 | name = 'join'
402 | derc = '进程阻塞'
403 |
404 | @classmethod
405 | def handle(cls, tokens):
406 | tokens = list(filter(
407 | lambda token: True if token != cls.words else False,
408 | tokens
409 | ))
410 | return tokens
411 |
412 |
413 | @Keyword.register
414 | class Export:
415 | """
416 | 设置环境变量
417 | """
418 | words = 'export'
419 | name = 'export'
420 | derc = '设置环境变量'
421 |
422 | @classmethod
423 | def handle(cls, tokens):
424 | tokens = tokens[1:]
425 | return Equal.handle(tokens)
426 |
427 |
428 | @Keyword.register
429 | class Exec:
430 | """
431 | 执行单条命令,或整体重定向shell输出
432 | """
433 | words = 'exec'
434 | name = 'exec'
435 | derc = '执行单条命令/重定向shell输出'
436 |
437 | @classmethod
438 | def handle(cls, tokens):
439 | return tokens
440 |
441 |
442 | # 以下为辅助函数
443 |
444 |
445 | def get_shell_id():
446 | """
447 | 得到当前shell id
448 | """
449 | processing = Processing.get_records()
450 | shell_processing = list(filter(
451 | lambda id: True if processing[id]['NAME'] == 'pysh' else False,
452 | processing
453 | ))
454 | return list(shell_processing)[-1]
455 |
456 |
457 | def variable_replace(token):
458 | """
459 | 替换命令里面的变量为值。
460 | """
461 | re_str = r'(?P\$[a-zA-Z][a-zA-Z1-9]*)'
462 | vr_declares = re.findall(re_str, token)
463 | for vr_declare in vr_declares:
464 | vr = Variable.variable.get(vr_declare[1:], False) \
465 | or EnvVariable.variable.get(vr_declare[1:], '')
466 | token = token.replace(vr_declare, vr)
467 |
468 | return token
469 |
470 |
471 | def get_sub_tokens(sub_tokens, tokens):
472 | """
473 | 递归清洗tokens,得到所有子命令
474 |
475 | :param sub_tokens: 收集子命令的列表
476 | :param tokens: 原token
477 | :return: sub_tokens
478 | """
479 | sub_token = []
480 | try:
481 | bq_1 = tokens.index(BackQuote.char)
482 | except ValueError:
483 | return sub_tokens
484 | else:
485 | find = False
486 | for token in tokens[bq_1 + 1:]:
487 | if token != BackQuote.char:
488 | sub_token.append(token)
489 | else:
490 | find = True
491 | break
492 | else:
493 | sub_token.clear()
494 |
495 | if find:
496 | tokens.pop(bq_1)
497 | bq_2 = tokens.index(BackQuote.char)
498 | sub_tokens.append(sub_token)
499 | try:
500 | get_sub_tokens(sub_tokens, tokens[bq_2 + 1:])
501 | except IndexError:
502 | return sub_tokens
503 | else:
504 | return sub_tokens
505 |
506 |
507 | def split_tokens(tokens, index):
508 | """
509 | 用于管道功能,按管道出现位置分割命令
510 |
511 | :param tokens: 初步分割后的命令列表
512 | :param index: 管道在列表中出现的位置列表
513 | :return: 分割后的列表
514 | """
515 | result = [[]]
516 | result[-1].extend(tokens[0:index[0]])
517 | for i, _ in enumerate(index):
518 | result.append([])
519 | if i < len(index) - 1:
520 | try:
521 | result[-1].extend(
522 | tokens[index[i] + 1:index[i + 1]]
523 | )
524 | except IndexError:
525 | result.pop(-1)
526 | break
527 | result[-1].extend(
528 | tokens[index[i] + 1:]
529 | )
530 | return result
531 |
--------------------------------------------------------------------------------
/pysh/contrib/parser.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | 解析输入的命令。如果命令有特殊符号,执行特殊符号指定的操作。如果命令有控制语句,将解析之后的命令列表交给控制语句。
4 | """
5 | from shlex import shlex
6 |
7 | from ..manage.dispatch import dispatch
8 |
9 |
10 | class Parser:
11 | def __init__(self, token):
12 | """
13 | 对输入的命令做初步分割处理,然后委派给后续类。
14 |
15 | :param token: 输入的命令
16 | """
17 | self.token = token
18 |
19 | self.shlex = shlex(self.token, punctuation_chars=True)
20 | self.shlex.wordchars += '$.\//:'
21 |
22 | try:
23 | self.tokens = list(self.shlex)
24 | except ValueError as e:
25 | print(e)
26 | return
27 |
28 | def run(self):
29 | Handler(self).run()
30 |
31 |
32 | class Handler:
33 | """
34 | 对初步分割之后的命令做进一步处理,并执行
35 | """
36 | _tokens = None
37 |
38 | def __init__(self, parser):
39 | try:
40 | tokens = parser.tokens
41 | self.raw_token = parser.token
42 | except AttributeError:
43 | # 传入Parser的实例对Handler进行初始化是后来为命令替代改变的特性
44 | # 原来是直接传入分割后的tokens
45 | # 所以加入try以适应旧的调用方式
46 | try:
47 | tokens = parser
48 | self.raw_token = ' '.join(parser)
49 | except TypeError as e:
50 | print(e)
51 | return
52 |
53 | tokens = list(filter(lambda token: token, tokens))
54 | self.tokens = list(map(lambda token: token.strip(), tokens))
55 |
56 | @property
57 | def tokens(self):
58 | return self._tokens
59 |
60 | @tokens.setter
61 | def tokens(self, tokens):
62 | """
63 | tokens是一个特殊属性,在tokens发生改变时,同步修改command和args.
64 |
65 | :param tokens: 变更的tokens
66 | :return: None
67 | """
68 | self.args = None
69 | self.command = None
70 |
71 | self._tokens = tokens
72 |
73 | if self.tokens:
74 | self.command = self.tokens[0]
75 | if len(self.tokens) > 1:
76 | self.args = self.tokens[1:]
77 |
78 | def _unfold_literal(self):
79 | """
80 | 展开字面量
81 | """
82 | if self.tokens:
83 | # 展开shell pid
84 | self.sa.dollars(self.tokens)
85 | # 展开用户目录
86 | self.sa.wave_line(self.tokens)
87 | # 设置变量
88 | self.sa.equal(self.tokens)
89 | # 展开变量
90 | self.sa.dollar(self.tokens)
91 | # 环境变量
92 | self.ka.export(self.tokens)
93 |
94 | def _backend(self):
95 | """
96 | 后台启动相关判断
97 | """
98 | # 是否后台启动
99 | backend = self.sa.backend(self.tokens)
100 | daemon = False
101 | join = False
102 | if backend:
103 | # 是否守护模式、阻塞
104 | daemon = self.ka.daemon(self.tokens)
105 | join = self.ka.join(self.tokens)
106 |
107 | self.backend = backend
108 | self.daemon = daemon
109 | self.join = join
110 |
111 | def _redirect(self):
112 | """
113 | 重定向、替换相关逻辑
114 | """
115 | if self.tokens:
116 | # 管道
117 | self.sa.pipe(self.tokens)
118 |
119 | if self.tokens:
120 | # 重定向输入
121 | self.sa.left_angel(self.tokens)
122 |
123 | if self.tokens:
124 | # 标记式重定向输入
125 | self.sa.double_left_angel(self.tokens)
126 |
127 | if self.tokens:
128 | # 重定向输出,覆盖文件
129 | self.sa.right_angel(self.tokens)
130 |
131 | if self.tokens:
132 | # 重定向输出,不覆盖
133 | self.sa.double_right_angel(self.tokens)
134 |
135 | if self.tokens:
136 | # 命令替换
137 | self.sa.back_quote(self.tokens)
138 |
139 | def _division(self):
140 | """
141 | 分割语句相关的判断
142 | """
143 | if self.tokens:
144 | # 分号分割语句
145 | self.sa.semicolon(self.tokens)
146 |
147 | if self.tokens:
148 | # 逻辑与
149 | self.sa.double_and(self.tokens)
150 |
151 | def _dispatch(self):
152 | """
153 | 分发命令
154 | """
155 | if self.tokens:
156 | # 如果没有tokens,说明命令在解析中已经递归执行完毕,直接返回。
157 | if self.args:
158 | rv = dispatch.dispatch(self.command, *self.args, backend=self.backend, daemon=self.daemon, join=self.join)
159 | else:
160 | rv = dispatch.dispatch(self.command, backend=self.backend, daemon=self.daemon, join=self.join)
161 | else:
162 | rv = True
163 |
164 | return rv
165 |
166 | def run(self):
167 | """
168 | 控制判断符号、关键字的具体逻辑。常在control具体命令中递归执行。
169 |
170 | :return: None
171 | """
172 | # 放在这里避免循环导入
173 | from .control import SymbolAction, KeywordAction
174 | self.sa = SymbolAction(self)
175 | self.ka = KeywordAction(self)
176 |
177 | if self.tokens:
178 | # exec语句
179 | self.ka.exec(self.tokens)
180 |
181 | self._division()
182 |
183 | self._unfold_literal()
184 |
185 | self._backend()
186 |
187 | self._redirect()
188 |
189 | rv = self._dispatch()
190 |
191 | return rv
192 |
--------------------------------------------------------------------------------
/pysh/contrib/tools.py:
--------------------------------------------------------------------------------
1 | """
2 | 存放一些不适合放在别处的工具性质的类或函数
3 | """
4 |
5 |
6 | class _Getch:
7 | """
8 | 用于行编辑过程。
9 | 读取单个字符。可读取任意字符,包括控制字符。并且兼容win和linux平台。
10 | 参考自stack overflow:
11 | https://stackoverflow.com/questions/510357/python-read-a-single-character-from-the-user/20865751
12 | """
13 |
14 | def __init__(self):
15 | self.getch = self._GetchLinux
16 |
17 | def __call__(self):
18 | self._getch()
19 |
20 | try:
21 | return self.char.decode()
22 | except AttributeError:
23 | return self.char
24 |
25 | def _getch(self):
26 | self.char = self.getch()
27 |
28 | def _GetchWindows(self):
29 | import msvcrt
30 | return msvcrt.getch()
31 |
32 | def _GetchLinux(self):
33 | try:
34 | import sys, tty, termios
35 | except ModuleNotFoundError:
36 | return self._GetchWindows()
37 | fd = sys.stdin.fileno()
38 | old_settings = termios.tcgetattr(fd)
39 | try:
40 | tty.setraw(sys.stdin.fileno())
41 | ch = sys.stdin.read(1)
42 | finally:
43 | termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
44 | return ch
45 |
46 |
47 | class LineEndError(ValueError):
48 | pass
49 |
50 |
51 | getch = _Getch()
52 |
--------------------------------------------------------------------------------
/pysh/manage/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Arianxx/Pysh/d44f7303bc4ce3764a300830d361115200ad3602/pysh/manage/__init__.py
--------------------------------------------------------------------------------
/pysh/manage/dispatch.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Dispatch接受解析之后的参数,将参数分发给应用执行,并执行环境需要的其它操作。
4 | """
5 | import os
6 | import subprocess
7 | import sys
8 | from multiprocessing import Process
9 |
10 | from .env import Application, Processing, History, Variable, EnvVariable
11 | from .middleware import StdinRedirection
12 |
13 | apps = Application.app
14 |
15 |
16 | class Task(Process):
17 | """
18 | 包装真正的命令。
19 | """
20 |
21 | def __init__(self, app, *args):
22 | super().__init__()
23 | self.app = app
24 | self.args = args
25 | self.name = self.app.__class__.__name__
26 | self.backend = False
27 | self.is_join = False
28 |
29 | if type(self.app) != str:
30 | # 不是字符串说明是内部注册的命令类
31 | self._task = self.app(*self.args)
32 |
33 | # 绑定当前环境到应用,以便多进程下能够访问
34 | self._task.env = {
35 | 'app': Application.app,
36 | 'process': Processing.process,
37 | 'history': History.history,
38 | 'variable': Variable.variable,
39 | }
40 | else:
41 | # 否则是外部命令路径
42 | self._task = None
43 |
44 | def run(self):
45 | if self.backend:
46 | # 如果是后台程序,强制打开输入流
47 | sys.stdin = os.fdopen(0)
48 |
49 | if self._task:
50 | Application.app = self._task.env['app']
51 | Processing.process = self._task.env['process']
52 | History.history = self._task.env['history']
53 |
54 | if self._task:
55 | self._task.env.update(
56 | Application=Application,
57 | Processing=Processing,
58 | History=History,
59 | Variable=Variable,
60 | )
61 |
62 | Processing.record(self)
63 |
64 | try:
65 | if self._task:
66 | # 内部命令,调用注册的方法
67 | rv = self._task.handler()
68 | else:
69 | # 外部命令打开子进程
70 | rv = subprocess.call([self.app] + [*self.args],
71 | stdin=sys.stdin,
72 | stdout=sys.stdout,
73 | stderr=sys.stderr)
74 | except EOFError as e:
75 | # 多进程可能会发生io错误
76 | print(e)
77 | rv = False
78 | except Exception:
79 | # 其它异常向上冒泡
80 | raise
81 | finally:
82 | # 清理记录
83 | Processing.remove(self.id)
84 |
85 | return rv
86 |
87 |
88 | class Script:
89 | """
90 | 如果传入一个文件路径,尝试使用shebang或内置的命令解析这个文件
91 | """
92 |
93 | def __init__(self, path):
94 | self.path = path.strip()
95 |
96 | def _read(self):
97 | """
98 | 尝试读取文件
99 | """
100 | try:
101 | file = open(self.path, 'rt')
102 | except FileNotFoundError as e:
103 | print(e)
104 | raise e
105 | except PermissionError as e:
106 | print(e)
107 | raise e
108 |
109 | self.file = file
110 |
111 | try:
112 | self.lines = self.file.readlines()
113 | except UnicodeDecodeError as e:
114 | print(e)
115 | raise e
116 | finally:
117 | self.file.close()
118 |
119 | return
120 |
121 | def _dispatch(self):
122 | """
123 | 如果有shebang则用shebang对应的程序运行。
124 | 否则自身运行
125 | """
126 | if not self.lines:
127 | raise ValueError("没有输入")
128 |
129 | shebang = None
130 | for line in self.lines:
131 | if line.startswith('#'):
132 | if line.startswith('#!'):
133 | shebang = line.strip('#!').strip()
134 | else:
135 | continue
136 | else:
137 | break
138 |
139 | if shebang:
140 | try:
141 | subprocess.call([shebang, self.path])
142 | return True
143 | except FileNotFoundError as e:
144 | print(e)
145 | return False
146 | else:
147 | return self._parse()
148 |
149 | def _parse(self):
150 | """
151 | 重定向输入到文件,一行一行解析
152 | """
153 | from ..contrib.parser import Parser
154 |
155 | tokens = self.lines[1:]
156 | si = StdinRedirection(source=tokens)
157 | rv = True
158 | with si.context():
159 | while True:
160 | # 虽然是while True,tokens读尽时,上下文管理器弹出StopIteration,捕获后会退出循环
161 | token = input()
162 | try:
163 | Parser(token).run()
164 | except Exception as e:
165 | print(e)
166 | rv = False
167 | break
168 | return rv
169 |
170 | def run(self):
171 | try:
172 | self._read()
173 | except Exception as e:
174 | print(e)
175 | return False
176 |
177 | return self._dispatch()
178 |
179 |
180 | class Dispatch:
181 | def __init__(self):
182 | pass
183 |
184 | def _thread(self, app, *args, daemon=False, join=False):
185 | """
186 | 后台命令就新建一个进程。
187 |
188 | :param app: 要启动的app类
189 | :param args: 命令的参数
190 | :return: None
191 | """
192 | task = Task(app, *args)
193 | task.backend = True
194 | if join:
195 | task.is_join = True
196 |
197 | # 守护进程
198 | # 如果进程以守护进程的方式启动,在父进程结束时,守护进程也立即结束。
199 | # 并且,以守护进程方式启动,会导致输入混乱。如果以守护进程启动新pysh
200 | # 会导致两个shell交替相应输入
201 | task.daemon = daemon
202 |
203 | try:
204 | task.start()
205 | except AssertionError as e:
206 | # 父进程以守护模式启动,就不能再创建子进程。
207 | # 会弹出:
208 | # AssertionError: daemonic processes are not allowed to have children
209 | # 虽然我并不明白为什么不能创建
210 | # 总之先忽略它吧……
211 | print(e)
212 |
213 | # 子进程是否阻塞
214 | # 在pysh中启动一个新pysh,应该使其阻塞,否则会产生混乱
215 | if join:
216 | task.join()
217 |
218 | # 启动子进程后在父进程也记录子进程的id
219 | Processing.record(task)
220 | return True
221 |
222 | def _front(self, app, *args):
223 | """
224 | 前台程序直接手动启动。
225 |
226 | :param app: 要启动的app类
227 | :param args: 命令的参数
228 | :return: None
229 | """
230 | task = Task(app, *args)
231 | return task.run()
232 |
233 | def _script(self, file_path):
234 | """
235 | 尝试解析脚本
236 | """
237 | return Script(file_path).run()
238 |
239 | @History.history_recorder
240 | def dispatch(self, command, *args, backend, daemon, join):
241 | # 先搜索注册的应用,没有就搜寻环境PATH变量
242 | app = apps.get(command, None) or EnvVariable.search_path(command)
243 |
244 | if app:
245 | if backend:
246 | # rv = return_value
247 | rv = self._thread(app, *args, daemon=daemon, join=join)
248 | else:
249 | rv = self._front(app, *args)
250 | elif os.path.isfile(command):
251 | # 如果是文件路径,尝试按照shebang运行或自身解析
252 | rv = self._script(command)
253 | else:
254 | print('{}: command not found'.format(command))
255 | rv = False
256 |
257 | return rv
258 |
259 |
260 | dispatch = Dispatch()
261 |
--------------------------------------------------------------------------------
/pysh/manage/env.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | 处理shell一切环境信息,包括记录注册的应用以及从外部环境读取变量等
4 | """
5 | import os
6 | import sys
7 | from collections import deque, OrderedDict
8 | from datetime import datetime
9 | from functools import wraps
10 |
11 |
12 | class Application:
13 | app = OrderedDict()
14 |
15 | @staticmethod
16 | def __new__(cls, *args, **kwargs):
17 | # Application不必要有多个实例
18 | if not getattr(cls, '_instance', None):
19 | cls._instance = super().__new__(cls, *args, **kwargs)
20 |
21 | try:
22 | import readline
23 | from .middleware import Completer
24 | except ImportError:
25 | # Win下没有readline模块
26 | pass
27 | else:
28 | # 后来在行编辑中自己实现了简单的命令补全
29 | readline.parse_and_bind("tab: complete")
30 | readline.set_completer(Completer.search_symbol)
31 |
32 | return cls._instance
33 |
34 | def register(self, cls):
35 | # 将注册的应用收集到app字典中
36 | app_name = cls.__name__
37 | self.app[app_name] = cls
38 |
39 | return cls
40 |
41 | @classmethod
42 | def registered_app(cls):
43 | return dict(cls.app)
44 |
45 | class Processing:
46 | """
47 | 记录没有退出的命令的id。
48 | """
49 | process = {}
50 |
51 | @classmethod
52 | def record(cls, task):
53 | """
54 | 记录命令实例。
55 | """
56 | if not cls.process:
57 | task.id = 0
58 | pid = task.id
59 | else:
60 | index = 0
61 | while True:
62 | if index not in cls.process:
63 | pid = index
64 | break
65 | index += 1
66 | task.id = pid
67 |
68 | cls.process[pid] = {}
69 | cls.process[pid]['NAME'] = task._task.__class__.__name__
70 | cls.process[pid]['TIME'] = datetime.now()
71 | cls.process[pid]['instance'] = task
72 | return True
73 |
74 | @classmethod
75 | def remove(cls, id):
76 | """
77 | 退出时执行此方法删除记录。
78 |
79 | :param id: 命令记录在process字典中的pid值。
80 | :return: 成功与否。
81 | """
82 | id = id
83 | if id in cls.process.keys():
84 | cls.process.pop(id)
85 | return True
86 | return False
87 |
88 | @classmethod
89 | def get_records(cls):
90 | """
91 | 每次访问,清理掉无效的记录
92 |
93 | :return: 记录
94 | """
95 | for id in list(cls.process):
96 | try:
97 | if cls.process[id]['instance'].backend and not cls.process[id]['instance'].is_alive():
98 | cls.process.pop(id)
99 | except AssertionError as e:
100 | # 多个pysh进程下,某些情况会弹出这个错误
101 | # AssertionError: can only test a child process
102 | # 经常在exit的时候弹出
103 | # 并不知道这是什么原因……
104 | print(e)
105 |
106 | return cls.process
107 |
108 |
109 | class History:
110 | """
111 | 记录命令执行的记录。
112 | """
113 | history = deque(maxlen=100)
114 |
115 | @classmethod
116 | def history_recorder(cls, func):
117 | """
118 | 给dispatch.Dispatch.dispatch函数装饰,记录执行的命令。
119 |
120 | :param func: 待装饰的函数
121 | :return: 装饰器
122 | """
123 |
124 | @wraps(func)
125 | def decorator(self, command, *args, backend=False, daemon=False, join=False):
126 | args = args or []
127 | cls.history.append(' '.join([command] + list(args)))
128 | return func(self, command, *args, backend=backend, daemon=daemon, join=join)
129 |
130 | return decorator
131 |
132 | @classmethod
133 | def clear(cls):
134 | """
135 | 清空历史命令
136 | :return: None
137 | """
138 |
139 | cls.history.clear()
140 |
141 | @classmethod
142 | def maxlen(cls):
143 | return cls.history.maxlen
144 |
145 | @classmethod
146 | def new_maxlen(cls, value):
147 | """
148 | 可以修改最大可容纳的历史命令数量。
149 |
150 | :param value: 可以记录的最大命令数
151 | :return: None
152 | """
153 | new_history = deque(maxlen=int(value))
154 | new_history.extend(cls.history.copy())
155 | cls.history = new_history
156 |
157 |
158 | class Variable:
159 | """
160 | 储存变量
161 | """
162 | variable = OrderedDict()
163 |
164 | @classmethod
165 | def add(cls, name, value):
166 | cls.variable[name] = value
167 |
168 | @classmethod
169 | def get_vr(cls, name):
170 | return cls.variable[name]
171 |
172 | @classmethod
173 | def remove(cls, name):
174 | if cls.variable.get(name) is not None:
175 | cls.variable.pop(name)
176 |
177 | @classmethod
178 | def clear(cls):
179 | cls.variable.clear()
180 |
181 |
182 | class EnvVariable:
183 | """
184 | 环境变量,以及外部PATH变量
185 | """
186 | env_file_path = '/.env'
187 | variable = OrderedDict()
188 | cached = {}
189 | PATH = None
190 |
191 | @classmethod
192 | def set_env_variable(cls, name, value):
193 | cls.variable.update({name: value})
194 | with open(cls.env_file_path, 'wt') as file:
195 | for key, value in cls.variable.items():
196 | file.write(str(key) + '=' + str(value) + '\n')
197 | return True
198 |
199 | @classmethod
200 | def remove_env_variable(cls, name):
201 | try:
202 | cls.variable.pop(name)
203 | except KeyError as e:
204 | print(e)
205 | else:
206 | with open(cls.env_file_path, 'wt') as file:
207 | for key, value in cls.variable.items():
208 | file.write(str(key) + '=' + str(value) + '\n')
209 | return True
210 |
211 | @classmethod
212 | def search_path(cls, name):
213 | if name in cls.cached:
214 | return cls.cached[name]
215 |
216 | for dir in cls.PATH:
217 | path = os.path.join(dir, name)
218 | if os.path.isfile(path):
219 | cls.cached_path(name, path)
220 | return path
221 | path = os.path.join(dir, name + '.exe')
222 | if os.path.isfile(path):
223 | cls.cached_path(name, path)
224 | return path
225 | else:
226 | return False
227 |
228 | @classmethod
229 | def cached_path(cls, name, path):
230 | cls.cached.update({name: path})
231 | return True
232 |
233 | @classmethod
234 | def clear_cached(cls):
235 | cls.cached.clear()
236 | return True
237 |
238 |
239 | # 以下辅助函数
240 |
241 | def get_env_path():
242 | PATH = os.environ['PATH']
243 | sep = ';' if sys.platform == 'win32' else ':'
244 | return PATH.split(sep)
245 |
246 | def get_env_variable(EnvVariable):
247 | """
248 | 从文件中读取环境变量。
249 |
250 | :param EnvVariable: EnvVariable类
251 | :return: 读取的变量名和值的字典
252 | """
253 | path = EnvVariable.env_file_path
254 | try:
255 | with open(path, 'rt') as file:
256 | lines = file.readlines()
257 | variable = {}
258 | for line in lines:
259 | try:
260 | name, value = line.strip().split('=')
261 | name, value = name.strip(), value.strip()
262 | except Exception:
263 | pass
264 | else:
265 | variable.update({name: value})
266 | except FileNotFoundError as e:
267 | variable = {}
268 | pass
269 | return variable
270 |
271 |
272 | if not EnvVariable.variable:
273 | EnvVariable.variable = get_env_variable(EnvVariable)
274 |
275 | if not EnvVariable.PATH:
276 | EnvVariable.PATH = get_env_path()
--------------------------------------------------------------------------------
/pysh/manage/middleware.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import contextlib
3 | import itertools
4 | import os
5 | import sys
6 | from itertools import chain
7 |
8 | from ..manage.env import Application, Variable, EnvVariable, History
9 |
10 |
11 | class StdoutRedirection():
12 | """
13 | 重定向输出
14 | """
15 |
16 | def __init__(self, file_path=None, file_override=True):
17 | """
18 | 如果提供了文件目录,就尝试打开文件,稍后将会将输出重定向到文件。
19 | 否则将输出重定向到内部列表pipe。
20 | """
21 | self.file = None
22 | self.tmp_file = None
23 | self.pipe = []
24 | if file_path:
25 | try:
26 | if file_override:
27 | self.file = open(file_path, 'wt')
28 | else:
29 | self.file = open(file_path, 'at')
30 | except FileNotFoundError as e:
31 | """
32 | 只是特别标注出来这里可能弹出的错误。
33 | 错误向上冒泡,具体处理措施交由调用者完成。
34 | """
35 | raise e
36 | except PermissionError as e:
37 | raise e
38 | else:
39 | for num in itertools.count():
40 | try:
41 | self.tmp_file = open('stdouttemp' + str(num) + '.txt', 'wt')
42 | except FileExistsError:
43 | continue
44 | else:
45 | break
46 |
47 | @contextlib.contextmanager
48 | def context(self):
49 | """
50 | 重定向输出的上下文管理器
51 |
52 | contextlib.contextmanager装饰器自动将协程转换为上下文管理器
53 | yield前为进入with时执行,yield为with语句返回值,yield后退出with时执行
54 | """
55 | origin_stdout = sys.stdout
56 |
57 | if self.file:
58 | sys.stdout = self.file
59 | else:
60 | sys.stdout = self.tmp_file
61 |
62 | try:
63 | yield self
64 | except Exception as e:
65 | raise e
66 | finally:
67 | # 不论弹出什么异常,都先还原输出流
68 | sys.stdout.flush()
69 | sys.stdout.close()
70 | sys.stdout = origin_stdout
71 |
72 | if self.tmp_file:
73 | with open(self.tmp_file.name, 'rt') as file:
74 | self.pipe = file.readlines()
75 |
76 | os.remove(self.tmp_file.name)
77 |
78 |
79 | class StdinRedirection():
80 | """
81 | 重定向输入
82 | """
83 |
84 | def __init__(self, file_path=None, source=None):
85 | """
86 | 如果有文件路径,将输入重定向到文件。否则将输入重定向到source。
87 | source应该是一个实现了__iter__方法的可迭代对象
88 | """
89 | self.file = None
90 | self.tmp_file = None
91 | self.source = source
92 |
93 | if file_path:
94 | try:
95 | self.file = open(file_path, 'rt')
96 | except FileNotFoundError as e:
97 | raise e
98 | else:
99 | self._from_source()
100 |
101 | def _from_source(self):
102 | for num in itertools.count():
103 | try:
104 | self.tmp_file = open('stdintemp' + str(num) + '.txt', 'wt')
105 | except FileExistsError:
106 | continue
107 | else:
108 | break
109 |
110 | self._to_temp_file(self.source)
111 | self.tmp_file.close()
112 | self.tmp_file = open(self.tmp_file.name, 'rt')
113 | return
114 |
115 | def _to_temp_file(self, source):
116 | for line in source:
117 | if type(line) != str:
118 | self._to_temp_file(line)
119 | else:
120 | self.tmp_file.write(line)
121 |
122 | return
123 |
124 | @contextlib.contextmanager
125 | def context(self):
126 | origin_stdin = sys.stdin
127 |
128 | if self.file:
129 | sys.stdin = self.file
130 | else:
131 | sys.stdin = self.tmp_file
132 |
133 | try:
134 | yield self
135 | except StopIteration:
136 | # _source迭代完毕
137 | pass
138 | except Exception as e:
139 | raise e
140 | finally:
141 | # 还原输入流
142 | sys.stdin.flush()
143 | sys.stdin.close()
144 | sys.stdin = origin_stdin
145 |
146 | if self.tmp_file:
147 | os.remove(self.tmp_file.name)
148 |
149 |
150 | class Completer:
151 | @classmethod
152 | def search_symbol(cls, text, state):
153 | """
154 | 用于行编辑,编辑tab键自动补全功能。
155 | """
156 | names = [name for name in chain(
157 | History.history,
158 | Application.app,
159 | Variable.variable,
160 | EnvVariable.variable
161 | ) if name.startswith(text)]
162 | try:
163 | return names[state]
164 | except IndexError:
165 | return False
166 |
--------------------------------------------------------------------------------