ro 1.4.6 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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/script.rb ADDED
@@ -0,0 +1,189 @@
1
+ module Ro
2
+ def self.script(*args, &block)
3
+ Script.run!(*args, &block)
4
+ end
5
+
6
+ class Script
7
+ attr_accessor :cls, :env, :argv, :options, :mode, :stdout, :stdin, :stderr, :help
8
+
9
+ def run!(env = ENV, argv = ARGV)
10
+ initialize!(env, argv)
11
+ parse_command_line!
12
+ run_mode!
13
+ end
14
+
15
+ def initialize!(env, argv)
16
+ @cls = self.class
17
+ @env = env.to_hash.dup
18
+ @argv = argv.map { |arg| arg.dup }
19
+ @options = {}
20
+ @mode = nil
21
+ @stdout = $stdout.dup
22
+ @stdin = $stdin.dup
23
+ @stderr = $stderr.dup
24
+ @help = @cls.help
25
+ end
26
+
27
+ def parse_command_line!
28
+ argv = []
29
+ head = []
30
+ tail = []
31
+
32
+ %w[--].each do |stop|
33
+ next unless (i = @argv.index(stop))
34
+
35
+ head = @argv.slice(0...i)
36
+ tail = @argv.slice((i + 1)...@argv.size)
37
+ @argv = head
38
+ break
39
+ end
40
+
41
+ @argv.each do |arg|
42
+ if arg =~ /^\s*--([^\s-]+)=(.+)/
43
+ key = ::Regexp.last_match(1)
44
+ val = ::Regexp.last_match(2)
45
+ @options[key.to_sym] = val
46
+ elsif arg =~ /^\s*(-+)(.+)/
47
+ switch = ::Regexp.last_match(1)
48
+ key = ::Regexp.last_match(2)
49
+ val = switch.size.even?
50
+ @options[key.to_sym] = val
51
+ else
52
+ argv.push(arg)
53
+ end
54
+ end
55
+
56
+ argv += tail
57
+
58
+ @argv.replace(argv)
59
+
60
+ @mode = (@argv.shift if respond_to?("run_#{@argv[0]}!"))
61
+ end
62
+
63
+ def run_mode!
64
+ if @mode
65
+ send("run_#{@mode}!")
66
+ else
67
+ run
68
+ end
69
+ end
70
+
71
+ def run
72
+ help!
73
+ end
74
+
75
+ def run_help!
76
+ @stdout.puts(@help)
77
+ end
78
+
79
+ def help!
80
+ @stderr.puts(@help)
81
+ abort
82
+ end
83
+
84
+ def log(*messages, io: $stdout)
85
+ ts = Time.now.utc.iso8601
86
+ prefix = File.basename($0)
87
+ msg = messages.join("\n\s\s").strip
88
+
89
+ io.write "---\n[#{prefix}@#{ts}]\n\s\s#{msg}\n\n\n"
90
+
91
+ begin
92
+ io.flush
93
+ rescue StandardError
94
+ nil
95
+ end
96
+ end
97
+
98
+ def err(*messages)
99
+ log(*messages, io: @stderr)
100
+ end
101
+
102
+ @@SAY = {
103
+ clear: "\e[0m",
104
+ reset: "\e[0m",
105
+ erase_line: "\e[K",
106
+ erase_char: "\e[P",
107
+ bold: "\e[1m",
108
+ dark: "\e[2m",
109
+ underline: "\e[4m",
110
+ underscore: "\e[4m",
111
+ blink: "\e[5m",
112
+ reverse: "\e[7m",
113
+ concealed: "\e[8m",
114
+ black: "\e[30m",
115
+ red: "\e[31m",
116
+ green: "\e[32m",
117
+ yellow: "\e[33m",
118
+ blue: "\e[34m",
119
+ magenta: "\e[35m",
120
+ cyan: "\e[36m",
121
+ white: "\e[37m",
122
+ on_black: "\e[40m",
123
+ on_red: "\e[41m",
124
+ on_green: "\e[42m",
125
+ on_yellow: "\e[43m",
126
+ on_blue: "\e[44m",
127
+ on_magenta: "\e[45m",
128
+ on_cyan: "\e[46m",
129
+ on_white: "\e[47m"
130
+ }
131
+
132
+ def say(phrase, *args)
133
+ options = args.last.is_a?(Hash) ? args.pop : {}
134
+ options[:color] = args.shift.to_s.to_sym unless args.empty?
135
+ keys = options.keys
136
+ keys.each { |key| options[key.to_s.to_sym] = options.delete(key) }
137
+
138
+ color = options[:color]
139
+ bold = options.has_key?(:bold)
140
+
141
+ parts = [phrase]
142
+ parts.unshift(@@SAY[color]) if color
143
+ parts.unshift(@@SAY[:bold]) if bold
144
+ parts.push(@@SAY[:clear]) if parts.size > 1
145
+
146
+ method = options[:method] || :puts
147
+
148
+ if STDOUT.tty?
149
+ ::Kernel.send(method, parts.join)
150
+ else
151
+ ::Kernel.send(method, phrase)
152
+ end
153
+ end
154
+
155
+ def self.help(*args)
156
+ @help = args.join("\n") unless args.empty?
157
+ @help
158
+ end
159
+
160
+ def self.run(*args, &block)
161
+ modes =
162
+ if args.empty?
163
+ [nil]
164
+ else
165
+ args
166
+ end
167
+
168
+ modes.each do |mode|
169
+ method_name =
170
+ if mode
171
+ "run_#{mode}!"
172
+ else
173
+ 'run'
174
+ end
175
+
176
+ define_method(method_name, &block)
177
+ end
178
+ end
179
+
180
+ def self.run!(&block)
181
+ STDOUT.sync = true
182
+ STDERR.sync = true
183
+
184
+ %w[PIPE INT].each { |signal| Signal.trap(signal, 'EXIT') }
185
+
186
+ Class.new(Script, &block).new.run!(ENV, ARGV)
187
+ end
188
+ end
189
+ end
data/lib/ro/slug.rb CHANGED
@@ -1,27 +1,28 @@
1
1
  module Ro
