ro 1.4.6 → 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.
Files changed (157) hide show
  1. checksums.yaml +6 -14
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +64 -0
  4. data/LICENSE +1 -0
  5. data/README.md +276 -105
  6. data/README.md.erb +159 -0
  7. data/Rakefile +129 -78
  8. data/bin/ro +241 -68
  9. data/lib/ro/_lib.rb +107 -0
  10. data/lib/ro/asset.rb +55 -0
  11. data/lib/ro/collection/list.rb +23 -0
  12. data/lib/ro/collection.rb +211 -0
  13. data/lib/ro/config.rb +72 -0
  14. data/lib/ro/error.rb +11 -0
  15. data/lib/ro/html.rb +23 -0
  16. data/lib/ro/html_safe.rb +143 -0
  17. data/lib/ro/klass.rb +25 -0
  18. data/lib/ro/methods.rb +295 -0
  19. data/lib/ro/model.rb +83 -114
  20. data/lib/ro/node.rb +209 -349
  21. data/lib/ro/pagination.rb +7 -4
  22. data/lib/ro/path.rb +229 -0
  23. data/lib/ro/root.rb +52 -31
  24. data/lib/ro/script/builder.rb +221 -0
  25. data/lib/ro/script/console.rb +47 -0
  26. data/lib/ro/script/server.rb +76 -0
  27. data/lib/ro/script.rb +189 -0
  28. data/lib/ro/slug.rb +19 -18
  29. data/lib/ro/template/rouge_formatter.rb +42 -0
  30. data/lib/ro/template.rb +145 -51
  31. data/lib/ro/text.rb +120 -0
  32. data/lib/ro.rb +89 -317
  33. data/public/api/ro/index-1.json +1065 -0
  34. data/public/api/ro/index.json +1055 -0
  35. data/public/api/ro/nerd/fastest-possible-embeddings/index.json +90 -0
  36. data/public/api/ro/nerd/ima/index.json +49 -0
  37. data/public/api/ro/nerd/index/index.json +74 -0
  38. data/public/api/ro/nerd/index-1.json +204 -0
  39. data/public/api/ro/nerd/index.json +194 -0
  40. data/public/api/ro/pages/about/index.json +60 -0
  41. data/public/api/ro/pages/contact/index.json +50 -0
  42. data/public/api/ro/pages/cv/index.json +49 -0
  43. data/public/api/ro/pages/disco/index.json +117 -0
  44. data/public/api/ro/pages/index/index.json +30 -0
  45. data/public/api/ro/pages/index-1.json +366 -0
  46. data/public/api/ro/pages/index.json +356 -0
  47. data/public/api/ro/pages/jess/index.json +62 -0
  48. data/public/api/ro/pages/now/index.json +43 -0
  49. data/public/api/ro/posts/almost-died-in-an-ice-cave/index.json +265 -0
  50. data/public/api/ro/posts/facebook-and-global-extremism/index.json +90 -0
  51. data/public/api/ro/posts/index-1.json +527 -0
  52. data/public/api/ro/posts/index.json +517 -0
  53. data/public/api/ro/posts/lemmings-considered-harmful/index.json +49 -0
  54. data/public/api/ro/posts/lost-in-the-desert/index.json +49 -0
  55. data/public/api/ro/posts/mission/index.json +49 -0
  56. data/public/api/ro/posts/return-your-laptop/index.json +61 -0
  57. data/public/ro/nerd/fastest-possible-embeddings/assets/giraffe.jpeg +0 -0
  58. data/public/ro/nerd/fastest-possible-embeddings/assets/let-me-in.jpg +0 -0
  59. data/public/ro/nerd/fastest-possible-embeddings/assets/src/fastembed.js +70 -0
  60. data/public/ro/nerd/fastest-possible-embeddings/assets/src/fastembed.rs +68 -0
  61. data/public/ro/nerd/fastest-possible-embeddings/assets/terminal.jpg +0 -0
  62. data/public/ro/nerd/fastest-possible-embeddings/attributes.yml +7 -0
  63. data/public/ro/nerd/fastest-possible-embeddings/body.md +266 -0
  64. data/public/ro/nerd/ima/assets/og.jpeg +0 -0
  65. data/public/ro/nerd/ima/attributes.yml +8 -0
  66. data/public/ro/nerd/ima/body.md +22 -0
  67. data/public/ro/nerd/index/assets/giraffe.jpeg +0 -0
  68. data/public/ro/nerd/index/assets/let-me-in.jpg +0 -0
  69. data/public/ro/nerd/index/assets/terminal.jpg +0 -0
  70. data/public/ro/nerd/index/attributes.yml +7 -0
  71. data/public/ro/nerd/index/body.md +130 -0
  72. data/public/ro/pages/about/assets/og.jpeg +0 -0
  73. data/public/ro/pages/about/assets/speak-english-pulp-fiction.gif +0 -0
  74. data/public/ro/pages/about/body.md +40 -0
  75. data/public/ro/pages/contact/assets/giraffe.jpeg +0 -0
  76. data/public/ro/pages/contact/attributes.yml +7 -0
  77. data/public/ro/pages/contact/body.md +9 -0
  78. data/public/ro/pages/cv/assets/ara.jpg +0 -0
  79. data/public/ro/pages/cv/attributes.yml +6 -0
  80. data/public/ro/pages/cv/body.md +122 -0
  81. data/public/ro/pages/disco/assets/disco.jpg +0 -0
  82. data/public/ro/pages/disco/assets/disco.png +0 -0
  83. data/public/ro/pages/disco/assets/speak-english-pulp-fiction.gif +0 -0
  84. data/public/ro/pages/disco/assets/src/environment.md +2354 -0
  85. data/public/ro/pages/disco/assets/src/fortune-500.md +2518 -0
  86. data/public/ro/pages/disco/assets/src/greed.md +2703 -0
  87. data/public/ro/pages/disco/assets/src/up-at-night.md +2337 -0
  88. data/public/ro/pages/disco/attributes.yml +9 -0
  89. data/public/ro/pages/disco/body.md +99 -0
  90. data/public/ro/pages/disco/samples/environment.md +2354 -0
  91. data/public/ro/pages/disco/samples/fortune-500.md +2518 -0
  92. data/public/ro/pages/disco/samples/greed.md +2703 -0
  93. data/public/ro/pages/disco/samples/up-at-night.md +2337 -0
  94. data/public/ro/pages/index/attributes.yml +1 -0
  95. data/public/ro/pages/index/body.md +15 -0
  96. data/public/ro/pages/jess/assets/og.jpg +0 -0
  97. data/public/ro/pages/jess/assets/speak-english-pulp-fiction.gif +0 -0
  98. data/public/ro/pages/jess/attributes.yml +7 -0
  99. data/public/ro/pages/jess/body.md +3 -0
  100. data/public/ro/pages/now/assets/speak-english-pulp-fiction.gif +0 -0
  101. data/public/ro/pages/now/attributes.yml +1 -0
  102. data/public/ro/pages/now/body.md +24 -0
  103. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image1.png +0 -0
  104. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image10.png +0 -0
  105. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image11.png +0 -0
  106. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image12.png +0 -0
  107. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image13.png +0 -0
  108. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image14.png +0 -0
  109. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image15.png +0 -0
  110. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image2.png +0 -0
  111. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image3.png +0 -0
  112. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image4.png +0 -0
  113. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image5.png +0 -0
  114. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image6.png +0 -0
  115. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image7.png +0 -0
  116. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image8.png +0 -0
  117. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image9.png +0 -0
  118. data/public/ro/posts/almost-died-in-an-ice-cave/assets/josh-pointing.jpg +0 -0
  119. data/public/ro/posts/almost-died-in-an-ice-cave/assets/levi-rawr.png +0 -0
  120. data/public/ro/posts/almost-died-in-an-ice-cave/assets/og.jpg +0 -0
  121. data/public/ro/posts/almost-died-in-an-ice-cave/assets/purple-heart.jpg +0 -0
  122. data/public/ro/posts/almost-died-in-an-ice-cave/attributes.yml +6 -0
  123. data/public/ro/posts/almost-died-in-an-ice-cave/body.md +419 -0
  124. data/public/ro/posts/facebook-and-global-extremism/assets/background.html +125 -0
  125. data/public/ro/posts/facebook-and-global-extremism/assets/background.md +95 -0
  126. data/public/ro/posts/facebook-and-global-extremism/assets/og.jpg +0 -0
  127. data/public/ro/posts/facebook-and-global-extremism/assets/prompt.txt +122 -0
  128. data/public/ro/posts/facebook-and-global-extremism/assets/results.md +183 -0
  129. data/public/ro/posts/facebook-and-global-extremism/assets/survey.txt +190 -0
  130. data/public/ro/posts/facebook-and-global-extremism/attributes.yml +7 -0
  131. data/public/ro/posts/facebook-and-global-extremism/body.md +393 -0
  132. data/public/ro/posts/lemmings-considered-harmful/assets/lemming.jpeg +0 -0
  133. data/public/ro/posts/lemmings-considered-harmful/attributes.yml +6 -0
  134. data/public/ro/posts/lemmings-considered-harmful/body.md +43 -0
  135. data/public/ro/posts/lost-in-the-desert/assets/og.jpg +0 -0
  136. data/public/ro/posts/lost-in-the-desert/attributes.yml +6 -0
  137. data/public/ro/posts/lost-in-the-desert/body.md +7 -0
  138. data/public/ro/posts/mission/assets/og.jpg +0 -0
  139. data/public/ro/posts/mission/attributes.yml +6 -0
  140. data/public/ro/posts/mission/body.md +4 -0
  141. data/public/ro/posts/return-your-laptop/assets/og.jpg +0 -0
  142. data/public/ro/posts/return-your-laptop/assets/return-your-laptop.png +0 -0
  143. data/public/ro/posts/return-your-laptop/attributes.yml +6 -0
  144. data/public/ro/posts/return-your-laptop/body.md +58 -0
  145. data/ro.gemspec +217 -28
  146. data/scripts/speedtest.rb +324 -0
  147. data/tmp/gem-details.oe +0 -0
  148. metadata +214 -74
  149. data/TODO.md +0 -50
  150. data/lib/ro/blankslate.rb +0 -7
  151. data/lib/ro/cache.rb +0 -26
  152. data/lib/ro/git.rb +0 -374
  153. data/lib/ro/initializers/env.rb +0 -5
  154. data/lib/ro/initializers/tilt.rb +0 -104
  155. data/lib/ro/lock.rb +0 -53
  156. data/lib/ro/node/list.rb +0 -142
  157. data/notes/ara.txt +0 -215
