ro 4.2.0 → 5.0.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 (252) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +57 -10
  3. data/LICENSE +1 -1
  4. data/MIGRATION.md +320 -0
  5. data/README.md +286 -111
  6. data/Rakefile +2 -2
  7. data/a.yml +60 -0
  8. data/bin/ro +10 -0
  9. data/lib/ro/_lib.rb +18 -6
  10. data/lib/ro/asset.rb +67 -16
  11. data/lib/ro/collection.rb +91 -10
  12. data/lib/ro/config.rb +4 -0
  13. data/lib/ro/error.rb +5 -2
  14. data/lib/ro/html.rb +23 -0
  15. data/lib/ro/html_safe.rb +143 -0
  16. data/lib/ro/methods.rb +95 -38
  17. data/lib/ro/migrator.rb +285 -0
  18. data/lib/ro/node.rb +128 -45
  19. data/lib/ro/path.rb +4 -0
  20. data/lib/ro/root.rb +75 -1
  21. data/lib/ro/script/migrate.rb +204 -0
  22. data/lib/ro/script/server.rb +1 -1
  23. data/lib/ro/template.rb +62 -22
  24. data/lib/ro/text.rb +120 -0
  25. data/lib/ro.rb +5 -0
  26. data/public/api/ro/index-1.json +997 -79
  27. data/public/api/ro/index.json +997 -79
  28. data/public/api/ro/nerd/fastest-possible-embeddings/index.json +90 -0
  29. data/public/api/ro/nerd/ima/index.json +49 -0
  30. data/public/api/ro/nerd/index/index.json +74 -0
  31. data/public/api/ro/nerd/index-1.json +204 -0
  32. data/public/api/ro/nerd/index.json +194 -0
  33. data/public/api/ro/pages/about/index.json +60 -0
  34. data/public/api/ro/pages/contact/index.json +50 -0
  35. data/public/api/ro/pages/cv/index.json +49 -0
  36. data/public/api/ro/pages/disco/index.json +117 -0
  37. data/public/api/ro/pages/index/index.json +30 -0
  38. data/public/api/ro/pages/index-1.json +366 -0
  39. data/public/api/ro/pages/index.json +356 -0
  40. data/public/api/ro/pages/jess/index.json +62 -0
  41. data/public/api/ro/pages/now/index.json +43 -0
  42. data/public/api/ro/posts/almost-died-in-an-ice-cave/index.json +265 -0
  43. data/public/api/ro/posts/facebook-and-global-extremism/index.json +90 -0
  44. data/public/api/ro/posts/index-1.json +461 -79
  45. data/public/api/ro/posts/index.json +461 -79
  46. data/public/api/ro/posts/lemmings-considered-harmful/index.json +49 -0
  47. data/public/api/ro/posts/lost-in-the-desert/index.json +49 -0
  48. data/public/api/ro/posts/mission/index.json +49 -0
  49. data/public/api/ro/posts/return-your-laptop/index.json +61 -0
  50. data/public/ro/nerd/fastest-possible-embeddings/assets/giraffe.jpeg +0 -0
  51. data/public/ro/nerd/fastest-possible-embeddings/assets/let-me-in.jpg +0 -0
  52. data/public/ro/nerd/fastest-possible-embeddings/assets/src/fastembed.js +70 -0
  53. data/public/ro/nerd/fastest-possible-embeddings/assets/src/fastembed.rs +68 -0
  54. data/public/ro/nerd/fastest-possible-embeddings/assets/terminal.jpg +0 -0
  55. data/public/ro/nerd/fastest-possible-embeddings/body.md +266 -0
  56. data/public/ro/nerd/fastest-possible-embeddings.yml +7 -0
  57. data/public/ro/nerd/ima/assets/og.jpeg +0 -0
  58. data/public/ro/nerd/ima/body.md +22 -0
  59. data/public/ro/nerd/ima.yml +8 -0
  60. data/public/ro/nerd/index/assets/giraffe.jpeg +0 -0
  61. data/public/ro/nerd/index/assets/let-me-in.jpg +0 -0
  62. data/public/ro/nerd/index/assets/terminal.jpg +0 -0
  63. data/public/ro/nerd/index/body.md +130 -0
  64. data/public/ro/nerd/index.yml +7 -0
  65. data/public/ro/pages/about/assets/og.jpeg +0 -0
  66. data/public/ro/pages/about/assets/speak-english-pulp-fiction.gif +0 -0
  67. data/public/ro/pages/about/body.md +40 -0
  68. data/public/ro/pages/contact/assets/giraffe.jpeg +0 -0
  69. data/public/ro/pages/contact/body.md +9 -0
  70. data/public/ro/pages/contact.yml +7 -0
  71. data/public/ro/pages/cv/assets/ara.jpg +0 -0
  72. data/public/ro/pages/cv/body.md +122 -0
  73. data/public/ro/pages/cv.yml +6 -0
  74. data/public/ro/pages/disco/assets/disco.jpg +0 -0
  75. data/public/ro/pages/disco/assets/disco.png +0 -0
  76. data/public/ro/pages/disco/assets/speak-english-pulp-fiction.gif +0 -0
  77. data/public/ro/pages/disco/assets/src/environment.md +2354 -0
  78. data/public/ro/pages/disco/assets/src/fortune-500.md +2518 -0
  79. data/public/ro/pages/disco/assets/src/greed.md +2703 -0
  80. data/public/ro/pages/disco/assets/src/up-at-night.md +2337 -0
  81. data/public/ro/pages/disco/body.md +99 -0
  82. data/public/ro/pages/disco/samples/environment.md +2354 -0
  83. data/public/ro/pages/disco/samples/fortune-500.md +2518 -0
  84. data/public/ro/pages/disco/samples/greed.md +2703 -0
  85. data/public/ro/pages/disco/samples/up-at-night.md +2337 -0
  86. data/public/ro/pages/disco.yml +9 -0
  87. data/public/ro/pages/index/body.md +15 -0
  88. data/public/ro/pages/index.yml +1 -0
  89. data/public/ro/pages/jess/assets/og.jpg +0 -0
  90. data/public/ro/pages/jess/assets/speak-english-pulp-fiction.gif +0 -0
  91. data/public/ro/pages/jess/body.md +3 -0
  92. data/public/ro/pages/jess.yml +7 -0
  93. data/public/ro/pages/now/assets/speak-english-pulp-fiction.gif +0 -0
  94. data/public/ro/pages/now/body.md +24 -0
  95. data/public/ro/pages/now.yml +1 -0
  96. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image1.png +0 -0
  97. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image10.png +0 -0
  98. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image11.png +0 -0
  99. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image12.png +0 -0
  100. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image13.png +0 -0
  101. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image14.png +0 -0
  102. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image15.png +0 -0
  103. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image2.png +0 -0
  104. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image3.png +0 -0
  105. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image4.png +0 -0
  106. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image5.png +0 -0
  107. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image6.png +0 -0
  108. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image7.png +0 -0
  109. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image8.png +0 -0
  110. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image9.png +0 -0
  111. data/public/ro/posts/almost-died-in-an-ice-cave/assets/josh-pointing.jpg +0 -0
  112. data/public/ro/posts/almost-died-in-an-ice-cave/assets/levi-rawr.png +0 -0
  113. data/public/ro/posts/almost-died-in-an-ice-cave/assets/og.jpg +0 -0
  114. data/public/ro/posts/almost-died-in-an-ice-cave/assets/purple-heart.jpg +0 -0
  115. data/public/ro/posts/almost-died-in-an-ice-cave/body.md +419 -0
  116. data/public/ro/posts/almost-died-in-an-ice-cave.yml +6 -0
  117. data/public/ro/posts/facebook-and-global-extremism/assets/background.html +125 -0
  118. data/public/ro/posts/facebook-and-global-extremism/assets/background.md +95 -0
  119. data/public/ro/posts/facebook-and-global-extremism/assets/og.jpg +0 -0
  120. data/public/ro/posts/facebook-and-global-extremism/assets/prompt.txt +122 -0
  121. data/public/ro/posts/facebook-and-global-extremism/assets/results.md +183 -0
  122. data/public/ro/posts/facebook-and-global-extremism/assets/survey.txt +190 -0
  123. data/public/ro/posts/facebook-and-global-extremism/body.md +393 -0
  124. data/public/ro/posts/facebook-and-global-extremism.yml +7 -0
  125. data/public/ro/posts/lemmings-considered-harmful/assets/lemming.jpeg +0 -0
  126. data/public/ro/posts/lemmings-considered-harmful/body.md +43 -0
  127. data/public/ro/posts/lemmings-considered-harmful.yml +6 -0
  128. data/public/ro/posts/lost-in-the-desert/assets/og.jpg +0 -0
  129. data/public/ro/posts/lost-in-the-desert/body.md +7 -0
  130. data/public/ro/posts/lost-in-the-desert.yml +6 -0
  131. data/public/ro/posts/mission/assets/og.jpg +0 -0
  132. data/public/ro/posts/mission/body.md +4 -0
  133. data/public/ro/posts/mission.yml +6 -0
  134. data/public/ro/posts/return-your-laptop/assets/og.jpg +0 -0
  135. data/public/ro/posts/return-your-laptop/assets/return-your-laptop.png +0 -0
  136. data/public/ro/posts/return-your-laptop/body.md +58 -0
  137. data/public/ro/posts/return-your-laptop.yml +6 -0
  138. data/ro.gemspec +369 -49
  139. data/scripts/speedtest.rb +324 -0
  140. data/specs/001-simplify-asset-structure/IMPLEMENTATION_SUMMARY.md +212 -0
  141. data/specs/001-simplify-asset-structure/checklists/requirements.md +36 -0
  142. data/specs/001-simplify-asset-structure/contracts/collection_api.md +407 -0
  143. data/specs/001-simplify-asset-structure/contracts/migrator_api.md +461 -0
  144. data/specs/001-simplify-asset-structure/contracts/node_api.md +294 -0
  145. data/specs/001-simplify-asset-structure/data-model.md +381 -0
  146. data/specs/001-simplify-asset-structure/plan.md +90 -0
  147. data/specs/001-simplify-asset-structure/quickstart.md +575 -0
  148. data/specs/001-simplify-asset-structure/research.md +333 -0
  149. data/specs/001-simplify-asset-structure/spec.md +127 -0
  150. data/specs/001-simplify-asset-structure/tasks.md +349 -0
  151. data/test/fixtures/new_structure/mixed/test-json.json +5 -0
  152. data/test/fixtures/new_structure/mixed/test-yaml.yml +3 -0
  153. data/test/fixtures/new_structure/posts/metadata-only.yml +7 -0
  154. data/test/fixtures/new_structure/posts/nested-test/assets/subdirectory/image.png +2 -0
  155. data/test/fixtures/new_structure/posts/nested-test.yml +7 -0
  156. data/test/fixtures/new_structure/posts/sample-post/assets/body.md +5 -0
  157. data/test/fixtures/new_structure/posts/sample-post/assets/image.jpg +2 -0
  158. data/test/fixtures/new_structure/posts/sample-post.yml +7 -0
  159. data/test/fixtures/old_structure/posts/assets-only/assets/test.txt +1 -0
  160. data/test/fixtures/old_structure/posts/sample-post/assets/body.md +5 -0
  161. data/test/fixtures/old_structure/posts/sample-post/assets/image.jpg +2 -0
  162. data/test/fixtures/old_structure/posts/sample-post/attributes.yml +2 -0
  163. data/test/integration/ro_integration_test.rb +165 -0
  164. data/test/test_helper.rb +149 -0
  165. data/test/tmp/migration_test_1760746513.backup.20251018001513/migration_test_1760746513/posts/sample-post/assets/image.jpg +2 -0
  166. data/test/tmp/migration_test_1760746513.backup.20251018001513/migration_test_1760746513/posts/sample-post/attributes.yml +7 -0
  167. data/test/tmp/migration_test_1760746513.backup.20251018001513/migration_test_1760746513/posts/sample-post/body.md +5 -0
  168. data/test/tmp/migration_test_1760746513.backup.20251018001513/posts/sample-post/assets/image.jpg +2 -0
  169. data/test/tmp/migration_test_1760746513.backup.20251018001513/posts/sample-post/attributes.yml +7 -0
  170. data/test/tmp/migration_test_1760746513.backup.20251018001513/posts/sample-post/body.md +5 -0
  171. data/test/tmp/migration_test_1760746556.backup.20251018001556/migration_test_1760746556/posts/sample-post/assets/image.jpg +2 -0
  172. data/test/tmp/migration_test_1760746556.backup.20251018001556/migration_test_1760746556/posts/sample-post/attributes.yml +7 -0
  173. data/test/tmp/migration_test_1760746556.backup.20251018001556/migration_test_1760746556/posts/sample-post/body.md +5 -0
  174. data/test/tmp/migration_test_1760746556.backup.20251018001556/posts/sample-post/assets/image.jpg +2 -0
  175. data/test/tmp/migration_test_1760746556.backup.20251018001556/posts/sample-post/attributes.yml +7 -0
  176. data/test/tmp/migration_test_1760746556.backup.20251018001556/posts/sample-post/body.md +5 -0
  177. data/test/tmp/migration_test_1760755248.backup.20251018024048/migration_test_1760755248/posts/sample-post/assets/image.jpg +2 -0
  178. data/test/tmp/migration_test_1760755248.backup.20251018024048/migration_test_1760755248/posts/sample-post/attributes.yml +7 -0
  179. data/test/tmp/migration_test_1760755248.backup.20251018024048/migration_test_1760755248/posts/sample-post/body.md +5 -0
  180. data/test/tmp/migration_test_1760755248.backup.20251018024048/posts/sample-post/assets/image.jpg +2 -0
  181. data/test/tmp/migration_test_1760755248.backup.20251018024048/posts/sample-post/attributes.yml +7 -0
  182. data/test/tmp/migration_test_1760755248.backup.20251018024048/posts/sample-post/body.md +5 -0
  183. data/test/tmp/migration_test_1760758803.backup.20251018034003/migration_test_1760758803/posts/sample-post/body.md +5 -0
  184. data/test/tmp/migration_test_1760758803.backup.20251018034003/migration_test_1760758803/posts/sample-post/image.jpg +2 -0
  185. data/test/tmp/migration_test_1760758803.backup.20251018034003/migration_test_1760758803/posts/sample-post.yml +7 -0
  186. data/test/tmp/migration_test_1760758803.backup.20251018034003/posts/sample-post/body.md +5 -0
  187. data/test/tmp/migration_test_1760758803.backup.20251018034003/posts/sample-post/image.jpg +2 -0
  188. data/test/tmp/migration_test_1760758803.backup.20251018034003/posts/sample-post.yml +7 -0
  189. data/test/tmp/migration_test_1760758869.backup.20251018034109/migration_test_1760758869/posts/sample-post/assets/body.md +5 -0
  190. data/test/tmp/migration_test_1760758869.backup.20251018034109/migration_test_1760758869/posts/sample-post/assets/image.jpg +2 -0
  191. data/test/tmp/migration_test_1760758869.backup.20251018034109/migration_test_1760758869/posts/sample-post/attributes.yml +2 -0
  192. data/test/tmp/migration_test_1760758869.backup.20251018034109/posts/sample-post/assets/body.md +5 -0
  193. data/test/tmp/migration_test_1760758869.backup.20251018034109/posts/sample-post/assets/image.jpg +2 -0
  194. data/test/tmp/migration_test_1760758869.backup.20251018034109/posts/sample-post/attributes.yml +2 -0
  195. data/test/tmp/migration_test_1760758920.backup.20251018034200/migration_test_1760758920/posts/sample-post/assets/body.md +5 -0
  196. data/test/tmp/migration_test_1760758920.backup.20251018034200/migration_test_1760758920/posts/sample-post/assets/image.jpg +2 -0
  197. data/test/tmp/migration_test_1760758920.backup.20251018034200/migration_test_1760758920/posts/sample-post/attributes.yml +2 -0
  198. data/test/tmp/migration_test_1760758920.backup.20251018034200/posts/sample-post/assets/body.md +5 -0
  199. data/test/tmp/migration_test_1760758920.backup.20251018034200/posts/sample-post/assets/image.jpg +2 -0
  200. data/test/tmp/migration_test_1760758920.backup.20251018034200/posts/sample-post/attributes.yml +2 -0
  201. data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/assets-only/assets/test.txt +1 -0
  202. data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/sample-post/assets/body.md +5 -0
  203. data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/sample-post/assets/image.jpg +2 -0
  204. data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/sample-post/attributes.yml +2 -0
  205. data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/assets-only/assets/test.txt +1 -0
  206. data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/sample-post/assets/body.md +5 -0
  207. data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/sample-post/assets/image.jpg +2 -0
  208. data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/sample-post/attributes.yml +2 -0
  209. data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/assets-only/assets/test.txt +1 -0
  210. data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/sample-post/assets/body.md +5 -0
  211. data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/sample-post/assets/image.jpg +2 -0
  212. data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/sample-post/attributes.yml +2 -0
  213. data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/assets-only/assets/test.txt +1 -0
  214. data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/sample-post/assets/body.md +5 -0
  215. data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/sample-post/assets/image.jpg +2 -0
  216. data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/sample-post/attributes.yml +2 -0
  217. data/test/tmp/new_structure_test_1760746452/mixed/test-json.json +5 -0
  218. data/test/tmp/new_structure_test_1760746452/mixed/test-yaml.yml +3 -0
  219. data/test/tmp/new_structure_test_1760746452/posts/metadata-only.yml +7 -0
  220. data/test/tmp/new_structure_test_1760746452/posts/nested-test/subdirectory/image.png +2 -0
  221. data/test/tmp/new_structure_test_1760746452/posts/nested-test.yml +7 -0
  222. data/test/tmp/new_structure_test_1760746452/posts/sample-post/body.md +5 -0
  223. data/test/tmp/new_structure_test_1760746452/posts/sample-post/image.jpg +2 -0
  224. data/test/tmp/new_structure_test_1760746452/posts/sample-post.yml +7 -0
  225. data/test/unit/asset_test.rb +90 -0
  226. data/test/unit/collection_test.rb +127 -0
  227. data/test/unit/migrator_test.rb +209 -0
  228. data/test/unit/node_test.rb +138 -0
  229. data/tmp/gem-details.oe +0 -0
  230. metadata +250 -33
  231. data/public/api/ro/posts/first_post/index.json +0 -52
  232. data/public/api/ro/posts/second_post/index.json +0 -51
  233. data/public/api/ro/posts/third_post/index.json +0 -51
  234. data/public/ro/posts/first_post/assets/foo/bar/baz.jpg +0 -0
  235. data/public/ro/posts/first_post/assets/foo.jpg +0 -0
  236. data/public/ro/posts/first_post/assets/src/foo/bar.rb +0 -3
  237. data/public/ro/posts/first_post/attributes.yml +0 -2
  238. data/public/ro/posts/first_post/blurb.erb.md +0 -7
  239. data/public/ro/posts/first_post/body.md +0 -16
  240. data/public/ro/posts/first_post/testing.txt +0 -3
  241. data/public/ro/posts/second_post/assets/foo/bar/baz.jpg +0 -0
  242. data/public/ro/posts/second_post/assets/foo.jpg +0 -0
  243. data/public/ro/posts/second_post/assets/src/foo/bar.rb +0 -3
  244. data/public/ro/posts/second_post/attributes.yml +0 -2
  245. data/public/ro/posts/second_post/blurb.erb.md +0 -5
  246. data/public/ro/posts/second_post/body.md +0 -16
  247. data/public/ro/posts/third_post/assets/foo/bar/baz.jpg +0 -0
  248. data/public/ro/posts/third_post/assets/foo.jpg +0 -0
  249. data/public/ro/posts/third_post/assets/src/foo/bar.rb +0 -3
  250. data/public/ro/posts/third_post/attributes.yml +0 -2
  251. data/public/ro/posts/third_post/blurb.erb.md +0 -5
  252. data/public/ro/posts/third_post/body.md +0 -16
