-
Notifications
You must be signed in to change notification settings - Fork 94
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding the FileSystemTrie class, a trie that stores nodes on the file…
… system
- Loading branch information
Showing
3 changed files
with
244 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |