Planter: Tree planting for your filesystem

[Tweet : nvALT]

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
Copy

And it creates:

./css
./img
./js
./js/libs
./js/mylibs
Copy

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:

In the template:

client-%%1%%
	expenses
	contracts
Copy

On the command line:

planter.rb client "Mrs. Yourmom"
Copy

Creates:

./client-Mrs. Yourmom
./client-Mrs. Yourmom/expenses
./client-Mrs. Yourmom/contracts
Copy

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
=begin
Planter v1.3
Brett Terpstra 2013
ruby 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 fly
You can put %%X%% variables into templates, where X is a number that corresponds to the index
of 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 variable
optional with default replacement.
If a line in the template matches a file or folder that exists in ~/.planter, that file/folder
will be copied to the destination folder.
=end
require 'yaml'
require 'tmpdir'
require 'fileutils'
def get_hierarchy(input,parent=".",dirs_to_create=[])
input.each do |dirs|
if dirs.kind_of? Hash
dirs.each do |k,v|
dirs_to_create.push(File.expand_path("#{parent}/#{k.strip}"))
dirs_to_create = get_hierarchy(v,"#{parent}/#{k.strip}",dirs_to_create)
end
elsif dirs.kind_of? Array
dirs_to_create = get_hierarchy(dirs,parent,dirs_to_create)
elsif dirs.kind_of? String
dirs_to_create.push(File.expand_path("#{parent}/#{dirs.strip}"))
end
end
return dirs_to_create
end
def text_to_yaml(input, replacements = [])
variables_count = input.scan(/%%\d+%%/).length
if variables_count > replacements.length
$stderr.puts('Mismatch variable/replacement counts!')
$stderr.puts("Template has #{variables_count} required replacements, #{replacements.length} provided.")
Process.exit 1
end
input.gsub!(/%%(\d+)(?:\|(.*?))?%%/) do |match|
if replacements[$1.to_i - 1]
replacements[$1.to_i - 1]
elsif !$2.nil?
$2
else
print "Invalid variable"
Process.exit 1
end
end
lines = input.split(/[\n\r]/)
output = []
prev_indent = 0
lines.each_with_index do |line, i|
indent = line.gsub(/ /,"\t").match(/(\t*).*$/)[1]
if indent.length > prev_indent
lines[i-1] = lines[i-1].chomp + ":"
end
prev_indent = indent.length
lines[i] = indent.gsub(/\t/,' ') + "- " + lines[i].strip # unless indent.length == 0
end
lines.delete_if {|line|
line == ''
}
return "---\n" + lines.join("\n")
end
if STDIN.stat.size > 0
data = STDIN.read
elsif ARGV.length > 0
template = File.expand_path("~/.planter/#{ARGV[0].gsub(/\.tpl$/,'')}.tpl")
ARGV.shift
if File.exists? template
File.open(template, 'r') do |infile|
data = infile.read
end
else
puts "Specified template not found in ~/.planter/*.tpl"
end
else
tmpfile = 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.read
end
end
data.strip!
yaml = YAML.load(text_to_yaml(data,ARGV))
dirs_to_create = get_hierarchy(yaml)
dirs_to_create.each do |dir|
curr_dir = ENV['PWD']
unless File.exists? dir
$stderr.puts "Creating #{dir.sub(/^#{curr_dir}\//,'')}"
if File.exists?(File.join(File.expand_path("~/.planter"),File.basename(dir)))
FileUtils.cp_r(File.join(File.expand_path("~/.planter"),File.basename(dir)), dir)
else
Dir.mkdir(dir)
end
else
$stderr.puts "Skipping #{dir.sub(/^#{curr_dir}\//,'')} (file exists)"
end
end
view raw planter.rb hosted with ❤ by GitHub

[Photo credit: simphonic]