document_file 0.0.1
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/LICENSE +22 -0
- data/README +49 -0
- data/Rakefile +5 -0
- data/lib/document_file/base.rb +105 -0
- data/lib/document_file/version.rb +3 -0
- data/lib/document_file.rb +2 -0
- data/test/document_file_test.rb +202 -0
- data/test/documents/2010-08-08-test-document-file.textile +8 -0
- data/test/documents/2010-08-09-oink-post.textile +7 -0
- metadata +72 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2010 Ralph von der Heyden
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
Document File
|
2
|
+
-------------
|
3
|
+
|
4
|
+
An attempt to create a simple model layer that abstracts flat text files.
|
5
|
+
|
6
|
+
Text files look like the ones used by jekyll (see
|
7
|
+
http://github.com/mojombo/jekyll). They consist of a preambel written in YAML
|
8
|
+
(also called YAML front matter), and some content in the format you prefer,
|
9
|
+
e.g. Textile. Example:
|
10
|
+
|
11
|
+
!!!document starts in the following line.
|
12
|
+
---
|
13
|
+
id: 1
|
14
|
+
title: The shizzle!
|
15
|
+
tags: [tag]
|
16
|
+
number_of_foos: 42
|
17
|
+
---
|
18
|
+
|
19
|
+
I like the flowers.
|
20
|
+
!!!document ends in the previous line.
|
21
|
+
|
22
|
+
Can be abstracted like this:
|
23
|
+
|
24
|
+
class MyDocument < DocumentFile::Base
|
25
|
+
end
|
26
|
+
|
27
|
+
MyDocument.documents_dir = './documents'
|
28
|
+
|
29
|
+
# You now have dynamic finders:
|
30
|
+
doc = MyDocument.find_by_title("The shizzle!") # => returns the document
|
31
|
+
doc = MyDocument.find_by_number_of_foos(42) # => returns the document
|
32
|
+
doc = MyDocument.find_by_file_name('foo.textile') # => returns the document
|
33
|
+
|
34
|
+
# You can list documents by Array attributes
|
35
|
+
docs = MyDocument.by_tags # => Returns {"tag" => [doc_1, doc2, ...], ...}
|
36
|
+
|
37
|
+
# You can access the attributes of single documents:
|
38
|
+
doc.title # => "The shizzle!"
|
39
|
+
doc.tags # => ["tag"]
|
40
|
+
doc.content # => "I like the flowers."
|
41
|
+
doc.filename # => returns the filename without extension
|
42
|
+
doc.file_name_with_extension # => does what it says
|
43
|
+
doc.file_extension # => does what it says
|
44
|
+
|
45
|
+
# You can initialize single documents, too:
|
46
|
+
doc = MyDocument.new('./documents/document-file.textile')
|
47
|
+
|
48
|
+
# If any of the files change, you must manually reload them:
|
49
|
+
MyDocument.reload!
|
data/Rakefile
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module DocumentFile
|
4
|
+
class Base
|
5
|
+
@@documents_dir = './documents'
|
6
|
+
@@documents = nil
|
7
|
+
attr_reader :content, :file_path
|
8
|
+
|
9
|
+
def initialize(new_file_path)
|
10
|
+
@file_path = new_file_path
|
11
|
+
define_attribute_finder('file_name')
|
12
|
+
read_yaml
|
13
|
+
end
|
14
|
+
|
15
|
+
def file_name
|
16
|
+
File.basename file_name_with_extension, file_extension
|
17
|
+
end
|
18
|
+
|
19
|
+
def file_name_with_extension
|
20
|
+
self.file_path.split('/').last
|
21
|
+
end
|
22
|
+
|
23
|
+
def file_extension
|
24
|
+
File.extname file_name_with_extension
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.all
|
28
|
+
return @@documents if @@documents
|
29
|
+
self.reload!
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.reload!
|
33
|
+
if File.directory?(@@documents_dir)
|
34
|
+
file_paths = Dir.glob("#{@@documents_dir}/*.*")
|
35
|
+
@@documents = file_paths.map { |file_path| self.new File.join(Dir.getwd, file_path) }
|
36
|
+
else
|
37
|
+
[]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.documents_dir
|
42
|
+
@@documents_dir
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.documents_dir=(new_dir)
|
46
|
+
@@documents_dir = new_dir
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def read_yaml
|
51
|
+
@content = File.read(@file_path)
|
52
|
+
|
53
|
+
if @content =~ /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
|
54
|
+
@content = @content[($1.size + $2.size)..-1]
|
55
|
+
@data = YAML.load($1)
|
56
|
+
end
|
57
|
+
@data ||= {}
|
58
|
+
define_dynamic_methods
|
59
|
+
end
|
60
|
+
|
61
|
+
def define_dynamic_methods
|
62
|
+
@data.each do |method_name, value|
|
63
|
+
value = "'#{value}'" if value.is_a? String
|
64
|
+
instance_eval "def #{method_name}; #{value}; end"
|
65
|
+
|
66
|
+
if value.is_a? Array
|
67
|
+
by_attribute_method = <<-eos
|
68
|
+
def self.by_#{method_name}
|
69
|
+
documents = self.all
|
70
|
+
#{method_name} = {}
|
71
|
+
documents.each do |document|
|
72
|
+
document.#{method_name}.each do |single_item|
|
73
|
+
if #{method_name}.has_key? single_item
|
74
|
+
#{method_name}[single_item] << document
|
75
|
+
else
|
76
|
+
#{method_name}[single_item] = [document]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
#{method_name}
|
81
|
+
end
|
82
|
+
eos
|
83
|
+
self.class.send(:module_eval, by_attribute_method)
|
84
|
+
end
|
85
|
+
|
86
|
+
define_attribute_finder(method_name)
|
87
|
+
end
|
88
|
+
@@dynamic_methods_defined = true
|
89
|
+
end
|
90
|
+
|
91
|
+
def define_attribute_finder(method_name)
|
92
|
+
find_by_attribute_method = <<-eos
|
93
|
+
def self.find_by_#{method_name}(attribute)
|
94
|
+
all.detect {|document| document.#{method_name} == attribute}
|
95
|
+
end
|
96
|
+
eos
|
97
|
+
self.class.send(:module_eval, find_by_attribute_method)
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.method_missing(method_name, *args)
|
101
|
+
self.all unless @@documents
|
102
|
+
super
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
require 'minitest/spec'
|
2
|
+
MiniTest::Unit.autorun
|
3
|
+
require 'set'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
testdir = File.dirname(__FILE__)
|
7
|
+
libdir = File.dirname(File.dirname(__FILE__)) + '/lib'
|
8
|
+
$LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir)
|
9
|
+
|
10
|
+
require 'document_file'
|
11
|
+
|
12
|
+
class MyDocument < DocumentFile::Base
|
13
|
+
end
|
14
|
+
|
15
|
+
describe MyDocument do
|
16
|
+
before do
|
17
|
+
MyDocument.documents_dir = testdir + '/documents'
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'when finding all document_files' do
|
21
|
+
before do
|
22
|
+
@document_files = MyDocument.all
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should return an Array' do
|
26
|
+
assert_equal Array, @document_files.class
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should find all document_files" do
|
30
|
+
assert_equal 2, @document_files.size
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'when initializing a MyDocument' do
|
35
|
+
before do
|
36
|
+
@document_file = MyDocument.new(testdir + '/documents/2010-08-08-test-document-file.textile')
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should initialize the content' do
|
40
|
+
assert_equal "I like the flowers.\n", @document_file.content
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should intitialize strings from the front matter' do
|
44
|
+
assert_equal String, @document_file.title.class
|
45
|
+
assert_equal 'The shizzle!', @document_file.title
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should intitialize strings from the front matter' do
|
49
|
+
assert_equal Array, @document_file.tags.class
|
50
|
+
assert_equal ['tag'], @document_file.tags
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should intitialize integers from the front matter' do
|
54
|
+
assert_equal Fixnum, @document_file.number_of_foos.class
|
55
|
+
assert_equal 42, @document_file.number_of_foos
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'when listing document_files by an array attribute' do
|
60
|
+
it 'should return a Hash' do
|
61
|
+
assert_equal Hash, MyDocument.by_tags.class
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should use the tags as Hash keys' do
|
65
|
+
assert_equal Set.new(['tag', 'tug']), MyDocument.by_tags.keys.to_set
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should use the document_files as Hash values' do
|
69
|
+
document_files = MyDocument.by_tags
|
70
|
+
assert_equal Set.new([1, 2]), document_files['tag'].map(&:id).to_set
|
71
|
+
assert_equal Set.new([2]), document_files['tug'].map(&:id).to_set
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe 'when finding a document_file' do
|
76
|
+
it 'should find the right document_file by an attribute' do
|
77
|
+
title = 'The shizzle!'
|
78
|
+
document_file = MyDocument.find_by_title(title)
|
79
|
+
assert_equal title, document_file.title
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should find the right document_file by file_name' do
|
83
|
+
file_name = '2010-08-08-test-document-file'
|
84
|
+
document_file = MyDocument.find_by_file_name file_name
|
85
|
+
assert_equal document_file.file_name, file_name
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe 'when getting the file name or file path' do
|
90
|
+
it 'should show the right file name' do
|
91
|
+
document_file = MyDocument.new './test/documents/2010-08-08-test-document-file.textile'
|
92
|
+
file_name = '2010-08-08-test-document-file'
|
93
|
+
assert_equal file_name, document_file.file_name
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should show the right file name with extension' do
|
97
|
+
document_file = MyDocument.new './test/documents/2010-08-08-test-document-file.textile'
|
98
|
+
file_name = '2010-08-08-test-document-file.textile'
|
99
|
+
assert_equal file_name, document_file.file_name_with_extension
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should show the right extension' do
|
103
|
+
document_file = MyDocument.new './test/documents/2010-08-08-test-document-file.textile'
|
104
|
+
extension = '.textile'
|
105
|
+
assert_equal extension, document_file.file_extension
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'should show the right file path' do
|
109
|
+
file_path = './test/documents/2010-08-08-test-document-file.textile'
|
110
|
+
document_file = MyDocument.new file_path
|
111
|
+
assert_equal file_path, document_file.file_path
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe 'when calling a method that was not defined dynamically' do
|
116
|
+
it 'should throw an error on the class level' do
|
117
|
+
assert_raises(NoMethodError) { MyDocument.hululu }
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'should throw an error on the instance level' do
|
121
|
+
document_file = MyDocument.new('./test/documents/2010-08-08-test-document-file.textile')
|
122
|
+
assert_raises(NoMethodError) { document_file.hululu }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe 'when reloading all document_files' do
|
127
|
+
before do
|
128
|
+
@default_dir = testdir + '/documents'
|
129
|
+
MyDocument.documents_dir = @default_dir
|
130
|
+
MyDocument.reload!
|
131
|
+
@document_files_before = MyDocument.all
|
132
|
+
@tmp_dir = "#{@default_dir}-#{Time.now.to_i}-#{rand(999999)}-test"
|
133
|
+
FileUtils.cp_r @default_dir, @tmp_dir
|
134
|
+
end
|
135
|
+
|
136
|
+
after do
|
137
|
+
FileUtils.rm_r(@tmp_dir) if Dir.exist?(@tmp_dir)
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'should get updated document_files' do
|
141
|
+
updated_document_file = <<-eos
|
142
|
+
---
|
143
|
+
id: 1
|
144
|
+
title: The shuzzle!
|
145
|
+
tags: [tig]
|
146
|
+
number_of_foos: 48
|
147
|
+
---
|
148
|
+
|
149
|
+
I like the foos.
|
150
|
+
eos
|
151
|
+
document_file_file_name = "#{@tmp_dir}/2010-08-08-test-document-file.textile"
|
152
|
+
File.open(document_file_file_name, 'w') {|f| f.write(updated_document_file) }
|
153
|
+
MyDocument.documents_dir = @tmp_dir
|
154
|
+
MyDocument.reload!
|
155
|
+
document_files_after = MyDocument.all
|
156
|
+
|
157
|
+
assert_equal @document_files_before.first.id, document_files_after.first.id
|
158
|
+
refute_equal @document_files_before.first.title, document_files_after.first.title
|
159
|
+
refute_equal @document_files_before.first.tags, document_files_after.first.tags
|
160
|
+
refute_equal @document_files_before.first.number_of_foos, document_files_after.first.number_of_foos
|
161
|
+
refute_equal @document_files_before.first.content, document_files_after.first.content
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'should get new document_files' do
|
165
|
+
new_document_file = <<-eos
|
166
|
+
---
|
167
|
+
id: 3
|
168
|
+
title: The shuzzle!
|
169
|
+
tags: [tig]
|
170
|
+
number_of_foos: 48
|
171
|
+
---
|
172
|
+
|
173
|
+
I like the cows.
|
174
|
+
eos
|
175
|
+
document_file_file_name = "#{@tmp_dir}/2010-08-15-new-test-document_file.textile"
|
176
|
+
File.open(document_file_file_name, 'w') {|f| f.write(new_document_file) }
|
177
|
+
MyDocument.documents_dir = @tmp_dir
|
178
|
+
MyDocument.reload!
|
179
|
+
document_files_after = MyDocument.all
|
180
|
+
|
181
|
+
assert_equal @document_files_before.size + 1, document_files_after.size
|
182
|
+
assert_equal 'The shuzzle!', document_files_after.last.title
|
183
|
+
assert_equal "I like the cows.\n", document_files_after.last.content
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'should not change if no document_files were changed' do
|
187
|
+
MyDocument.reload!
|
188
|
+
document_files_after = MyDocument.all
|
189
|
+
assert_equal @document_files_before.map(&:id), document_files_after.map(&:id)
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'should not show deleted document_files' do
|
193
|
+
document_file_file_name = "#{@tmp_dir}/2010-08-08-test-document-file.textile"
|
194
|
+
FileUtils.rm document_file_file_name
|
195
|
+
MyDocument.documents_dir = @tmp_dir
|
196
|
+
MyDocument.reload!
|
197
|
+
document_files_after = MyDocument.all
|
198
|
+
refute_equal @document_files_before.map(&:id), document_files_after.map(&:id)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: document_file
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Ralph von der Heyden
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-08-19 00:00:00 +02:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: " Makes your plain text files accessible in Ruby. Supports YAML front matter.\n"
|
22
|
+
email: ralph@rvdh.de
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- README
|
31
|
+
- Rakefile
|
32
|
+
- LICENSE
|
33
|
+
- lib/document_file/base.rb
|
34
|
+
- lib/document_file/version.rb
|
35
|
+
- lib/document_file.rb
|
36
|
+
- test/document_file_test.rb
|
37
|
+
- test/documents/2010-08-08-test-document-file.textile
|
38
|
+
- test/documents/2010-08-09-oink-post.textile
|
39
|
+
has_rdoc: true
|
40
|
+
homepage: http://github.com/ralph/document_file
|
41
|
+
licenses: []
|
42
|
+
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
segments:
|
54
|
+
- 0
|
55
|
+
version: "0"
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
requirements: []
|
65
|
+
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.3.7
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: Write documents in your fav editor. Read them in your Ruby app.
|
71
|
+
test_files: []
|
72
|
+
|