I wrote a script this morning called “Planter.” It plants directory trees. I made a logo for it; not because it’s an official project or deserving of the extra effort, but because I had Photoshop open anyway.
Planter takes in simple, indented text files to define the structure of the directory tree it’s going to create. You pass it something like:
css
img
js
libs
mylibs
And it creates:
./css
./img
./js
./js/libs
./js/mylibs
You can nest directories as deeply as you like. You can pass the indented list to it on STDIN (piping from another command), or run planter.rb alone and it will open up your default editor and let you define the list on the fly. You can also use templates…
Create ~/.planter/ and add text files named “[template].tpl”, where “[template]” is the short name you’ll call it with. Say I have “~/.planter/client.tpl”, I can just run planter.rb client and it will read that template in and create the directory structure in whatever directory I’m in when I run it.
You can also use a very basic template variable system to add variable content. In your template, use %%X%% where X is an integer. The number corresponds to the arguments passed on the command line after the template name, so %%1%% is replaced with the first argument:
Nifty. Here’s the script. It may evolve a bit from here, but it does everything I needed it to right now. Hope it’s useful for you, too. Feel free to fork the gist and play with it.
#!/usr/bin/ruby=beginPlanter v1.3Brett Terpstra 2013ruby script to create a directory structure from indented data.Three ways to use it:- Pipe indented (tabs or 2 spaces) text to the script - e.g. `cat "mytemplate" | planter.rb- Create template.tpl files in ~/.planter and call them by their base name - e.g. Create a text file in ~/.planter/site.tpl - `planter.rb site`- Call planter.rb without input and it will open your $EDITOR to create the tree on the flyYou can put %%X%% variables into templates, where X is a number that corresponds to the indexof the argument passed when planter is called. e.g. `planter.rb client "Mr. Butterfinger"`would replace %%1%% in client.tpl with "Mr. Butterfinger". Use %%X|default%% to make a variableoptional with default replacement.If a line in the template matches a file or folder that exists in ~/.planter, that file/folderwill be copied to the destination folder.=endrequire'yaml'require'tmpdir'require'fileutils'defget_hierarchy(input,parent=".",dirs_to_create=[])input.eachdo|dirs|ifdirs.kind_of?Hashdirs.eachdo|k,v|dirs_to_create.push(File.expand_path("#{parent}/#{k.strip}"))dirs_to_create=get_hierarchy(v,"#{parent}/#{k.strip}",dirs_to_create)endelsifdirs.kind_of?Arraydirs_to_create=get_hierarchy(dirs,parent,dirs_to_create)elsifdirs.kind_of?Stringdirs_to_create.push(File.expand_path("#{parent}/#{dirs.strip}"))endendreturndirs_to_createenddeftext_to_yaml(input,replacements=[])variables_count=input.scan(/%%\d+%%/).lengthifvariables_count>replacements.length$stderr.puts('Mismatch variable/replacement counts!')$stderr.puts("Template has #{variables_count} required replacements, #{replacements.length} provided.")Process.exit1endinput.gsub!(/%%(\d+)(?:\|(.*?))?%%/)do|match|ifreplacements[$1.to_i-1]replacements[$1.to_i-1]elsif!$2.nil?$2elseprint"Invalid variable"Process.exit1endendlines=input.split(/[\n\r]/)output=[]prev_indent=0lines.each_with_indexdo|line,i|indent=line.gsub(/ /,"\t").match(/(\t*).*$/)[1]ifindent.length>prev_indentlines[i-1]=lines[i-1].chomp+":"endprev_indent=indent.lengthlines[i]=indent.gsub(/\t/,' ')+"- "+lines[i].strip# unless indent.length == 0endlines.delete_if{|line|line==''}return"---\n"+lines.join("\n")endifSTDIN.stat.size>0data=STDIN.readelsifARGV.length>0template=File.expand_path("~/.planter/#{ARGV[0].gsub(/\.tpl$/,'')}.tpl")ARGV.shiftifFile.exists?templateFile.open(template,'r')do|infile|data=infile.readendelseputs"Specified template not found in ~/.planter/*.tpl"endelsetmpfile=File.expand_path(Dir.tmpdir+"/planter.tmp")File.new(tmpfile,'a+')# at_exit {FileUtils.rm(tmpfile) if File.exists?(tmpfile)}%x{$EDITOR "#{tmpfile}"}data=""File.open(tmpfile,'r')do|infile|data=infile.readendenddata.strip!yaml=YAML.load(text_to_yaml(data,ARGV))dirs_to_create=get_hierarchy(yaml)dirs_to_create.eachdo|dir|curr_dir=ENV['PWD']unlessFile.exists?dir$stderr.puts"Creating #{dir.sub(/^#{curr_dir}\//,'')}"ifFile.exists?(File.join(File.expand_path("~/.planter"),File.basename(dir)))FileUtils.cp_r(File.join(File.expand_path("~/.planter"),File.basename(dir)),dir)elseDir.mkdir(dir)endelse$stderr.puts"Skipping #{dir.sub(/^#{curr_dir}\//,'')} (file exists)"endend