2
2
  class Slug < ::String
3
- Join = '-'
3
+ @@JOIN = '-'
4
4
 
5
- def Slug.for(*args)
5
+ class << Slug
6
+ def for(arg, *args, &block)
7
+ return arg if arg.is_a?(Slug) && args.empty? && block.nil?
8
+ new(arg, *args, &block)
9
+ end
10
+ end
11
+
12
+ def initialize(*args)
6
13
  options = args.last.is_a?(Hash) ? args.pop : {}
7
- join = (options[:join]||options['join']||Join).to_s
14
+
15
+ join = (options[:join] || options['join'] || @@JOIN).to_s
16
+
8
17
  string = args.flatten.compact.join(join)
9
- string = unidecode(string)
10
- words = string.to_s.scan(%r|[/\w]+|)
11
- words.map!{|word| word.gsub %r|[_-]|, join}
12
- words.map!{|word| word.gsub %r|[^/0-9a-zA-Z_-]|, ''}
13
- words.delete_if{|word| word.nil? or word.strip.empty?}
14
- new(words.join(join).downcase.gsub('/', (join * 2)))
15
- end
18
+ words = string.to_s.scan(%r{[/\w]+})
19
+ words.map! { |word| word.gsub(/[_-]/, join) }
20
+ words.map! { |word| word.gsub %r{[^/0-9a-zA-Z_-]}, '' }
21
+ words.delete_if { |word| word.nil? or word.strip.empty? }
16
22
 
17
- unless defined?(Stringex::Unidecoder)
18
- def Slug.unidecode(string)
19
- string
20
- end
21
- else
22
- def Slug.unidecode(string)
23
- Stringex::Unidecoder.decode(string)
24
- end
23
+ slug = words.join(join).downcase.gsub('/', (join * 2))
24
+
25
+ super(slug)
25
26
  end
