I thought this was too simple to bother posting about, but someone requested it, so here it is. Having made my journey to Linux about a year ago, I've found a new appreciation for the simplicity of plain old text files and the file system in general.
Some of the tools I've come to rely on like Daemontools from Daniel Bernstein also show this appreciation of the Unix way, small simple reliable tools that just work and are configured easily with plain text files. The most recent versions of Apache have also broken up the monolithic httpd.config into much smaller simpler individual files spread out over various directories that greatly ease automation by keeping individual site settings in separate files and using simple symbolic links to enable or disable individual sites.
Inspired by the simplicity and ease of managing these tools, I decided my Smalltalk programs, while ungodly easy to write, were much too hard to configure and deploy because configuration was based either on code in the image or in the case of Seaside, its custom configuration system, neither of which are simple or easy to move around between images and servers. I also like how Ruby on Rails configuration system allows multiple configurations that you can easily switch between to run your code in your various environments.
Rather than trying to design some ultimate configuration system, I just looked at how I write and deploy my applications, which is always one image per application in its own directory (possibly launched multiple times on various ports) and decided the simplest thing that could possibly work for me was just a directory named config, in my image directory, with three subdirectories named prod, dev, and test, and a single file named config which tells me which config subdirectory is active.
So I'll declare a configuration class...
Object subclass: #SSConfig instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'SentorsaSeaside-Configuration'
and a class side #create method to setup this structure on the filesystem...
create | configDir | configDir := (FileDirectory default directoryNamed: #config) assureExistence. FileStream forceNewFileNamed: (configDir fullNameFor: #config) do: [ : f | f nextPutAll: 'dev' ]. (configDir directoryNamed: #prod) assureExistence. (configDir directoryNamed: #dev) assureExistence. (configDir directoryNamed: #test) assureExistence
Next, I want a simple dictionary style API, "SSConfig at: #someSetting", so I'll need the core class side method #at: which grabs the current configuration and uses the key you hand it to looks for the file name that matches, opening it in read only mode if it exists or simply creating it if it doesn't.
at: aKey | currentConfig configDir value | currentConfig := FileStream readOnlyFileNamed: ((FileDirectory default directoryNamed: #config) fullNameFor: #config) do: [ : f | f contentsOfEntireFile ]. configDir := (FileDirectory default directoryNamed: #config) directoryNamed: currentConfig. value := (configDir fileExists: aKey) ifTrue: [ FileStream readOnlyFileNamed: (configDir fullNameFor: aKey) do: [ : f | f contentsOfEntireFile ] ] ifFalse: [ FileStream forceNewFileNamed: (configDir fullNameFor: aKey) do: [ : f | f contentsOfEntireFile ] ]. ^ (value endsWith: Character lf asString) ifTrue: [ value allButLast ] ifFalse: [ value ]
Next I want to be able to ask for a value and provide a default in case there is no configured value, built simply upon the previous method...
at: aKey default: aValue ^(self at: aKey) ifEmpty: [ aValue ] ifNotEmptyDo: [ : it | it ]
I also want to be able to ask for a config value typed to a boolean (SSConfig can: #useBlaBla) for the common case of true/false settings, another simple method built upon #at:...
can: aKey ^(self at: aKey) = 'true'
And of course, having strings and booleans, I now want a couple of simple methods for asking for integers from config...
numAt: aKey ^(self at: aKey) ifEmpty: [ 0 ] ifNotEmptyDo: [ : it | it asInteger ] numAt: aKey default: aValue ^(self at: aKey) ifEmpty: [ aValue ] ifNotEmptyDo: [ : it | it asInteger ]
These are all class side methods, and so far I've not found the need for anything more than strings, bools, and integers. If I do, I'll extend it more, however, this seems to be all I need for all of my configuration needs thus far. It's simple, every setting is its own file, and it allows me to easily switch between various configs and easily share configurations between various images with nothing more than a simple file copy. I can also easily edit the configuration from the shell, where I do most of my administration tasks.
NOTE: This method is not application aware, so it won't work well if you run multiple applications within a single image, but I'm not writing code for use cases I don't have and it works well for how I deploy my applications.