text-db 0.2.0.beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +149 -0
- data/Rakefile +7 -0
- data/lib/textdb/block_method.rb +28 -0
- data/lib/textdb/configuration.rb +50 -0
- data/lib/textdb/data/key.rb +114 -0
- data/lib/textdb/data/value.rb +50 -0
- data/lib/textdb/data.rb +8 -0
- data/lib/textdb/default_configuration.rb +11 -0
- data/lib/textdb/error.rb +18 -0
- data/lib/textdb/event/listener.rb +59 -0
- data/lib/textdb/event/processor.rb +91 -0
- data/lib/textdb/event.rb +16 -0
- data/lib/textdb/version.rb +3 -0
- data/lib/textdb.rb +99 -0
- data/spec/lib/01_configuration_spec.rb +46 -0
- data/spec/lib/02_block_method_spec.rb +14 -0
- data/spec/lib/03_create_spec.rb +48 -0
- data/spec/lib/04_update_spec.rb +24 -0
- data/spec/lib/05_read_spec.rb +38 -0
- data/spec/lib/06_delete_spec.rb +48 -0
- data/spec/spec_helper.rb +24 -0
- data/text-db.gemspec +25 -0
- metadata +128 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Ondřej Moravčík
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
# Textdb
|
2
|
+
|
3
|
+
Textdb is a database which structure is determined by folders and data are represented by files.
|
4
|
+
|
5
|
+
Current state: **beta version**.
|
6
|
+
|
7
|
+
Example:
|
8
|
+
```ruby
|
9
|
+
# .
|
10
|
+
# |-- key1
|
11
|
+
# | |-- key1_1
|
12
|
+
# | | `-- value1_1_1.data
|
13
|
+
# | `-- value1_1.data
|
14
|
+
# `-- key2
|
15
|
+
# `-- value2_1.data
|
16
|
+
|
17
|
+
{
|
18
|
+
'key1' => {
|
19
|
+
'key1_1' => {
|
20
|
+
'value1_1_1' => 'text'
|
21
|
+
},
|
22
|
+
'value1_1_1' => 'text'
|
23
|
+
},
|
24
|
+
'key2' => {
|
25
|
+
'value2_1' => 'text'
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
```
|
30
|
+
|
31
|
+
## Installation
|
32
|
+
|
33
|
+
Add this line to your application's Gemfile:
|
34
|
+
|
35
|
+
```
|
36
|
+
gem 'text-db'
|
37
|
+
```
|
38
|
+
|
39
|
+
And then execute:
|
40
|
+
|
41
|
+
```bash
|
42
|
+
$ bundle
|
43
|
+
```
|
44
|
+
|
45
|
+
Or install it yourself as:
|
46
|
+
|
47
|
+
```bash
|
48
|
+
$ gem install text-db
|
49
|
+
```
|
50
|
+
|
51
|
+
## Configuration
|
52
|
+
|
53
|
+
`Textdb.config` or `Textdb.configuration(&block)`.
|
54
|
+
|
55
|
+
How:
|
56
|
+
```ruby
|
57
|
+
Textdb.configuration do
|
58
|
+
key "/tmp/1"
|
59
|
+
end
|
60
|
+
|
61
|
+
# --- OR ---
|
62
|
+
|
63
|
+
Textdb.config.key = "/tmp/1"
|
64
|
+
```
|
65
|
+
|
66
|
+
<table>
|
67
|
+
<thead>
|
68
|
+
<tr>
|
69
|
+
<th>Key</th>
|
70
|
+
<th>Default</th>
|
71
|
+
<th>Description</th>
|
72
|
+
</tr>
|
73
|
+
</thead>
|
74
|
+
|
75
|
+
<tbody>
|
76
|
+
<tr>
|
77
|
+
<td><b>base_folder</b></td>
|
78
|
+
<td>/tmp/textdb</td>
|
79
|
+
<td>The root folder for Textdb. Folder must exist.</td>
|
80
|
+
</tr>
|
81
|
+
<tr>
|
82
|
+
<td><b>data_file_extension</b></td>
|
83
|
+
<td>.data</td>
|
84
|
+
<td>Extension for data files.</td>
|
85
|
+
</tr>
|
86
|
+
<tr>
|
87
|
+
<td><b>listen</b></td>
|
88
|
+
<td>false</td>
|
89
|
+
<td>
|
90
|
+
Automatic listening for changes in the root folder.<br>
|
91
|
+
<br>
|
92
|
+
Current works onyl with adding file.
|
93
|
+
</td>
|
94
|
+
</tr>
|
95
|
+
</tbody>
|
96
|
+
</table>
|
97
|
+
|
98
|
+
## Usage
|
99
|
+
|
100
|
+
Folder structure:
|
101
|
+
```
|
102
|
+
.
|
103
|
+
|-- key1
|
104
|
+
| |-- key1_1
|
105
|
+
| | `-- key1_1_1
|
106
|
+
| | |-- value1_1_1_1
|
107
|
+
| | `-- value1_1_1_2
|
108
|
+
| |-- value1_1
|
109
|
+
| `-- value1_2
|
110
|
+
`-- key2
|
111
|
+
`-- value2_1
|
112
|
+
```
|
113
|
+
|
114
|
+
### Read
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
Textdb.read { key1 } # -> list of all in the folder /key1
|
118
|
+
Textdb.read { key2.value2_1 } # -> content of file /key2/value2_1.data
|
119
|
+
```
|
120
|
+
|
121
|
+
### Create
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
Textdb.create { a.b.c } # -> create a file /a/b/c.data
|
125
|
+
Textdb.create { a.b.c }.update("text") # -> create a file /a/b/c.data with content "text"
|
126
|
+
```
|
127
|
+
|
128
|
+
### Update
|
129
|
+
|
130
|
+
`\n` is not included at the end.
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
Textdb.update("text") { a.b.c } # -> update /a/b/c.data with new content
|
134
|
+
```
|
135
|
+
|
136
|
+
### Delete
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
Textdb.delete { key1 } # -> delete everything in the folder /key1
|
140
|
+
Textdb.delete { key2.value2_1 } # -> delete only /key/value2_1.data
|
141
|
+
```
|
142
|
+
|
143
|
+
## Contributing
|
144
|
+
|
145
|
+
1. Fork it
|
146
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
147
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
148
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
149
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Textdb
|
2
|
+
class BlockMethod
|
3
|
+
|
4
|
+
instance_methods.each do |meth|
|
5
|
+
undef_method(meth) unless meth =~ /\A__/ || meth == :object_id || meth == :instance_eval
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :methods_seq
|
9
|
+
|
10
|
+
def self.get(&block)
|
11
|
+
begin
|
12
|
+
Textdb::BlockMethod.new.instance_eval(&block).methods_seq
|
13
|
+
rescue NoMethodError
|
14
|
+
[]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@methods_seq = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def method_missing(method, *args, &block)
|
23
|
+
@methods_seq << method.to_s
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Textdb
|
2
|
+
class Configuration
|
3
|
+
|
4
|
+
def initialize(config)
|
5
|
+
config.each do |key, value|
|
6
|
+
eval <<-METHOD
|
7
|
+
def #{key}(value = nil, &block)
|
8
|
+
if block_given?
|
9
|
+
@#{key}.instance_eval(&block)
|
10
|
+
end
|
11
|
+
|
12
|
+
if value.nil?
|
13
|
+
if @#{key}.is_a?(Proc)
|
14
|
+
return @#{key}.call
|
15
|
+
end
|
16
|
+
return @#{key}
|
17
|
+
end
|
18
|
+
|
19
|
+
self.#{key} = value
|
20
|
+
end
|
21
|
+
METHOD
|
22
|
+
|
23
|
+
if value.is_a?(Array)
|
24
|
+
eval <<-METHOD
|
25
|
+
def #{key}=(value)
|
26
|
+
@#{key} = value
|
27
|
+
#{value[1]}
|
28
|
+
value
|
29
|
+
end
|
30
|
+
METHOD
|
31
|
+
|
32
|
+
instance_variable_set :"@#{key}", value[0]
|
33
|
+
else
|
34
|
+
eval <<-METHOD
|
35
|
+
def #{key}=(value)
|
36
|
+
@#{key} = value
|
37
|
+
end
|
38
|
+
METHOD
|
39
|
+
|
40
|
+
instance_variable_set :"@#{key}", value
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Textdb
|
2
|
+
module Data
|
3
|
+
class Key < Hash
|
4
|
+
|
5
|
+
attr_reader :full_path, :path, :name, :parent
|
6
|
+
|
7
|
+
def initialize(path, parent)
|
8
|
+
@full_path = File.join(Textdb.config.base_folder, path)
|
9
|
+
@path = path
|
10
|
+
@name = path.split('/')[-1]
|
11
|
+
|
12
|
+
@parent = parent
|
13
|
+
|
14
|
+
load_keys
|
15
|
+
load_values
|
16
|
+
end
|
17
|
+
|
18
|
+
def default(key)
|
19
|
+
raise Textdb::ExistError, "#{key} does not exist."
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
# Auto load
|
25
|
+
# -----------------------------------------------------------------
|
26
|
+
def keys_glob
|
27
|
+
File.join(@full_path, "*/")
|
28
|
+
end
|
29
|
+
|
30
|
+
def values_glob
|
31
|
+
File.join(@full_path, "*#{Textdb.config.data_file_extension}")
|
32
|
+
end
|
33
|
+
|
34
|
+
def load_keys
|
35
|
+
Dir.glob(keys_glob) do |f|
|
36
|
+
build_key(File.basename(f))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def load_values
|
41
|
+
Dir.glob(values_glob) do |f|
|
42
|
+
build_value(File.basename(f))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
|
48
|
+
# Build and create
|
49
|
+
# -----------------------------------------------------------------
|
50
|
+
def build_key(name)
|
51
|
+
self[name] = Textdb::Data::Key.new(File.join(@path, name), self)
|
52
|
+
self[name]
|
53
|
+
end
|
54
|
+
|
55
|
+
def build_value(name)
|
56
|
+
name_without_ext = name.gsub(Textdb.config.data_file_extension, "")
|
57
|
+
|
58
|
+
self[name_without_ext] = Textdb::Data::Value.new(File.join(@path, name), self)
|
59
|
+
self[name_without_ext]
|
60
|
+
end
|
61
|
+
|
62
|
+
def create_key(name)
|
63
|
+
if Textdb.config.listen
|
64
|
+
Textdb::Event.listener.create_skip << File.join(@path, name)
|
65
|
+
end
|
66
|
+
|
67
|
+
Dir.mkdir(File.join(@full_path, name))
|
68
|
+
|
69
|
+
build_key(name)
|
70
|
+
end
|
71
|
+
|
72
|
+
def create_value(name)
|
73
|
+
name = "#{name}#{Textdb.config.data_file_extension}"
|
74
|
+
|
75
|
+
if Textdb.config.listen
|
76
|
+
Textdb::Event.listener.create_skip << File.join(@path, name)
|
77
|
+
end
|
78
|
+
|
79
|
+
File.open(File.join(@full_path, name), 'w') { |f| f.write('') }
|
80
|
+
build_value(name)
|
81
|
+
end
|
82
|
+
|
83
|
+
def show
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
# This method is available only in the Value class.
|
88
|
+
def update
|
89
|
+
raise Textdb::UpdateOnKey, "Key cannot be updated."
|
90
|
+
end
|
91
|
+
|
92
|
+
def destroy
|
93
|
+
self.each do |key, value|
|
94
|
+
# Value -> key representing name of the value
|
95
|
+
if key.is_a?(String)
|
96
|
+
value.destroy
|
97
|
+
else
|
98
|
+
key.destroy
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
begin
|
103
|
+
FileUtils.remove_entry_secure(@full_path)
|
104
|
+
parent.delete(@name)
|
105
|
+
rescue
|
106
|
+
return false
|
107
|
+
end
|
108
|
+
|
109
|
+
return true
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Textdb
|
2
|
+
module Data
|
3
|
+
class Value
|
4
|
+
|
5
|
+
attr_reader :full_path, :path, :name, :parent
|
6
|
+
|
7
|
+
def initialize(path, parent)
|
8
|
+
@full_path = File.join(Textdb.config.base_folder, path)
|
9
|
+
@path = path
|
10
|
+
@name = path.split('/')[-1].gsub(Textdb.config.data_file_extension, '')
|
11
|
+
|
12
|
+
@parent = parent
|
13
|
+
end
|
14
|
+
|
15
|
+
# [] can be only in Key
|
16
|
+
def [](key)
|
17
|
+
raise Textdb::ValueCannotBeKey, "#{@name} is value."
|
18
|
+
end
|
19
|
+
|
20
|
+
def show
|
21
|
+
@data ||= show!
|
22
|
+
end
|
23
|
+
|
24
|
+
def show!
|
25
|
+
@data = File.read(@full_path)
|
26
|
+
end
|
27
|
+
|
28
|
+
def update(value)
|
29
|
+
File.open(@full_path, 'w') { |f| f.write(value) }
|
30
|
+
show!
|
31
|
+
end
|
32
|
+
|
33
|
+
def destroy
|
34
|
+
begin
|
35
|
+
File.delete(@full_path)
|
36
|
+
parent.delete(@name)
|
37
|
+
rescue
|
38
|
+
return false
|
39
|
+
end
|
40
|
+
|
41
|
+
return true
|
42
|
+
end
|
43
|
+
|
44
|
+
def inspect
|
45
|
+
%{#<Value::0x#{object_id} @data="#{@data}">}
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/textdb/data.rb
ADDED
data/lib/textdb/error.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Textdb
|
2
|
+
|
3
|
+
class ExistError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
class UpdateOnKey < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
class ValueCannotBeKey < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
class BlockRequired < StandardError
|
13
|
+
end
|
14
|
+
|
15
|
+
class AlreadyExist < StandardError
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "listen"
|
2
|
+
|
3
|
+
module Textdb
|
4
|
+
module Event
|
5
|
+
class Listener
|
6
|
+
|
7
|
+
attr_accessor :create_skip
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@create_skip = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def config
|
14
|
+
Textdb.config
|
15
|
+
end
|
16
|
+
|
17
|
+
def change
|
18
|
+
if config.listen
|
19
|
+
start
|
20
|
+
else
|
21
|
+
stop
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def start
|
26
|
+
@listener = Listen.to(config.base_folder)
|
27
|
+
.filter(filter)
|
28
|
+
.change(&callback)
|
29
|
+
|
30
|
+
@listener.start
|
31
|
+
end
|
32
|
+
|
33
|
+
def stop
|
34
|
+
@listener.stop
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
# Options for listen gem
|
40
|
+
# -----------------------------------------------------------------
|
41
|
+
def filter
|
42
|
+
/#{config.data_file_extension.gsub('.', '\.')}$/
|
43
|
+
end
|
44
|
+
|
45
|
+
def processor
|
46
|
+
Textdb::Event.processor
|
47
|
+
end
|
48
|
+
|
49
|
+
def callback
|
50
|
+
Proc.new do |modified, added, removed|
|
51
|
+
processor.create(added) unless added.empty?
|
52
|
+
processor.update(modified) unless modified.empty?
|
53
|
+
processor.delete(removed) unless removed.empty?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Textdb
|
2
|
+
module Event
|
3
|
+
class Processor
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
end
|
7
|
+
|
8
|
+
def create(files)
|
9
|
+
puts "create: ---#{files}---"
|
10
|
+
|
11
|
+
files.each do |file|
|
12
|
+
pointer, value = get(file, true)
|
13
|
+
pointer.build_value(value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def update(files)
|
18
|
+
puts "update: ---#{files}---"
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete(files)
|
22
|
+
puts "delete: ---#{files}---"
|
23
|
+
end
|
24
|
+
|
25
|
+
def get(relative, create = false)
|
26
|
+
keys = relative.split('/')
|
27
|
+
value = keys.pop
|
28
|
+
|
29
|
+
pointer = Textdb.root
|
30
|
+
keys.each do |key|
|
31
|
+
begin
|
32
|
+
pointer = pointer[key]
|
33
|
+
rescue Textdb::ExistError
|
34
|
+
if create
|
35
|
+
pointer = pointer.build_key(key)
|
36
|
+
else
|
37
|
+
pointer[key]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
return pointer, value
|
43
|
+
end
|
44
|
+
|
45
|
+
# def get(r, remove_ext = false)
|
46
|
+
# keys = r.split('/')
|
47
|
+
# last = keys.pop
|
48
|
+
|
49
|
+
# if remove_ext
|
50
|
+
# last = last.gsub(config.data_file_extension, '')
|
51
|
+
# end
|
52
|
+
|
53
|
+
# pointer = Textdb.root
|
54
|
+
# keys.each do |key|
|
55
|
+
# pointer = pointer[key]
|
56
|
+
# end
|
57
|
+
|
58
|
+
# return pointer, last
|
59
|
+
# end
|
60
|
+
|
61
|
+
# def create(b, r, t)
|
62
|
+
# pointer, last = get(r)
|
63
|
+
|
64
|
+
# unless @create_skip.delete('/' + r).nil?
|
65
|
+
# return pointer[last]
|
66
|
+
# end
|
67
|
+
|
68
|
+
# if t == :directory
|
69
|
+
# pointer.build_key(last)
|
70
|
+
# else
|
71
|
+
# pointer.build_value(last)
|
72
|
+
# end
|
73
|
+
|
74
|
+
# puts "create (directory: #{t == :directory ? "true " : "false"}): ---#{r}---"
|
75
|
+
# end
|
76
|
+
|
77
|
+
# def update(b, r, t)
|
78
|
+
# pointer, last = get(r, true)
|
79
|
+
|
80
|
+
# pointer[last].show!
|
81
|
+
|
82
|
+
# puts "update (directory: #{t == :directory ? "true " : "false"}): ---#{r}---"
|
83
|
+
# end
|
84
|
+
|
85
|
+
# def delete(b, r, t)
|
86
|
+
# puts "delete (directory: #{t == :directory ? "true " : "false"}): ---#{r}---"
|
87
|
+
# end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/textdb/event.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module Textdb
|
2
|
+
module Event
|
3
|
+
|
4
|
+
autoload :Processor, 'textdb/event/processor'
|
5
|
+
autoload :Listener, 'textdb/event/listener'
|
6
|
+
|
7
|
+
def self.listener
|
8
|
+
@listener ||= Listener.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.processor
|
12
|
+
@processor ||= Processor.new
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
data/lib/textdb.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require "textdb/configuration"
|
2
|
+
require "textdb/default_configuration"
|
3
|
+
require "textdb/error"
|
4
|
+
require "textdb/version"
|
5
|
+
|
6
|
+
# TODO:
|
7
|
+
# - nicer method
|
8
|
+
# - check data file extension only at the end of name
|
9
|
+
# - documentation
|
10
|
+
# - unite textdb.* and event.processor.* methods
|
11
|
+
|
12
|
+
module Textdb
|
13
|
+
|
14
|
+
autoload :Data, 'textdb/data'
|
15
|
+
autoload :BlockMethod, 'textdb/block_method'
|
16
|
+
autoload :Event, 'textdb/event'
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
# Global
|
21
|
+
# -----------------------------------------------------------------
|
22
|
+
def self.config
|
23
|
+
@config ||= Textdb::Configuration.new(DEFAULT_CONFIGURATION)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.configure(&block)
|
27
|
+
config.instance_eval(&block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.root
|
31
|
+
@root ||= Textdb::Data::Key.new('/', nil)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.rebuild
|
35
|
+
@root = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
# Data
|
41
|
+
# -----------------------------------------------------------------
|
42
|
+
def self.get(&block)
|
43
|
+
unless block_given?
|
44
|
+
raise Textdb::BlockRequired, "This action require a block."
|
45
|
+
end
|
46
|
+
|
47
|
+
keys = Textdb::BlockMethod.get(&block)
|
48
|
+
pointer = root
|
49
|
+
|
50
|
+
keys.each do |key|
|
51
|
+
pointer = pointer[key.to_s]
|
52
|
+
end
|
53
|
+
|
54
|
+
pointer
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.read(&block)
|
58
|
+
get(&block).show
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.update(value, &block)
|
62
|
+
get(&block).update(value)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.destroy(&block)
|
66
|
+
get(&block).destroy
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.delete(&block)
|
70
|
+
destroy(&block)
|
71
|
+
end
|
72
|
+
|
73
|
+
# TODO: Make the method nicer
|
74
|
+
def self.create(&block)
|
75
|
+
unless block_given?
|
76
|
+
raise Textdb::BlockRequired, "Creates action require a block."
|
77
|
+
end
|
78
|
+
|
79
|
+
keys = Textdb::BlockMethod.get(&block)
|
80
|
+
value = keys.pop
|
81
|
+
pointer = root
|
82
|
+
|
83
|
+
keys.each do |key|
|
84
|
+
begin
|
85
|
+
pointer = pointer[key]
|
86
|
+
rescue Textdb::ExistError
|
87
|
+
pointer = pointer.create_key(key)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
begin
|
92
|
+
pointer = pointer[value]
|
93
|
+
raise Textdb::AlreadyExist, "#{value} already exist."
|
94
|
+
rescue Textdb::ExistError
|
95
|
+
pointer.create_value(value)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Textdb::Configuration do
|
4
|
+
context "in line" do
|
5
|
+
let(:config) { Textdb.config }
|
6
|
+
|
7
|
+
it "should be OK" do
|
8
|
+
expect { config.base_folder = "/" }.to_not raise_error(NoMethodError)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should raise error" do
|
12
|
+
expect { config.abcdefghijkl = "/" }.to raise_error(NoMethodError)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should be updated" do
|
16
|
+
config.base_folder = "/abcdefghijkl"
|
17
|
+
expect(config.base_folder).to eql("/abcdefghijkl")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "in DSL" do
|
22
|
+
it "should be OK" do
|
23
|
+
expect {
|
24
|
+
Textdb.configure do
|
25
|
+
base_folder "/"
|
26
|
+
end
|
27
|
+
}.to_not raise_error(NoMethodError)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should raise error" do
|
31
|
+
expect {
|
32
|
+
Textdb.configure do
|
33
|
+
abcdefghijkl "/"
|
34
|
+
end
|
35
|
+
}.to raise_error(NoMethodError)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should be updated" do
|
39
|
+
Textdb.configure do
|
40
|
+
base_folder "/abcdefghijkl"
|
41
|
+
end
|
42
|
+
expect(Textdb.configure { base_folder }).to eql("/abcdefghijkl")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Textdb::BlockMethod do
|
4
|
+
|
5
|
+
let(:block) { Textdb::BlockMethod }
|
6
|
+
|
7
|
+
it "sequence of calling" do
|
8
|
+
expect( block.get { a.b.c } ).to eql(['a', 'b', 'c'])
|
9
|
+
end
|
10
|
+
|
11
|
+
it "empty block" do
|
12
|
+
expect( block.get { } ).to eql([])
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
# .
|
4
|
+
# |-- key1
|
5
|
+
# | |-- key1_1
|
6
|
+
# | | `-- key1_1_1
|
7
|
+
# | | |-- value1_1_1_1
|
8
|
+
# | | `-- value1_1_1_2
|
9
|
+
# | |-- value1_1
|
10
|
+
# | `-- value1_2
|
11
|
+
# `-- key2
|
12
|
+
# `-- value2_1
|
13
|
+
|
14
|
+
describe "Textdb.create" do
|
15
|
+
before(:all) do
|
16
|
+
Textdb.config.base_folder = RSpec.configuration.dir
|
17
|
+
end
|
18
|
+
|
19
|
+
it "create keys" do
|
20
|
+
expect { Textdb.create { key1.key1_1.key1_1_1.value1_1_1_1 } }.to_not raise_error
|
21
|
+
expect { Textdb.create { key1.key1_1.key1_1_1.value1_1_1_2 } }.to_not raise_error
|
22
|
+
expect { Textdb.create { key1.value1_1 } }.to_not raise_error
|
23
|
+
expect { Textdb.create { key1.value1_2 } }.to_not raise_error
|
24
|
+
expect { Textdb.create { key2.value2_1 } }.to_not raise_error
|
25
|
+
|
26
|
+
list = Dir.glob(File.join(RSpec.configuration.dir, '**/*')).map { |x| x.gsub(RSpec.configuration.dir, '') }
|
27
|
+
expect(list).to eql(["/key1",
|
28
|
+
"/key1/value1_2#{Textdb.config.data_file_extension}",
|
29
|
+
"/key1/value1_1#{Textdb.config.data_file_extension}",
|
30
|
+
"/key1/key1_1",
|
31
|
+
"/key1/key1_1/key1_1_1",
|
32
|
+
"/key1/key1_1/key1_1_1/value1_1_1_1#{Textdb.config.data_file_extension}",
|
33
|
+
"/key1/key1_1/key1_1_1/value1_1_1_2#{Textdb.config.data_file_extension}",
|
34
|
+
"/key2",
|
35
|
+
"/key2/value2_1#{Textdb.config.data_file_extension}"])
|
36
|
+
end
|
37
|
+
|
38
|
+
it "require a block" do
|
39
|
+
expect { Textdb.create }.to raise_error(Textdb::BlockRequired)
|
40
|
+
end
|
41
|
+
|
42
|
+
context "listen for changes" do
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Textdb.update" do
|
4
|
+
it "create with update" do
|
5
|
+
file = File.join RSpec.configuration.dir, "key3", "value3_1#{Textdb.config.data_file_extension}"
|
6
|
+
|
7
|
+
expect( File.exist?(file) ).to eql(false)
|
8
|
+
|
9
|
+
expect { Textdb.create{ key3.value3_1 }.update("text") }.to_not raise_error
|
10
|
+
|
11
|
+
expect( File.exist?(file) ).to eql(true)
|
12
|
+
|
13
|
+
expect( File.read(file) ).to eql("text")
|
14
|
+
end
|
15
|
+
|
16
|
+
it "update" do
|
17
|
+
file = File.join RSpec.configuration.dir, "key3", "value3_1#{Textdb.config.data_file_extension}"
|
18
|
+
|
19
|
+
Textdb.update("text 2") { key3.value3_1 }
|
20
|
+
|
21
|
+
expect( File.read(file) ).to eql("text 2")
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Textdb.read" do
|
4
|
+
before(:all) do
|
5
|
+
Textdb.create{ key4.key4_1.value4_1_1 }.update("text 1")
|
6
|
+
Textdb.create{ key4.key4_1.value4_1_2 }.update("text 2")
|
7
|
+
Textdb.create{ key4.key4_1.value4_1_3 }.update("text 3")
|
8
|
+
Textdb.create{ key4.value4_1 }.update("text 4")
|
9
|
+
Textdb.create{ value1 }.update("text 5")
|
10
|
+
end
|
11
|
+
|
12
|
+
it "read before create" do
|
13
|
+
expect { Textdb.read { key4.key4_2.value4_2_1 } }.to raise_error(Textdb::ExistError)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "read" do
|
17
|
+
expect( Textdb.read { key4.key4_1.value4_1_1 } ).to eql("text 1")
|
18
|
+
expect( Textdb.read { key4.key4_1.value4_1_2 } ).to eql("text 2")
|
19
|
+
expect( Textdb.read { key4.key4_1.value4_1_3 } ).to eql("text 3")
|
20
|
+
expect( Textdb.read { key4.value4_1 } ).to eql("text 4")
|
21
|
+
expect( Textdb.read { value1 } ).to eql("text 5")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "file read" do
|
25
|
+
dir = Textdb.config.base_folder
|
26
|
+
ext = Textdb.config.data_file_extension
|
27
|
+
|
28
|
+
expect( File.read File.join(dir, "key4", "key4_1", "value4_1_1#{ext}") ).to eql("text 1")
|
29
|
+
expect( File.read File.join(dir, "key4", "key4_1", "value4_1_2#{ext}") ).to eql("text 2")
|
30
|
+
expect( File.read File.join(dir, "key4", "key4_1", "value4_1_3#{ext}") ).to eql("text 3")
|
31
|
+
expect( File.read File.join(dir, "key4", "value4_1#{ext}" ) ).to eql("text 4")
|
32
|
+
expect( File.read File.join(dir, "value1#{ext}" ) ).to eql("text 5")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "without block" do
|
36
|
+
expect { Textdb.read }.to raise_error(Textdb::BlockRequired)
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Textdb.delete" do
|
4
|
+
before(:all) do
|
5
|
+
Textdb.create { key5.key5_1.value5_1_1 }
|
6
|
+
Textdb.create { key5.key5_1.value5_1_2 }
|
7
|
+
Textdb.create { key5.key5_1.value5_1_3 }
|
8
|
+
Textdb.create { key5.value5_1 }
|
9
|
+
Textdb.create { value6 }
|
10
|
+
Textdb.create { key6.value6_1 }
|
11
|
+
end
|
12
|
+
|
13
|
+
it "value" do
|
14
|
+
file = File.join Textdb.config.base_folder, "key5", "key5_1", "value5_1_1#{Textdb.config.data_file_extension}"
|
15
|
+
|
16
|
+
expect( File.exist?(file) ).to eql(true)
|
17
|
+
|
18
|
+
Textdb.delete { key5.key5_1.value5_1_1 }
|
19
|
+
|
20
|
+
expect( File.exist?(file) ).to eql(false)
|
21
|
+
|
22
|
+
expect { Textdb.read { key5.key5_1.value5_1_1 } }.to raise_error(Textdb::ExistError)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "key" do
|
26
|
+
dir = File.join Textdb.config.base_folder, "key6"
|
27
|
+
|
28
|
+
expect( Dir.exist?(dir) ).to eql(true)
|
29
|
+
|
30
|
+
Textdb.delete { key6 }
|
31
|
+
|
32
|
+
expect( Dir.exist?(dir) ).to eql(false)
|
33
|
+
|
34
|
+
expect { Textdb.read { key6 } }.to raise_error(Textdb::ExistError)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "recursive" do
|
38
|
+
dir = File.join Textdb.config.base_folder, "key5"
|
39
|
+
|
40
|
+
Textdb.delete { key5 }
|
41
|
+
|
42
|
+
expect( Dir.exist?(dir) ).to eql(false)
|
43
|
+
|
44
|
+
expect { Textdb.read { key5.key5_1.value5_1_2 } }.to raise_error(Textdb::ExistError)
|
45
|
+
expect { Textdb.read { key5.key5_1.value5_1_3 } }.to raise_error(Textdb::ExistError)
|
46
|
+
expect { Textdb.read { key5.value5_1 } }.to raise_error(Textdb::ExistError)
|
47
|
+
end
|
48
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
2
|
+
require "tmpdir"
|
3
|
+
require "textdb"
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
7
|
+
config.run_all_when_everything_filtered = true
|
8
|
+
# config.filter_run :focus
|
9
|
+
config.color_enabled = true
|
10
|
+
config.formatter = 'documentation'
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
config.add_setting :dir
|
15
|
+
|
16
|
+
config.before(:suite) {
|
17
|
+
RSpec.configuration.dir = Dir.mktmpdir
|
18
|
+
Textdb.config.base_folder = RSpec.configuration.dir
|
19
|
+
Textdb.config.listen = true
|
20
|
+
}
|
21
|
+
config.after(:suite) {
|
22
|
+
FileUtils.remove_entry_secure RSpec.configuration.dir
|
23
|
+
}
|
24
|
+
end
|
data/text-db.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'textdb/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "text-db"
|
9
|
+
spec.version = Textdb::VERSION
|
10
|
+
spec.authors = ["Ondřej Moravčík"]
|
11
|
+
spec.email = ["moravcik.ondrej@gmail.com"]
|
12
|
+
spec.description = %q{Textdb is a database which structure is determined by folders and data are represented by files.}
|
13
|
+
spec.summary = %q{}
|
14
|
+
spec.homepage = "https://github.com/ondra-m/textdb"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files`.split($/)
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: text-db
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0.beta
|
5
|
+
prerelease: 6
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ondřej Moravčík
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-07-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.3'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.3'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Textdb is a database which structure is determined by folders and data
|
63
|
+
are represented by files.
|
64
|
+
email:
|
65
|
+
- moravcik.ondrej@gmail.com
|
66
|
+
executables: []
|
67
|
+
extensions: []
|
68
|
+
extra_rdoc_files: []
|
69
|
+
files:
|
70
|
+
- .gitignore
|
71
|
+
- Gemfile
|
72
|
+
- LICENSE.txt
|
73
|
+
- README.md
|
74
|
+
- Rakefile
|
75
|
+
- lib/textdb.rb
|
76
|
+
- lib/textdb/block_method.rb
|
77
|
+
- lib/textdb/configuration.rb
|
78
|
+
- lib/textdb/data.rb
|
79
|
+
- lib/textdb/data/key.rb
|
80
|
+
- lib/textdb/data/value.rb
|
81
|
+
- lib/textdb/default_configuration.rb
|
82
|
+
- lib/textdb/error.rb
|
83
|
+
- lib/textdb/event.rb
|
84
|
+
- lib/textdb/event/listener.rb
|
85
|
+
- lib/textdb/event/processor.rb
|
86
|
+
- lib/textdb/version.rb
|
87
|
+
- spec/lib/01_configuration_spec.rb
|
88
|
+
- spec/lib/02_block_method_spec.rb
|
89
|
+
- spec/lib/03_create_spec.rb
|
90
|
+
- spec/lib/04_update_spec.rb
|
91
|
+
- spec/lib/05_read_spec.rb
|
92
|
+
- spec/lib/06_delete_spec.rb
|
93
|
+
- spec/spec_helper.rb
|
94
|
+
- text-db.gemspec
|
95
|
+
homepage: https://github.com/ondra-m/textdb
|
96
|
+
licenses:
|
97
|
+
- MIT
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ! '>='
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ! '>'
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: 1.3.1
|
114
|
+
requirements: []
|
115
|
+
rubyforge_project:
|
116
|
+
rubygems_version: 1.8.23
|
117
|
+
signing_key:
|
118
|
+
specification_version: 3
|
119
|
+
summary: ''
|
120
|
+
test_files:
|
121
|
+
- spec/lib/01_configuration_spec.rb
|
122
|
+
- spec/lib/02_block_method_spec.rb
|
123
|
+
- spec/lib/03_create_spec.rb
|
124
|
+
- spec/lib/04_update_spec.rb
|
125
|
+
- spec/lib/05_read_spec.rb
|
126
|
+
- spec/lib/06_delete_spec.rb
|
127
|
+
- spec/spec_helper.rb
|
128
|
+
has_rdoc:
|