26
27
  end
27
28
  end
@@ -0,0 +1,42 @@
1
+ module Ro
2
+ class Template
3
+ require 'rouge'
4
+
5
+ class RougeFormatter < ::Rouge::Formatters::HTMLInline
6
+ def initialize(opts)
7
+ theme = if opts.is_a?(Hash)
8
+ opts[:theme] || opts['theme']
9
+ elsif opts.is_a?(Symbol)
10
+ opts.to_s
11
+ else
12
+ opts
13
+ end
14
+
15
+ super(theme)
16
+ end
17
+
18
+ def stream(tokens)
19
+ lineno = 1
20
+ yield "<code class='code' style='white-space:pre;'>"
21
+ token_lines(tokens).each do |line_tokens|
22
+ yield "<div class='code-line code-line-#{lineno}'>"
23
+ line_tokens.each do |token, value|
24
+ yield span(token, value)
25
+ end
26
+ yield "\n"
27
+ yield '</div>'
28
+ lineno += 1
29
+ end
30
+ yield '</code>'
31
+ end
32
+
33
+ def safe_span(tok, safe_val)
34
+ # return safe_val if tok == ::Rouge::Token::Tokens::Text
35
+ return safe_val.gsub(/\s/, '&nbsp;').gsub(/\n/, '<br />') if tok == ::Rouge::Token::Tokens::Text
36
+
37
+ rules = @theme.style_for(tok).rendered_rules
38
+ "<span style=\"#{rules.to_a.join(';')}\">#{safe_val}</span>"
39
+ end
40
+ end
41
+ end
42
+ end
data/lib/ro/template.rb CHANGED
@@ -1,77 +1,131 @@
1
1
  module Ro
2
2
  class Template
3
- def Template.render(path, node = nil)
4
- parts = File.basename(path).split('.')
5
- base = parts.shift
6
- exts = parts.reverse
3
+ require 'erb'
4
+ require 'rouge'
5
+ require 'kramdown'
6
+ require 'kramdown-parser-gfm'
7
+
8
+ require_relative 'template/rouge_formatter.rb'
9
+
10
+ class HTML < ::String
11
+ end
12
+
13
+ def self.render(*args, &block)
14
+ render_file(*args, &block)
15
+ end
16
+
17
+ def self.render_file(path, options = {})
18
+ path = File.expand_path(path.to_s.strip)
19
+ options = Map.for(options.is_a?(Hash) ? options : { context: options })
7
20
 
8
21
  content = IO.binread(path).force_encoding('utf-8')
22
+ engines = File.basename(path).split('.')[1..-1]
23
+ context = options[:context]
9
24
 
10
- loop do
11
- break if exts.empty?
12
- ext = exts.shift
25
+ render_string(content, path: path, engines: engines, context: context)
26
+ end
13
27
 
14
- case ext.to_s.downcase
15
- when 'erb', 'eruby'
16
- content = Ro.erb(content, node)
28
+ def self.render_string(content, options = {})
29
+ content = String(content).force_encoding('utf-8')
30
+ options = Map.for(options.is_a?(Hash) ? options : { context: options })
31
+ engines = Array(options.fetch(:engines) { ['md'] }).flatten.compact
32
+ path = options.fetch(:path) { '(string)' }
33
+ context = options[:context]
17
34
 
18
- when 'yml'
19
- content = YAML.load(content)
35
+ loop do
36
+ break if engines.empty?
20
37
 
21
- else
22
- tilt = Tilt[ext] || Tilt['txt']
38
+ engine = engines.shift.to_s.strip.downcase
23
39
 
24
- if tilt.nil?
40
+ content =
41
+ case engine
42
+ when 'txt', 'text', 'html'
25
43
  content
