Skip to content

Commit

Permalink
Adding the FileSystemTrie class, a trie that stores nodes on the file…
Browse files Browse the repository at this point in the history
… system
  • Loading branch information
camertron committed Nov 23, 2015
1 parent 4203672 commit 420c99d
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/twitter_cldr/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module TwitterCldr
module Utils

autoload :CodePoints, 'twitter_cldr/utils/code_points'
autoload :FileSystemTrie, 'twitter_cldr/utils/file_system_trie'
autoload :RangeSet, 'twitter_cldr/utils/range_set'
autoload :RegexpAst, 'twitter_cldr/utils/regexp_ast'
autoload :RegexpSampler, 'twitter_cldr/utils/regexp_sampler'
Expand Down
145 changes: 145 additions & 0 deletions lib/twitter_cldr/utils/file_system_trie.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# encoding: UTF-8

# Copyright 2012 Twitter, Inc
# http://www.apache.org/licenses/LICENSE-2.0

require 'yaml'
require 'fileutils'

module TwitterCldr
module Utils

class FileSystemTrie
VALUE_FILE = 'value.dump'

attr_reader :path_root

def initialize(path_root, root = Node.new)
@path_root = path_root
@root = root
end

def empty?
!@root.has_children?
end

def add(key, value)
store(key, value, false)
end

def set(key, value)
store(key, value)
end

def get(key)
node = get_node(key)
node && node.value
end

def get_node(key)
traverse(key) do |node, key_element|
return unless node
node.child(key_element)
end
end

# to prevent printing of a possibly huge children list in the IRB
alias_method :inspect, :to_s

private

def store(key, value, override = true)
final = store_p(key)

unless final.value && !override
final.value = value

path = File.join(path_root, *key, VALUE_FILE)
File.open(path, 'w+') { |f| f.write(Marshal.dump(value)) }
end
end

def store_p(key)
current_path = path_root

traverse(key) do |node, key_element|
current_path = File.join(current_path, key_element)
mkdir(current_path)
node.child(key_element) || node.set_child(key_element, Node.new)
end
end

def traverse(key)
current_path = path_root

key.inject(@root) do |node, key_element|
next unless node
next unless key_element
current_path = File.join(current_path, key_element)
fill_in_path(current_path, key_element, node)
fill_in_value(current_path, key_element, node)
yield node, key_element if block_given?
end
end

def fill_in_path(current_path, key_element, parent)
if File.exist?(current_path)
unless parent.child(key_element)
parent.set_child(key_element, Node.new)
end
end
end

def fill_in_value(current_path, key_element, parent)
value_file = File.join(current_path, VALUE_FILE)
child = parent.child(key_element)

if File.exist?(value_file) && child && !child.value
parent.child(key_element).value = ::Marshal.load(
File.read(value_file)
)
end
end

def mkdir(path)
FileUtils.mkdir(path) unless File.exist?(path)
end

class Node

attr_accessor :value, :children

def initialize(value = nil, children = {})
@value = value
@children = children
end

def child(key)
@children[key]
end

def set_child(key, child)
@children[key] = child
end

def has_children?
!@children.empty?
end

def each_key_and_child(&block)
@children.each(&block)
end

def keys
@children.keys
end

def to_trie
Trie.new(self.class.new(nil, @children)).lock
end

end
end

end
end
98 changes: 98 additions & 0 deletions spec/utils/file_system_trie_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# encoding: UTF-8

# Copyright 2012 Twitter, Inc
# http://www.apache.org/licenses/LICENSE-2.0

require 'spec_helper'

require 'fileutils'
require 'securerandom'
require 'tmpdir'

include TwitterCldr::Utils

describe FileSystemTrie do
let(:tmp_dir) do
File.join(Dir.tmpdir, SecureRandom.hex)
end

let(:trie) { FileSystemTrie.new(tmp_dir) }

before(:each) do
FileUtils.mkdir_p(tmp_dir)
end

after(:each) do
FileUtils.rm_rf(tmp_dir)
end

describe '#empty?' do
it 'returns true if the trie is empty' do
expect(trie).to be_empty
end

it 'returns false if the trie is not empty' do
trie.add(%w(foo bar), 'baz')
expect(trie).to_not be_empty
end
end

describe '#add' do
it 'creates a nested folder structure' do
trie.add(%w(foo bar baz), 'boo')
path = Pathname(tmp_dir)
.join('foo/bar/baz')
.join(FileSystemTrie::VALUE_FILE)

expect(path).to exist
end

it 'adds the entry to the trie' do
key = %w(foo bar baz)
trie.add(key, 'boo')
expect(trie.get(key)).to eq('boo')
end
end

describe '#set' do
it "adds an entry to the trie if the key doesn't exist" do
key = %w(foo bar baz)
trie.set(key, 'boo')
expect(trie.get(key)).to eq('boo')
end

it 'updates the entry in the trie if the key already exists' do
key = %w(foo bar baz)
trie.add(key, 'boo')
expect(trie.get(key)).to eq('boo')
trie.set(key, 'blarg')
expect(trie.get(key)).to eq('blarg')
end
end

describe '#get' do
let(:key) { %w(foo bar baz) }

before(:each) do
trie.add(key, 'boo')
end

it 'retrieves the value at the given key' do
expect(trie.get(key)).to eq('boo')
end
end

describe '#get_node' do
let(:key) { %w(foo bar baz) }

before(:each) do
trie.add(key, 'boo')
end

it 'retrieves the trie node at the given path' do
node = trie.get_node(key)
expect(node).to be_a(FileSystemTrie::Node)
expect(node.value).to eq('boo')
end
end
end

0 comments on commit 420c99d

Please sign in to comment.