document_file 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,5 @@
1
+ require 'rake/testtask'
2
+ task :default => :test
3
+ Rake::TestTask.new(:test) do |t|
4
+ t.test_files = FileList['test/*_test.rb']
5
+ end
@@ -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,3 @@
1
+ module DocumentFile
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,2 @@
1
+ require 'document_file/base'
2
+ require 'document_file/version'
@@ -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
+
@@ -0,0 +1,8 @@
1
+ ---
2
+ id: 1
3
+ title: The shizzle!
4
+ tags: [tag]
5
+ number_of_foos: 42
6
+ ---
7
+
8
+ I like the flowers.
@@ -0,0 +1,7 @@
1
+ ---
2
+ id: 2
3
+ title: The big pink Oink!
4
+ tags: [tag, tug]
5
+ ---
6
+
7
+ I'm on a horse!
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
+