PackageDir

I've become spoiled with Python's module and package system. So here I give
you a half baked (but mostly working) alternative package loader.

The usage is pretty simple. Given a directory "MyPackage" with some *.st files
and a directory "MyPackage/Subdir" with more *.st files:


"Load the PackageDirLoader and PackageDir classes"
st> FileStream fileIn: 'PackageDir.st'
FileStream

"Load all of the *.st files in Directory "MyPackage" into Namespace Package"
st> PackageDirLoader loadPackage: 'MyPackage'     
Package

"Load all of the *.st files in Directory "MyPackage/Subdir" into Namespace Package.Subdir"
st> PackageDirLoader loadPackage: 'MyPackage.Subdir'

So Enjoy!

Object subclass: PackageDirLoader [

    Loaded := nil.
    SearchPaths := nil.

    PackageDirLoader class >> initialize [
        Loaded := Set new.
        SearchPaths := OrderedCollection new.
    ]

    PackageDirLoader class >> loadPackage: aString [
        ^ self loadPackageSpec: (self packageSpecFor: aString) 
    ]

    PackageDirLoader class >> packageSpecFor: aString [
        ^ (aString subStrings: $.) collect: [:comp | 
            comp asSymbol
        ]
    ]

    PackageDirLoader class >> loadPackageSpec: packageSpec [
        | package |
        package := self findPackageDir: packageSpec.
        ^ package load
    ]

    PackageDirLoader class >> pathForPackage: packageList [
        ^ (packageList collect: [:s | s asString ]) join: '/'
    ]

    PackageDirLoader class >> searchPaths [
        ^ { Directory working }, SearchPaths
    ]

    PackageDirLoader class >> addSearchPath: aDir [
        SearchPaths add: aDir
    ]

    PackageDirLoader class >> findPackageDir: packageSpec [
        | path |
        path := self pathForPackage: packageSpec.

        self searchPaths do: [:p || dir |
            dir := Directory name: (p, '/', path).
            dir exists ifTrue: [
               ^ PackageDir onDir: dir name: packageSpec
            ]
        ].
        self error: 'Package not found'
    ]

    PackageDirLoader class >> loaded: packageSpec [
        Loaded add: packageSpec
    ]

    PackageDirLoader class >> hasLoaded: packageSpec [
        ^ Loaded includes: packageSpec
    ]

]

Object subclass: PackageDir [
    | dir packageSpec |

    PackageDir class >> onDir: dir name: packageSpec [
        ^ self new
            dir: dir;
            packageSpec: packageSpec
            yourself
    ]

    dir [
        
        ^ dir
    ]

    dir: anObject [
        
        dir := anObject
    ]

    packageSpec [
        
        ^ packageSpec
    ]

    packageSpec: anObject [
        
        packageSpec := anObject
    ]

    packageNs [
        | ns |
        ns := Namespace current.

        packageSpec do: [:n |
            ns := ns addSubspace: n
        ].
        ^ ns
    ]

    load [
        | origNs ns |

        ns := self packageNs.

        (PackageDirLoader hasLoaded: packageSpec) ifTrue: [
            ^ self packageNs.
        ].

        origNs := Namespace current.
        [
            Namespace current: ns.

            dir namesMatching: '*.st' do: [:f |
                ('Loading file ', f) displayNl. 
                FileStream fileIn: f.
            ].

            PackageDirLoader loaded: self packageSpec.
        ] ensure: [ Namespace current: origNs ].
        ^ ns
    ]

]

Eval [ 
    PackageDirLoader initialize
]

Note that you could just say

   Loaded := Set new.
   SearchPaths := OrderedCollection new.

Though that transition hasn't happened yet in kernel code.

Also, you may want to add Directory kernel, '../', Smalltalk version and a few others (involving Directory kernel, Directory userBase and/or Directory image) to the default search paths.

I wasn't sure about the class var notation. Thanks for the tip.

I was pretty sure that there would need to be more directories scanned
that's why I added, PackageDirLoader class >> addSearchPath:. :)

User login