*** empty log message ***
authorClaus Gittinger <cg@exept.de>
Tue, 09 Sep 1997 19:35:21 +0200
changeset 2911 9964e6839a8e
parent 2910 1a3e44b10f9b
child 2912 d196f7a67d74
*** empty log message ***
Filename.st
OpenVMSFilename.st
PCFilename.st
UnixFilename.st
--- a/Filename.st	Tue Sep 09 19:31:56 1997 +0200
+++ b/Filename.st	Tue Sep 09 19:35:21 1997 +0200
@@ -10,7 +10,7 @@
  hereby transferred.
 "
 
-'From Smalltalk/X, Version:3.1.9 on 8-sep-1997 at 12:38:49 am'                  !
+'From Smalltalk/X, Version:3.1.9 on 9-sep-1997 at 10:54:08 pm'                  !
 
 Object subclass:#Filename
 	instanceVariableNames:'nameString'
@@ -46,35 +46,35 @@
     for being correct or existing.
     Thus, it is possible to do queries such as:
 
-	'/fee/foo/foe' asFilename exists     
-	'/not_existing' asFilename isDirectory 
-	'/foo/bar' asFilename isReadable 
+        '/fee/foo/foe' asFilename exists     
+        '/not_existing' asFilename isDirectory 
+        '/foo/bar' asFilename isReadable 
 
     (all of the above examples will probably return false on your machine ;-).
 
     examples:
 
-	'Makefile' asFilename readStream
-
-	'newFile' asFilename writeStream
-
-	Filename newTemporary writeStream
+        'Makefile' asFilename readStream
+
+        'newFile' asFilename writeStream
+
+        Filename newTemporary writeStream
 
     Beside lots of protocol to query for a files attributes, the class
     protocol offers methods for filename completion, to construct pathes
     (in an OS-independent way) and to create temporary files.