data/README.md CHANGED
@@ -1,178 +1,353 @@
1
- NAME
2
- ====
1
+ TL;DR:
2
+ ------
3
3
 
4
- `ro`
4
+ `ro` stands for `read only`, and it is the tiniest, best, headless cms.
5
5
 
6
- INSTALL
7
- =======
6
+ it lets you keep all your content in github, as god intended.
7
+
8
+ it is one part cms, one part database, one part api, and one part magic.
9
+
10
+ if you used a headless cms, it's like that, before they existed, except way better, and free.
11
+
12
+ with `ro`, you can separate your content from presentation, like a sane and decent human being.
13
+
14
+ > silent code whispers
15
+ > github's gentle, peaceful hush
16
+ > simplicity reigns
17
+
18
+ README
19
+ ------
20
+
21
+ `ro` is has been in professional, production use for over a decade, powering
22
+ many, many websites built by http://dojo4.com, and others.
23
+
24
+ from static sites, to rails monoliths, to simple ruby sites, to javascript
25
+ sites built on next.js, `ro` is the portable, simple, future-proof content
26
+ system you always dreamed about then started to write but found out the web is
27
+ a mess.
28
+
29
+ with `ro`, your shit will be *clean*.
30
+
31
+ ---
32
+
33
+ to grok `ro` it is best to start with an example of some real content,
34
+ for a real site.
8
35
 
