Over this past weekend I began templating a project structure that we use at my day/evening/night job to allow developers to simply execute a script and provide a few parameters to initialize a new development project (including build script, fxcop analysis, unit testing, visual studio projects, etc.). I didn’t think this would take that long despite being relatively new to the Ruby world.
Oddly enough, I had trouble finding similar examples on the interwebs – even StackOverflow didn’t have the answer! The horror, the horror!
No matter, I’m a developer after all and should be, better be capable of handling this simple script. I decided to start simple and just use the directory global searching method to recursively get everything within my project template’s directory:
def packageInitiator( startDirectory )
Dir.glob( startDirectory ).each do | path |
puts path
end
end
packageInitiator( './**/*' )
OK, good start. Now I have all the items (files and directories) within my current executing directory. I figured it would be easy to then replace the templated text in the directory and file paths at that point.
Not so fast!
The global directory search - Dir.glob() – actually traverses the directory structure and returns an array of all the file/directory names within the executing directory. Take a look at the ruby documentation on the it here. What ended up happening was that as I changed directory names using a top-down approach, I was invalidating paths used later in the loop. For example, /project/store is the first directory to change in the example structure below:
Original Directory Structure
- /project/store/ - [first to change]
- /project/store/hats
- /project/store/jackets
When we try to change /project/store/hats ruby throws an error because the root path /project/store would have changed. The fix for this was simple,reverse the array and go from bottom-up:
def packageInitiator( startDirectory )
directoryItems = Dir.glob( startDirectory ).reverse
directoryItems.each do | path |
puts path
end
end
packageInitiator( './**/*' )
Better. Time to add the logic to replace the placeholders I put in the file and directory names. At this point I don’t care about whether the item is a file or directory so I treat them the same:
def packageInitiator( startDirectory, patternToReplace )
directoryItems = Dir.glob( startDirectory ).reverse
directoryItems.each do | path |
if( path.include? patternToReplace )
oldPath = File.dirname(path)
newPath = oldPath + '/' + File.basename( path ).gsub( patternToReplace, 'MyNewProjectName' )
File.rename( path, newPath )
end
end
end
packageInitiator( './**/*', '{projectName}' )
Much better. Now I need to determine if I am dealing with a file or a directory since I will need to replace the contents of files where I find the placeholder text as well. Turns out that was pretty darn simple.
Final Script
def replaceFileContents( path, patternToReplace, newProjectName )
fileContents = File.read ( path )
fileContents = fileContents.gsub( patternToReplace, newProjectName )
File.open( path, 'w' ) { | file | file.puts fileContents }
end
def packageInitiator( startDirectory, patternToReplace, newProjectName )
directoryItems = Dir.glob( startDirectory ).reverse
directoryItems.each do | path |
# ignoring ruby scripts so I don't change contents of this file
if( FileTest.file? ( path ) and File.extname( path ) != '.rb' )
replaceFileContents( path, patternToReplace, newProjectName )
end
if( path.include? patternToReplace )
oldPath = File.dirname(path)
newPath = oldPath + '/' + File.basename( path ).gsub( patternToReplace, newProjectName )
File.rename( path, newPath )
end
end
end
packageInitiator( './**/*', '{projectName}', 'MyNewProjectName' )
I can think of a lot of ways to improve this for more complicated scenarios, but this worked just fine for my needs. Hopefully this helps someone else too. And as I mentioned previously, I’m relatively new to Ruby and welcome and constructive feedback or suggestions for improvement that any Ruby experts have for me.