plain_record 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/.rspec +1 -0
- data/.travis.yml +6 -0
- data/.yardopts +4 -0
- data/ChangeLog +9 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +24 -0
- data/LICENSE +3 -4
- data/README.md +124 -0
- data/Rakefile +27 -50
- data/lib/plain_record/association_proxy.rb +59 -0
- data/lib/plain_record/associations.rb +271 -0
- data/lib/plain_record/callbacks.rb +146 -0
- data/lib/plain_record/filepath.rb +141 -0
- data/lib/plain_record/model/entry.rb +17 -9
- data/lib/plain_record/model/list.rb +22 -9
- data/lib/plain_record/model.rb +119 -64
- data/lib/plain_record/resource.rb +72 -19
- data/lib/plain_record/version.rb +1 -1
- data/lib/plain_record.rb +21 -2
- data/plain_record.gemspec +30 -0
- data/spec/associations_spec.rb +142 -0
- data/spec/callbacks_spec.rb +59 -0
- data/spec/data/1/comments.yml +5 -0
- data/spec/data/1/{post.m → post.md} +0 -0
- data/spec/data/2/{post.m → post.md} +1 -1
- data/spec/data/3/post.md +4 -0
- data/spec/data/authors/extern.yml +2 -2
- data/spec/data/authors/intern.yml +4 -4
- data/spec/data/best/4/post.md +1 -0
- data/spec/filepath_spec.rb +53 -0
- data/spec/model_spec.rb +90 -42
- data/spec/resource_spec.rb +70 -27
- data/spec/spec_helper.rb +33 -14
- metadata +122 -70
- data/README.rdoc +0 -96
- data/spec/data/3/post.m +0 -1
@@ -0,0 +1,146 @@
|
|
1
|
+
=begin
|
2
|
+
Module to add before/after hooks.
|
3
|
+
|
4
|
+
Copyright (C) 2009 Andrey “A.I.” Sitnik <andrey@sitnik.ru>
|
5
|
+
|
6
|
+
This program is free software: you can redistribute it and/or modify
|
7
|
+
it under the terms of the GNU Lesser General Public License as published by
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
9
|
+
(at your option) any later version.
|
10
|
+
|
11
|
+
This program is distributed in the hope that it will be useful,
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
GNU Lesser General Public License for more details.
|
15
|
+
|
16
|
+
You should have received a copy of the GNU Lesser General Public License
|
17
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
=end
|
19
|
+
|
20
|
+
module PlainRecord
|
21
|
+
# Callbacks are hooks that allow you to define methods to run before and
|
22
|
+
# after some method, to change it logic.
|
23
|
+
module Callbacks
|
24
|
+
# Hash of class callbacks with property.
|
25
|
+
attr_accessor :callbacks
|
26
|
+
|
27
|
+
# Set block as callback before +events+. Callback with less +priority+ will
|
28
|
+
# start earlier.
|
29
|
+
#
|
30
|
+
# class File
|
31
|
+
# include PlainRecord::Callbacks
|
32
|
+
#
|
33
|
+
# attr_accessor :name
|
34
|
+
# attr_accessor :content
|
35
|
+
#
|
36
|
+
# def save
|
37
|
+
# use_callbacks(:save, self) do
|
38
|
+
# File.open(@name, 'w') { |io| io.puts @content }
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# class NewFile < File
|
44
|
+
# before :save do |file|
|
45
|
+
# while File.exists? file.name
|
46
|
+
# file.name = 'another ' + file.name
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# before, :save do
|
51
|
+
# raise ArgumentError if 255 < @name.length
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
def before(events, priority = 1, &block)
|
55
|
+
Array(events).each do |event|
|
56
|
+
add_callback(:before, event, priority, block)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Set block as callback after +events+. Callback with less +priority+ will
|
61
|
+
# start earlier.
|
62
|
+
#
|
63
|
+
# After callbacks may change method return, which will be pass as first
|
64
|
+
# argument for first callback. It return will be pass for next callback and
|
65
|
+
# so on.
|
66
|
+
#
|
67
|
+
# class Person
|
68
|
+
# include PlainRecord::Callbacks
|
69
|
+
#
|
70
|
+
# def name
|
71
|
+
# use_callbacks(:name) do
|
72
|
+
# 'John'
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# class GreatPerson < Person
|
78
|
+
# after :name, 2 do |name|
|
79
|
+
# 'Great ' + name
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# after :name do |name|
|
83
|
+
# 'The ' + name
|
84
|
+
# end
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# GreatPerson.new.name #=> "The Great John"
|
88
|
+
def after(events, priority = 1, &block)
|
89
|
+
Array(events).each do |event|
|
90
|
+
add_callback(:after, event, priority, block)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Call +before+ callbacks for +event+ with +params+. In your
|
95
|
+
# code use more pretty +use_callbacks+ method.
|
96
|
+
def call_before_callbacks(event, params)
|
97
|
+
init_callbacks(event)
|
98
|
+
@callbacks[:before][event].each do |before, priority|
|
99
|
+
before.call(*params)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Call +before+ callbacks for +event+ with +params+. Callbacks can change
|
104
|
+
# +result+. In your code use more pretty +use_callbacks+ method.
|
105
|
+
def call_after_callbacks(event, result, params)
|
106
|
+
init_callbacks(event)
|
107
|
+
@callbacks[:after][event].each do |after, priority|
|
108
|
+
result = after.call(result, *params)
|
109
|
+
end
|
110
|
+
result
|
111
|
+
end
|
112
|
+
|
113
|
+
# Call before callback for +event+, run block and give it result to
|
114
|
+
# after callbacks.
|
115
|
+
#
|
116
|
+
# def my_save_method(entry)
|
117
|
+
# use_callbacks(:save, enrty) do
|
118
|
+
# entry.file.write
|
119
|
+
# end
|
120
|
+
# end
|
121
|
+
def use_callbacks(event, *params, &block)
|
122
|
+
call_before_callbacks(event, params)
|
123
|
+
result = yield
|
124
|
+
call_after_callbacks(event, result, params)
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
# Backend for +before+ and +after+ method to add callback.
|
130
|
+
def add_callback(type, event, priority, block)
|
131
|
+
init_callbacks(event)
|
132
|
+
|
133
|
+
@callbacks[type][event] << [block, priority]
|
134
|
+
@callbacks[type][event].sort! { |a, b| a[1] <=> b[1] }
|
135
|
+
end
|
136
|
+
|
137
|
+
# Check and create Hash into +callbacks+ for +event+ if necessary.
|
138
|
+
def init_callbacks(event)
|
139
|
+
unless @callbacks
|
140
|
+
@callbacks = { :before => { }, :after => { } }
|
141
|
+
end
|
142
|
+
@callbacks[:before][event] = [] unless @callbacks[:before][event]
|
143
|
+
@callbacks[:after][event] = [] unless @callbacks[:after][event]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
=begin
|
2
|
+
Extention to get property from entry file path.
|
3
|
+
|
4
|
+
Copyright (C) 2009 Andrey “A.I.” Sitnik <andrey@sitnik.ru>
|
5
|
+
|
6
|
+
This program is free software: you can redistribute it and/or modify
|
7
|
+
it under the terms of the GNU Lesser General Public License as published by
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
9
|
+
(at your option) any later version.
|
10
|
+
|
11
|
+
This program is distributed in the hope that it will be useful,
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
GNU Lesser General Public License for more details.
|
15
|
+
|
16
|
+
You should have received a copy of the GNU Lesser General Public License
|
17
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
=end
|
19
|
+
|
20
|
+
module PlainRecord
|
21
|
+
# Extention to get properties from enrty file path. For example, your blog
|
22
|
+
# post may stored in <tt>_name_/post.md</tt>, and post model will have +name+
|
23
|
+
# property. Also if you set name property to Model#first or Model#all method,
|
24
|
+
# they will load entry directly only by it file.
|
25
|
+
#
|
26
|
+
# To define filepath property:
|
27
|
+
# 1. Use <tt>*</tt> or <tt>**</tt> pattern in model path in +enrty_in+ or
|
28
|
+
# +list_in+.
|
29
|
+
# 2. In +virtual+ method use <tt>in_filepath(i)</tt> definer after name with
|
30
|
+
# <tt>*</tt> or <tt>**</tt> number (start from 1).
|
31
|
+
#
|
32
|
+
# Define filepath property only after +entry_in+ or +list_in+ call.
|
33
|
+
#
|
34
|
+
# class Post
|
35
|
+
# include PlainRecord::Resource
|
36
|
+
#
|
37
|
+
# entry_in '*/*/post.md'
|
38
|
+
#
|
39
|
+
# virtual :category, in_filepath(1)
|
40
|
+
# virtual :name, in_filepath(1)
|
41
|
+
# …
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# superpost = Post.new
|
45
|
+
# superpost.name = 'superpost'
|
46
|
+
# superpost.category = 'best/'
|
47
|
+
# superpost.save # Save to best/superpost/post.md
|
48
|
+
#
|
49
|
+
# bests = Post.all(category: 'best') # Look up only in best/ dir
|
50
|
+
module Filepath
|
51
|
+
attr_accessor :filepath_properties
|
52
|
+
attr_accessor :filepath_regexp
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# Return definer for filepath property for +number+ <tt>*</tt> or
|
57
|
+
# <tt>**</tt> pattern in path.
|
58
|
+
def in_filepath(number)
|
59
|
+
proc do |property, caller|
|
60
|
+
if :virtual != caller
|
61
|
+
raise ArgumentError, "You must create filepath property #{property}" +
|
62
|
+
' virtual creator'
|
63
|
+
end
|
64
|
+
Filepath.define_property(self, property, number)
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class << self
|
70
|
+
# Define class variables and events in +klass+. It should be call once on
|
71
|
+
# same class after +entry_in+ or +list_in+ call.
|
72
|
+
def install(klass)
|
73
|
+
klass.filepath_properties = { }
|
74
|
+
|
75
|
+
path = Regexp.escape(klass.path).gsub(/\\\*\\\*(\/|$)/, '(.*)').
|
76
|
+
gsub('\\*', '([^/]+)')
|
77
|
+
klass.filepath_regexp = Regexp.new(path)
|
78
|
+
|
79
|
+
klass.class_eval do
|
80
|
+
attr_accessor :filepath_data
|
81
|
+
end
|
82
|
+
|
83
|
+
klass.after :load do |result, entry|
|
84
|
+
if entry.path
|
85
|
+
data = klass.filepath_regexp.match(entry.path)
|
86
|
+
entry.filepath_data = { }
|
87
|
+
klass.filepath_properties.each_pair do |number, name|
|
88
|
+
entry.filepath_data[name] = data[number]
|
89
|
+
end
|
90
|
+
else
|
91
|
+
entry.filepath_data = { }
|
92
|
+
klass.filepath_properties.each_value do |name|
|
93
|
+
entry.filepath_data[name] = entry.data[name]
|
94
|
+
entry.data.delete(name)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
result
|
98
|
+
end
|
99
|
+
|
100
|
+
klass.after :path do |path, matchers|
|
101
|
+
i = 0
|
102
|
+
path.gsub /(\*\*(\/|$)|\*)/ do |pattern|
|
103
|
+
i += 1
|
104
|
+
property = klass.filepath_properties[i]
|
105
|
+
unless matchers[property].is_a? Regexp or matchers[property].nil?
|
106
|
+
matchers[property]
|
107
|
+
else
|
108
|
+
pattern
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
klass.before :save do |entry|
|
114
|
+
unless entry.file
|
115
|
+
path = klass.path(entry.filepath_data)
|
116
|
+
entry.file = path unless path =~ /[\*\[\?\{]/
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Define in +klass+ filepath property with +name+ for +number+ <tt>*</tt>
|
122
|
+
# or <tt>**</tt> pattern in path.
|
123
|
+
def define_property(klass, name, number)
|
124
|
+
unless klass.filepath_properties
|
125
|
+
install(klass)
|
126
|
+
end
|
127
|
+
|
128
|
+
klass.filepath_properties[number] = name
|
129
|
+
|
130
|
+
klass.class_eval <<-EOS, __FILE__, __LINE__
|
131
|
+
def #{name}
|
132
|
+
@filepath_data[:#{name}]
|
133
|
+
end
|
134
|
+
def #{name}=(value)
|
135
|
+
@filepath_data[:#{name}] = value
|
136
|
+
end
|
137
|
+
EOS
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -30,23 +30,31 @@ module PlainRecord
|
|
30
30
|
end
|
31
31
|
@loaded[file]
|
32
32
|
end
|
33
|
-
|
34
|
-
def each_entry
|
35
|
-
files.each do |file|
|
33
|
+
|
34
|
+
def each_entry(matcher = { })
|
35
|
+
files(matcher).each do |file|
|
36
36
|
yield load_file(file)
|
37
37
|
end
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
def delete_entry(file, entry = nil)
|
41
41
|
delete_file(file)
|
42
42
|
end
|
43
|
-
|
43
|
+
|
44
|
+
def move_entry(entry, from, to)
|
45
|
+
if from
|
46
|
+
@loaded.delete(from)
|
47
|
+
delete_file(from)
|
48
|
+
end
|
49
|
+
@loaded[to] = entry
|
50
|
+
end
|
51
|
+
|
44
52
|
private
|
45
|
-
|
46
|
-
def all_entries
|
47
|
-
files.map { |file| load_file(file) }
|
53
|
+
|
54
|
+
def all_entries(matcher = { })
|
55
|
+
files(matcher).map { |file| load_file(file) }
|
48
56
|
end
|
49
|
-
|
57
|
+
|
50
58
|
def entries_string(entry)
|
51
59
|
entry.to_yaml + entry.texts.map{ |i| "---\n" + i }.join("\n")
|
52
60
|
end
|
@@ -29,15 +29,15 @@ module PlainRecord
|
|
29
29
|
end
|
30
30
|
@loaded[file]
|
31
31
|
end
|
32
|
-
|
33
|
-
def each_entry
|
34
|
-
files.each do |file|
|
32
|
+
|
33
|
+
def each_entry(matcher = { })
|
34
|
+
files(matcher).each do |file|
|
35
35
|
load_file(file).each do |entry|
|
36
36
|
yield entry
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
def delete_entry(file, entry = nil)
|
42
42
|
if entry.nil? or 1 == @loaded[file].length
|
43
43
|
delete_file(file)
|
@@ -46,13 +46,26 @@ module PlainRecord
|
|
46
46
|
save_file(file)
|
47
47
|
end
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
|
+
def move_entry(entry, from, to)
|
51
|
+
if from
|
52
|
+
@loaded[from].delete(entry)
|
53
|
+
if @loaded[from].empty?
|
54
|
+
delete_file(from)
|
55
|
+
else
|
56
|
+
save_file(from)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
@loaded[to] = [] unless @loaded.has_key? to
|
60
|
+
@loaded[to] << entry
|
61
|
+
end
|
62
|
+
|
50
63
|
private
|
51
|
-
|
52
|
-
def all_entries
|
53
|
-
files.map { |file| load_file(file) }.flatten
|
64
|
+
|
65
|
+
def all_entries(matcher = { })
|
66
|
+
files(matcher).map { |file| load_file(file) }.flatten
|
54
67
|
end
|
55
|
-
|
68
|
+
|
56
69
|
def entries_string(entries)
|
57
70
|
entries.to_yaml
|
58
71
|
end
|