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/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,171 @@
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 'html.rb'
9
+ require_relative 'template/rouge_formatter.rb'
10
+
11
+ def Template.render(*args, &block)
12
+ render_file(*args, &block)
13
+ end
14
+
15
+ def Template.render_file(path, options = {})
16
+ path = File.expand_path(path.to_s.strip)
17
+ options = Map.for(options.is_a?(Hash) ? options : { context: options })
7
18
 
8
19
  content = IO.binread(path).force_encoding('utf-8')
20
+ engines = File.basename(path).split('.')[1..-1]
21
+ context = options[:context]
9
22
 
10
- loop do
11
- break if exts.empty?
12
- ext = exts.shift
23
+ begin
24
+ render_string(content, path: path, engines: engines, context: context)
25
+ rescue Ro::Error => e
26
+ msg = e.message
27
+ Ro.error! "failed to render #{ path } with `#{ msg }`"
28
+ end
29
+ end
13
30
 
14
- case ext.to_s.downcase
15
- when 'erb', 'eruby'
16
- content = Ro.erb(content, node)
31
+ def Template.render_string(content, options = {})
32
+ content = String(content).force_encoding('utf-8')
33
+ options = Map.for(options.is_a?(Hash) ? options : { context: options })
34
+ engines = Array(options.fetch(:engines) { ['md'] }).flatten.compact
35
+ path = options.fetch(:path) { '(string)' }
36
+ context = options[:context]
17
37
 
18
- when 'yml'
19
- content = YAML.load(content)
38
+ loop do
39
+ break if engines.empty?
20
40
 
21
- else
22
- tilt = Tilt[ext] || Tilt['txt']
41
+ engine = engines.shift.to_s.strip.downcase
23
42
 
24
- if tilt.nil?
25
- content
43
+ content =
44
+ case engine
45
+ when 'html'
46
+ render_html(content)
47
+ when 'txt', 'text'
48
+ render_text(content)
49
+ when 'erb'
50
+ render_erb(content, context:)
51
+ when 'md', 'markdown'
52
+ render_markdown(content)
53
+ when 'yml', 'yaml'
54
+ render_yaml(content)
55
+ when 'json'
56
+ render_json(content)
57
+ when 'rb'
58
+ render_ruby(content)
59
+ when 'txt', 'text'
60
+ render_text(content)
26
61
  else
27
- content = tilt.new{ content }.render(node)
28
- end
29
- end
62
+ Ro.error!("no engine found for engine=#{ engine.inspect } engines=#{ engines.inspect }")
63
+ end
30
64
  end
31
65
 
32
66
  content
33
67
  end
34
68
 
35
- def Template.render_source(path, node = nil)
36
- parts = File.basename(path).split('.')
37
- base = parts.shift
38
- exts = parts.reverse
69
+ def Template.render_html(html)
70
+ HTML.new(html)
71
+ end
39
72
 
40
- content = IO.binread(path).force_encoding('utf-8')
73
+ def Template.render_json(json)
74
+ data = JSON.parse(json)
75
+ Ro.mapify(data)
76
+ end
41
77
 
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
78
+ def Template.render_yaml(yaml)
79
+ data = YAML.load(yaml)
80
+ Ro.mapify(data)
81
+ end
82
+
83
+ def Template.render_ruby(code)
84
+ string = IO.popen('ruby', 'w+'){|ruby| ruby.puts(code); ruby.close_write; ruby.read}
85
+
86
+ if $? == 0
87
+ string
88
+ else
89
+ Ro.error!("ruby:\n\n#{ code }")
90
+ end
91
+ end
92
+
93
+ def Template.render_text(text)
94
+ html = Text.render(text)
95
+ HTML.new(html)
96
+ end
97
+
98
+ def Template.render_erb(content, options = {})
99
+ content = String(content).force_encoding('utf-8')
100
+ options = Map.for(options.is_a?(Hash) ? options : { context: options })
101
+ context = options[:context]
102
+
103
+ erb = ERB.new(content, trim_mode: '%<>')
104
+
105
+ html =
106
+ if context.respond_to?(:to_hash)
107
+ hash = context.to_hash
108
+ erb.result_with_hash(hash)
55
109
  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