data/lib/ro/pagination.rb CHANGED
@@ -8,9 +8,9 @@ module Ro
8
8
 
9
9
  page = [page.abs, 1].max
10
10
  per = [per.abs, 1].max
11
-
11
+ d
12
12
  offset = (page - 1) * per
13
- length = per
13
+ length = per
14
14
 
15
15
  slice = dup.slice(offset, length)
16
16
  slice.page = page
@@ -23,12 +23,13 @@ module Ro
23
23
  @page = page
24
24
  end
25
25
 
26
- def page(*args)
26
+ def page(*_args)
27
+ paginate unless @page
27
28
  @page
28
29
  end
29
30
 
30
31
  def current_page
31
- @page
32
+ page
32
33
  end
33
34
 
34
35
  def per=(per)
@@ -36,6 +37,7 @@ module Ro
36
37
  end
37
38
 
38
39
  def per
40
+ paginate unless @per
39
41
  @per
40
42
  end
41
43
 
@@ -44,6 +46,7 @@ module Ro
44
46
  end
45
47
 
46
48
  def num_pages
49
+ paginate unless @num_pages
47
50
  @num_pages
48
51
  end
49
52
 
data/lib/ro/path.rb ADDED
@@ -0,0 +1,229 @@
1
+ require 'pathname'
2
+
3
+ module Ro
4
+ class Path < ::String
5
+ include Klass
6
+
7
+ class << Path
8
+ def clean(arg, *args)
9
+ Pathname.new([arg, *args].join('/')).cleanpath.to_s
10
+ end
11
+
12
+ def expand(arg, *args)
13
+ new(Pathname.new(clean(arg, *args).expand_path))
14
+ end
15
+
16
+ def absolute(...)
17
+ new(...).absolute
18
+ end
19
+
20
+ def absolute?(arg, *args)
21
+ Path.for(arg, *args).absolute?
22
+ end
23
+
24
+ def relative(...)
25
+ new(...).relative
26
+ end
27
+
28
+ def relative?(arg, *args)
29
+ Path.for(arg, *args).relative?
30
+ end
31
+ end
32
+
33
+ def initialize(arg, *args)
34
+ super Path.clean(arg, *args)
35
+ end
36
+
37
+ def pn
38
+ Pathname.new(self)
39
+ end
40
+
41
+ def klass
42
+ self.class
43
+ end
44
+
45
+ {
46
+ 'exist?' => 'exist?',
47
+ 'file?' => 'file?',
48
+ 'directory?' => 'directory?',
49
+ 'absolute?' => 'absolute?',
50
+ 'relative?' => 'relative?',
51
+ 'expand' => 'expand_path',
52
+ 'clean' => 'cleanpath'
53
+ }.each do |path_method, pathname_method|
54
+ class_eval <<-____, __FILE__, __LINE__ + 1
55
+
56
+ def #{ path_method }(...)
57
+ result = pn.#{ pathname_method }(...)
58
+
59
+ result.is_a?(Pathname) ? klass.for(result) : result
60
+ end
61
+
62
+ ____
63
+ end
64
+
65
+ def absolute
66
+ Path.new('/' + self)
67
+ end
68
+
69
+ def relative
70
+ Path.new(absolute.gsub(%r{^/+}, ''))
71
+ end
72
+
73
+ def parts
74
+ parts = scan(%r`[^/]+`)
75
+
76
+ if absolute?
77
+ %w[ / ] + parts
78
+ else
79
+ parts
80
+ end
81
+ end
82
+
83
+ def key
84
+ parts
85
+ end
86
+
87
+ def relative_to(other)
88
+ a = Pathname.new(self).expand_path
89
+ b = Pathname.new(other).expand_path
90
+ Path.for(a.relative_path_from(b))
91
+ end
92
+
93
+ def relative_from(...)
94
+ relative_to(...)
95
+ end
96
+
97
+ def relative_to!(other)
98
+ a = Pathname.new(self).realpath
99
+ b = Pathname.new(other).realpath
100
+
101
+ Path.for(a.relative_path_from(b))
102
+ end
103
+
104
+ def glob(arg = '**/**', *args, **kws, &block)
105
+ glob = Path.for(self, arg, *args, **kws)
106
+
107
+ accum = []
108
+
109
+ Dir.glob(glob) do |entry|
110
+ path = Path.new(entry)
111
+ block ? block.call(path) : accum.push(path)
112
+ end
113
+
114
+ accum
115
+ end
116
+ alias ls glob
117
+
118
+ def files(arg = '**/**', *args, **kws, &block)
119
+ glob = Path.for(self, arg, *args, **kws)
120
+
121
+ accum = []
122
+
123
+ Dir.glob(glob) do |entry|
124
+ next unless test(?f, entry)
125
+ path = Path.new(entry)
126
+ block ? block.call(path) : accum.push(path)
127
+ end
128
+
129
+ accum
130
+ end
131
+
132
+ def select(&block)
133
+ glob.select(&block)
134
+ end
135
+
136
+ def detect(&block)
137
+ glob.detect(&block)
138
+ end
139
+
140
+ def parent
141
+ Path.for(File.dirname(self))
142
+ end
143
+ alias dirname parent
144
+
145
+ def basename
146
+ Path.for(File.basename(self))
147
+ end
148
+ alias name basename
149
+
150
+ def base
151
+ base, ext = basename.split('.', 2)
152
+ base
153
+ end
154
+
155
+ def extension
156
+ base, ext = basename.split('.', 2)
157
+ ext
158
+ end
159
+ alias ext extension
160
+
161
+ def join(arg, *args)
162
+ Path.for(self, Path.clean(arg, *args))
163
+ end
164
+
165
+ def binwrite(data)
166
+ FileUtils.mkdir_p(dirname)
167
+ IO.binwrite(self, data)
168
+ end
169
+
170
+ def sibling?(other)
171
+ expand.dirname == Path.for(other).expand.dirname
172
+ end
173
+
174
+ def child?(other)
175
+ parent = expand
176
+ child = Path.for(other).expand
177
+ (parent.size < child.size && child.start_with?(parent))
178
+ end
179
+
180
+ def parent?(other)
181
+ child = expand
182
+ parent = Path.for(other).expand
183
+ (parent.size < child.size && child.start_with?(parent))
184
+ end
185
+
186
+ def file?
187
+ test(?f, self)
188
+ end
189
+
190
+ def directory?
191
+ test(?d, self)
192
+ end
193
+
194
+ def subdirectories(&block)
195
+ accum = []
196
+
197
+ glob('*') do |entry|
198
+ next unless entry.directory?
199
+ block ? block.call(entry) : accum.push(entry)
200
+ end
201
+
202
+ block ? self : accum
203
+ end
204
+ alias subdirs subdirectories
205
+
206
+ def subdirectory_for(subdirectory)
207
+ join(Path.relative(subdirectory))
208
+ end
209
+ alias subdir_for subdirectory_for
210
+
211
+ def subdirectory?(subdirectory)
212
+ subdirectory = join(Path.relative(subdirectory))
213
+ subdirectory.exist?
214
+ end
215
+ alias subdir? subdirectory?
216
+
217
+ def sort_key
218
+ [dirname.to_s, basename.to_s]
219
+ end
220
+
221
+ def <=>(other)
222
+ sort_key <=> other.sort_key
223
+ end
224
+
225
+ def stat
226
+ File.stat(self)
227
+ end
228
+ end
229
+ end
data/lib/ro/root.rb CHANGED
@@ -1,52 +1,73 @@
1
1
  module Ro
2
- class Root < ::String
3
- def initialize(root)
4
- super(Ro.realpath(root.to_s))
5
- ensure
6
- raise ArgumentError.new("root=#{ root.inspect }") if root.nil?
7
- raise ArgumentError.new("root=#{ root.inspect }") unless test(?d, self)
8
- end
9
-
10
- def root
2
+ class Root < Path
3
+ def identifier
11
4
  self
12
5
  end
13
6
 
14
- def nodes
15
- Node::List.new(root) do |list|
16
- node_directories do |path|
17
- list.load(path)
18
- end
7
+ def collections(&block)
8
+ accum = Collection::List.for(self)
9
+
10
+ subdirectories do |subdirectory|
11
+ collection = collection_for(subdirectory)
12
+ block ? block.call(collection) : accum.push(collection)
19
13
  end
20
- end
21
14
 
22
- def directories(&block)
23
- Dir.glob(File.join(root, '*/'), &block)
15
+ block ? self : accum
24
16
  end
25
17
 
26
- def node_directories(&block)
27
- Dir.glob(File.join(root, '*/*/'), &block)
18
+ def collection_for(subdirectory)
19
+ Collection.new(subdirectory)
28
20
  end
29
21
 
30
- def git
31
- @git ||= Git.new(self)
22
+ def paths_for(name)
23
+ [
24
+ subdirectory_for(name),
25
+ subdirectory_for(Slug.for(name, :join => '-')),
26
+ subdirectory_for(Slug.for(name, :join => '_')),
27
+ ]
32
28
  end
