Ruby Создать рекурсивное дерево каталогов

Мне нужно рекурсивно пересечь каталог и создать дерево, которое будет использоваться с элементом jsTree. Элемент управления принимает формат JSON так же. Мне нужна рубиновая магия, чтобы это происходило чисто и быстро.

Любая помощь приветствуется.

Ответ 1

Вероятно, вы хотите что-то вроде этого (untested):

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 с вашей любимой библиотекой сериализации.

Ответ 2

Сначала возьмите свое дерево, преобразуйте его в список путей к листьям, похожий на:

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

И вы должны быть в порядке.

Изменить: Простая версия:

Вам не нужно создавать список листьев, просто пересечь все дерево и создать каталог для каждого node:

# 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"=>{}}}

Надеюсь, вам удастся удовлетворить его потребности.

Ответ 4

Принятый ответ не работал с июня 2015 года. Я изменил ключ :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