ro 1.1.1 → 1.2.0
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.
- checksums.yaml +8 -8
- data/README.md +26 -1
- data/Rakefile +1 -1
- data/TODO.md +50 -0
- data/bin/ro +13 -3
- data/lib/ro.rb +75 -26
- data/lib/ro/cache.rb +10 -4
- data/lib/ro/git.rb +359 -0
- data/lib/ro/initializers/tilt.rb +15 -8
- data/lib/ro/lock.rb +53 -0
- data/lib/ro/model.rb +150 -0
- data/lib/ro/node.rb +66 -17
- data/lib/ro/node/list.rb +87 -11
- data/lib/ro/root.rb +22 -0
- data/lib/ro/slug.rb +1 -0
- data/lib/ro/template.rb +15 -3
- data/notes/ara.txt +119 -0
- data/ro.gemspec +14 -40
- metadata +50 -19
- data/: +0 -9
- data/:w +0 -29
- data/lib/co/db.rb +0 -4
- data/lib/co/node.rb +0 -4
- data/lib/co/node/list.rb +0 -16
- data/lib/co/util.rb +0 -5
- data/ro/people/ara/assets/ara-glacier.jpg +0 -0
- data/ro/people/ara/assets/source/a.rb +0 -5
- data/ro/people/ara/attributes.yml +0 -12
- data/ro/people/ara/bio.md.erb +0 -18
- data/ro/people/noah/attributes.yml +0 -5
- data/ro/posts/hello-world/attributes.yml +0 -3
- data/ro/posts/hello-world/body.md +0 -3
- data/ro/posts/second-awesome-post/attributes.yml +0 -1
- data/ro/posts/second-awesome-post/body.md +0 -3
data/lib/ro/initializers/tilt.rb
CHANGED
@@ -19,14 +19,21 @@ module Tilt
|
|
19
19
|
Redcarpet::Markdown.new(
|
20
20
|
syntax_highlighting_renderer,
|
21
21
|
|
22
|
-
:no_intra_emphasis
|
23
|
-
:tables
|
24
|
-
:fenced_code_blocks
|
25
|
-
:autolink
|
26
|
-
:
|
27
|
-
:
|
28
|
-
:
|
29
|
-
:
|
22
|
+
:no_intra_emphasis => true,
|
23
|
+
:tables => true,
|
24
|
+
:fenced_code_blocks => true,
|
25
|
+
:autolink => true,
|
26
|
+
:disable_indented_code_blocks => true,
|
27
|
+
:strikethrough => true,
|
28
|
+
:lax_spacing => true,
|
29
|
+
:space_after_headers => false,
|
30
|
+
:superscript => true,
|
31
|
+
:underline => true,
|
32
|
+
:highlight => true,
|
33
|
+
:quote => true,
|
34
|
+
|
35
|
+
:with_toc_data => true,
|
36
|
+
:hard_wrap => true,
|
30
37
|
)
|
31
38
|
|
32
39
|
@output = nil
|
data/lib/ro/lock.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
module Ro
|
2
|
+
class Lock
|
3
|
+
def initialize(path)
|
4
|
+
@path = path.to_s
|
5
|
+
@fd = false
|
6
|
+
end
|
7
|
+
|
8
|
+
def lock(&block)
|
9
|
+
open!
|
10
|
+
|
11
|
+
if block
|
12
|
+
begin
|
13
|
+
lock!
|
14
|
+
block.call
|
15
|
+
ensure
|
16
|
+
unlock!
|
17
|
+
end
|
18
|
+
else
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def open!
|
24
|
+
@fd ||= (
|
25
|
+
fd =
|
26
|
+
begin
|
27
|
+
open(@path, 'ab+')
|
28
|
+
rescue
|
29
|
+
unless test(?e, @path)
|
30
|
+
FileUtils.mkdir_p(@path)
|
31
|
+
FileUtils.touch(@path)
|
32
|
+
end
|
33
|
+
|
34
|
+
open(@path, 'ab+')
|
35
|
+
end
|
36
|
+
|
37
|
+
fd.close_on_exec = true
|
38
|
+
|
39
|
+
fd
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def lock!
|
44
|
+
open!
|
45
|
+
@fd.flock File::LOCK_EX
|
46
|
+
end
|
47
|
+
|
48
|
+
def unlock!
|
49
|
+
open!
|
50
|
+
@fd.flock File::LOCK_UN
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/ro/model.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
begin
|
2
|
+
require 'active_model'
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext/string/inflections.rb'
|
5
|
+
rescue LoadError => e
|
6
|
+
abort "you need to add the 'active_model' and 'active_support' gems to use Ro::Model"
|
7
|
+
end
|
8
|
+
|
9
|
+
module Ro
|
10
|
+
class Model
|
11
|
+
#
|
12
|
+
extend ActiveModel::Naming
|
13
|
+
extend ActiveModel::Translation
|
14
|
+
include ActiveModel::Validations
|
15
|
+
include ActiveModel::Conversion
|
16
|
+
|
17
|
+
#
|
18
|
+
Fattr(:collection){ default_collection_name }
|
19
|
+
Fattr(:default_collection_name){ self.name.to_s.split(/::/).last.underscore.pluralize }
|
20
|
+
Fattr(:root){ Ro.root }
|
21
|
+
Fattr(:prefix){ File.join(root, collection) }
|
22
|
+
|
23
|
+
def Model.nodes(*args, &block)
|
24
|
+
root.nodes.send(collection)
|
25
|
+
end
|
26
|
+
|
27
|
+
def Model.all(*args, &block)
|
28
|
+
models_for(nodes)
|
29
|
+
end
|
30
|
+
|
31
|
+
def Model.select(*args, &block)
|
32
|
+
all.select(*args, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def Model.detect(*args, &block)
|
36
|
+
all.detect(*args, &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def Model.count(*args, &block)
|
40
|
+
if args.empty? and block.nil?
|
41
|
+
all.size
|
42
|
+
else
|
43
|
+
where(*args, &block).size
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def Model.where(*args, &block)
|
48
|
+
all.select do |model|
|
49
|
+
!!model.instance_eval(&block)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def Model.first
|
54
|
+
all.first
|
55
|
+
end
|
56
|
+
|
57
|
+
def Model.last
|
58
|
+
all.last
|
59
|
+
end
|
60
|
+
|
61
|
+
def Model.find(*args, &block)
|
62
|
+
models_for(nodes.find(*args, &block))
|
63
|
+
end
|
64
|
+
|
65
|
+
def Model.paginate(*args, &block)
|
66
|
+
models_for(nodes.paginate(*args, &block))
|
67
|
+
end
|
68
|
+
|
69
|
+
def Model.models_for(result)
|
70
|
+
case result
|
71
|
+
when Array
|
72
|
+
Array(result).flatten.compact.map{|element| new(element)}
|
73
|
+
else
|
74
|
+
new(result)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
attr_accessor(:node)
|
80
|
+
|
81
|
+
def initialize(*args, &block)
|
82
|
+
attributes = Map.options_for!(args)
|
83
|
+
|
84
|
+
node = args.detect{|arg| arg.is_a?(Node)}
|
85
|
+
model = args.detect{|arg| arg.is_a?(Model)}
|
86
|
+
|
87
|
+
if node.nil? and not model.nil?
|
88
|
+
node = model.node
|
89
|
+
end
|
90
|
+
|
91
|
+
if node
|
92
|
+
@node = node
|
93
|
+
else
|
94
|
+
path = File.join(prefix, ':new')
|
95
|
+
node = Node.new(path)
|
96
|
+
@node = node
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def attributes
|
101
|
+
@node.attributes
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
def prefix
|
106
|
+
self.class.prefix
|
107
|
+
end
|
108
|
+
|
109
|
+
def directory
|
110
|
+
File.join(prefix, id)
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
def method_missing(method, *args, &block)
|
115
|
+
node.send(method, *args, &block)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
|
122
|
+
|
123
|
+
if __FILE__ == $0
|
124
|
+
|
125
|
+
ENV['RO_ROOT'] = '../sample_ro_data'
|
126
|
+
|
127
|
+
class Person < Ro::Model
|
128
|
+
|
129
|
+
#field(:first_name, :type => :string)
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
p Person.collection
|
134
|
+
p Person.all
|
135
|
+
p Person.find(:ara).attributes
|
136
|
+
ara = Person.find(:ara)
|
137
|
+
|
138
|
+
p ara.url_for(:ara_glacier)
|
139
|
+
|
140
|
+
p Person.paginate(:per => 2, :page => 1)
|
141
|
+
p Person.name
|
142
|
+
|
143
|
+
p Person.prefix
|
144
|
+
p ara.id
|
145
|
+
p ara.first_name
|
146
|
+
|
147
|
+
require 'pry'
|
148
|
+
binding.pry
|
149
|
+
|
150
|
+
end
|
data/lib/ro/node.rb
CHANGED
@@ -5,6 +5,7 @@ module Ro
|
|
5
5
|
fattr :name
|
6
6
|
fattr :type
|
7
7
|
fattr :loaded
|
8
|
+
fattr :fields
|
8
9
|
|
9
10
|
def initialize(path)
|
10
11
|
@path = Ro.realpath(path.to_s)
|
@@ -14,12 +15,17 @@ module Ro
|
|
14
15
|
@loaded = false
|
15
16
|
@loading = false
|
16
17
|
@attributes = Map.new
|
18
|
+
@fields = Map.new
|
17
19
|
end
|
18
20
|
|
19
21
|
def id
|
20
22
|
@name
|
21
23
|
end
|
22
24
|
|
25
|
+
def slug
|
26
|
+
attributes[:slug] || id
|
27
|
+
end
|
28
|
+
|
23
29
|
def identifier
|
24
30
|
"#{ type }/#{ name }"
|
25
31
|
end
|
@@ -52,6 +58,24 @@ module Ro
|
|
52
58
|
File.join(relative_path, 'assets')
|
53
59
|
end
|
54
60
|
|
61
|
+
def asset_dir
|
62
|
+
File.join(path, 'assets')
|
63
|
+
end
|
64
|
+
|
65
|
+
def asset_paths
|
66
|
+
Dir.glob("#{ asset_dir }/**/**").select{|entry| test(?f, entry)}
|
67
|
+
end
|
68
|
+
|
69
|
+
def assets
|
70
|
+
asset_paths.map do |asset|
|
71
|
+
asset.sub(asset_dir + "/", "")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def asset_urls
|
76
|
+
asset_names.map{|asset_name| url_for(asset_name)}
|
77
|
+
end
|
78
|
+
|
55
79
|
def url_for(*args, &block)
|
56
80
|
options = Map.options_for!(args)
|
57
81
|
|
@@ -61,11 +85,13 @@ module Ro
|
|
61
85
|
|
62
86
|
path = File.join(@path.to_s, 'assets', path_info)
|
63
87
|
|
88
|
+
glob = path_info.gsub(/[_-]/, '[_-]')
|
89
|
+
|
64
90
|
globs =
|
65
91
|
[
|
66
|
-
File.join(@path.to_s, 'assets', "#{
|
67
|
-
File.join(@path.to_s, 'assets', "#{
|
68
|
-
File.join(@path.to_s, 'assets', "**/#{
|
92
|
+
File.join(@path.to_s, 'assets', "#{ glob }"),
|
93
|
+
File.join(@path.to_s, 'assets', "#{ glob }*"),
|
94
|
+
File.join(@path.to_s, 'assets', "**/#{ glob }")
|
69
95
|
]
|
70
96
|
|
71
97
|
candidates = globs.map{|glob| Dir.glob(glob)}.flatten.compact.uniq
|
@@ -79,14 +105,15 @@ module Ro
|
|
79
105
|
path_info = path.gsub(/^#{ Regexp.escape(Ro.root) }/, '')
|
80
106
|
|
81
107
|
if cache_buster
|
82
|
-
|
108
|
+
#options['_'] = Ro.md5(IO.binread(path))
|
109
|
+
timestamp = File.stat(path).mtime.utc.to_i
|
83
110
|
options['_'] = timestamp
|
84
111
|
end
|
85
112
|
else
|
86
113
|
raise ArgumentError.new("too many assets (#{ candidates.inspect }) matching #{ globs.inspect }")
|
87
114
|
end
|
88
115
|
|
89
|
-
url = File.join(Ro.
|
116
|
+
url = File.join(Ro.route, path_info)
|
90
117
|
|
91
118
|
if options.empty?
|
92
119
|
url
|
@@ -96,13 +123,21 @@ module Ro
|
|
96
123
|
end
|
97
124
|
end
|
98
125
|
|
126
|
+
def url_for?(*args, &block)
|
127
|
+
begin
|
128
|
+
url_for(*args, &block)
|
129
|
+
rescue
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
99
134
|
def source_for(*args)
|
100
135
|
key = Ro.relative_path_for(:assets, :source, args).split('/')
|
101
136
|
get(key)
|
102
137
|
end
|
103
138
|
|
104
|
-
def
|
105
|
-
path_info = Ro.absolute_path_for(Ro.
|
139
|
+
def route(*args)
|
140
|
+
path_info = Ro.absolute_path_for(Ro.route, relative_path)
|
106
141
|
[Ro.asset_host, path_info].compact.join('/')
|
107
142
|
end
|
108
143
|
|
@@ -180,6 +215,15 @@ module Ro
|
|
180
215
|
}
|
181
216
|
end
|
182
217
|
|
218
|
+
def reload(&block)
|
219
|
+
@loaded = false
|
220
|
+
_load(&block)
|
221
|
+
end
|
222
|
+
|
223
|
+
def loaded
|
224
|
+
attributes
|
225
|
+
end
|
226
|
+
|
183
227
|
def _load(&block)
|
184
228
|
unless @loaded
|
185
229
|
if @loading
|
@@ -213,7 +257,7 @@ module Ro
|
|
213
257
|
_load_attributes_yml
|
214
258
|
_load_attribute_files
|
215
259
|
_load_sources
|
216
|
-
|
260
|
+
_load_assets
|
217
261
|
|
218
262
|
Ro.cache.write(cache_key, @attributes)
|
219
263
|
|
@@ -238,19 +282,24 @@ module Ro
|
|
238
282
|
end
|
239
283
|
|
240
284
|
def _load_attribute_files
|
241
|
-
glob = File.join(@path, '
|
285
|
+
glob = File.join(@path, '**/**')
|
242
286
|
node = self
|
243
287
|
|
244
288
|
Dir.glob(glob) do |path|
|
245
289
|
next if test(?d, path)
|
246
290
|
|
247
291
|
basename = File.basename(path)
|
248
|
-
key, ext = basename.split('.', 2)
|
249
|
-
|
250
292
|
next if basename == 'attributes.yml'
|
251
293
|
|
252
|
-
|
253
|
-
|
294
|
+
relative_path = Ro.relative_path(path, :to => @path)
|
295
|
+
next if relative_path =~ /^assets\//
|
296
|
+
|
297
|
+
key = relative_path.split('.', 2).first.split('/')
|
298
|
+
|
299
|
+
html = Ro.render(path, node)
|
300
|
+
html = Ro.expand_asset_urls(html, node)
|
301
|
+
|
302
|
+
@attributes.set(key => html)
|
254
303
|
end
|
255
304
|
end
|
256
305
|
|
@@ -271,7 +320,7 @@ module Ro
|
|
271
320
|
end
|
272
321
|
end
|
273
322
|
|
274
|
-
def
|
323
|
+
def _load_assets
|
275
324
|
glob = File.join(@path, 'assets/**/**')
|
276
325
|
node = self
|
277
326
|
|
@@ -311,11 +360,11 @@ module Ro
|
|
311
360
|
entries.push([relative_path, timestamp.iso8601(2)])
|
312
361
|
end
|
313
362
|
|
314
|
-
|
363
|
+
fingerprint = entries.map{|pair| pair.join('@')}.join(', ')
|
315
364
|
|
316
|
-
md5 = Ro.md5(
|
365
|
+
md5 = Ro.md5(fingerprint)
|
317
366
|
|
318
|
-
|
367
|
+
[@path, md5]
|
319
368
|
end
|
320
369
|
|
321
370
|
def _load_from_cache
|
data/lib/ro/node/list.rb
CHANGED
@@ -18,6 +18,10 @@ module Ro
|
|
18
18
|
block.call(self) if block
|
19
19
|
end
|
20
20
|
|
21
|
+
def nodes
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
21
25
|
def load(path)
|
22
26
|
add( node = Node.new(path) )
|
23
27
|
end
|
@@ -46,15 +50,22 @@ module Ro
|
|
46
50
|
related
|
47
51
|
end
|
48
52
|
|
49
|
-
def [](
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
53
|
+
def [](*args, &block)
|
54
|
+
key = args.first
|
55
|
+
|
56
|
+
case key
|
57
|
+
when String, Symbol
|
58
|
+
if @type.nil?
|
59
|
+
type = key.to_s
|
60
|
+
list = select{|node| type == node.type}
|
61
|
+
list.type = type
|
62
|
+
list
|
63
|
+
else
|
64
|
+
name = Slug.for(key.to_s)
|
65
|
+
detect{|node| name == node.name}
|
66
|
+
end
|
67
|
+
else
|
68
|
+
super(*args, &block)
|
58
69
|
end
|
59
70
|
end
|
60
71
|
|
@@ -103,6 +114,72 @@ module Ro
|
|
103
114
|
[root, type].compact.join('/')
|
104
115
|
end
|
105
116
|
|
117
|
+
def paginate(*args)
|
118
|
+
options = Map.options_for!(args)
|
119
|
+
|
120
|
+
ensure_pagination_state!
|
121
|
+
|
122
|
+
page = Integer(args.shift || options[:page] || @page)
|
123
|
+
per = Integer(args.shift || options[:per] || options[:size] || @per)
|
124
|
+
|
125
|
+
@page = [page.abs, 1].max
|
126
|
+
@per = [per.abs, 1].max
|
127
|
+
|
128
|
+
offset = (@page - 1) * @per
|
129
|
+
length = @per
|
130
|
+
|
131
|
+
replace(self.slice(offset, length))
|
132
|
+
|
133
|
+
self
|
134
|
+
end
|
135
|
+
|
136
|
+
def page(*args)
|
137
|
+
ensure_pagination_state!
|
138
|
+
|
139
|
+
if args.empty?
|
140
|
+
return @page
|
141
|
+
else
|
142
|
+
options = Map.options_for!(args)
|
143
|
+
page = args.shift || options[:page]
|
144
|
+
options[:page] = page
|
145
|
+
paginate(options)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
alias_method(:current_page, :page)
|
150
|
+
|
151
|
+
def per(*args)
|
152
|
+
ensure_pagination_state!
|
153
|
+
|
154
|
+
if args.empty?
|
155
|
+
return @per
|
156
|
+
else
|
157
|
+
options = Map.options_for!(args)
|
158
|
+
per = args.shift || options[:per]
|
159
|
+
options[:per] = per
|
160
|
+
paginate(options)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def num_pages
|
165
|
+
(size.to_f / per).ceil
|
166
|
+
end
|
167
|
+
|
168
|
+
def total_pages
|
169
|
+
num_pages
|
170
|
+
end
|
171
|
+
|
172
|
+
def ensure_pagination_state!
|
173
|
+
unless defined?(@page)
|
174
|
+
@page = 1
|
175
|
+
end
|
176
|
+
unless defined?(@per)
|
177
|
+
@per = size
|
178
|
+
end
|
179
|
+
[@page, @per]
|
180
|
+
end
|
181
|
+
|
182
|
+
|
106
183
|
def method_missing(method, *args, &block)
|
107
184
|
Ro.log "Ro::List(#{ identifier })#method_missing(#{ method.inspect }, #{ args.inspect })"
|
108
185
|
|
@@ -112,8 +189,7 @@ module Ro
|
|
112
189
|
super unless list
|
113
190
|
list.empty? ? super : list
|
114
191
|
else
|
115
|
-
|
116
|
-
node = self[name]
|
192
|
+
node = self[Slug.for(method, :join => '-')] || self[Slug.for(method, :join => '_')]
|
117
193
|
node.nil? ? super : node
|
118
194
|
end
|
119
195
|
end
|