9
- as a gem
10
36
 
11
37
  ```sh
12
- ~> gem install ro
38
+
39
+ drawohara@drawohara.dev:ro[main] #=> tree public/ro/posts/almost-died-in-an-ice-cave/
40
+ public/ro/posts/
41
+ ├── almost-died-in-an-ice-cave.yml # ← metadata file at collection level
42
+ └── almost-died-in-an-ice-cave/ # ← node directory
43
+ ├── assets/ # ← assets subdirectory
44
+ │ ├── image1.png
45
+ │ ├── image2.png
46
+ │ ├── image3.png
47
+ │ ├── og.jpg
48
+ │ └── purple-heart.jpg
49
+ └── body.md # ← other content files
50
+
13
51
  ```
14
52
 
53
+ in this example you can see a few things, regarding the layout of a `ro` directory:
54
+
55
+ - `ro` content often, but is not required, to live in `public`. more on this below.
15
56
 
16
- SYNOPSIS
17
- ========
57
+ - the essential layout is (v5.0+):
18
58
 
19
- keep all your content in git as god intended.
59
+ ```ruby
20
60
 
21
- even images.
61
+ @root = "ro"
62
+ @collection = "posts"
63
+ @id = "almost-died-in-an-ice-cave"
22
64
 
23
- fuck wordpress.
65
+ # metadata file
66
+ "#{ @root }/#{ @collection }/#{ @id }.yml"
24
67
 
25
- # TL;DR;
68
+ # node directory
69
+ "#{ @root }/#{ @collection }/#{ @id }/"
26
70
 
27
- `ro` is the world's tiniest, simplest, zero-config, and most bestest headless CMS.
71
+ # assets
72
+ "#{ @root }/#{ @collection }/#{ @id }/assets/"
28
73
 
29
- it keeps your content in an on disk bundle that will make sense to an 11 year
74
+ ```
30
75
 
