plain_record 0.1 → 0.2
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 +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
|