-    Especially the path-construction methods (i.e. #construct:) is highly
+    Especially the path-construction methods (i.e. #construct:) are highly
     recommended in order to avoid having OS details (like directory separators
     being slash or backslash) spreaded in your application.
 
     [author:]
-	Claus Gittinger
+        Claus Gittinger
 
     [see also:]
-	String
-	FileStream DirectoryStream PipeStream Socket
-	OperatingSystem
-	Date Time
+        String
+        FileStream DirectoryStream PipeStream Socket
+        OperatingSystem
+        Date Time
 "
 !
 
@@ -82,125 +82,125 @@
 "
     does a file/directory exist ?:
 
-	|f|
-
-	f := 'foobar' asFilename.
-	^ f exists  
+        |f|
+
+        f := 'foobar' asFilename.
+        ^ f exists  
 
 
     is it a directory ?:
 
-	|f|
-
-	f := '/tmp' asFilename.
-	^ f isDirectory.   
+        |f|
+
+        f := '/tmp' asFilename.
+        ^ f isDirectory.   
 
         
     get the working directory:
 
-	^ Filename defaultDirectory
+        ^ Filename defaultDirectory
 
 
     get a files full pathname 
     (caring for relative names or symbolic links):
 
-	|f|
-
-	f := '..' asFilename.
-	^ f pathName  
+        |f|
+
+        f := '..' asFilename.
+        ^ f pathName  
 
 
     get a directories directory:
 
-	|f|
-
-	f := Filename defaultDirectory.
-	^ f directory 
+        |f|
+
+        f := Filename defaultDirectory.
+        ^ f directory 
 
 
     get a files directory:
 
-	|f|
-
-	f := './smalltalk' asFilename.
-	^ f directory 
+        |f|
+
+        f := './smalltalk' asFilename.
+        ^ f directory 
 
 
     getting access & modification times:
 
 
-	|f|
-
-	f := '/tmp' asFilename.
-	^ f dates
+        |f|
+
+        f := '/tmp' asFilename.
+        ^ f dates
 
     access time only:
 
-	|f|
-
-	f := '/tmp' asFilename.
-	^ f dates at:#accessed  
+        |f|
+
+        f := '/tmp' asFilename.
+        ^ f dates at:#accessed  
         
 
     getting all information on a file/directory:
 
 
-	|f|
-
-	f := '/tmp' asFilename.
-	^ f info
+        |f|
+
+        f := '/tmp' asFilename.
+        ^ f info
 
 
     getting a temporary file (unique name):
 
-	|f|
-
-	f := Filename newTemporary.
-	^ f    
+        |f|
+
+        f := Filename newTemporary.
+        ^ f    
 
 
     creating, writing, reading and removing a temporary file:
-									[exBegin]
-	|f writeStream readStream|
-
-	f := Filename newTemporary.
-	writeStream := f writeStream.
-	writeStream nextPutAll:'hello world'.
-	writeStream cr.
-	writeStream close.
-
-	'contents (as seen by unix''s cat command:' printNL.
-	OperatingSystem executeCommand:('cat ' , f pathName).
-
-	readStream := f readStream.
-	Transcript showCR:'contents as seen by smalltalk:'.
-	Transcript showCR:(readStream upToEnd).
-	readStream close.
-
-	f delete.
-									[exEnd]
+                                                                        [exBegin]
+        |f writeStream readStream|
+
+        f := Filename newTemporary.
+        writeStream := f writeStream.
+        writeStream nextPutAll:'hello world'.
+        writeStream cr.
+        writeStream close.
+
+        'contents (as seen by unix''s cat command:' printNL.
+        OperatingSystem executeCommand:('cat ' , f pathName).
+
+        readStream := f readStream.
+        Transcript showCR:'contents as seen by smalltalk:'.
+        Transcript showCR:(readStream upToEnd).
+        readStream close.
+
+        f delete.
+                                                                        [exEnd]
         
 
     getting a directories contents:
-									[exBegin]
-	|f files|
-
-	f := '.' asFilename.
-	files := f directoryContents.
-	Transcript showCR:'the files are:'.
-	Transcript showCR:(files printString).
-									[exEnd]
+                                                                        [exBegin]
+        |f files|
+
+        f := Filename currentDirectory.
+        files := f directoryContents.
+        Transcript showCR:'the files are:'.
+        Transcript showCR:(files printString).
+                                                                        [exEnd]
 
 
     editing a file:
-									[exBegin]
-	|f|
-
-	f := '/tmp/fooBar' asFilename.
-	(f writeStream) nextPutAll:'hello world'; close.
-
-	f edit
-									[exEnd]
+                                                                        [exBegin]
+        |f|
+
+        f := Filename newTemporary.
+        (f writeStream) nextPutAll:'hello world'; close.
+
+        f edit
+                                                                        [exEnd]
 "
 ! !
 
@@ -1633,6 +1633,32 @@
     "
 
     "Modified: 29.2.1996 / 20:55:06 / cg"
+!
+
+constructString:subname
+    "taking the receiver as a directory name, construct a new
+     filename-string for an entry within this directory 
+     (i.e. for a file or a subdirectory in that directory)."
+
+    |sepString|
+
+    sepString := self class separator asString.
+    nameString = sepString ifTrue:[
+	"I am the root"
+	^ sepString  , subname
+    ].
+    ^ nameString , sepString , subname asString
+
+    "
+     '/tmp' asFilename constructString:'foo'   
+     '/' asFilename constructString:'foo'         
+     '/usr/tmp' asFilename constructString:'foo'
+     '/foo/bar' asFilename constructString:'baz' 
+    "
+
+    "Modified: 7.9.1995 / 10:15:22 / claus"
+    "Modified: 29.2.1996 / 20:55:18 / cg"
+    "Created: 9.9.1997 / 08:57:08 / cg"
 ! !
 
 !Filename methodsFor:'misc'!
@@ -1944,7 +1970,8 @@
 baseName
     "return my baseName as a string.
      - thats the file/directory name without leading parent-dirs.
-     See also: #pathName, #directoryName and #directoryPathName"
+     See also: #pathName, #directoryName and #directoryPathName.
+     Compatibility note: use #tail for ST-80 compatibility."
 
     ^ OperatingSystem baseNameOf:nameString "/ (self pathName) 
 
@@ -1958,7 +1985,7 @@
      '../../libbasic/Object.st' asFilename baseName        
     "
 
-    "Modified: 21.12.1996 / 15:22:16 / cg"
+    "Modified: 9.9.1997 / 10:53:57 / cg"
 !
 
 directory
@@ -2451,6 +2478,6 @@
 !Filename class methodsFor:'documentation'!
 
 version
-    ^ '$Header: /cvs/stx/stx/libbasic/Filename.st,v 1.96 1997-09-09 02:57:00 cg Exp $'
+    ^ '$Header: /cvs/stx/stx/libbasic/Filename.st,v 1.97 1997-09-09 17:35:17 cg Exp $'
 ! !
 Filename initialize!
--- a/OpenVMSFilename.st	Tue Sep 09 19:31:56 1997 +0200
+++ b/OpenVMSFilename.st	Tue Sep 09 19:35:21 1997 +0200
@@ -1,4 +1,4 @@
-'From Smalltalk/X, Version:3.1.9 on 8-sep-1997 at 12:39:22 am'                  !
+'From Smalltalk/X, Version:3.1.9 on 9-sep-1997 at 11:55:40 pm'                  !
 
 Filename subclass:#OpenVMSFilename
 	instanceVariableNames:''
@@ -7,8 +7,93 @@
 	category:'OS-OpenVMS'
 !
 
+Object subclass:#NameComponents
+	instanceVariableNames:'volume directory filename'
+	classVariableNames:''
+	poolDictionaries:''
+	privateIn:OpenVMSFilename
+!
+
+!OpenVMSFilename class methodsFor:'documentation'!
+
+documentation
+"
+    Filenames in OpenVMS.
+"
+! !
+
+!OpenVMSFilename class methodsFor:'helpers'!
+
+nameFromComponents:aComponentObject
+    "concatenate the components, return a fileNameString"
+
+    |s volume directory filename|
+
+    volume := aComponentObject volume.
+    directory := aComponentObject directory.
+    filename := aComponentObject filename.
+
+    volume notNil ifTrue:[
+        s := volume , ':'
+    ] ifFalse:[
+        s := ''
+    ].
+    directory notNil ifTrue:[
+        s := s , '[' , directory , ']'
+    ].
+    filename notNil ifTrue:[
+        s := s , filename
+    ].
+    s isEmpty ifTrue:[
+        ^ '[]'
+    ].
+    ^ s
+
+    "Modified: 9.9.1997 / 09:38:41 / cg"
+!
+
+parseComponentsFrom:aString
+    "given a pathName, decompose into volume, directory & filename.
+     Return the components as a componentObject."
+
+    |v d f idx0 idx|
+
+    idx := aString indexOf:$:.
+    idx ~~ 0 ifTrue:[
+        v := aString copyTo:idx - 1.
+    ].
+    idx := idx + 1.
+    (aString at:idx) == $[ ifTrue:[
+        idx0 := idx + 1.
+        idx := aString indexOf:$] startingAt:idx0.
+        idx == 0 ifTrue:[
+            "/ mhmh - malformed. what should we do here ?
+            self error:'malformed filename'
+        ].
+        d := aString copyFrom:idx0 to:(idx-1).
+        idx := idx + 1.
+    ].
+    f := aString copyFrom:idx.
+    ^ NameComponents basicNew volume:v directory:d filename:f
+
+    "
+     OpenVMSFilename nameFromComponents:(OpenVMSFilename parseComponentsFrom:'dka100:[stx.libbasic]Object.st')
+     OpenVMSFilename nameFromComponents:(OpenVMSFilename parseComponentsFrom:'dka100:[stx.libbasic.-]Object.st')
+     OpenVMSFilename nameFromComponents:(OpenVMSFilename parseComponentsFrom:'dka100:[-]Object.st')
+     OpenVMSFilename nameFromComponents:(OpenVMSFilename parseComponentsFrom:'dka100:[]Object.st')
+     OpenVMSFilename nameFromComponents:(OpenVMSFilename parseComponentsFrom:'dka100:Object.st')
+     OpenVMSFilename nameFromComponents:(OpenVMSFilename parseComponentsFrom:'dka100:[stx.libbasic]')
+    "
+
+    "Modified: 9.9.1997 / 08:50:04 / cg"
+! !
+
 !OpenVMSFilename class methodsFor:'queries'!
 
+directorySuffix
+    ^ '.DIR'
+!
+
 isBadCharacter:aCharacter
     "return true, if aCharacter is unallowed in a filename."
 
@@ -18,10 +103,29 @@
     "Created: 8.9.1997 / 00:14:47 / cg"
 !
 
+isCaseSensitive
+    "return true, if filenames are case sensitive.return true, if filenames are case sensitive."
+
+    ^ false
+!
+
+maxComponentLength
+    "return the maximum number of characters a filename component
+     may have in VMS"
+
+    ^ 39
+!
+
+maxLength
+    "return the maximum number of characters a filename may have in VMS"
+
+    ^ 1024
+!
+
 separator
     "return the file/directory separator.
      For openVMS, the separator concept does not really fit,
-     since names are composed as volume:[dir.dir.dir]file."
+     since names are composed as 'volume:[dir.dir.dir]file.ext'."
 
      ^ $.
 
@@ -30,6 +134,7 @@
      "
 
     "Created: 8.9.1997 / 00:17:28 / cg"
+    "Modified: 9.9.1997 / 08:51:01 / cg"
 !
 
 tempFileNameTemplate
@@ -43,3 +148,256 @@
     "Modified: 8.9.1997 / 00:29:23 / cg"
 ! !
 
+!OpenVMSFilename methodsFor:'instance creation'!
+
+constructString:subname
+    "taking the receiver as a directory name, construct a new
+     filename-string for an entry within this directory
+     (i.e. for a file or a subdirectory in that directory)."
+
+    |v d f comps|
+
+    comps := self class parseComponentsFrom:nameString.
+    (f := comps filename) notNil ifTrue:[
+        "/ path was of the form vol:[d1...dN]foo
+        "/ assume foo is a directory and append it to directory path.
+
+        (f asUppercase endsWith:'.DIR') ifTrue:[
+            f := f copyWithoutLast:4
+        ].
+        d := comps directory , '.' , f.
+        comps directory:d.
+    ].
+    comps filename:subname.
+    ^ self class nameFromComponents:comps
+
+    "
+     (OpenVMSFilename named:'sys$root:[foo.bar]') construct:'baz'
+     (OpenVMSFilename named:'sys$root:[foo.bar]baz') construct:'foo'
+    "
+
+    "Modified: 9.9.1997 / 05:26:04 / cg"
+! !
+
+!OpenVMSFilename methodsFor:'queries'!
+
+isAbsolute
+    "return true, if the receiver represents an absolute pathname
+     (in contrast to one relative to the current directory)."
+
+    |comps dir|
+
+    comps := self class parseComponentsFrom:nameString.
+    (dir := comps directory) isNil ifTrue:[
+        "/ mhmh ...
+        ^ false
+    ].
+    dir size == 0 ifTrue:[
+        "/ [] - the current directory is relative
+        ^ false
+    ].
+    dir = '-' ifTrue:[
+        "/ [-] - the parent directory is relative
+        ^ false
+    ].
+    (dir startsWith:'-.') ifTrue:[
+        "/ [-.] - some parent directory is relative
+        ^ false
+    ].
+    ^ (dir startsWith:'.') not
+
+    "
+     (OpenVMSFilename named:'dka:[foo.bar]baz.st') isAbsolute   
+     (OpenVMSFilename named:'[foo.bar]baz.st') isAbsolute    
+     (OpenVMSFilename named:'[.foo.bar]baz.st') isAbsolute   
+     (OpenVMSFilename named:'[]baz.st') isAbsolute           
+     (OpenVMSFilename named:'[-]baz.st') isAbsolute    
+     (OpenVMSFilename named:'[-.-]baz.st') isAbsolute   
+    "
+
+    "Modified: 9.9.1997 / 09:03:42 / cg"
+! !
+
+!OpenVMSFilename methodsFor:'queries-path & name'!
+
+baseName
+    "return my baseName as a string.
+     - thats the file/directory name without leading parent-dirs.
+     See also: #pathName, #directoryName and #directoryPathName.
+     Compatibility note: use #tail for ST-80 compatibility."
+
+    |idx d f comps|
+
+    comps := self class parseComponentsFrom:nameString.
+    (f := comps filename) size > 0  ifTrue:[
+        ^ f
+    ].
+
+    "/ path was of the form vol:[d1...dN]
+    "/ cut off the last directory.
+    d := comps directory.
+    d notNil ifTrue:[
+        d = '' ifTrue:[
+            "/ [] 
+            "/ ought to get the current directory ...
+            ^ ''
+        ].
+        d = '-' ifTrue:[
+            "/ [-]
+            "/ ought to get the current directory ...
+            ^ ''
+        ].
+        (d endsWith:'.-') ifTrue:[
+            "/ [rest.-]
+            "/ ought to expand and get final directory ...
+            ^ ''
+        ].
+        idx := d lastIndexOf:$..
+        idx ~~ 0 ifTrue:[
+            ^ (d copyFrom:idx+1) , '.DIR'
+        ].
+        ^ d , '.DIR'
+    ].
+    ^ ''
+
+    "
+     (OpenVMSFilename named:'dka100:[stx.libbasic]Object.st') baseName
+     (OpenVMSFilename named:'stx$root:[stx.libbasic.-]Object.st') baseName 
+     (OpenVMSFilename named:'[-]Object.st') baseName                       
+     (OpenVMSFilename named:'[]Object.st') baseName                        
+     (OpenVMSFilename named:'Object.st') baseName                          
+     (OpenVMSFilename named:'[stx.libbasic]') baseName                     
+     (OpenVMSFilename named:'[stx]') baseName                     
+     (OpenVMSFilename named:'[]') baseName                     
+     (OpenVMSFilename named:'[-]') baseName                     
+    "
+
+    "Created: 9.9.1997 / 09:23:15 / cg"
+    "Modified: 9.9.1997 / 10:52:04 / cg"
+!
+
+directoryName
+    "return the directory name part of the file/directory as a string.
+     - thats the name of the directory where the file/dir represented by
+       the receiver is contained in.
+     (this is almost equivalent to #directory, but returns
+      a string instead of a Filename instance).
+     See also: #pathName, #directoryPathName and #baseName.
+     Compatibility note: use #head for ST-80 compatibility."
+
+    |idx d f comps|
+
+    comps := self class parseComponentsFrom:nameString.
+    d := comps directory.
+    (f := comps filename) size > 0  ifTrue:[
+        comps filename:nil.
+        ^ self class nameFromComponents:comps
+    ].
+
+    "/ path was of the form vol:[d1...dN]
+    "/ cut off the last directory.
+
+    d isNil ifFalse:[
+        d = '' ifTrue:[
+            "/ [] -> [-]
+        ] ifFalse:[
+            d = '-' ifTrue:[
+                "/ [-] -> [-.-]
+                d := '-.-'
+            ] ifFalse:[
+                (d endsWith:'.-') ifTrue:[
+                    "/ [rest.-] -> [rest.-.-]
+                    d := d , '.-'
+                ] ifFalse:[
+                    idx := d lastIndexOf:$..
+                    idx ~~ 0 ifTrue:[
+                        d := d copyTo:idx-1
+                    ]
+                ]
+            ]
+        ].
+        comps directory:d.
+    ].
+    ^ self class nameFromComponents:comps
+
+    "
+     (OpenVMSFilename named:'dka100:[stx.libbasic]Object.st') directoryName
+     (OpenVMSFilename named:'stx$root:[stx.libbasic.-]Object.st') directoryName 
+     (OpenVMSFilename named:'[-]Object.st') directoryName                       
+     (OpenVMSFilename named:'[]Object.st') directoryName                        
+     (OpenVMSFilename named:'Object.st') directoryName                          
+     (OpenVMSFilename named:'[stx.libbasic]') directoryName                     
+    "
+
+    "Created: 9.9.1997 / 09:23:15 / cg"
+    "Modified: 9.9.1997 / 10:36:42 / cg"
+!
+
+volume
+    "return the disc volume part of the name or an empty string."
+
+    ^ (self class parseComponentsFrom:nameString) volume ? ''
+
+    "
+     (OpenVMSFilename named:'dka100:[stx.libbasic]Object.st') volume  
+     (OpenVMSFilename named:'stx$root:[stx.libbasic.-]Object.st') volume 
+     (OpenVMSFilename named:'[-]Object.st') volume                       
+     (OpenVMSFilename named:'[]Object.st') volume                        
+     (OpenVMSFilename named:'Object.st') volume                          
+     (OpenVMSFilename named:'[stx.libbasic]') volume                     
+    "
+
+    "Modified: 9.9.1997 / 08:56:05 / cg"
+! !
+
+!OpenVMSFilename::NameComponents methodsFor:'accessing'!
+
+directory
+    ^ directory
+
+    "Created: 9.9.1997 / 05:21:04 / cg"
+!
+
+directory:aString
+    directory := aString
+
+    "Created: 9.9.1997 / 05:21:11 / cg"
+!
+
+filename
+    ^ filename
+
+    "Created: 9.9.1997 / 05:21:23 / cg"
+!
+
+filename:aString
+    filename := aString
+
+    "Created: 9.9.1997 / 05:21:28 / cg"
+!
+
+volume
+    ^ volume
+
+    "Created: 9.9.1997 / 05:20:58 / cg"
+!
+
+volume:aString
+    volume := aString
+
+    "Created: 9.9.1997 / 05:21:17 / cg"
+!
+
+volume:v directory:d filename:n
+    volume := v.
+    directory := d.
+    filename := n.
+
+    "Created: 9.9.1997 / 05:20:53 / cg"
+! !
+
+!OpenVMSFilename class methodsFor:'documentation'!
+
+version
+    ^ '$Header: /cvs/stx/stx/libbasic/OpenVMSFilename.st,v 1.2 1997-09-09 17:35:20 cg Exp $'
+! !
--- a/PCFilename.st	Tue Sep 09 19:31:56 1997 +0200
+++ b/PCFilename.st	Tue Sep 09 19:35:21 1997 +0200
@@ -1,4 +1,4 @@
-'From Smalltalk/X, Version:3.1.9 on 8-sep-1997 at 12:39:38 am'                  !
+'From Smalltalk/X, Version:3.1.9 on 9-sep-1997 at 11:55:53 pm'                  !
 
 Filename subclass:#PCFilename
 	instanceVariableNames:''
@@ -7,6 +7,14 @@
 	category:'OS-PC'
 !
 
+!PCFilename class methodsFor:'documentation'!
+
+documentation
+"
+    Filenames in Windows-NT / Win95.
+"
+! !
+
 !PCFilename class methodsFor:'queries'!
 
 isBadCharacter:aCharacter
@@ -59,3 +67,8 @@
     "Created: 7.9.1997 / 23:58:06 / cg"
 ! !
 
+!PCFilename class methodsFor:'documentation'!
+
+version
+    ^ '$Header: /cvs/stx/stx/libbasic/PCFilename.st,v 1.3 1997-09-09 17:35:21 cg Exp $'
+! !
--- a/UnixFilename.st	Tue Sep 09 19:31:56 1997 +0200
+++ b/UnixFilename.st	Tue Sep 09 19:35:21 1997 +0200
@@ -1,4 +1,4 @@
-'From Smalltalk/X, Version:3.1.9 on 8-sep-1997 at 12:39:54 am'                  !
+'From Smalltalk/X, Version:3.1.9 on 9-sep-1997 at 11:56:06 pm'                  !
 
 Filename subclass:#UnixFilename
 	instanceVariableNames:''
@@ -7,6 +7,15 @@
 	category:'OS-Unix'
 !
 
+!UnixFilename class methodsFor:'documentation'!
+
+documentation
+"
+    Filenames in Unix.
+"
+
+! !
+
 !UnixFilename class methodsFor:'queries'!
 
 isBadCharacter:aCharacter
@@ -84,31 +93,8 @@
     "Modified: 7.9.1997 / 23:43:03 / cg"
 ! !
 
-!UnixFilename methodsFor:'instance creation'!
-
-constructString:subname
-    "taking the receiver as a directory name, construct a new
-     filename-string for an entry within this directory 
-     (i.e. for a file or a subdirectory in that directory)."
-
-    |sepString|
+!UnixFilename class methodsFor:'documentation'!
 
-    sepString := self class separator asString.
-    nameString = sepString ifTrue:[
-	"I am the root"
-	^ sepString  , subname
-    ].
-    ^ nameString , sepString , subname asString
-
-    "
-     '/tmp' asFilename constructString:'foo'   
-     '/' asFilename constructString:'foo'         
-     '/usr/tmp' asFilename constructString:'foo'
-     '/foo/bar' asFilename constructString:'baz' 
-    "
-
-    "Modified: 7.9.1995 / 10:15:22 / claus"
-    "Modified: 29.2.1996 / 20:55:18 / cg"
-    "Created: 7.9.1997 / 23:44:55 / cg"
+version
+    ^ '$Header: /cvs/stx/stx/libbasic/UnixFilename.st,v 1.3 1997-09-09 17:35:21 cg Exp $'
 ! !
-