31
- it depends on nothing but GitHub itself for the storage, management, and
32
- delivery of rich web content and assets.
76
+ if you learn best by example, you can examine the `ro` directory of my own website here -> https://github.com/ahoward/drawohara.io/tree/main/public/ro
33
77
 
34
- ## Storage
78
+ - several environment variables control how `ro` works, most notably
35
79
 
36
- `ro` keeps your structured web content in a super sane structure, that keeps
37
- assets close to its related content, allows for structured data to be kept
38
- along side markdown/html content, and which supports source code as a first
39
- class citizen.
80
+ ```ruby
40
81
 
41
- For example, given:
82
+ # this is the fs root of all ro content
83
+ #
84
+
85
+ Ro.root = ENV['RO_ROOT'] || './public/ro'
86
+
87
+ # this is the url where ro content, especially images, will be expected to
88
+ # live at when live
89
+
90
+ Ro.url = ENV['RO_URL'] || '/ro'
42
91
 
43
- ```sh
44
- ~> tree ro/data
45
-
46
- # ro/data
47
- # └── posts
48
- # ├── first-post
49
- # │   ├── attributes.yml
50
- # │   └── body.md
51
- # │   ├── blurb.erb.md
52
- # │   ├── assets
53
- # │   │   ├── foo.jpg
54
92
  ```
55
93
 
56
- `ro` will provide an interface logically consistent with:
94
+ more about this can be rtfm'd here -> https://github.com/ahoward/ro/blob/main/lib/ro.rb#L29-L55
95
+
96
+ because not having a repl sucks, `ro` has one. you should too.
97
+
98
+ ```sh
99
+
100
+ # fire up the KONSOULLLLL!
101
+
102
+ ~> ro console ./public/ro/
103
+
104
+ ```
57
105
 
58
106
  ```ruby
