-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathCommandShell.pck.st
More file actions
13752 lines (11137 loc) · 485 KB
/
CommandShell.pck.st
File metadata and controls
13752 lines (11137 loc) · 485 KB
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
98
99
100
101
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
149
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
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
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
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
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
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
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
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
'From Cuis 5.0 [latest update: #4928] on 21 October 2021 at 10:03:07 pm'!
'Description '!
!provides: 'CommandShell' 1 2!
!requires: 'OSProcess' 1 17 nil!
SystemOrganization addCategory: 'CommandShell-Base'!
SystemOrganization addCategory: 'CommandShell-Commands'!
SystemOrganization addCategory: 'CommandShell-Morphic'!
SystemOrganization addCategory: 'CommandShell-MVC'!
SystemOrganization addCategory: 'CommandShell-Piping'!
SystemOrganization addCategory: 'CommandShell-UI'!
SystemOrganization addCategory: 'CommandShell-Tests'!
!classDefinition: #CuisShellWindow category: 'CommandShell-Morphic'!
TextModel subclass: #CuisShellWindow
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-Morphic'!
!classDefinition: 'CuisShellWindow class' category: 'CommandShell-Morphic'!
CuisShellWindow class
instanceVariableNames: ''!
!classDefinition: #CommandShell category: 'CommandShell-Base'!
Model subclass: #CommandShell
instanceVariableNames: 'outputStream errorStream shellSyntax environment builtinCommands commandHistory historyIndex promptString promptStringTwo foregroundProxies backgroundProxies backgroundPipelines commandLineProcesses backgroundMessageQueue keyboardConnector activeController safeToExit readSyncSemaphore'
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-Base'!
!classDefinition: 'CommandShell class' category: 'CommandShell-Base'!
CommandShell class
instanceVariableNames: ''!
!classDefinition: #CrLfFileEditor category: 'CommandShell-Commands'!
Model subclass: #CrLfFileEditor
instanceVariableNames: 'contents lineEndConvention directory fileName viewStyle'
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-Commands'!
!classDefinition: 'CrLfFileEditor class' category: 'CommandShell-Commands'!
CrLfFileEditor class
instanceVariableNames: ''!
!classDefinition: #PipeJunction category: 'CommandShell-Piping'!
Model subclass: #PipeJunction
instanceVariableNames: 'pipeToInput pipeFromOutput errorPipelineStream'
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-Piping'!
!classDefinition: 'PipeJunction class' category: 'CommandShell-Piping'!
PipeJunction class
instanceVariableNames: ''!
!classDefinition: #PipeableEvaluator category: 'CommandShell-Piping'!
PipeJunction subclass: #PipeableEvaluator
instanceVariableNames: 'argumentArray runState exitStatus outputHasBeenRedirected'
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-Piping'!
!classDefinition: 'PipeableEvaluator class' category: 'CommandShell-Piping'!
PipeableEvaluator class
instanceVariableNames: ''!
!classDefinition: #ExpressionEvaluator category: 'CommandShell-Piping'!
PipeableEvaluator subclass: #ExpressionEvaluator
instanceVariableNames: 'expression evaluationBlock'
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-Piping'!
!classDefinition: 'ExpressionEvaluator class' category: 'CommandShell-Piping'!
ExpressionEvaluator class
instanceVariableNames: ''!
!classDefinition: #ShellBuiltinEvaluator category: 'CommandShell-Piping'!
PipeableEvaluator subclass: #ShellBuiltinEvaluator
instanceVariableNames: 'shellBuiltin commandShell'
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-Piping'!
!classDefinition: 'ShellBuiltinEvaluator class' category: 'CommandShell-Piping'!
ShellBuiltinEvaluator class
instanceVariableNames: ''!
!classDefinition: #PipeableOSProcess category: 'CommandShell-Piping'!
PipeJunction subclass: #PipeableOSProcess
instanceVariableNames: 'pipeFromError processProxy keepInitialStdOutOpen keepInitialStdErrOpen accessProtect'
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-Piping'!
!classDefinition: 'PipeableOSProcess class' category: 'CommandShell-Piping'!
PipeableOSProcess class
instanceVariableNames: ''!
!classDefinition: #ProxyPipeline category: 'CommandShell-Piping'!
OrderedCollection subclass: #ProxyPipeline
instanceVariableNames: 'commandLine background completionSemaphore completionWatcher'
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-Piping'!
!classDefinition: 'ProxyPipeline class' category: 'CommandShell-Piping'!
ProxyPipeline class
instanceVariableNames: ''!
!classDefinition: #CommandShellTranscript category: 'CommandShell-UI'!
WriteStream subclass: #CommandShellTranscript
instanceVariableNames: 'cliShell lastPromptString activeController labelStringBlock'
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-UI'!
!classDefinition: 'CommandShellTranscript class' category: 'CommandShell-UI'!
CommandShellTranscript class
instanceVariableNames: ''!
!classDefinition: #InternalPipe category: 'CommandShell-Piping'!
Stream subclass: #InternalPipe
instanceVariableNames: 'queue writerClosed nonBlockingMode'
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-Piping'!
!classDefinition: 'InternalPipe class' category: 'CommandShell-Piping'!
InternalPipe class
instanceVariableNames: ''!
!classDefinition: #CommandShellTestCase category: 'CommandShell-Tests'!
TestCase subclass: #CommandShellTestCase
instanceVariableNames: 'shell checkFileHandleCount procDir'
classVariableNames: 'CheckFileHandleCount TempResult'
poolDictionaries: ''
category: 'CommandShell-Tests'!
!classDefinition: 'CommandShellTestCase class' category: 'CommandShell-Tests'!
CommandShellTestCase class
instanceVariableNames: ''!
!classDefinition: #ExpressionEvaluatorTestCase category: 'CommandShell-Tests'!
TestCase subclass: #ExpressionEvaluatorTestCase
instanceVariableNames: 'pipeJunction'
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-Tests'!
!classDefinition: 'ExpressionEvaluatorTestCase class' category: 'CommandShell-Tests'!
ExpressionEvaluatorTestCase class
instanceVariableNames: ''!
!classDefinition: #InternalPipeTestCase category: 'CommandShell-Tests'!
TestCase subclass: #InternalPipeTestCase
instanceVariableNames: 'pipe'
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-Tests'!
!classDefinition: 'InternalPipeTestCase class' category: 'CommandShell-Tests'!
InternalPipeTestCase class
instanceVariableNames: ''!
!classDefinition: #PipeableOSProcessTestCase category: 'CommandShell-Tests'!
TestCase subclass: #PipeableOSProcessTestCase
instanceVariableNames: 'procDir'
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-Tests'!
!classDefinition: 'PipeableOSProcessTestCase class' category: 'CommandShell-Tests'!
PipeableOSProcessTestCase class
instanceVariableNames: ''!
!classDefinition: #PipelineEventsTestCase category: 'CommandShell-Tests'!
TestCase subclass: #PipelineEventsTestCase
instanceVariableNames: 'shell checkFileHandleCount procDir'
classVariableNames: 'TempResult'
poolDictionaries: ''
category: 'CommandShell-Tests'!
!classDefinition: 'PipelineEventsTestCase class' category: 'CommandShell-Tests'!
PipelineEventsTestCase class
instanceVariableNames: ''!
!classDefinition: #ShellSyntaxTestCase category: 'CommandShell-Tests'!
TestCase subclass: #ShellSyntaxTestCase
instanceVariableNames: 'shellSyntax'
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-Tests'!
!classDefinition: 'ShellSyntaxTestCase class' category: 'CommandShell-Tests'!
ShellSyntaxTestCase class
instanceVariableNames: ''!
!classDefinition: #ShellWindowMorph category: 'CommandShell-Morphic'!
PluggableTextMorph subclass: #ShellWindowMorph
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-Morphic'!
!classDefinition: 'ShellWindowMorph class' category: 'CommandShell-Morphic'!
ShellWindowMorph class
instanceVariableNames: ''!
!classDefinition: #ShellSyntax category: 'CommandShell-Base'!
Object subclass: #ShellSyntax
instanceVariableNames: 'cwdDictionary cwdIndex'
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-Base'!
!classDefinition: 'ShellSyntax class' category: 'CommandShell-Base'!
ShellSyntax class
instanceVariableNames: ''!
!classDefinition: #ShellBuiltin category: 'CommandShell-Commands'!
Object subclass: #ShellBuiltin
instanceVariableNames: 'name procedureSelector helpSelector needsPrompt'
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-Commands'!
!classDefinition: 'ShellBuiltin class' category: 'CommandShell-Commands'!
ShellBuiltin class
instanceVariableNames: ''!
!classDefinition: #RemoteTask category: 'CommandShell-Piping'!
Object subclass: #RemoteTask
instanceVariableNames: 'task taskIdentifier childProxy completionBlock aioHandler'
classVariableNames: 'HeadlessChild Materializer Serializer SerializingStrategy'
poolDictionaries: ''
category: 'CommandShell-Piping'!
!classDefinition: 'RemoteTask class' category: 'CommandShell-Piping'!
RemoteTask class
instanceVariableNames: ''!
!classDefinition: #ExternalCommandShell category: 'CommandShell-UI'!
Object subclass: #ExternalCommandShell
instanceVariableNames: 'cliShell isRunning inputStream lineBuffer sigIntSema sigIntHandler'
classVariableNames: 'ExternalShell'
poolDictionaries: ''
category: 'CommandShell-UI'!
!classDefinition: 'ExternalCommandShell class' category: 'CommandShell-UI'!
ExternalCommandShell class
instanceVariableNames: ''!
!classDefinition: #TtyTextMorphForEditView category: 'CommandShell-Morphic'!
ProtoObject subclass: #TtyTextMorphForEditView
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-Morphic'!
!classDefinition: 'TtyTextMorphForEditView class' category: 'CommandShell-Morphic'!
TtyTextMorphForEditView class
instanceVariableNames: ''!
!classDefinition: #ShellWindowView category: 'CommandShell-MVC'!
ProtoObject subclass: #ShellWindowView
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-MVC'!
!classDefinition: 'ShellWindowView class' category: 'CommandShell-MVC'!
ShellWindowView class
instanceVariableNames: ''!
!classDefinition: #TtyPluggableTextController category: 'CommandShell-MVC'!
ProtoObject subclass: #TtyPluggableTextController
instanceVariableNames: 'acceptOnCr callbackQueue closed readSyncSemaphore'
classVariableNames: ''
poolDictionaries: ''
category: 'CommandShell-MVC'!
!classDefinition: 'TtyPluggableTextController class' category: 'CommandShell-MVC'!
TtyPluggableTextController class
instanceVariableNames: ''!
!CuisShellWindow commentStamp: '<historical>' prior: 0!
I am a simple teletype shell session morph, providing a view on an instance of CommandShell.!
!CommandShell commentStamp: '<historical>' prior: 0!
I am a command shell, similar to /bin/sh, with a simple command line user interface. I collaborate with process proxies to provide command execution, and I provide a limited set of built in commands similar to those in /bin/sh. My built in commands are implemented in Smalltalk, and any other commands are passed to process proxies to be executed either internally as Smalltalk "doIt" expressions, or externally as commands passed to the external operating system. I am similar to a TranscriptStream (some methods are copied directly from TranscriptStream), but I also know how to accept lines of command input, parse them, and hand them off to process proxies for execution.
Three types of commands may be executed from a CommandShell: internal "builtin" commands implemented in Smalltalk; internal Smalltalk "doIt" commands; and external commands. Internal commands (builtin commands or doIt commands) may be freely mixed with external operating system commands in a command pipeline. See CommandShell class>>commandProcessing for more information.
Each command line is first evaluated as a Smalltalk expression, and is subject to further parsing only if the Smalltalk evaluation fails. In practice, this permits complete Smalltalk expressions to be evaluated easily without conflicting with shell syntax, and allows Smalltalk and unix shell commands to be freely mixed.
Simple command scripting is supported (method category 'command scripting'). Any mix of internal and external commands may be included in a script. Conditional branching is supported based on command exit status.
Open a new shell window with "CommandShell open". Type 'help' followed by <return> or <enter> for help on builtin commands.
Things that work reasonably well:
- Simple command execution for running command line programs or starting
X programs.
- Command pipelines. Built in commands can be mixed with external
commands, as in "help sqsh | wc -l".
- Command IO redirection with '<', '>', '>>', '2>', and '2>>'.
- Command history and command history recall.
- Background command execution, as in "xterm&".
- <ctl-C> to interrupt a running external command.
- <ctl-D> to indicate end of file in terminal input.
Limitations include:
- Dumb tty only. Do not try to run vi.
- Standard Unix shell syntax is not completely implemented.
Race conditions are possible for certain command pipelines. See CommandShell class>>raceConditions for more information.
!
!CrLfFileEditor commentStamp: '<historical>' prior: 0!
I provide an editor on a single file. I attempt to avoid changing the line termination character convention when writing my text back to a file.
Bug: On older Squeak systems which do not support FilesStream>>truncate, file permissions may be lost when a file is rewritten at a shorter length.
!
!PipeJunction commentStamp: '<historical>' prior: 0!
I am a connector junction for input, output, and error pipelines. I obtain input from an input pipeline and, after possibly performing some kind of operation on the input objects, I send output to an output pipeline. I may also append objects onto an error pipeline.
My subclasses implement the operations on the objects which pass through a pipe junction.
In general, input pipes are connected to output pipes in a serial fashion, and error pipelines are shared by one or more instances of my subclasses.
!
!PipeableEvaluator commentStamp: 'dtl 2/1/2003 16:56' prior: 0!
I evaluate Smalltalk expressions, taking input from my pipeToInput, and print the results of the evaluation on my pipeFromOutput. I may append error messages to my errorPipelineStream. I provide a framework for pipelines of expressions, possibly combined in the same pipeline with ExternalOSProcess proxies executing external operating system commands.
My evaluationBlock may be supplied directly, or can be compiled from an expression string. If compiled from an expression string, the names 'stdin', 'stdout', 'stderr', and 'args' are used to refer to the input stream, output stream, error stream, and an array of arguments. If any of these names are used in the expression string, then the evaluation block will be compiled in such as way as to connect these names with their corresponding streams or argument array.
!
!ExpressionEvaluator commentStamp: 'dtl 2/1/2003 16:58' prior: 0!
I provide a context in which a Smalltalk expression can be evaluated as an element of a command pipeline.!
!ShellBuiltinEvaluator commentStamp: 'dtl 2/1/2003 17:47' prior: 0!
I provide the context in which a ShellBuiltin is evaluated as an element of a command pipeline.!
!PipeableOSProcess commentStamp: '<historical>' prior: 0!
I am a connector junction for input, output, and error pipelines. I collaborate with a process proxy to evaluate an internal or external process, and to move objects through the pipelines as the proxy is evaluated.
From Smalltalk, you can write to my pipeToInput, and read from my pipeFromOutput and pipeFromError streams. I implement simple streaming protocol as a convenience for reading and writing these pipe streams. I support command pipelines with the #| message, with the pipeFromOutput of one instance connected to the pipeToInput of the next instance, and a shared errorPipelineStream collecting error output text for the command pipeline.
All reading and writing should be done with the streaming protocol, rather than by direct access to the pipe streams. This is because the output pipe streams may be silently replaced by simple ReadStreams following the exit of the child process.
Normal exit for the external process may not happen when expected. If the process is writing to the output pipe, it may block on write until enough of its data is read from the pipeFromOutput pipe, after which it will exit normally.!
!ProxyPipeline commentStamp: 'dtl 12/13/2002 19:30' prior: 0!
A collection of connected PipeJunctions, representing external OS processes or internal evaluators. This class exists primarily to make the functioning of a collection of command pipelines, some of which may be evaluated as asynchronous "background" processes, easier to understand.
Events triggered by my proxies are handled and forwarded in such a way that a client (such as a CommandShell) will receive events from a ProxyPipeline as if it were an individual PipeJunction.
The user of a ProxyPipeline is responsible for closing the external resources associated with the proxies by sending either #closePipes or #finalize.!
!CommandShellTranscript commentStamp: 'dtl 1/20/2007 09:11' prior: 0!
CommandShellTranscript is a user interface for a ComandShell. It behaves like a simple teletype text terminal.!
!InternalPipe commentStamp: '<historical>' prior: 0!
I am a first-in, first-out queue with streaming behavior. I behave similarly to an OSPipe,
but am implemented in the Smalltalk image rather than with external OS pipes. I can
behave either as a blocking pipe or as a nonblocking pipe, similar to an OS pipe with
its reader end set in blocking or nonblocking mode.!
!CommandShellTestCase commentStamp: '<historical>' prior: 0!
Unit tests for CommandShell. This tests a reasonable range of command line inputs, verifying command execution and IO redirection for internal and external process proxies.
Many of the tests can try to check for unclosed file handles. The #setUp method sets the value of checkFileHandleCount (true or false) to control this. In addition, the #runAll method can separately do a count of open file handles after running all the tests. Unfortunately, there are a couple of problems with this. First, the check makes use of the /proc filesystem on Linux, which will not be available (or may be implemented differently) on other systems. Second, for reasons which I do not quite understand, files may stay open (or perhaps just appear to stay open in the /proc filesystem) after Squeak has apparently closed them. This produces intermittent failures in the unit tests. For these reasons, I recommend running the file handle count checks once or twice to make sure things are working correctly, then turning the checks off in the #setUp method to avoid seeing intermittent errors later on. If anyone figures out what is going on here, please let me know. -dtl!
!InternalPipeTestCase commentStamp: 'dtl 12/30/2001 17:05' prior: 0!
Test operation of InternalPipe in blocking and nonBlocking mode.!
!PipeableOSProcessTestCase commentStamp: 'dtl 5/21/2009 23:23' prior: 0!
Tests for PipeableOSProcess. A PipeableOSProcess provides a proxy for an external OS process with stdin, stdout, and stderr pipes connected by the proxy to the external process.!
!PipelineEventsTestCase commentStamp: 'dtl 4/26/2003 10:31' prior: 0!
This test case does not directly correspond to a class. It tests the setup of events
in the proxies and pipes of a ProxyPipeline constructed in cooperation with a
CommandShell.
!
!ShellSyntaxTestCase commentStamp: '<historical>' prior: 0!
Unit tests for ShellSyntax. Conditional tests are needed for Windows and other platforms, so not all tests are effective on all platforms. Unix is the development platform, and many of the tests are specific to a Unix file system environment.!
!ShellWindowMorph commentStamp: '<historical>' prior: 0!
I am a simple teletype shell session morph, providing a view on an instance of CommandShell.!
!ShellSyntax commentStamp: 'dtl 9/7/2009 12:36' prior: 0!
My instances implement parsing of strings in a manner similar to a simple Unix command shell. I provide path name expansion in the context of an external file system, and support the syntax required for IO redirection. All file name globbing and PATH searching are implemented in Smalltalk rather than in C library functions or an external command shell.
Most of my syntax is applicable for any operating system. Where needed, platform specific methods are in my "platform dependent" category. Currently, Unix and Windows are supported, and other platforms have not been tested. The primary difference between Unix and Windows support is that device names (such as 'C:') are used in Windows path strings. Separate current working directory strings are maintained for all Windows device names. For Unix, a single current working directory path is used. On Windows, this permits the CommandShell 'cd' command to support changing directories to another device without losing track of the current working directory for the previous device.
Command pipeline syntax is not supported here. See CommandShell for the implementation of command pipelines.
!
!ShellBuiltin commentStamp: 'dtl 2/1/2003 12:25' prior: 0!
A shell builtin command, intended to be invoked by a CommandShell.
A builtin command consists of:
name - a String identifier, used by the CommandShell to identify and invoke the commmand.
helpString - a String to describe the function of the command.
procedure - a BlockContext with parameters to be passed by the CommandShell.
Commands are implemented in instance methods. To create a new command, write a
new command implementation method and a new help method for the command. Write
a new class side instance creation method patterned after one of the existing commands
and install the instance creation method in an instance of CommandShell with
CommandShell>>installCommand:.
A command implementation is a method which receives six paramaters. The parameters are
to be interpreted as follows:
- First parameter: The EvaluatorProxy which is evaluating the command. If a command fails,
send #fail to this object.
- Second parameter: The CommandShell from which the command was evaluated. Use this
for any additional parsing which may be required in the command implementation.
- Third parameter: The input stream for the command.
- Forth parameter: The output stream for the command.
- Fifth parameter: The error stream for the command.
- Sixth parameter: An array of optional command arguments.
!
!RemoteTask commentStamp: 'dtl 2/19/2018 19:18' prior: 0!
A RemoteTask represents a block (or any object responding to #value) to be evaluated in a headless Squeak child process. The results of evaluating the block are returned to the sender through a reference stream on an OS pipe. Asynchronous event notification may be used to signal completion of remote processing.
The block is evaluated in a remote headless image beginning with a clone of the sender image at the time of the message send. All side effects of evaluating the task block are localized to the remote image, and have no effect on the sending image. The result object may be an object of any complexity, such as a dictionary or array of result values.
On a unix system, the creation of child Squeak images is a relatively light weight operation, so tasks of varying degrees of complexity can be assigned to remote tasks with only moderate overhead.
Examples:
" RemoteTask do: [2 + 2] "
" (RemoteTask
do: [ | oc |
oc _ OrderedCollection new.
(1 to: 5000000) do: [ :e | e isPrime ifTrue: [oc add: e]].
oc ]
whenComplete: [ :result | result inspect ]
taskIdentifier: 'A forked Squeak image finding primes') inspect "
!
!ExternalCommandShell commentStamp: '<historical>' prior: 0!
On some platforms it is possible to use OSProcess to read and write the standard input, output and error streams. In that case, ExternalCommandShell allows Squeak to be used as a traditional text mode command shell.
When used in a headless Squeak image, ExternalCommandShell behaves like a traditional Unix command shell, except that it has the ability to evaluate Smalltalk expressions as well as other programs and commands. On a Unix system running a Squeak shell in a headless image, the Smalltalk display can be opened by entering the command "UnixProcess recapitate" at the Squeak shell prompt.
ExternalCommandShell has one instance, which is accessible as "ExternalCommandShell inSmalltalk". The shell can be stopped with "ExternalCommandShell stop" and restarted with "ExternalCommandShell start". If the shell is running when Squeak is shut down, it will be automatically be restarted when the image is restarted.
!
!TtyTextMorphForEditView commentStamp: 'dtl 5/2/2020 10:03' prior: 0!
I add a keyboard hook to a TextMorphForEditView to allow filtering of keystrokes for a simple tty terminal emulator. This class in no longer required for recent Squeak, but older versions such as Squeak 3.8 require it.!
!ShellWindowView commentStamp: '<historical>' prior: 0!
I am a simple teletype shell session view, providing a view on an instance of CommandShell.!
!TtyPluggableTextController commentStamp: '<historical>' prior: 0!
I add keyboard hooks to a PluggableTextController to allow filtering of keystrokes for a simple tty terminal emulator.!
!CommandShell class methodsFor: 'system startup' stamp: 'dtl 7/12/2002 19:26'!
startUp: resuming
"A CommandShell may be running external processes in background, and waiting
for those processes to complete. If the Squeak image is stopped and restarted, the
background processes are no longer children of the Squeak process, and there is
no way to receive notification when the processes exit. This would result in leftover
Smalltalk processes associated with the CommandShell waiting forever for the
external processes to exit. The simplest way to prevent this is to terminate all
the Smalltalk processes unconditionally after an image restart."
resuming ifTrue: [self allSubInstances do: [:e | e startUpInNewSession]]
! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 4/29/2020 19:49'!
environment
^ environment ifNil:
[environment _ (Smalltalk at: #OSProcess ifPresent:
[:osp | osp thisOSProcess environment copy]) ifNil: [Dictionary new]].
! !
!PipeableEvaluator methodsFor: 'printing' stamp: 'dtl 1/12/2003 00:45'!
printOn: aStream
super printOn: aStream.
aStream nextPutAll: ' ('; nextPutAll: self runState; nextPut: $)
! !
!ExpressionEvaluator methodsFor: 'evaluating' stamp: 'dtl 6/15/2019 11:21'!
compilerClass
(Smalltalk hasClassNamed: #Compiler)
ifTrue: [ ^ Smalltalk at: #Compiler ].
(Smalltalk hasClassNamed: #OpalCompiler)
ifTrue: [ ^ Smalltalk at: #OpalCompiler ].
self error: 'no system compiler'
! !
!ExpressionEvaluator methodsFor: 'printing' stamp: 'dtl 3/24/2003 07:23'!
printOn: aStream
super printOn: aStream.
self expression isNil
ifTrue:
[self evaluationBlock isNil
ifTrue:
[aStream nextPutAll: ' for '.
self evaluationBlock printOn: aStream]
ifFalse:
[aStream nextPutAll: ' for a '.
self evaluationBlock class printOn: aStream]]
ifFalse:
[aStream nextPutAll: ' for '.
self expression printOn: aStream]
! !
!ShellBuiltinEvaluator methodsFor: 'printing' stamp: 'dtl 3/22/2003 13:58'!
printOn: aStream
super printOn: aStream.
aStream nextPutAll: ' on '.
self shellBuiltin printOn: aStream! !
!PipeableOSProcess methodsFor: 'printing' stamp: 'dtl 4/8/2006 19:32'!
printOn: aStream
super printOn: aStream.
aStream nextPutAll: ' on '.
self processProxy printOn: aStream
! !
!ProxyPipeline methodsFor: 'printing' stamp: 'dtl 11/20/2002 19:32'!
printOn: aStream
self background
ifTrue: [aStream nextPutAll: 'a background ']
ifFalse: [aStream nextPutAll: 'a foreground '].
aStream nextPutAll: self class name, ' for "', self commandLine asString, '"'! !
!ShellBuiltin methodsFor: 'accessing' stamp: 'dtl 12/26/2001 19:54'!
name
^ name! !
!ShellBuiltin methodsFor: 'printing' stamp: 'dtl 3/22/2003 13:53'!
printOn: aStream
super printOn: aStream.
aStream nextPutAll: ' for '.
self name printOn: aStream.! !
!RemoteTask methodsFor: 'printing' stamp: 'dtl 2/17/2018 18:08'!
printOn: aStream
super printOn: aStream.
aStream
nextPutAll: ' ('.
taskIdentifier ifNotNil: [ aStream
nextPutAll: taskIdentifier printString;
nextPutAll: ' - ' ].
aStream
nextPutAll: self runState;
nextPut: $)! !
!ExternalCommandShell class methodsFor: 'system startup' stamp: 'dtl 9/3/2010 17:10'!
shutDown: quitting
"System is going down. Stop input processing so we will not try to
refer to invalid IO streams when the image is restarted. If OSProcess
is in this image, it important that #startup be send to ThisOSProcess
before it is sent to ExternalCommandShell. Therefore ensure that
ExternalCommandShell appears after ThisOSProcess in the system startup
list."
| shell |
quitting
ifTrue: [(shell _ self default)
ifNotNil: [shell shutdownInOldSession]]
! !
!ExternalCommandShell class methodsFor: 'system startup' stamp: 'dtl 9/3/2010 17:10'!
startUp: resuming
| shell |
resuming ifTrue:
[(shell _ self default)
ifNotNil: [shell startUpInNewSession]]! !
!ExternalCommandShell methodsFor: 'printing' stamp: 'dtl 4/9/2006 11:11'!
printOn: aStream
super printOn: aStream.
aStream nextPutAll: (self isRunning ifTrue: [' (running)'] ifFalse: [' (stopped)'])
! !
!TtyPluggableTextController methodsFor: 'control defaults' stamp: 'dtl 6/24/2001 20:45'!
startUp
self closed ifFalse: [^ super startUp]! !
!CuisShellWindow methodsFor: 'menu commands' stamp: 'dtl 9/15/2012 18:59'!
accept
self model cr; flush; processCommand: self commandLineInput asString echo: false
! !
!CuisShellWindow methodsFor: 'updating' stamp: 'dtl 1/21/2007 13:02'!
appendEntry
| str |
"Append the text in the model's writeStream to the editable text. "
textMorph asText size > model characterLimit ifTrue:
["Knock off first half of text"
self selectInvisiblyFrom: 1 to: textMorph asText size // 2.
self replaceSelectionWith: Text new].
self selectInvisiblyFrom: textMorph asText size + 1 to: textMorph asText size.
str := model contents.
(str size > 0) ifTrue:
[self replaceSelectionWith: (Text
string: str
attribute: (TextFontChange fontNumber: self textStyle defaultFontIndex)).
self selectInvisiblyFrom: textMorph asText size + 1 to: textMorph asText size.
model reset]
! !
!CuisShellWindow methodsFor: 'updating' stamp: 'dtl 5/2/2020 10:53'!
update: something
(something == #doCommand)
ifTrue:
[^ self accept].
(something == #exit)
ifTrue:
[^ self owner delete].
^ super update: something
! !
!CuisShellWindow methodsFor: 'command input' stamp: 'dtl 3/18/2001 18:17'!
commandLineInput
^ (self text copyFrom: self positionAfterPromptString to: self text size) asString.
! !
!CuisShellWindow methodsFor: 'command input' stamp: 'dtl 4/7/2001 12:21'!
positionAfterPromptString
"Answer the index of the first character after the last prompt string in my text. If
not found, then assume that the contents of the text are all intended to be command
input."
| t loc |
t := self text.
(1 to: (t size - model promptString size))
reverseDo: [:i |
((loc := t findString: model promptString startingAt: i) ~= 0)
ifTrue: [^ loc + model promptString size]].
^ 1
! !
!CuisShellWindow methodsFor: 'model access' stamp: 'dtl 1/21/2007 10:33'!
setText: aText
scrollBar setValue: 0.0.
textMorph
ifNil: [textMorph := TtyTextMorphForEditView new
contents: aText wrappedTo: self innerBounds width-6.
textMorph setEditView: self.
textMorph setTextStyle: self textStyle.
scroller addMorph: textMorph]
ifNotNil: [textMorph newContents: aText].
self hasUnacceptedEdits: false.
self setScrollDeltas.! !
!CuisShellWindow methodsFor: 'model access' stamp: 'dtl 11/18/2009 22:20'!
textStyle
"A fixed width font for the text morph"
^ (TextStyle named: 'DefaultFixedTextStyle')
ifNil: [TextStyle default]! !
!CuisShellWindow class methodsFor: 'instance creation' stamp: 'dtl 5/3/2020 12:28:03'!
open
"Open a new CommandShell, and answer the instance of CuisShellWindow which it uses."
"CuisShellWindow open"
"TODO: label should update to CWD"
^ self openLabel: 'Command Shell'! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 6/24/2001 19:09'!
activeController
"In Morphic, alway nil. In MVC, the controller which most recently invoked
a command."
^ activeController! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 6/24/2001 19:09'!
activeController: aController
"In Morphic, alway nil. In MVC, the controller which most recently invoked
a command."
activeController _ aController! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 6/19/2002 07:13'!
backgroundMessageQueue
"When a background pipeline completes, it places a message in this queue.
The message will be displayed the next time the user accepts a line of
text (presses CR)."
^ backgroundMessageQueue ifNil: [backgroundMessageQueue _ OrderedCollection new]
! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 8/19/2006 13:14'!
backgroundMessageQueue: anObject
"Set the value of backgroundMessageQueue"
backgroundMessageQueue _ anObject! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 12/15/2002 16:23'!
backgroundPipelines
"The pipelines which are currently being evaluated in background. This
collection is used to keep track of the background command lines by giving
each background pipeline an index number corresponding to its position in
this collection. New pipelines are inserted at the first available nil position
in the collection, and the collection is expanded as needed."
^ backgroundPipelines ifNil: [backgroundPipelines _ OrderedCollection new]
! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 8/19/2006 13:14'!
backgroundPipelines: anObject
"Set the value of backgroundPipelines"
backgroundPipelines _ anObject! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 12/9/2006 10:23'!
backgroundProxies
"All of my process proxies that are being evaluated in the background."
backgroundProxies ifNil: [backgroundProxies _ OrderedCollection new].
^ backgroundProxies! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 12/9/2006 10:23'!
backgroundProxies: aCollection
"All of my process proxies that are being evaluated in the background."
backgroundProxies _ aCollection
! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 12/27/2001 12:24'!
builtinCommands
^ builtinCommands ifNil: [builtinCommands _ Dictionary new]! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 7/14/2002 11:54'!
builtinCommands: aCommandDictionary
builtinCommands _ aCommandDictionary! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 3/16/2001 05:58'!
characterLimit
"Tell the views how much to retain on screen"
^ 20000! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 4/7/2001 12:48'!
commandHistory
"Automatically trim the history list, and answer the resulting list."
| trimIncrement |
trimIncrement _ 1. "Could be a larger number to copy the collection less often"
commandHistory isNil
ifTrue:
[commandHistory _ OrderedCollection new]
ifFalse:
[(commandHistory size > (self historyDepth * 2 + (trimIncrement * 2) - 2))
ifTrue:
[commandHistory _ commandHistory
copyFrom: (trimIncrement * 2 + 1) to: commandHistory size]].
^ commandHistory! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 6/15/2002 09:21'!
commandLineProcesses
"The Smalltalk processes associated with currently executing command lines. Keep
track of them here so that they can be conveniently terminated if something gets
out of whack and needs cleaning up."
commandLineProcesses ifNil: [commandLineProcesses _ OrderedCollection new].
^ commandLineProcesses
! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 8/19/2006 13:14'!
commandLineProcesses: anObject
"Set the value of commandLineProcesses"
commandLineProcesses _ anObject! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 8/19/2006 13:14'!
environment: anObject
"Set the value of environment"
environment _ anObject! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 2/24/2013 10:23'!
errorStream
"Answer the value of errorStream"
^ errorStream ifNil: [errorStream _ WriteStream on: String new]! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 8/19/2006 13:14'!
errorStream: anObject
"Set the value of errorStream"
errorStream _ anObject! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 12/9/2006 10:23'!
foregroundProxies
"All of my process proxies that are being evaluated in the foreground."
^ foregroundProxies ifNil: [foregroundProxies _ OrderedCollection new]
! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 12/9/2006 10:23'!
foregroundProxies: aCollection
"All of my process proxies that are being evaluated in the foreground."
foregroundProxies _ aCollection
! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 4/3/2001 22:48'!
historyIndex
historyIndex ifNil: [historyIndex _ 1].
^ historyIndex
! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 4/22/2001 18:49'!
keyboardConnector
"If nil, then my views should behave normally. If a WriteStream, then my
views should attempt to forward all keyboard inputs to the WriteStream.
This is to permit the keyboard to be connected to an externally executing
process, then reconnected to its normal functions when the external process
is complete."
^ keyboardConnector! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 4/22/2001 18:56'!
keyboardConnector: aWriteStreamOrNil
"If nil, then my views should behave normally. If a WriteStream, then my
views should attempt to forward all keyboard inputs to the WriteStream.
This is to permit the keyboard to be connected to an externally executing
process, then reconnected to its normal functions when the external process
is complete."
keyboardConnector _ aWriteStreamOrNil
! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 2/24/2013 10:23'!
outputStream
"Answer the value of outputStream"
^ outputStream ifNil: [outputStream _ WriteStream on: String new]! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 8/19/2006 13:14'!
outputStream: anObject
"Set the value of outputStream"
outputStream _ anObject! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 11/23/2002 13:31'!
processProxies
"All of my process proxies."
^ self foregroundProxies, self backgroundProxies
! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 6/17/2002 23:45'!
promptString
"The primary command prompt string, equivalent to $PS1 in a Unix shell"
^ promptString ifNil: [promptString _ self defaultPromptString]
! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 6/17/2002 23:47'!
promptString: aString
"The primary command prompt string, equivalent to $PS1 in a Unix shell"
promptString _ aString
! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 6/17/2002 23:46'!
promptStringTwo
"The secondary command prompt string, equivalent to $PS2 in a Unix shell"
^ promptStringTwo ifNil: [promptStringTwo _ self defaultPromptStringTwo]
! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 6/17/2002 23:48'!
promptStringTwo: aString
"The secondary command prompt string, equivalent to $PS2 in a Unix shell"
promptStringTwo _ aString
! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 6/25/2001 23:07'!
readSyncSemaphore
"Used in MVC to pause the screen output when presenting a dialogue to the user"
readSyncSemaphore ifNil: [readSyncSemaphore _ Semaphore forMutualExclusion].
^ readSyncSemaphore! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 6/25/2001 20:34'!
readSyncSemaphore: aSemaphore
"Used in MVC to pause the screen output when presenting a dialogue to the user"
readSyncSemaphore _ aSemaphore! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 7/14/2002 15:56'!
safeToExit
"This is a guard variable to prevent the user from running a command line containing both
a snapshot command and an exit command. This prevents the user from entering a command
such as 'snapshot; exit' which would cause the image to immediately exit when next restarted."
^ safeToExit ifNil: [safeToExit _ true]
! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 7/14/2002 15:56'!
safeToExit: trueOrFalse
"This is a guard variable to prevent the user from running a command line containing both
a snapshot command and an exit command. This prevents the user from entering a command
such as 'snapshot; exit' which would cause the image to immediately exit when next restarted."
safeToExit _ trueOrFalse
! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 12/17/2001 11:34'!
shellSyntax
"A pluggable instance of a command processing syntax"
^ shellSyntax ifNil: [shellSyntax _ ShellSyntax new]! !
!CommandShell methodsFor: 'accessing' stamp: 'dtl 9/8/2001 11:10'!
shellSyntax: aSyntax
"A pluggable instance of a command processing syntax"
shellSyntax _ aSyntax! !
!CommandShell methodsFor: 'event handling' stamp: 'dtl 5/2/2020 10:55'!
backgroundPipelineComplete: aPipeline commandIndex: index
"This message is sent when aPipeline has completed evaluation.
Ensure that the output and error event handlers have had a chance
to completely empty the pipes prior to cleaning things up."
self waitUntilPipesAreEmpty: aPipeline.
aPipeline closePipes.
self backgroundMessageQueue addLast:
((WriteStream on: String new)
nextPutAll: '[', index asString, ']+ Done ';
nextPutAll: aPipeline commandLine;
cr;
contents).
self backgroundProxies removeAll: aPipeline.
self unRegisterBackgroundPipeline: aPipeline at: index.
self notifyRestorePrompt
! !
!CommandShell methodsFor: 'event handling' stamp: 'dtl 1/13/2007 19:10'!
copyAllErrorFrom: aProxy
"An event driven data source may send this message to cause its available
error stream characters to be processed."
| s |
self readSyncSemaphore critical:
[s _ aProxy errorUpToEnd.
s isEmpty ifFalse:
[[self errorStream nextPutAll: s]
on: Error
do: [:ex | ex retryUsing:
["If the error pipe overflows we get an unhandled error,
so wait a while and retry the write."
(Delay forMilliseconds: self pollingDelayTime) wait.
self errorStream nextPutAll: s]]]]
! !
!CommandShell methodsFor: 'event handling' stamp: 'dtl 1/16/2007 07:05'!
copyAllOutputFrom: aProxy
"An event driven data source may send this message to cause its available
characters to be processed."
| s |
self readSyncSemaphore critical:
[s _ aProxy upToEnd.
s isEmpty ifFalse:
[[self outputStream nextPutAll: s]
on: Error
do: [:ex | ex retryUsing:
["If the output pipe overflows we get an unhandled error,
so wait a while and retry the write."
(Delay forMilliseconds: self pollingDelayTime) wait.
self outputStream nextPutAll: s]]]]
! !
!CommandShell methodsFor: 'event handling' stamp: 'dtl 1/13/2007 19:10'!
copyErrorToEofFrom: aProxy
"An event driven data source may send this message to cause its available
error stream characters to be processed."
| s |
self readSyncSemaphore critical:
[s _ aProxy errorUpToEndOfFile.
s isEmpty ifFalse:
[[self errorStream nextPutAll: s]
on: Error
do: [:ex | ex retryUsing:
["If the error pipe overflows we get an unhandled error,
so wait a while and retry the write."
(Delay forMilliseconds: self pollingDelayTime) wait.