2011-04-02 79 views
10

我需要递归遍历一个目录并创建一个树来与jsTree控件一起使用。该控件接受JSON格式like so。我需要一些红宝石魔法才能使这件事情干净而迅速地发生。Ruby创建递归目录树

任何帮助表示赞赏。

回答

24

你可能想是这样的(未经测试):

def directory_hash(path, name=nil) 
    data = {:data => (name || path)} 
    data[:children] = children = [] 
    Dir.foreach(path) do |entry| 
    next if (entry == '..' || entry == '.') 
    full_path = File.join(path, entry) 
    if File.directory?(full_path) 
     children << directory_hash(full_path, entry) 
    else 
     children << entry 
    end 
    end 
    return data 
end 

递归走在树,建立一个哈希值。把它变成你最喜欢的序列化库的json。

+0

我做了一个小小的修改,以防止它遍历太多:'next if(entry =='..'|| entry =='。 ')' 非常感谢您的帮助。对此,我真的非常感激。 – Mem 2011-04-03 18:12:37

+0

啊谢谢,好点。我已经修改了上面的答案。 – Glenjamin 2011-04-03 18:31:59

1

Ruby的查找模块(require 'find')是简约,但处理目录递归得好:http://www.ruby-doc.org/stdlib/libdoc/find/rdoc/classes/Find.html

+0

感谢您的快速回复。我试图使用Find,但我不确定如何创建树所必需的结构(例如,一个目录具有子目录的子目录以及那些有子目录的子目录等)。 – Mem 2011-04-02 01:40:54

7

首先把你的树,将其转换为路径,以树叶,类似的列表:

def leaves_paths tree 
    if tree[:children] 
    tree[:children].inject([]){|acc, c| 
     leaves_paths(c).each{|p| 
     acc += [[tree[:name]] + p] 
     } 
     acc 
    } 
    else 
    [[tree[:name]]] 
    end 
end 

(不当然,如果上面完全遵循你的jsTree结构,但原理是一样的。)

下面是一个输入和输出示例:

tree = {name: 'foo', children: [ 
     {name: 'bar'}, 
     {name: 'baz', children: [ 
     {name: 'boo'}, 
     {name: 'zoo', children: [ 
      {name: 'goo'} 
     ]} 
     ]} 
    ]} 

p leaves_paths tree 
#=> [["foo", "bar"], ["foo", "baz", "boo"], ["foo", "baz", "zoo", "goo"]] 

然后,对于每个通路,向FileUtils#mkdir_p

paths = leaves_paths tree 
paths.each do |path| 
    FileUtils.mkdir_p(File.join(*path)) 
end 

而且你应该没问题。

编辑:简单的版本:

你并不需要创建叶的名单,只是遍历整个树,并为每个节点创建一个目录:

# executes block on each tree node, recursively, passing the path to the block as argument 
def traverse_with_path tree, path = [], &block 
    path += [tree[:name]] 
    yield path 
    tree[:children].each{|c| traverse_with_path c, path, &block} if tree[:children] 
end 

traverse_with_path tree do |path| 
    FileUtils.mkdir(File.join(*path)) 
end 

EDIT2:

哦,对不起,我误解了。所以,这里有一个方法,使基于目录树磁盘上的哈希:

Dir.glob('**/*'). # get all files below current dir 
    select{|f| 
    File.directory?(f) # only directories we need 
    }.map{|path| 
    path.split '/' # split to parts 
    }.inject({}){|acc, path| # start with empty hash 
    path.inject(acc) do |acc2,dir| # for each path part, create a child of current node 
     acc2[dir] ||= {} # and pass it as new current node 
    end 
    acc 
    } 

因此,对于以下结构:

#$ mkdir -p foo/bar 
#$ mkdir -p baz/boo/bee 
#$ mkdir -p baz/goo 

码以上的回报这个哈希:

{ 
    "baz"=>{ 
    "boo"=>{ 
     "bee"=>{}}, 
    "goo"=>{}}, 
    "foo"=>{ 
    "bar"=>{}}} 

希望你能设法满足你的需求。

+0

嘿,非常感谢回复。我可能在我的原始文章中不清楚,但我不需要实际创建任何目录,但递归列出给定路径的所有现有文件/目录。 – Mem 2011-04-02 09:12:00

+0

非常感谢您的帮助。该代码工作很好,但只抓取目录,但仍然很简单,可以修改。欣赏它。 – Mem 2011-04-03 18:12:09

+0

这帮助我纯粹是为了Mkdir_p() – 2012-10-03 19:46:33

1

我接受的答案在2015年6月份不生效。我将密钥:data更改为'text'。我还概括了排除目录和文件的代码。

def directory_hash(path, name=nil, exclude = [])         
    exclude.concat(['..', '.', '.git', '__MACOSX', '.DS_Store'])     
    data = {'text' => (name || path)}            
    data[:children] = children = []            
    Dir.foreach(path) do |entry|             
    next if exclude.include?(entry)            
    full_path = File.join(path, entry)           
    if File.directory?(full_path)            
     children << directory_hash(full_path, entry)        
    else                   
     children << {'icon' => 'jstree-file', 'text' => entry}      
    end                   
    end                   
    return data                 
end