59
- node.attributes #=> any/all the data loaded from 'attributes.yml'
60
- node.attributes.body #=> an HTML representation of 'body.md'
61
- node.attributes.blurb #=> an HTML representation of 'blurb.md'
62
- node.attributes.assets #=> list of assets with url and path info
107
+
108
+ ro.posts.almost_died_in_an_ice_cave.attributes.keys #=>
109
+
110
+ ["og", "body", "assets", "_meta"]
111
+
112
+ ro.posts.almost_died_in_an_ice_cave.attributes.og #=>
113
+
114
+ {"image"=>{"url"=>"/ro/posts/almost-died-in-an-ice-cave/assets/og.jpg"},
115
+ "title"=>"Almost Died In An Ice Cave",
116
+ "description"=>"On April of 2024, I, along with 6 of my friends, dug for our lives to come out on the other side."}
117
+
118
+ ro.posts.almost_died_in_an_ice_cave.attributes.body.slice(0,420) #=>
119
+
120
+ "<div class=\"ro markdown\">\n <p><a href=\"#tl;dr;\">tl;dr;</a></p>\n\n<blockquote>\n <p>in april, 2024, myself, and 6 brave men attempted to cross the harding\nicefield.</p>\n\n <p>in celebration of <a href=\"https://en.wikipedia.org/wiki/Harding_Icefield#History\">yule kilcher’s 1968 crossing</a></p>\n\n <p>midway through our trip, at the point of no return, we were beseiged by a storm of storms.</p>\n\n <p>we dug shoulder to "
121
+
122
+ ro.posts.almost_died_in_an_ice_cave.assets.first #=>
123
+
124
+ "public/ro/posts/almost-died-in-an-ice-cave/assets/image1.png"
125
+
126
+ ro.posts.almost_died_in_an_ice_cave.assets.first.url #=>
127
+
128
+ "/ro/posts/almost-died-in-an-ice-cave/assets/image1.png"
129
+
130
+
63
131
  ```
64
132
 
65
- To learn more, clone this repo, `bundle install`, and fire up a console to
66
- check play with this idea:
133
+ __boom__ // **now we're cooking with gas!**
67
134
 
68
- eg: [given this node](https://github.com/ahoward/ro/tree/main/ro/data/posts/first-post)
135
+ serveral things will be obvious to the non-ai enabled observer:
69
136
 
70
- ```ruby
71
- ~> ro console
137
+ - every node has a hash of attributes
138
+ - 'file' attributes are *rendered* based on file type
139
+ - assets in rendered content are __url aware__
140
+
141
+ you can see the list of file types `ro` supports here -> https://github.com/ahoward/ro/blob/main/lib/ro/template.rb#L43-L63
142
+
143
+ if you can't build a website with just these...
144
+
145
+ 🙊 [you've come to the wrong place!](https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExd2NpbmsxMHRxNnRsNjFzNWczN2JtejE5aXc0YXA3MHV3a3pwb3hodyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/vDlw1tKNwx40BGbWcQ/giphy.gif)
146
+
147
+ ---
148
+ now, about that rendering process...
149
+
150
+ for any 'html-ish' type, links to relative assets, will be expanded such that
151
+ images will be resolved to thier final deployment destination.
152
+
153
+ eg. given __ro/posts/foo-bar/body.md__ containing the markdown
154
+
155
+ ```markdown
72
156
 
73
- ro[./ro/data]:001:0> ro.posts.first_post.title
74
- => "First Post"
157
+ [linky](./assets/report.pdf)
75
158
 
76
- ro[./ro/data]:002:0> ro.collections.posts.first_post.assets.first.url
77
- => "/ro/posts/first-post/assets/foo.jpg"
159
+ ![alty](./assets/pretty.png)
78
160
 
79
- ro[./ro/data]:003:0> ro.collections.posts.first_post.body.slice(0,42)
80
- => "<div class='ro markdown'>\n <ul>\n <li>one"
81
161
  ```
82
162
 
163
+ you will end up with html that looks like so:
83
164
 
84
- ## Management
165
+ ```html
85
166
 
86
- Managing `ro` is as simple as using the built-in GitHub Markdown editor. The
87
- file system layout, which supports relative asset urls, means the built-in
88
- editor preview renders just fine. Of course, you are free to manage content
89
- programatically as well. Either way, updates to the the content will result
90
- in an automated API build of a static API which is then deployed to GitHub
91
- Pages.
167
+ <a href='/ro/posts/foo-bar/assets/report.pdf'>linky</a>
92
168
 
93
- This is made possible by certain design decisions `ro` has made, specifically
94
- allowing assets/ to be stored and rendered relative to their parent content.
169
+ <img src='/ro/posts/foo-bar/assets/pretty.png' alt='alty'>
95
170
 
96
- Of course, you have all the power of `git` so other methods of managing the
97
- content are available, programtic, locally in vs-code, etc. You have lots of
98
- simply options, none of which require drivers or databases, and all of which
99
- provide perfect history over your valuable web content and assets.
100
171
 
101
- A word on managing assets, if you plan to have many large images, you probably
102
- want to enable GitLFS on your content repository, `ro` plays perfectly with
103
- it.
172
+ ```
104
173
 
174
+ ---
175
+ __OH MAN THAT SUX I DO NOT WANT MY IMAGES STORED IN GH!!!!__
105
176
 
106
- ## Delivery
177
+ *sit down... breathe...*
107
178
 
108
- Delivery of `ro` content, to remote clients, is via http+json. To output your
109
- content as json, you simply need to run
179
+ and enable [git-lfs](https://git-lfs.com/)
110
180
 
111
- ```sh
112
- ~> ro build
113
-
114
- ro.build: public/ro -> public/api/ro
115
- ro.build: public/api/ro/posts/first_post/index.json
116
- ro.build: public/api/ro/posts/second_post/index.json
117
- ro.build: public/api/ro/posts/third_post/index.json
118
- ro.build: public/api/ro/posts/index/0.json
119
- ro.build: public/api/ro/posts/index.json
120
- ro.build: public/api/ro/index/0.json
121
- ro.build: public/api/ro/index.json
122
- ro.build: public/api/ro/index.html
123
- ro.build: public/ro -> public/api/ro in 0.08s
181
+ it used to be fragile but, since everyone the in the world is now checking in massive jupyter notebooks it 'just works'
182
+
183
+ ---
184
+ now, recall what we said about `public` and `RO_URL`? by default, this is is `/ro`...
185
+
186
+ therefore, if we have:
187
+
188
+ `./public/ro/posts/foobar/...'
189
+
190
+ paths found under `public` are going to start with `/ro`...
124
191
 
192
+ > are you tracking yet?
193
+
194
+ also, if you do not host your `ro` directory under public (but why not? it
195
+ *is* public content after all..), then you will need to set `RO_URL` to be the
196
+ ultimate destination of your image deployment. this can be cloudinary, s3,
197
+ whatever. it just needs to be the url prefix to whack in front of relative
198
+ image urls.
199
+
200
+ note - not only html has this 'expansion' applied, relative assets in your
201
+ attributes will be expanded too, at any depth. eg. -> https://github.com/ahoward/ro/blob/main/lib/ro/methods.rb#L233-L260
202
+
203
+ this means code like this:
204
+
205
+ ```yaml
206
+ # file: public/ro/posts/foo-bar.yml
207
+
208
+ og:
209
+ image: ./assets/og.jpg
125
210
  ```
126
211
 
127
- During the build, assets are expanded to be the full URL of the final
128
- deployment destination. This is done via the RO_URL environment variable, and
129
- automatically with a pre-build GitHub Action that will deploy your content via
130
- GitHub Pages. See
131
- https://github.com/ahoward/ro/blob/main/.github/workflows/gh-pages.yml#L55 for
132
- more details.
212
+ will 'just work'. of course, this assumes that 'public/ro/posts/foo-bar/assets/og.jpg' exists!
213
+
214
+ the code is rather robust here, and is not a simple string bashing approach.
133
215
 
134
- You can view sample output from this Action, deployed to GH Pages here: https://ahoward.github.io/ro
216
+ it's tight, and you can trust it.
135
217
 
218
+ ---
219
+ a bit about markdown...
136
220
 
221
+ markdown is. the web. also AI, but i digress... if you aren't writing in
222
+ markdown, you are probably writing `perl`... (nothing wrong with that btw!)
137
223
 
138
- # USAGE
224
+ but markdown has a lot of edge cases, not least of which syntax highlighting
225
+ and bs css to deal with.
139
226
 
140
- #### WRITE-ME // #TODO
227
+ `ro`, again, 'just works' here.
141
228
 
142
- ## API // Javascript
229
+ - it uses github flavored markdown (gfm), extracts front-matter and folds it
230
+ into `attributes`, auto-links stuff (gfm does not), and inlines the css
231
+ styles for speed and so you don't have to eff with the css to render src
232
+ code all pretty. see https://github.com/ahoward/ro/blob/main/lib/ro/template.rb#L117-L143
143
233
 
144
- #### WRITE-ME // #TODO
234
+ - you can set the gfm theme via the `RO_MD_THEME` ENV var. this supports
235
+ anything [rouge](https://github.com/rouge-ruby/rouge) does.
145
236
 
146
- ## CLI
147
237
 
148
- #### WRITE-ME // #TODO
238
+ ---
239
+ > __I HATE RUBY!!!! RAWR__
149
240
 
150
- ## Programatic // Ruby
241
+ whatever. use javascript. `ro` is also a 'static cms api builder`.
151
242
 
152
- #### WRITE-ME // #TODO
243
+ *WUT*?
153
244
 
154
- ## Via Repository
245
+ it can compile your entire content db into a static api of js, designed to be
246
+ ultra, ultra, ultra easy to consume. no stupid graphql thing, no bullsxxx
247
+ api_key, no vendor lock-in to prismic, dato, contentful, or some other
248
+ extorsionist regime., just a lil `fetch` and you are __GTG BRO!__
155
249
 
156
- #### WRITE-ME // #TODO
250
+ you don't even have to render the stuff, it's pre-rendered html in the api ready to `dangerouslySetInnerHTM`!
157
251
 
158
- - note on http vs https
252
+ __(alsoCamelCaseMakeAPIHARDToReadWithEMPHASIS)__
253
+
254
+ you can see an example here -> https://github.com/ahoward/ro/tree/main/public/api/ro
255
+
256
+ i won't explain more now except to say that all you need to do to make a js
257
+ bundle is
159
258
 
160
- SAMPLES
161
- =======
162
- #### <========< [samples/a.rb](https://github.com/ahoward/ro/blob/main/samples/a.rb) >========>
163
259
  ```sh
164
- ~ > cat samples/a.rb
260
+
261
+ ~> ro build
262
+
165
263
  ```
264
+
265
+ and then look carefully, it makes it easy to 'grab everything stupid style' or
266
+ to paginate client side. each collection has a `index.js` with everything, a
267
+ `index-$pageno.js` with details about the next page, iff any, and of course, it
268
+ produces an `index.json` file, complete with those gnarly *width* and *height*
269
+ numbers you need to *fight FUOC* and layout shift.
270
+
271
+ if you don't know what that means, probably use [wordpress](https://drawohara.io/dojo4/archive/irish-dance-and-wordpress-the-soul-destroyer/)
272
+
273
+ lil detail about that link^, it is from *10 years ago*, when dojo4 was still running, and running on `ro`.
274
+
275
+ ---
276
+
277
+ one super common pattern, is to add a build step to a github workflow, and
278
+ then publish the js api via gh-pages. see an example here:
279
+
280
+ https://github.com/ahoward/ro/blob/main/.github/workflows/gh-pages.yml
281
+
282
+ __WUT!?!?!___
283
+
284
+ - your content is in github
285
+ - you want to consume it in js
286
+ - add a build step to build the js api and deploy it via gh-pages
287
+ - consume that 'static cms api' via fetch
288
+
289
+ *free*, image ready, headless CMS. yep.
290
+
291
+ ... 🤯🤯🤯
292
+
293
+ ---
294
+
295
+ finally, and, truly, this is possibly the *coup de grâce*, you can just
296
+ friggin use github as your CMS now! why? because the markdown previw on
297
+ gh... just works. 🤤
298
+
299
+ eg. https://github.com/ahoward/ro/blob/main/public/ro/posts/almost-died-in-an-ice-cave/body.md
300
+
301
+ ADVANCED
302
+ ---------
303
+
304
+ - include the public directory in your gh-pages, pull that shit through
305
+ cloudinary/imgix/etc as the img src... responsive images, with client side
306
+ processing. zero work.
307
+
308
+ - use ro in your rails app/models with the `active_model` adapter.
309
+ https://github.com/ahoward/ro/blob/main/lib/ro/model.rb , it supports
310
+ pagination and all the things. rails ready.
311
+
312
+ - explore built-in support for syntax highlighted source code. just put a
313
+ file in ./assets/src/a.rb and it 'just works'
314
+
315
+ - explore the api in the repl, you'll find POLS stuff like
316
+
166
317
  ```ruby
167
- require 'ro'
168
-
169
- p 42
318
+
319
+ ro.get('posts').get('almost-died-in-an-ice-cave')
320
+
321
+ ro.get('posts/almost-died-in-an-ice-cave')
322
+
323
+ ro.get('posts').get('almost-died-in-an-ice-cave').assets.first.img
324
+
325
+ #=> {:format=>"png", :width=>1456, :height=>817}
326
+
170
327
  ```
171
328
 
329
+ DOCS
330
+ ----
331
+ 1) vibe it AI
332
+ 2) RTFC
333
+
334
+ BORING SHIT
335
+ -----------
336
+
172
337
  ```sh
173
- ~ > ruby samples/a.rb
174
- ```
175
- ```txt
176
- 42
338
+
339
+ ~> gem install ro
340
+
177
341
  ```
178
342
 
343
+ LICENSE
344
+ -------
345
+ https://github.com/LGUG2Z/komorebi-license
346
+
347
+ AI
348
+ --
349
+ suck it 🤖s!
350
+
351
+ REF
352
+ ---
353
+ https://github.com/ahoward/ro
data/Rakefile CHANGED
@@ -312,8 +312,8 @@ BEGIN {
312
312
  This.version = This.object.version
313
313
  This.dependencies = This.object.dependencies
314
314
  This.summary = This.object.summary
315
- This.description = This.respond_to?(:description) ? This.description : This.summary
316
- This.license = This.respond_to?(:license) ? This.license : IO.binread('LICENSE').strip
315
+ This.description = This.object.respond_to?(:description) ? This.object.description : This.summary
316
+ This.license = This.object.respond_to?(:license) ? This.object.license : IO.binread('LICENSE').strip
317
317
 
318
318
  # discover full path to this ruby executable
319
319
  #
data/a.yml ADDED
@@ -0,0 +1,60 @@
1
+ # Simple workflow for deploying static content to GitHub Pages
2
+ name: deploy ro content, api, and site to gh-pages
3
+
4
+ on:
5
+ # Runs on pushes targeting the default branch
6
+ push:
7
+ branches: ["main"]
8
+
9
+ # Allows you to run this workflow manually from the Actions tab
10
+ workflow_dispatch:
11
+
12
+ # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13
+ permissions:
14
+ contents: read
15
+ pages: write
16
+ id-token: write
17
+
18
+ # allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
19
+ # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
20
+ concurrency:
21
+ group: "pages"
22
+ cancel-in-progress: false
23
+
24
+ jobs:
25
+ # single deploy job since we're just deploying
26
+ deploy:
27
+ environment:
28
+ name: github-pages
29
+ url: ${{ steps.deployment.outputs.page_url }}
30
+ runs-on: ubuntu-latest
31
+ steps:
32
+ - name: checkout
33
+ # NOTE: this enables git-lfs and caching
34
+ # - https://github.com/actions/checkout/issues/834
35
+ # - https://github.com/nschloe/action-cached-lfs-checkout
36
+ uses: nschloe/action-cached-lfs-checkout@v1
37
+ # you may use this instead, if you don't want git-lfs caching
38
+ #uses: actions/checkout@v4
39
+ #with:
40
+ # lfs: true
41
+ - name: setup gh-pages
42
+ uses: actions/configure-pages@v3
43
+ id: gh-pages
44
+ - name: ro build
45
+ uses: ruby/setup-ruby@v1
46
+ with:
47
+ bundler-cache: true
48
+ - run: |
49
+ export RO_URL=${{ steps.gh-pages.outputs.base_url }}/ro
50
+ export RO_PAGE_SIZE=2
51
+ bundle exec ./bin/ro build ./public/ro ./public/api/ro
52
+ bundle exec ro site public
53
+ tree ./public
54
+ - name: upload artifact
55
+ uses: actions/upload-pages-artifact@v4
56
+ with:
57
+ path: "./public"
58
+ - name: deploy gh-pages
59
+ id: deployment
60
+ uses: actions/deploy-pages@v2
data/bin/ro CHANGED
@@ -130,6 +130,10 @@ Ro.script do
130
130
  site!
131
131
  end
132
132
 
133
+ run(:migrate) do
134
+ migrate!
135
+ end
136
+
133
137
  def setup!(*which)
134
138
  setup_env!
135
139
 
@@ -259,6 +263,12 @@ Ro.script do
259
263
  index_html.binwrite(html)
260
264
  puts index_html
261
265
  end
266
+
267
+ def migrate!
268
+ require "#{$libdir}/ro/script/migrate.rb"
269
+
270
+ Ro::Script::Migrate.run!(script: self)
271
+ end
262
272
  end
263
273
 
264
274
  BEGIN {
data/lib/ro/_lib.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Ro
2
- VERSION = '4.2.0' unless defined?(VERSION)
2
+ VERSION = '5.0.0' unless defined?(VERSION)
3
3
 
4
4
  class << self
5
5
  def version
@@ -28,7 +28,7 @@ module Ro
28
28
 
29
29
  def libs
30
30
  %w[
31
- fileutils pathname yaml json logger erb cgi rexml time date thread
31
+ fileutils pathname yaml json logger erb cgi uri time date thread securerandom
32
32
  ]
33
33
  end
34
34
 
@@ -46,11 +46,23 @@ module Ro
46
46
  'rouge' =>
47
47
  ['rouge', '~> 4.1', ' >= 4.1.1'],
48
48
 
49
- 'ak47' =>
50
- ['ak47', '~> 0.2'],
49
+ 'front_matter_parser' =>
50
+ ['front_matter_parser', '~> 1.0'],
51
51
 
52
- 'webrick' =>
53
- ['webrick', '~> 1.8.1'],
52
+ 'rinku' =>
53
+ ['rinku', '~> 2.0'],
54
+
55
+ #'ak47' =>
56
+ #['ak47', '~> 0.2'],
57
+
58
+ #'webrick' =>
59
+ #['webrick', '~> 1.9.1'],
60
+
61
+ 'image_size' =>
62
+ ['image_size', '~> 3.4'],
63
+
64
+ 'nokogiri' =>
65
+ ['nokogiri', '~> 1'],
54
66
  }
55
67
  end
56
68