ro 4.2.0 → 4.4.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 +4 -4
- data/Gemfile.lock +29 -8
- data/LICENSE +1 -1
- data/README.md +274 -111
- data/Rakefile +2 -2
- data/lib/ro/_lib.rb +18 -6
- data/lib/ro/asset.rb +23 -13
- data/lib/ro/collection.rb +47 -4
- data/lib/ro/config.rb +4 -0
- data/lib/ro/error.rb +5 -2
- data/lib/ro/html.rb +23 -0
- data/lib/ro/html_safe.rb +143 -0
- data/lib/ro/methods.rb +95 -38
- data/lib/ro/node.rb +78 -35
- data/lib/ro/path.rb +4 -0
- data/lib/ro/template.rb +62 -22
- data/lib/ro/text.rb +120 -0
- data/lib/ro.rb +4 -0
- data/public/api/ro/index-1.json +997 -79
- data/public/api/ro/index.json +997 -79
- data/public/api/ro/nerd/fastest-possible-embeddings/index.json +90 -0
- data/public/api/ro/nerd/ima/index.json +49 -0
- data/public/api/ro/nerd/index/index.json +74 -0
- data/public/api/ro/nerd/index-1.json +204 -0
- data/public/api/ro/nerd/index.json +194 -0
- data/public/api/ro/pages/about/index.json +60 -0
- data/public/api/ro/pages/contact/index.json +50 -0
- data/public/api/ro/pages/cv/index.json +49 -0
- data/public/api/ro/pages/disco/index.json +117 -0
- data/public/api/ro/pages/index/index.json +30 -0
- data/public/api/ro/pages/index-1.json +366 -0
- data/public/api/ro/pages/index.json +356 -0
- data/public/api/ro/pages/jess/index.json +62 -0
- data/public/api/ro/pages/now/index.json +43 -0
- data/public/api/ro/posts/almost-died-in-an-ice-cave/index.json +265 -0
- data/public/api/ro/posts/facebook-and-global-extremism/index.json +90 -0
- data/public/api/ro/posts/index-1.json +461 -79
- data/public/api/ro/posts/index.json +461 -79
- data/public/api/ro/posts/lemmings-considered-harmful/index.json +49 -0
- data/public/api/ro/posts/lost-in-the-desert/index.json +49 -0
- data/public/api/ro/posts/mission/index.json +49 -0
- data/public/api/ro/posts/return-your-laptop/index.json +61 -0
- data/public/ro/nerd/fastest-possible-embeddings/assets/giraffe.jpeg +0 -0
- data/public/ro/nerd/fastest-possible-embeddings/assets/let-me-in.jpg +0 -0
- data/public/ro/nerd/fastest-possible-embeddings/assets/src/fastembed.js +70 -0
- data/public/ro/nerd/fastest-possible-embeddings/assets/src/fastembed.rs +68 -0
- data/public/ro/nerd/fastest-possible-embeddings/assets/terminal.jpg +0 -0
- data/public/ro/nerd/fastest-possible-embeddings/attributes.yml +7 -0
- data/public/ro/nerd/fastest-possible-embeddings/body.md +266 -0
- data/public/ro/nerd/ima/assets/og.jpeg +0 -0
- data/public/ro/nerd/ima/attributes.yml +8 -0
- data/public/ro/nerd/ima/body.md +22 -0
- data/public/ro/nerd/index/assets/giraffe.jpeg +0 -0
- data/public/ro/nerd/index/assets/let-me-in.jpg +0 -0
- data/public/ro/nerd/index/assets/terminal.jpg +0 -0
- data/public/ro/nerd/index/attributes.yml +7 -0
- data/public/ro/nerd/index/body.md +130 -0
- data/public/ro/pages/about/assets/og.jpeg +0 -0
- data/public/ro/pages/about/assets/speak-english-pulp-fiction.gif +0 -0
- data/public/ro/pages/about/body.md +40 -0
- data/public/ro/pages/contact/assets/giraffe.jpeg +0 -0
- data/public/ro/pages/contact/attributes.yml +7 -0
- data/public/ro/pages/contact/body.md +9 -0
- data/public/ro/pages/cv/assets/ara.jpg +0 -0
- data/public/ro/pages/cv/attributes.yml +6 -0
- data/public/ro/pages/cv/body.md +122 -0
- data/public/ro/pages/disco/assets/disco.jpg +0 -0
- data/public/ro/pages/disco/assets/disco.png +0 -0
- data/public/ro/pages/disco/assets/speak-english-pulp-fiction.gif +0 -0
- data/public/ro/pages/disco/assets/src/environment.md +2354 -0
- data/public/ro/pages/disco/assets/src/fortune-500.md +2518 -0
- data/public/ro/pages/disco/assets/src/greed.md +2703 -0
- data/public/ro/pages/disco/assets/src/up-at-night.md +2337 -0
- data/public/ro/pages/disco/attributes.yml +9 -0
- data/public/ro/pages/disco/body.md +99 -0
- data/public/ro/pages/disco/samples/environment.md +2354 -0
- data/public/ro/pages/disco/samples/fortune-500.md +2518 -0
- data/public/ro/pages/disco/samples/greed.md +2703 -0
- data/public/ro/pages/disco/samples/up-at-night.md +2337 -0
- data/public/ro/pages/index/attributes.yml +1 -0
- data/public/ro/pages/index/body.md +15 -0
- data/public/ro/pages/jess/assets/og.jpg +0 -0
- data/public/ro/pages/jess/assets/speak-english-pulp-fiction.gif +0 -0
- data/public/ro/pages/jess/attributes.yml +7 -0
- data/public/ro/pages/jess/body.md +3 -0
- data/public/ro/pages/now/assets/speak-english-pulp-fiction.gif +0 -0
- data/public/ro/pages/now/attributes.yml +1 -0
- data/public/ro/pages/now/body.md +24 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/assets/image1.png +0 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/assets/image10.png +0 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/assets/image11.png +0 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/assets/image12.png +0 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/assets/image13.png +0 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/assets/image14.png +0 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/assets/image15.png +0 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/assets/image2.png +0 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/assets/image3.png +0 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/assets/image4.png +0 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/assets/image5.png +0 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/assets/image6.png +0 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/assets/image7.png +0 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/assets/image8.png +0 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/assets/image9.png +0 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/assets/josh-pointing.jpg +0 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/assets/levi-rawr.png +0 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/assets/og.jpg +0 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/assets/purple-heart.jpg +0 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/attributes.yml +6 -0
- data/public/ro/posts/almost-died-in-an-ice-cave/body.md +419 -0
- data/public/ro/posts/facebook-and-global-extremism/assets/background.html +125 -0
- data/public/ro/posts/facebook-and-global-extremism/assets/background.md +95 -0
- data/public/ro/posts/facebook-and-global-extremism/assets/og.jpg +0 -0
- data/public/ro/posts/facebook-and-global-extremism/assets/prompt.txt +122 -0
- data/public/ro/posts/facebook-and-global-extremism/assets/results.md +183 -0
- data/public/ro/posts/facebook-and-global-extremism/assets/survey.txt +190 -0
- data/public/ro/posts/facebook-and-global-extremism/attributes.yml +7 -0
- data/public/ro/posts/facebook-and-global-extremism/body.md +393 -0
- data/public/ro/posts/lemmings-considered-harmful/assets/lemming.jpeg +0 -0
- data/public/ro/posts/lemmings-considered-harmful/attributes.yml +6 -0
- data/public/ro/posts/lemmings-considered-harmful/body.md +43 -0
- data/public/ro/posts/lost-in-the-desert/assets/og.jpg +0 -0
- data/public/ro/posts/lost-in-the-desert/attributes.yml +6 -0
- data/public/ro/posts/lost-in-the-desert/body.md +7 -0
- data/public/ro/posts/mission/assets/og.jpg +0 -0
- data/public/ro/posts/mission/attributes.yml +6 -0
- data/public/ro/posts/mission/body.md +4 -0
- data/public/ro/posts/return-your-laptop/assets/og.jpg +0 -0
- data/public/ro/posts/return-your-laptop/assets/return-your-laptop.png +0 -0
- data/public/ro/posts/return-your-laptop/attributes.yml +6 -0
- data/public/ro/posts/return-your-laptop/body.md +58 -0
- data/ro.gemspec +178 -49
- data/scripts/speedtest.rb +324 -0
- data/tmp/gem-details.oe +0 -0
- metadata +157 -33
- data/public/api/ro/posts/first_post/index.json +0 -52
- data/public/api/ro/posts/second_post/index.json +0 -51
- data/public/api/ro/posts/third_post/index.json +0 -51
- data/public/ro/posts/first_post/assets/foo/bar/baz.jpg +0 -0
- data/public/ro/posts/first_post/assets/foo.jpg +0 -0
- data/public/ro/posts/first_post/assets/src/foo/bar.rb +0 -3
- data/public/ro/posts/first_post/attributes.yml +0 -2
- data/public/ro/posts/first_post/blurb.erb.md +0 -7
- data/public/ro/posts/first_post/body.md +0 -16
- data/public/ro/posts/first_post/testing.txt +0 -3
- data/public/ro/posts/second_post/assets/foo/bar/baz.jpg +0 -0
- data/public/ro/posts/second_post/assets/foo.jpg +0 -0
- data/public/ro/posts/second_post/assets/src/foo/bar.rb +0 -3
- data/public/ro/posts/second_post/attributes.yml +0 -2
- data/public/ro/posts/second_post/blurb.erb.md +0 -5
- data/public/ro/posts/second_post/body.md +0 -16
- data/public/ro/posts/third_post/assets/foo/bar/baz.jpg +0 -0
- data/public/ro/posts/third_post/assets/foo.jpg +0 -0
- data/public/ro/posts/third_post/assets/src/foo/bar.rb +0 -3
- data/public/ro/posts/third_post/attributes.yml +0 -2
- data/public/ro/posts/third_post/blurb.erb.md +0 -5
- data/public/ro/posts/third_post/body.md +0 -16
data/lib/ro/collection.rb
CHANGED
@@ -42,13 +42,56 @@ module Ro
|
|
42
42
|
@path.subdirectory_for(name)
|
43
43
|
end
|
44
44
|
|
45
|
-
def each(&block)
|
45
|
+
def each(offset:nil, limit:nil, &block)
|
46
46
|
accum = []
|
47
47
|
|
48
|
-
|
49
|
-
|
48
|
+
if offset
|
49
|
+
i = -1
|
50
|
+
n = 0
|
51
|
+
subdirectories do |subdirectory|
|
52
|
+
i += 1
|
53
|
+
next if i < offset
|
54
|
+
node = node_for(subdirectory)
|
55
|
+
block ? block.call(node) : accum.push(node)
|
56
|
+
n += 1
|
57
|
+
break if limit && n >= limit
|
58
|
+
end
|
59
|
+
else
|
60
|
+
subdirectories do |subdirectory|
|
61
|
+
node = node_for(subdirectory)
|
62
|
+
block ? block.call(node) : accum.push(node)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
block ? self : accum
|
67
|
+
end
|
68
|
+
|
69
|
+
class Page < ::Array
|
70
|
+
attr_accessor :number
|
71
|
+
|
72
|
+
def initialize(nodes = [], number: 1)
|
73
|
+
replace(nodes)
|
74
|
+
@number = number
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def page(number, size: 10)
|
79
|
+
offset = [(number - 1), 0].max * size
|
80
|
+
limit = [size, 1].max
|
81
|
+
|
82
|
+
nodes = each(offset:, limit:)
|
83
|
+
Page.new(nodes, number:)
|
84
|
+
end
|
85
|
+
|
86
|
+
def paginate(size: 10, &block)
|
87
|
+
number = 0
|
88
|
+
accum = []
|
50
89
|
|
51
|
-
|
90
|
+
loop do
|
91
|
+
number += 1
|
92
|
+
page = self.page(number, size:)
|
93
|
+
break if page.empty?
|
94
|
+
block ? block.call(page) : accum.push(page)
|
52
95
|
end
|
53
96
|
|
54
97
|
block ? self : accum
|
data/lib/ro/config.rb
CHANGED
@@ -11,6 +11,9 @@ module Ro
|
|
11
11
|
:url =>
|
12
12
|
(Ro.env.url || Ro.defaults.url),
|
13
13
|
|
14
|
+
:img_url =>
|
15
|
+
(Ro.env.img_url || Ro.defaults.img_url),
|
16
|
+
|
14
17
|
:page_size =>
|
15
18
|
(Ro.env.page_size || Ro.defaults.page_size),
|
16
19
|
|
@@ -48,6 +51,7 @@ module Ro
|
|
48
51
|
:root => :root,
|
49
52
|
:build => :path,
|
50
53
|
:url => :url,
|
54
|
+
:img_url => :url,
|
51
55
|
:page_size => :int,
|
52
56
|
:log => :bool,
|
53
57
|
:debug => :bool,
|
data/lib/ro/error.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
module Ro
|
2
2
|
class Error < ::StandardError
|
3
|
-
|
4
|
-
|
3
|
+
attr_reader :context
|
4
|
+
|
5
|
+
def initialize(message, **context)
|
5
6
|
@context = context
|
7
|
+
msg = context.empty? ? "#{ message }" : "#{ message }, #{ context.inspect }"
|
8
|
+
super(msg)
|
6
9
|
end
|
7
10
|
end
|
8
11
|
end
|
data/lib/ro/html.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Ro
|
2
|
+
require_relative 'html_safe'
|
3
|
+
|
4
|
+
class HTML < ::ActiveSupport::SafeBuffer
|
5
|
+
def initialize(*args, **kws, &block)
|
6
|
+
self.front_matter = kws.fetch(:front_matter){ {} }
|
7
|
+
|
8
|
+
super(args.join)
|
9
|
+
end
|
10
|
+
|
11
|
+
def front_matter
|
12
|
+
@front_matter ||= Map.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def front_matter=(hash = {})
|
16
|
+
@front_matter = Map.for(hash)
|
17
|
+
end
|
18
|
+
|
19
|
+
def attributes
|
20
|
+
front_matter
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/ro/html_safe.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
unless defined?(ActiveSupport::SafeBuffer)
|
4
|
+
|
5
|
+
class Object
|
6
|
+
def html_safe?
|
7
|
+
false
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Numeric
|
12
|
+
def html_safe?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ActiveSupport #:nodoc:
|
18
|
+
class SafeBuffer < String
|
19
|
+
UNSAFE_STRING_METHODS = %w(
|
20
|
+
capitalize chomp chop delete downcase gsub lstrip next reverse rstrip
|
21
|
+
slice squeeze strip sub succ swapcase tr tr_s upcase
|
22
|
+
)
|
23
|
+
|
24
|
+
alias_method :original_concat, :concat
|
25
|
+
private :original_concat
|
26
|
+
|
27
|
+
# Raised when <tt>ActiveSupport::SafeBuffer#safe_concat</tt> is called on unsafe buffers.
|
28
|
+
class SafeConcatError < StandardError
|
29
|
+
def initialize
|
30
|
+
super "Could not concatenate to the buffer because it is not html safe."
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def [](*args)
|
35
|
+
if args.size < 2
|
36
|
+
super
|
37
|
+
elsif html_safe?
|
38
|
+
new_safe_buffer = super
|
39
|
+
|
40
|
+
if new_safe_buffer
|
41
|
+
new_safe_buffer.instance_variable_set :@html_safe, true
|
42
|
+
end
|
43
|
+
|
44
|
+
new_safe_buffer
|
45
|
+
else
|
46
|
+
to_str[*args]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def safe_concat(value)
|
51
|
+
raise SafeConcatError unless html_safe?
|
52
|
+
original_concat(value)
|
53
|
+
end
|
54
|
+
|
55
|
+
def initialize(str = "")
|
56
|
+
@html_safe = true
|
57
|
+
super
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize_copy(other)
|
61
|
+
super
|
62
|
+
@html_safe = other.html_safe?
|
63
|
+
end
|
64
|
+
|
65
|
+
def clone_empty
|
66
|
+
self[0, 0]
|
67
|
+
end
|
68
|
+
|
69
|
+
def concat(value)
|
70
|
+
super(html_escape_interpolated_argument(value))
|
71
|
+
end
|
72
|
+
alias << concat
|
73
|
+
|
74
|
+
def prepend(value)
|
75
|
+
super(html_escape_interpolated_argument(value))
|
76
|
+
end
|
77
|
+
|
78
|
+
def +(other)
|
79
|
+
dup.concat(other)
|
80
|
+
end
|
81
|
+
|
82
|
+
def %(args)
|
83
|
+
case args
|
84
|
+
when Hash
|
85
|
+
escaped_args = Hash[args.map { |k, arg| [k, html_escape_interpolated_argument(arg)] }]
|
86
|
+
else
|
87
|
+
escaped_args = Array(args).map { |arg| html_escape_interpolated_argument(arg) }
|
88
|
+
end
|
89
|
+
|
90
|
+
self.class.new(super(escaped_args))
|
91
|
+
end
|
92
|
+
|
93
|
+
def html_safe?
|
94
|
+
defined?(@html_safe) && @html_safe
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_s
|
98
|
+
self
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_param
|
102
|
+
to_str
|
103
|
+
end
|
104
|
+
|
105
|
+
def encode_with(coder)
|
106
|
+
coder.represent_object nil, to_str
|
107
|
+
end
|
108
|
+
|
109
|
+
UNSAFE_STRING_METHODS.each do |unsafe_method|
|
110
|
+
if unsafe_method.respond_to?(unsafe_method)
|
111
|
+
class_eval <<-EOT, __FILE__, __LINE__ + 1
|
112
|
+
def #{unsafe_method}(*args, &block) # def capitalize(*args, &block)
|
113
|
+
to_str.#{unsafe_method}(*args, &block) # to_str.capitalize(*args, &block)
|
114
|
+
end # end
|
115
|
+
|
116
|
+
def #{unsafe_method}!(*args) # def capitalize!(*args)
|
117
|
+
@html_safe = false # @html_safe = false
|
118
|
+
super # super
|
119
|
+
end # end
|
120
|
+
EOT
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def html_escape_interpolated_argument(arg)
|
127
|
+
(!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class String
|
133
|
+
# Marks a string as trusted safe. It will be inserted into HTML with no
|
134
|
+
# additional escaping performed. It is your responsibility to ensure that the
|
135
|
+
# string contains no malicious content. This method is equivalent to the
|
136
|
+
# +raw+ helper in views. It is recommended that you use +sanitize+ instead of
|
137
|
+
# this method. It should never be called on user input.
|
138
|
+
def html_safe
|
139
|
+
ActiveSupport::SafeBuffer.new(self)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
data/lib/ro/methods.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Ro
|
2
|
-
|
2
|
+
module Methods
|
3
3
|
# cast methods
|
4
4
|
# |
|
5
5
|
# v
|
@@ -51,10 +51,19 @@ module Ro
|
|
51
51
|
# v
|
52
52
|
def url_for(path, *args)
|
53
53
|
options = Map.extract_options!(args)
|
54
|
-
|
54
|
+
|
55
|
+
base = (options.delete(:base) || options.delete(:url))
|
55
56
|
|
56
57
|
path = Path.for(path, *args)
|
57
58
|
|
59
|
+
base ||= (
|
60
|
+
if Ro.is_image?(path)
|
61
|
+
Ro.config.img_url
|
62
|
+
else
|
63
|
+
Ro.config.url
|
64
|
+
end
|
65
|
+
)
|
66
|
+
|
58
67
|
fragment = options.delete(:fragment)
|
59
68
|
query = options.delete(:query) || options
|
60
69
|
|
@@ -63,8 +72,7 @@ module Ro
|
|
63
72
|
uri.path = '' if uri.path == '/'
|
64
73
|
|
65
74
|
uri.query = query_string_for(query) unless query.empty?
|
66
|
-
|
67
|
-
uri.fragment = fragment if fragment
|
75
|
+
uri.fragment = fragment unless fragment.nil?
|
68
76
|
|
69
77
|
uri.to_s
|
70
78
|
end
|
@@ -127,8 +135,8 @@ module Ro
|
|
127
135
|
end
|
128
136
|
end
|
129
137
|
|
130
|
-
def error!(message, context
|
131
|
-
error = Error.new(message, context)
|
138
|
+
def error!(message, **context)
|
139
|
+
error = Error.new(message, **context)
|
132
140
|
|
133
141
|
begin
|
134
142
|
raise error
|
@@ -139,6 +147,14 @@ module Ro
|
|
139
147
|
end
|
140
148
|
end
|
141
149
|
|
150
|
+
def emsg(e)
|
151
|
+
if e.is_a?(Exception)
|
152
|
+
"#{ e.message } (#{ e.class.name })\n#{ Array(e.backtrace).join(10.chr) }"
|
153
|
+
else
|
154
|
+
e.to_s
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
142
158
|
# template methods
|
143
159
|
# |
|
144
160
|
# v
|
@@ -158,7 +174,8 @@ module Ro
|
|
158
174
|
# |
|
159
175
|
# v
|
160
176
|
EXPAND_ASSET_URL_STRATEGIES = %i[
|
161
|
-
accurate_expand_asset_urls
|
177
|
+
accurate_expand_asset_urls
|
178
|
+
sloppy_expand_asset_urls
|
162
179
|
]
|
163
180
|
|
164
181
|
def expand_asset_url_strategies
|
@@ -166,55 +183,58 @@ module Ro
|
|
166
183
|
end
|
167
184
|
|
168
185
|
def expand_asset_urls(html, node)
|
169
|
-
|
186
|
+
strategies = expand_asset_url_strategies
|
187
|
+
error = nil
|
170
188
|
|
171
|
-
|
189
|
+
strategies.each do |strategy|
|
172
190
|
return send(strategy, html, node)
|
173
191
|
rescue Object => e
|
174
|
-
|
175
|
-
|
176
|
-
Ro.log(
|
192
|
+
error = e
|
193
|
+
Ro.log(:error, emsg(error))
|
194
|
+
Ro.log(:error, "failed to expand assets via #{ strategy }")
|
177
195
|
end
|
178
196
|
|
179
|
-
|
197
|
+
raise error
|
180
198
|
end
|
181
199
|
|
182
200
|
def accurate_expand_asset_urls(html, node)
|
183
|
-
doc =
|
201
|
+
doc = Nokogiri::HTML.fragment(html.to_str)
|
184
202
|
|
185
|
-
doc.
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
element.attributes.each do |key, value|
|
190
|
-
src[key] = value
|
191
|
-
end
|
192
|
-
|
193
|
-
dst = expand_asset_values(src, node)
|
194
|
-
|
195
|
-
dst.each do |k, v|
|
196
|
-
element.attributes[k] = v
|
203
|
+
doc.traverse do |element|
|
204
|
+
if element.respond_to?(:attributes)
|
205
|
+
attributes = element.attributes
|
206
|
+
expand_asset_values!(attributes) unless attributes.empty?
|
197
207
|
end
|
198
208
|
end
|
199
209
|
|
200
|
-
doc.to_s.
|
201
|
-
|
202
|
-
|
203
|
-
xml.strip!
|
204
|
-
end
|
210
|
+
expanded = doc.to_s.strip
|
211
|
+
|
212
|
+
HTML.new(expanded)
|
205
213
|
end
|
206
214
|
|
207
215
|
def sloppy_expand_asset_urls(html, node)
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
216
|
+
re = %r`\s*=\s*['"](?:[.]/)?(assets/[^'"\s]+)['"]`
|
217
|
+
|
218
|
+
expanded =
|
219
|
+
html.to_str.gsub(re) do |match|
|
220
|
+
path = match[%r`assets/[^'"\s]+`]
|
221
|
+
|
222
|
+
if node.path_for(path).exist?
|
223
|
+
url = node.url_for(path)
|
224
|
+
"='#{url}'"
|
225
|
+
else
|
226
|
+
match
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
HTML.new(expanded)
|
213
231
|
end
|
214
232
|
|
215
|
-
|
233
|
+
def expand_asset_values(hash, node)
|
216
234
|
src = Map.for(hash)
|
217
|
-
dst = Map.new
|
235
|
+
dst = Map.new(hash)
|
236
|
+
|
237
|
+
return dst if src.empty?
|
218
238
|
|
219
239
|
re = %r{\A(?:[.]/)?(assets/[^\s]+)\s*\z}
|
220
240
|
|
@@ -232,6 +252,43 @@ module Ro
|
|
232
252
|
|
233
253
|
dst.to_hash
|
234
254
|
end
|
255
|
+
|
256
|
+
def expand_asset_values!(hash, node)
|
257
|
+
expand_asset_values(hash, node).each do |key, value|
|
258
|
+
hash[key] = value
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
#
|
263
|
+
DEFAULT_IMAGE_EXTENSIONS = %i[
|
264
|
+
webp jpg jpeg png gif tif tiff svg
|
265
|
+
]
|
266
|
+
|
267
|
+
DEFAULT_IMAGE_PATTERNS = [
|
268
|
+
/[.](#{ DEFAULT_IMAGE_EXTENSIONS.join('|') })$/i
|
269
|
+
]
|
270
|
+
|
271
|
+
def image_patterns
|
272
|
+
@image_patterns ||= DEFAULT_IMAGE_PATTERNS.dup
|
273
|
+
end
|
274
|
+
|
275
|
+
def image_pattern
|
276
|
+
Regexp.union(Ro.image_patterns)
|
277
|
+
end
|
278
|
+
|
279
|
+
def is_image?(path)
|
280
|
+
!!(URI.parse(path.to_s).path =~ Ro.image_pattern)
|
281
|
+
end
|
282
|
+
|
283
|
+
def image_info(path)
|
284
|
+
is = ImageSize.path(path)
|
285
|
+
format, width, height = is.format.to_s, is.width, is.height
|
286
|
+
{format:, width:, height:}
|
287
|
+
end
|
288
|
+
|
289
|
+
def uuid
|
290
|
+
SecureRandom.uuid_v7.to_s
|
291
|
+
end
|
235
292
|
end
|
236
293
|
|
237
294
|
extend Methods
|
data/lib/ro/node.rb
CHANGED
@@ -47,40 +47,34 @@ module Ro
|
|
47
47
|
@attributes = Map.new
|
48
48
|
|
49
49
|
_load_base_attributes
|
50
|
+
_load_file_attributes
|
50
51
|
_load_asset_attributes
|
51
52
|
_load_meta_attributes
|
52
|
-
_load_file_attributes
|
53
53
|
|
54
54
|
@attributes
|
55
55
|
end
|
56
56
|
|
57
57
|
def _load_base_attributes
|
58
|
-
|
59
|
-
%w[
|
60
|
-
assets
|
61
|
-
_meta
|
62
|
-
]
|
63
|
-
|
64
|
-
glob =
|
65
|
-
"attributes.{yml,yaml,json}"
|
58
|
+
glob = "attributes.{yml,yaml,json}"
|
66
59
|
|
67
60
|
@path.glob(glob) do |file|
|
68
61
|
attrs = _render(file)
|
69
|
-
|
70
|
-
disallowed.each do |key|
|
71
|
-
Ro.error!("#{ file } must not contain the key #{key.inspect}") if attrs.has_key?(key)
|
72
|
-
end
|
73
|
-
|
74
|
-
@attributes.update(attrs)
|
62
|
+
update_attributes!(attrs, file:)
|
75
63
|
end
|
76
64
|
end
|
77
65
|
|
78
|
-
|
79
66
|
def _load_asset_attributes
|
80
67
|
{}.tap do |hash|
|
81
68
|
assets.each do |asset|
|
82
69
|
key = asset.name
|
83
|
-
|
70
|
+
url = asset.url
|
71
|
+
path = asset.path.relative_to(@root)
|
72
|
+
src = asset.src
|
73
|
+
img = asset.img
|
74
|
+
size = asset.size
|
75
|
+
|
76
|
+
value = { url:, path:, size:, img:, src: }
|
77
|
+
|
84
78
|
hash[key] = value
|
85
79
|
end
|
86
80
|
|
@@ -94,7 +88,9 @@ module Ro
|
|
94
88
|
identifier:,
|
95
89
|
type:,
|
96
90
|
id:,
|
97
|
-
urls
|
91
|
+
urls:,
|
92
|
+
created_at:,
|
93
|
+
updated_at:,
|
98
94
|
)
|
99
95
|
|
100
96
|
@attributes.set(_meta: hash)
|
@@ -114,16 +110,46 @@ module Ro
|
|
114
110
|
base = basename.split('.', 2).first
|
115
111
|
key.push(base)
|
116
112
|
|
117
|
-
|
118
|
-
|
113
|
+
value = _render(file)
|
114
|
+
|
115
|
+
if value.is_a?(HTML)
|
116
|
+
attrs = value.front_matter
|
117
|
+
update_attributes!(attrs, file:)
|
119
118
|
end
|
120
119
|
|
121
|
-
|
120
|
+
if @attributes.has?(key)
|
121
|
+
raise Error.new("path=#{ @path.inspect } masks #{ key.inspect } in #{ @attributes.inspect }!")
|
122
|
+
end
|
122
123
|
|
123
124
|
@attributes.set(key => value)
|
124
125
|
end
|
125
126
|
end
|
126
127
|
|
128
|
+
def update_attributes!(attrs = {}, **context)
|
129
|
+
attrs = Map.for(attrs)
|
130
|
+
|
131
|
+
blacklist = %w[
|
132
|
+
assets
|
133
|
+
_meta
|
134
|
+
]
|
135
|
+
|
136
|
+
blacklist.each do |key|
|
137
|
+
if attrs.has_key?(key)
|
138
|
+
Ro.error!("#{ key } is blacklisted!", **context)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
keys = @attributes.depth_first_keys
|
143
|
+
|
144
|
+
attrs.depth_first_keys.each do |key|
|
145
|
+
if keys.include?(key)
|
146
|
+
Ro.error!("#{ attrs.inspect } clobbers #{ @attributes.inspect }!", **context)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
@attributes.update(attrs)
|
151
|
+
end
|
152
|
+
|
127
153
|
def _ignored_files
|
128
154
|
ignored_files =
|
129
155
|
%w[
|
@@ -139,11 +165,19 @@ module Ro
|
|
139
165
|
end
|
140
166
|
|
141
167
|
def _render(file)
|
168
|
+
node = self
|
169
|
+
|
142
170
|
value = Ro.render(file, _render_context)
|
143
171
|
|
144
|
-
if value.is_a?(
|
145
|
-
|
146
|
-
|
172
|
+
if value.is_a?(HTML)
|
173
|
+
front_matter = value.front_matter
|
174
|
+
html = Ro.expand_asset_urls(value, node)
|
175
|
+
value = HTML.new(html, front_matter:)
|
176
|
+
end
|
177
|
+
|
178
|
+
if value.is_a?(Hash)
|
179
|
+
attributes = value
|
180
|
+
value = Ro.expand_asset_values(attributes, node)
|
147
181
|
end
|
148
182
|
|
149
183
|
value
|
@@ -156,6 +190,10 @@ module Ro
|
|
156
190
|
end
|
157
191
|
end
|
158
192
|
|
193
|
+
def fetch(*args)
|
194
|
+
attributes.fetch(*args)
|
195
|
+
end
|
196
|
+
|
159
197
|
def get(*args)
|
160
198
|
attributes.get(*args)
|
161
199
|
end
|
@@ -219,14 +257,13 @@ module Ro
|
|
219
257
|
end
|
220
258
|
|
221
259
|
def url_for(relative_path, options = {})
|
222
|
-
raise ArgumentError, relative_path if Path.absolute?(relative_path)
|
223
|
-
|
224
|
-
fullpath = Path.for(path, relative_path).expand
|
225
|
-
raise ArgumentError, "#{relative_path.inspect} -- DOES NOT EXIST" unless fullpath.exist?
|
226
|
-
|
227
260
|
Ro.url_for(self.relative_path, relative_path, options)
|
228
261
|
end
|
229
262
|
|
263
|
+
def path_for(...)
|
264
|
+
@path.join(...)
|
265
|
+
end
|
266
|
+
|
230
267
|
def src_for(*args)
|
231
268
|
key = Path.relative(:assets, :src, args).split('/')
|
232
269
|
get(key)
|
@@ -250,6 +287,10 @@ module Ro
|
|
250
287
|
to_json(...)
|
251
288
|
end
|
252
289
|
|
290
|
+
def to_str(...)
|
291
|
+
to_json(...)
|
292
|
+
end
|
293
|
+
|
253
294
|
def to_json(...)
|
254
295
|
JSON.pretty_generate(to_hash, ...)
|
255
296
|
end
|
@@ -262,12 +303,6 @@ module Ro
|
|
262
303
|
to_hash.to_yaml(...)
|
263
304
|
end
|
264
305
|
|
265
|
-
def _mapify(data)
|
266
|
-
converted = 'this_recursively_converts_nested_hashes_into_maps'
|
267
|
-
|
268
|
-
Map.for(converted => data)[converted]
|
269
|
-
end
|
270
|
-
|
271
306
|
def files
|
272
307
|
path.glob('**/**').select { |entry| entry.file? }.sort
|
273
308
|
end
|
@@ -291,5 +326,13 @@ module Ro
|
|
291
326
|
|
292
327
|
[position, published_at, created_at, name]
|
293
328
|
end
|
329
|
+
|
330
|
+
def created_at
|
331
|
+
files.map{|file| File.stat(file).ctime}.min
|
332
|
+
end
|
333
|
+
|
334
|
+
def updated_at
|
335
|
+
files.map{|file| File.stat(file).mtime}.max
|
336
|
+
end
|
294
337
|
end
|
295
338
|
end
|