33
29
 
34
- def patch(*args, &block)
35
- git.patch(*args, &block)
30
+ def get(name)
31
+ name = name.to_s
32
+
33
+ if name.index('/')
34
+ collection_name, node_name = name.split('/', 2)
35
+ collection = get(collection_name)
36
+
37
+ if collection
38
+ node = collection.get(node_name)
39
+ return node
40
+ else
41
+ return nil
42
+ end
43
+ end
44
+
45
+ paths_for(name).each do |path|
46
+ next unless path.directory?
47
+ return collection_for(path)
48
+ end
49
+
50
+ nil
36
51
  end
37
52
 
38
- def dotdir
39
- File.join(self, '.ro')
53
+ def [](name)
54
+ get(name)
40
55
  end
41
56
 
42
- def lockpath
43
- File.join(dotdir, 'lock')
57
+ def nodes(&block)
58
+ accum = []
59
+
60
+ collections.each do |collection|
61
+ collection.nodes do |node|
62
+ block ? block.call(node) : accum.push(node)
63
+ end
64
+ end
65
+
66
+ block ? self : accum
44
67
  end
45
68
 
46
- def lock(&block)
47
- FileUtils.mkdir_p(File.dirname(lockpath))
48
- @lock ||= Lock.new(lockpath)
49
- block ? @lock.lock(&block) : @lock
69
+ def method_missing(name, *args, **kws, &block)
70
+ get(name) || super
50
71
  end
