2011-04-02 117 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