44
+ when 'erb'
45
+ render_erb(content, context:)
46
+ when 'md', 'markdown'
47
+ render_markdown(content)
48
+ when 'yml'
49
+ data = YAML.load(content)
50
+ pod = Ro.pod(data)
51
+ Ro.mapify(data)
52
+ when 'json'
53
+ data = JSON.parse(content)
54
+ pod = Ro.pod(data)
55
+ Ro.mapify(data)
56
+ when 'rb'
57
+ eval(content)
26
58
  else
27
- content = tilt.new{ content }.render(node)
28
- end
29
- end
59
+ Ro.error!("no engine found for engine=#{ engine.inspect } engines=#{ engines.inspect }")
60
+ end
30
61
  end
31
62
 
32
63
  content
33
64
  end
34
65
 
35
- def Template.render_source(path, node = nil)
36
- parts = File.basename(path).split('.')
37
- base = parts.shift
38
- exts = parts.reverse
66
+ def self.render_erb(content, options = {})
67
+ content = String(content).force_encoding('utf-8')
68
+ options = Map.for(options.is_a?(Hash) ? options : { context: options })
69
+ context = options[:context]
39
70
 
40
- content = IO.binread(path).force_encoding('utf-8')
71
+ erb = ERB.new(content, trim_mode: '%<>')
41
72
 
42
- loop do
43
- break if exts.empty?
44
- ext = exts.shift
45
-
46
- if exts.empty?
47
- code = content
48
- language = ext
49
- content =
50
- begin
51
- ::Pygments.highlight(code, :lexer => language, :options => {:encoding => 'utf-8'})
52
- rescue
53
- content
54
- end
73
+ html =
74
+ if context.respond_to?(:to_hash)
75
+ hash = context.to_hash
76
+ erb.result_with_hash(hash)
55
77
  else
56
- case ext.to_s.downcase
57
- when 'erb', 'eruby'
58
- content = Ro.erb(content, node)
59
- when 'yml'
60
- content = YAML.load(content)
61
- else
62
- tilt = Tilt[ext].new{ content }
63
- content = tilt.render(node)
64
- end
78
+ binding = context ? context.instance_eval{ binding } : ::Kernel.binding
79
+ erb.result(binding)
65
80
  end
66
- end
67
81
 
68
- content
82
+ HTML.new(html)
69
83
  end
70
84
 
71
- fattr :path
85
+ def self.render_markdown(content, options = {})
86
+ content = String(content).force_encoding('utf-8')
87
+ options = Map.for(options.is_a?(Hash) ? options : { context: options })
88
+
89
+ theme = options.fetch(:theme) { Ro.config.md_theme }
90
+
91
+ opts = {
92
+ input: 'GFM',
93
+ syntax_highlighter_opts: { formatter: RougeFormatter, theme: theme }
94
+ }
95
+
96
+ HTML.new(
97
+ <<~_____
98
+ <div class="ro markdown">
99
+ #{ ::Kramdown::Document.new(content, opts).to_html }
100
+ </div>
101
+ _____
102
+ )
103
+ end
104
+
105
+ def self.render_src(path, options = {})
106
+ path = File.expand_path(path.to_s.strip)
107
+ options = Map.for(options.is_a?(Hash) ? options : { context: options })
108
+
109
+ content = IO.binread(path).force_encoding('utf-8')
110
+ engines = File.basename(path).split('.')[1..-1].reverse
111
+ context = options[:context]
112
+
113
+ theme = options.fetch(:theme) { Ro.config.md_theme }
114
+ formatter = RougeFormatter.new(theme: theme)
115
+
116
+ language = engines.shift
117
+
118
+ lexer = Rouge::Lexer.find(language) || Ro.error!('no lexer found for ')
119
+
120
+ content = render_string(content, path: path, engines: engines, context: context) if engines.size.nonzero?
72
121
 
73
- def initialize(path)
74
- @path = Ro.realpath(path)
122
+ HTML.new(
123
+ <<~_____
124
+ <div class="ro markdown src">
125
+ #{ formatter.format(lexer.lex(content)) }
126
+ </div>
127
+ _____
128
+ )
75
129
  end
76
130
  end
77
131
  end