110
+ binding = context ? context.instance_eval{ binding } : ::Kernel.binding
111
+ erb.result(binding)
65
112
  end
66
- end
67
113
 
68
- content
114
+ HTML.new(html)
69
115
  end
70
116
 
71
- fattr :path
117
+ def Template.render_markdown(content, options = {})
118
+ content = String(content).force_encoding('utf-8')
119
+ options = Map.for(options.is_a?(Hash) ? options : { context: options })
120
+
121
+ theme = options.fetch(:theme) { Ro.config.md_theme }
122
+
123
+ parsed = FrontMatterParser::Parser.new(:md).call(content)
124
+ front_matter = parsed.front_matter
125
+ content = parsed.content
126
+
127
+ opts = {
128
+ input: 'GFM',
129
+ hard_wrap: false,
130
+ syntax_highlighter_opts: { formatter: RougeFormatter, theme: theme }
131
+ }
132
+
133
+ div =
134
+ <<~_____
135
+ <div class="ro markdown">
136
+ #{ ::Kramdown::Document.new(content, opts).to_html }
137
+ </div>
138
+ _____
139
+
140
+ html = Rinku.auto_link(div, :all, 'target="_blank"')
141
+
142
+ HTML.new(html, front_matter:)
143
+ end
144
+
145
+ def Template.render_src(path, options = {})
146
+ path = File.expand_path(path.to_s.strip)
147
+ options = Map.for(options.is_a?(Hash) ? options : { context: options })
148
+
149
+ content = IO.binread(path).force_encoding('utf-8')
150
+ engines = File.basename(path).split('.')[1..-1].reverse
151
+ context = options[:context]
152
+
153
+ theme = options.fetch(:theme) { Ro.config.md_theme }
154
+ formatter = RougeFormatter.new(theme: theme)
155
+
156
+ language = engines.shift
157
+
158
+ lexer = Rouge::Lexer.find(language) || Ro.error!('no lexer found for ')
159
+
160
+ content = render_string(content, path: path, engines: engines, context: context) if engines.size.nonzero?
72
161
 
73
- def initialize(path)
74
- @path = Ro.realpath(path)
162
+ HTML.new(
163
+ <<~_____
164
+ <div class="ro markdown src">
165
+ #{ formatter.format(lexer.lex(content)) }
166
+ </div>
167
+ _____
168
+ )
75
169
  end
76
170
  end
77
171
  end
