ro 1.4.6 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +6 -14
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +43 -0
  4. data/LICENSE +1 -0
  5. data/README.md +120 -112
  6. data/README.md.erb +159 -0
  7. data/Rakefile +129 -78
  8. data/bin/ro +241 -68
  9. data/lib/ro/_lib.rb +95 -0
  10. data/lib/ro/asset.rb +45 -0
  11. data/lib/ro/collection/list.rb +23 -0
  12. data/lib/ro/collection.rb +168 -0
  13. data/lib/ro/config.rb +68 -0
  14. data/lib/ro/error.rb +8 -0
  15. data/lib/ro/klass.rb +25 -0
  16. data/lib/ro/methods.rb +238 -0
  17. data/lib/ro/model.rb +83 -114
  18. data/lib/ro/node.rb +177 -360
  19. data/lib/ro/pagination.rb +7 -4
  20. data/lib/ro/path.rb +225 -0
  21. data/lib/ro/root.rb +52 -31
  22. data/lib/ro/script/builder.rb +221 -0
  23. data/lib/ro/script/console.rb +47 -0
  24. data/lib/ro/script/server.rb +76 -0
  25. data/lib/ro/script.rb +189 -0
  26. data/lib/ro/slug.rb +19 -18
  27. data/lib/ro/template/rouge_formatter.rb +42 -0
  28. data/lib/ro/template.rb +104 -50
  29. data/lib/ro.rb +85 -317
  30. data/public/api/ro/index-1.json +147 -0
  31. data/public/api/ro/index.json +137 -0
  32. data/public/api/ro/posts/first_post/index.json +52 -0
  33. data/public/api/ro/posts/index-1.json +145 -0
  34. data/public/api/ro/posts/index.json +135 -0
  35. data/public/api/ro/posts/second_post/index.json +51 -0
  36. data/public/api/ro/posts/third_post/index.json +51 -0
  37. data/public/ro/posts/first_post/assets/foo/bar/baz.jpg +0 -0
  38. data/public/ro/posts/first_post/assets/foo.jpg +0 -0
  39. data/public/ro/posts/first_post/assets/src/foo/bar.rb +3 -0
  40. data/public/ro/posts/first_post/attributes.yml +2 -0
  41. data/public/ro/posts/first_post/blurb.erb.md +7 -0
  42. data/public/ro/posts/first_post/body.md +16 -0
  43. data/public/ro/posts/first_post/testing.txt +3 -0
  44. data/public/ro/posts/second_post/assets/foo/bar/baz.jpg +0 -0
  45. data/public/ro/posts/second_post/assets/foo.jpg +0 -0
  46. data/public/ro/posts/second_post/assets/src/foo/bar.rb +3 -0
  47. data/public/ro/posts/second_post/attributes.yml +2 -0
  48. data/public/ro/posts/second_post/blurb.erb.md +5 -0
  49. data/public/ro/posts/second_post/body.md +16 -0
  50. data/public/ro/posts/third_post/assets/foo/bar/baz.jpg +0 -0
  51. data/public/ro/posts/third_post/assets/foo.jpg +0 -0
  52. data/public/ro/posts/third_post/assets/src/foo/bar.rb +3 -0
  53. data/public/ro/posts/third_post/attributes.yml +2 -0
  54. data/public/ro/posts/third_post/blurb.erb.md +5 -0
  55. data/public/ro/posts/third_post/body.md +16 -0
  56. data/ro.gemspec +89 -29
  57. metadata +106 -90
  58. data/TODO.md +0 -50
  59. data/lib/ro/blankslate.rb +0 -7
  60. data/lib/ro/cache.rb +0 -26
  61. data/lib/ro/git.rb +0 -374
  62. data/lib/ro/initializers/env.rb +0 -5
  63. data/lib/ro/initializers/tilt.rb +0 -104
  64. data/lib/ro/lock.rb +0 -53
  65. data/lib/ro/node/list.rb +0 -142
  66. data/notes/ara.txt +0 -215
data/lib/ro/path.rb ADDED
@@ -0,0 +1,225 @@
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
+ end
225
+ 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