51
72
  end
52
73
  end
@@ -0,0 +1,221 @@
1
+ module Ro
2
+ class Script::Builder
3
+ class << self
4
+ def run!(...)
5
+ new(...).run!
6
+ end
7
+ end
8
+
9
+ def initialize(script:)
10
+ @script = script
11
+ end
12
+
13
+ def run!
14
+ @url = Ro.config.url
15
+ @root = Ro.config.root
16
+ @directory = Ro.config.build
17
+ @page_size = Ro.config.page_size
18
+ @collections = Ro.root.collections.sort_by(&:name)
19
+
20
+ @build = {}
21
+ @nodes = []
22
+
23
+ @started_at = Time.now.to_f
24
+ @finished_at = nil
25
+ @elapsed = nil
26
+
27
+ # for all node collections...
28
+ #
29
+ @collections.each do |collection|
30
+ type = collection.type
31
+ name = collection.name
32
+ nodes = nodes_for(collection)
33
+
34
+ count = nodes.size
35
+ last = count - 1
36
+ page_count = (count / @page_size.to_f).ceil
37
+ paginator = paginator_for(page_count, @page_size)
38
+
39
+ nodes.each_with_index do |node, index|
40
+ @nodes << node
41
+ id = node.id
42
+
43
+ # enhance with links to next/prev
44
+ #
45
+ rel = rel_for(nodes, index)
46
+ node.attributes[:_meta][:rel] = rel
47
+
48
+ # node data
49
+ #
50
+ data = data_for(node)
51
+ _meta = meta_for(type:, id:)
52
+ path = "#{type}/#{id}/index.json"
53
+ @build[path] = { data:, _meta: }
54
+
55
+ # paginate data
56
+ #
57
+ paginator[:data].push(node)
58
+ generate_page = ((paginator[:data].size == paginator[:size]) || (index == last))
59
+ next unless generate_page
60
+
61
+ page = page_for(paginator)
62
+ data = data_for(paginator[:data])
63
+ _meta = meta_for(type:, page:)
64
+ pageno = page[:index] + 1
65
+ path = "#{type}/index-#{pageno}.json" # eg. posts/page-$pageno.json
66
+ @build[path] = { data:, _meta: }
67
+
68
+ paginator[:data].clear
69
+ paginator[:index] += 1
70
+ end
71
+
72
+ data = data_for(nodes)
73
+ _meta = meta_for(type:)
74
+ path = "#{type}/index.json" # eg. posts/index.json
75
+ @build[path] = { data:, _meta: }
76
+ end
77
+
78
+ # annnnd for all nodes...
79
+ #
80
+ count = @nodes.size
81
+ last = count - 1
82
+ page_count = (count / @page_size.to_f).ceil
83
+ paginator = paginator_for(page_count, @page_size)
84
+
85
+ @nodes.each_with_index do |node, index|
86
+ # enhance with links to next/prev
87
+ #
88
+ rel = rel_for(@nodes, index)
89
+ node.attributes[:_meta][:rel] = rel
90
+
91
+ paginator[:data].push(node)
92
+ should_generate_page = ((paginator[:data].size == paginator[:size]) || (index == last))
93
+ next unless should_generate_page
94
+
95
+ types = paginator[:data].map(&:type).sort.uniq
96
+ page = page_for(paginator)
97
+ data = data_for(paginator[:data])
98
+ _meta = meta_for(types:, page:)
99
+
100
+ pageno = page[:index] + 1
101
+ path = "index-#{pageno}.json" # eg. page-$pageno.json
102
+ @build[path] = { data:, _meta: }
103
+
104
+ paginator[:data].clear
105
+ paginator[:index] += 1
106
+ end
107
+
108
+ # index.json
109
+ #
110
+ types = @collections.map(&:type).sort
111
+ data = data_for(@nodes)
112
+ _meta = meta_for(types:)
113
+ path = 'index.json'
114
+ @build[path] = { data:, _meta: }
115
+
116
+ # now output the build
117
+ #
118
+ @script.say("ro.build: #{@root} -> #{@directory}", color: :magenta)
119
+
120
+ FileUtils.rm_rf(@directory)
121
+ FileUtils.mkdir_p(File.dirname(@directory))
122
+ #FileUtils.cp_r(@root, @directory)
123
+
124
+ Ro::Path.for(@directory).glob('**/**') do |entry|
125
+ next unless test('f', entry)
126
+
127
+ @script.say("ro.build: #{entry}", color: :blue)
128
+ end
129
+
130
+ @build.each do |subpath, data|
131
+ path = Ro::Path.for(@directory, subpath)
132
+ @script.say("ro.build: #{path}", color: :yellow)
133
+ Ro.error! "#{path} would be clobbered" if path.exist?
134
+
135
+ if data.is_a?(String)
136
+ path.binwrite(data)
137
+ else
138
+ path.binwrite(JSON.pretty_generate(data))
139
+ end
140
+ end
141
+
142
+ # show stats
143
+ #
144
+ @finished_at = Time.now.to_f
145
+ @elapsed = (@finished_at - @started_at).round(2)
146
+
147
+ @script.say("ro.build: #{@root} -> #{@directory} in #{@elapsed}s", color: :green)
148
+ end
149
+
150
+ def global_meta
151
+ { url: @url }
152
+ end
153
+
154
+ def meta_for(meta)
155
+ global_meta.merge(meta)
156
+ end
157
+
158
+ def rel_for(list, index)
159
+ { curr: list[index].identifier, prev: nil, next: nil }.tap do |rel|
160
+ rel[:prev] = list[index - 1].identifier if (index - 1) >= 0
161
+ rel[:next] = list[index + 1].identifier if (index + 1) <= (list.size - 1)
162
+ end
163
+ end
164
+
165
+ def paginator_for(count, size)
166
+ {
167
+ count:,
168
+ size:,
169
+ first: 0,
170
+ last: count - 1,
171
+ index: 0,
172
+ data: []
173
+ }
174
+ end
175
+
176
+ def page_for(paginator)
177
+ paginator.except(:data).merge(
178
+ {
179
+ curr: paginator[:index],
180
+ prev: ((paginator[:index] - 1) >= paginator[:first] ? (paginator[:index] - 1) : nil),
181
+ next: ((paginator[:index] + 1) <= paginator[:last] ? (paginator[:index] + 1) : nil)
182
+ }
183
+ )
184
+ end
185
+
186
+ def data_for(value)
187
+ {}.tap do |hash|
188
+ case value
189
+ when Array
190
+ value.each { |node| hash.update(data_for(node)) }
191
+ when Ro::Node
192
+ hash[value.identifier] = value.to_hash
193
+ else
194
+ raise ArgumentError, "#{value.class}(#{value.inspect})"
195
+ end
196
+ end
197
+ end
198
+
199
+ def nodes_for(list)
200
+ filtered_nodes_for(sorted_nodes_for(list))
201
+ end
202
+
203
+ def sorted_nodes_for(list)
204
+ list.to_a.sort
205
+ end
206
+
207
+ def filtered_nodes_for(list)
208
+ list.select { |node| select_node?(node) }
209
+ end
210
+
211
+ def select_node?(node)
212
+ now = Time.now.utc
213
+
214
+ hidden = Ro.cast(:bool, node.attributes.fetch(:hidden) { false })
215
+ published = Ro.cast(:bool, node.attributes.fetch(:published) { true })
216
+ published_at = Ro.cast(:time, node.attributes.fetch(:published_at) { now.iso8601 })
217
+
218
+ ((not hidden) && (published) && (published_at <= now))
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,47 @@
1
+ module Ro
2
+ class Script::Console
3
+ class << self
4
+ def run!(...)
5
+ new(...).run!
6
+ end
7
+ end
8
+
9
+ def initialize(script:)
10
+ @script = script
11
+ end
12
+
13
+ def run!
14
+ $A_GIANT_FUCKING_HACK_FOR_IBB = ARGV.clear
15
+
16
+ require 'irb'
17
+
18
+ $GIANT_FUCKING_HACK = IRB.method(:load_modules)
19
+
20
+ Kernel.module_eval do
21
+ def ro
22
+ Ro.root
23
+ end
24
+ end
25
+
26
+ def IRB.load_modules
27
+ $GIANT_FUCKING_HACK.call
28
+
29
+ prompt = "ro[./#{Ro.root.relative_to(Dir.pwd)}]"
30
+
31
+ IRB.conf[:PROMPT][:RO] = {
32
+ PROMPT_I: "#{prompt}:%03n:%i> ",
33
+ PROMPT_N: "#{prompt}:%03n:%i> ",
34
+ PROMPT_S: "#{prompt}:%03n:%i%l ",
35
+ PROMPT_C: "#{prompt}:%03n:%i* ",
36
+ RETURN: "=> %s\n"
37
+
38
+ }
39
+
40
+ IRB.conf[:PROMPT_MODE] = :RO
41
+ IRB.conf[:AUTO_INDENT] = true
42
+ end
43
+
44
+ ::IRB.start
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,76 @@
1
+ module Ro
2
+ class Script::Server
3
+ class << self
4
+ def run!(...)
5
+ new(...).run!
6
+ end
7
+ end
8
+
9
+ def initialize(script:)
10
+ @script = script
11
+
12
+ @port = @script.opts.fetch(:port)
13
+ end
14
+
15
+ def run!
16
+ Ro.config.set(:url, server_url)
17
+
18
+ #build!
19
+
20
+ threads = [watcher!, server!]
21
+
22
+ trap('INT') do
23
+ threads.each do |thread|
24
+ thread.kill
25
+ rescue StandardError
26
+ nil
27
+ end
28
+ exit
29
+ end
30
+
31
+ sleep
32
+ end
33
+
34
+ def server_url
35
+ build = Ro.config.build
36
+ document_root = build.dirname
37
+ path_info = build.relative_to(document_root)
38
+ Ro.normalize_url("http://localhost:#{@port}/#{path_info}")
39
+ end
40
+
41
+ def build!
42
+ system "RO_URL=#{Ro.config.url} RO_ROOT=#{Ro.config.root} ro build"
43
+ end
44
+
45
+ def watcher!
46
+ Thread.new do
47
+ Thread.current.abort_on_exception = true
48
+ system "RO_URL=#{Ro.config.url} RO_ROOT=#{Ro.config.root} ro build --watch"
49
+ end
50
+ end
51
+
52
+ def server!
53
+ require 'webrick'
54
+
55
+ build = Ro.config.build
56
+ document_root = build.dirname
57
+
58
+ index_url = File.join(server_url, 'index.json')
59
+
60
+ @script.say("ro.server: @ #{index_url}", color: :magenta)
61
+
62
+ Thread.new do
63
+ Thread.current.abort_on_exception = true
64
+
65
+ server = WEBrick::HTTPServer.new(
66
+ DocumentRoot: document_root,
67
+ Port: @port
68
+ )
69
+
70
+ ::Kernel.at_exit { server.shutdown }
71
+
72
+ server.start
73
+ end
74
+ end
75
+ end
76
+ end