data/lib/ro/text.rb ADDED
@@ -0,0 +1,120 @@
1
+ module Ro
2
+ require 'cgi'
3
+
4
+ module Text
5
+ def render(text)
6
+ text = text.to_s.strip
7
+ return '' if text.empty?
8
+
9
+ text = escape_html(text)
10
+ text = auto_link_urls(text)
11
+ text = auto_link_email_addresses(text)
12
+ text = paragraphs_for(text)
13
+
14
+ return text
15
+ end
16
+
17
+ def paragraphs_for(text)
18
+ paragraphs = text.split(%r`\n\n+`).map{|chunk| lines_for(chunk)}
19
+ paragraphs.map{|p| "<p>\n#{ p }\n</p>"}.join("\n\n").strip
20
+ end
21
+
22
+ def lines_for(text)
23
+ text.strip.gsub(/\n/, "<br />\n")
24
+ end
25
+
26
+ def escape_html(text)
27
+ CGI.escapeHTML(text)
28
+ end
29
+
30
+ AUTO_LINK_RE = %r{
31
+ (?: ((?:ed2k|ftp|http|https|irc|mailto|news|gopher|nntp|telnet|webcal|xmpp|callto|feed|svn|urn|aim|rsync|tag|ssh|sftp|rtsp|afs|file):)// | www\.\w )
32
+ [^\s<\u00A0"]+
33
+ }ix
34
+
35
+ # regexps for determining context, used high-volume
36
+ AUTO_LINK_CRE = [/<[^>]+$/, /^[^>]*>/, /<a\b.*?>/i, /<\/a>/i]
37
+
38
+ AUTO_EMAIL_LOCAL_RE = /[\w.!#\$%&'*\/=?^`{|}~+-]/
39
+ AUTO_EMAIL_RE = /(?<!#{AUTO_EMAIL_LOCAL_RE})[\w.!#\$%+-]\.?#{AUTO_EMAIL_LOCAL_RE}*@[\w-]+(?:\.[\w-]+)+/
40
+
41
+ BRACKETS = { ']' => '[', ')' => '(', '}' => '{' }
42
+
43
+ WORD_PATTERN = '\p{Word}'
44
+
45
+ # Turns all urls into clickable links. If a block is given, each url
46
+ # is yielded and the result is used as the link text.
47
+ def auto_link_urls(text)
48
+ text.gsub(AUTO_LINK_RE) do
49
+ scheme, href = $1, $&
50
+ punctuation = []
51
+ trailing_gt = ""
52
+
53
+ if auto_linked?($`, $')
54
+ # do not change string; URL is already linked
55
+ href
56
+ else
57
+ # don't include trailing punctuation character as part of the URL
58
+ while href.sub!(/[^#{WORD_PATTERN}\/\-=;]$/, '')
59
+ punctuation.push $&
60
+ if opening = BRACKETS[punctuation.last] and href.scan(opening).size > href.scan(punctuation.last).size
61
+ href << punctuation.pop
62
+ break
63
+ end
64
+ end
65
+
66
+ # don't include trailing &gt; entities as part of the URL
67
+ trailing_gt = $& if href.sub!(/&gt;$/, '')
68
+ href = 'http://' + href unless scheme
69
+ link = escape_html(href)
70
+ "<a href='#{ link }'>#{ link }</a>" + punctuation.reverse.join('') + trailing_gt
71
+ end
72
+ end
73
+ end
74
+
75
+ # Turns all email addresses into clickable links. If a block is given,
76
+ # each email is yielded and the result is used as the link text.
77
+ def auto_link_email_addresses(text)
78
+ text.gsub(AUTO_EMAIL_RE) do
79
+ text = $&
80
+
81
+ if auto_linked?($`, $')
82
+ text
83
+ else
84
+ email = escape_html(text)
85
+ "<a href='mailto:#{ email }'>#{ email }</a>"
86
+ end
87
+ end
88
+ end
89
+
90
+ # Detects already linked context or position in the middle of a tag
91
+ def auto_linked?(left, right)
92
+ (left =~ AUTO_LINK_CRE[0] and right =~ AUTO_LINK_CRE[1]) or
93
+ (left.rindex(AUTO_LINK_CRE[2]) and $' !~ AUTO_LINK_CRE[3])
94
+ end
95
+
96
+ extend self
97
+ end
98
+ end
99
+
100
+
101
+ if $0 == __FILE__
102
+ text = <<~____
103
+ this is a paragraph that is very long this is a paragraph that is very long this is a paragraph that is very long this is a paragraph that is very long
104
+ this is a paragraph that is very long this is a paragraph that is very long this is a paragraph that is very long this is a paragraph that is very long
105
+
106
+ this is a paragraph that is very long this is a paragraph that is very long this is a paragraph that is very long this is a paragraph that is very long
107
+
108
+ - a
109
+ - b
110
+ - c
111
+
112
+ & < > me
113
+
114
+ http://drawohara.io>
115
+
116
+ ara.t.howard@gmail.com
117
+ ____
118
+
119
+ puts Ro::Text.render(text)
120
+ end