runeblog 0.3.02

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. checksums.yaml +7 -0
  2. data/README.lt3 +279 -0
  3. data/README.md +312 -0
  4. data/bin/blog +200 -0
  5. data/bin/mkwidget +164 -0
  6. data/data/EDITOR +1 -0
  7. data/data/ROOT +1 -0
  8. data/data/VIEW +1 -0
  9. data/data/features.txt +18 -0
  10. data/data/global.lt3 +20 -0
  11. data/data/universal.lt3 +18 -0
  12. data/empty_view/assets/austin-pano.jpg +0 -0
  13. data/empty_view/assets/sky2.jpg +0 -0
  14. data/empty_view/config/exper/2svg.lt3 +38 -0
  15. data/empty_view/config/exper/gen_svg.rb +60 -0
  16. data/empty_view/config/exper/meta.html +10 -0
  17. data/empty_view/config/exper/s2.html +25 -0
  18. data/empty_view/config/exper/varmint.rb +50 -0
  19. data/empty_view/config/facebook/credentials.txt +7 -0
  20. data/empty_view/config/facebook/facebook.rb +42 -0
  21. data/empty_view/config/facebook/fb.html +10 -0
  22. data/empty_view/config/facebook/fb.js.lt3 +15 -0
  23. data/empty_view/config/reddit/credentials.txt +6 -0
  24. data/empty_view/config/reddit/notes.txt +4 -0
  25. data/empty_view/config/reddit/reddit_post_url.py +34 -0
  26. data/empty_view/config/reddit/redpost.rb +43 -0
  27. data/empty_view/config/reddit/the-graffiti-wall.html +91 -0
  28. data/empty_view/config/twitter/credentials.txt +3 -0
  29. data/empty_view/config/twitter/tw.html +12 -0
  30. data/empty_view/config/twitter/tw.js +5 -0
  31. data/empty_view/config/twitter/twitter.rb +35 -0
  32. data/empty_view/posts/GIT_IS_DUMB +1 -0
  33. data/empty_view/remote/assets/GIT_IS_DUMB +1 -0
  34. data/empty_view/remote/banner/navbar/GIT_IS_DUMB +0 -0
  35. data/empty_view/remote/etc/GIT_IS_DUMB +1 -0
  36. data/empty_view/remote/permalink/GIT_IS_DUMB +1 -0
  37. data/empty_view/remote/widgets/ad/GIT_IS_DUMB +2 -0
  38. data/empty_view/remote/widgets/links/GIT_IS_DUMB +2 -0
  39. data/empty_view/remote/widgets/news/GIT_IS_DUMB +2 -0
  40. data/empty_view/remote/widgets/pages/GIT_IS_DUMB +2 -0
  41. data/empty_view/remote/widgets/pinned/GIT_IS_DUMB +2 -0
  42. data/empty_view/settings/features.txt +18 -0
  43. data/empty_view/settings/publish.txt +5 -0
  44. data/empty_view/settings/recent.txt +6 -0
  45. data/empty_view/settings/view.txt +4 -0
  46. data/empty_view/themes/standard/README +59 -0
  47. data/empty_view/themes/standard/banner/banner.lt3 +5 -0
  48. data/empty_view/themes/standard/banner/navbar/about.lt3 +18 -0
  49. data/empty_view/themes/standard/banner/navbar/contact.lt3 +18 -0
  50. data/empty_view/themes/standard/banner/navbar/faq.lt3 +1 -0
  51. data/empty_view/themes/standard/banner/navbar/list.data +3 -0
  52. data/empty_view/themes/standard/banner/top.lt3 +20 -0
  53. data/empty_view/themes/standard/blog/generate.lt3 +21 -0
  54. data/empty_view/themes/standard/blog/head.lt3 +16 -0
  55. data/empty_view/themes/standard/blog/index.lt3 +17 -0
  56. data/empty_view/themes/standard/blog/post_entry.lt3 +21 -0
  57. data/empty_view/themes/standard/etc/blog.css.lt3 +62 -0
  58. data/empty_view/themes/standard/etc/externals.lt3 +24 -0
  59. data/empty_view/themes/standard/etc/favicon.ico +0 -0
  60. data/empty_view/themes/standard/etc/misc.js +20 -0
  61. data/empty_view/themes/standard/post/generate.lt3 +38 -0
  62. data/empty_view/themes/standard/post/head.lt3 +7 -0
  63. data/empty_view/themes/standard/post/index.lt3 +24 -0
  64. data/empty_view/themes/standard/post/permalink.lt3 +32 -0
  65. data/empty_view/themes/standard/widgets/README +4 -0
  66. data/empty_view/themes/standard/widgets/ad/ad.lt3 +22 -0
  67. data/empty_view/themes/standard/widgets/ad/ad1.png +0 -0
  68. data/empty_view/themes/standard/widgets/ad/ad2.png +0 -0
  69. data/empty_view/themes/standard/widgets/ad/ad3.png +0 -0
  70. data/empty_view/themes/standard/widgets/ad/ad4.png +0 -0
  71. data/empty_view/themes/standard/widgets/bydates/README +2 -0
  72. data/empty_view/themes/standard/widgets/bydates/bydates.rb +18 -0
  73. data/empty_view/themes/standard/widgets/bydates/card.css +1 -0
  74. data/empty_view/themes/standard/widgets/bydates/custom.rb +1 -0
  75. data/empty_view/themes/standard/widgets/bydates/main.css +2 -0
  76. data/empty_view/themes/standard/widgets/links/README +2 -0
  77. data/empty_view/themes/standard/widgets/links/card.css +1 -0
  78. data/empty_view/themes/standard/widgets/links/custom.rb +1 -0
  79. data/empty_view/themes/standard/widgets/links/links.rb +90 -0
  80. data/empty_view/themes/standard/widgets/links/list.data +3 -0
  81. data/empty_view/themes/standard/widgets/links/main.css +2 -0
  82. data/empty_view/themes/standard/widgets/news/README +2 -0
  83. data/empty_view/themes/standard/widgets/news/card.css +1 -0
  84. data/empty_view/themes/standard/widgets/news/custom.rb +1 -0
  85. data/empty_view/themes/standard/widgets/news/list.data +4 -0
  86. data/empty_view/themes/standard/widgets/news/main.css +2 -0
  87. data/empty_view/themes/standard/widgets/news/news.rb +88 -0
  88. data/empty_view/themes/standard/widgets/pages/README +2 -0
  89. data/empty_view/themes/standard/widgets/pages/card.css +1 -0
  90. data/empty_view/themes/standard/widgets/pages/custom.rb +1 -0
  91. data/empty_view/themes/standard/widgets/pages/disclaim.lt3 +10 -0
  92. data/empty_view/themes/standard/widgets/pages/faq.lt3 +40 -0
  93. data/empty_view/themes/standard/widgets/pages/like-dislike.lt3 +11 -0
  94. data/empty_view/themes/standard/widgets/pages/list.data +4 -0
  95. data/empty_view/themes/standard/widgets/pages/local.rb +0 -0
  96. data/empty_view/themes/standard/widgets/pages/main.css +2 -0
  97. data/empty_view/themes/standard/widgets/pages/other-stuff.lt3 +10 -0
  98. data/empty_view/themes/standard/widgets/pages/pages.rb +95 -0
  99. data/empty_view/themes/standard/widgets/pinned/README +2 -0
  100. data/empty_view/themes/standard/widgets/pinned/card.css +1 -0
  101. data/empty_view/themes/standard/widgets/pinned/custom.rb +1 -0
  102. data/empty_view/themes/standard/widgets/pinned/main.css +2 -0
  103. data/empty_view/themes/standard/widgets/pinned/pinned.rb +99 -0
  104. data/empty_view/themes/standard/widgets/search/README +2 -0
  105. data/empty_view/themes/standard/widgets/search/card.css +1 -0
  106. data/empty_view/themes/standard/widgets/search/custom.rb +1 -0
  107. data/empty_view/themes/standard/widgets/search/main.css +2 -0
  108. data/empty_view/themes/standard/widgets/search/search.rb +18 -0
  109. data/empty_view/themes/standard/widgets/sitemap/README +2 -0
  110. data/empty_view/themes/standard/widgets/sitemap/card.css +1 -0
  111. data/empty_view/themes/standard/widgets/sitemap/custom.rb +1 -0
  112. data/empty_view/themes/standard/widgets/sitemap/main.css +2 -0
  113. data/empty_view/themes/standard/widgets/sitemap/sitemap.rb +18 -0
  114. data/empty_view/themes/standard/widgets/tag-cloud/OLD-example.lt3 +12 -0
  115. data/empty_view/themes/standard/widgets/tag-cloud/README +2 -0
  116. data/empty_view/themes/standard/widgets/tag-cloud/card.css +1 -0
  117. data/empty_view/themes/standard/widgets/tag-cloud/custom.rb +1 -0
  118. data/empty_view/themes/standard/widgets/tag-cloud/main.css +2 -0
  119. data/empty_view/themes/standard/widgets/tag-cloud/tag-cloud.lt3 +3 -0
  120. data/empty_view/themes/standard/widgets/tag-cloud/tag-cloud.rb +18 -0
  121. data/lib/Javascript.stuff +69 -0
  122. data/lib/helpers-blog.rb +155 -0
  123. data/lib/helpers-repl.rb +201 -0
  124. data/lib/liveblog.rb +825 -0
  125. data/lib/logging.rb +44 -0
  126. data/lib/lowlevel.rb +73 -0
  127. data/lib/pathmagic.rb +14 -0
  128. data/lib/post.rb +211 -0
  129. data/lib/processing.rb +60 -0
  130. data/lib/publish.rb +73 -0
  131. data/lib/repl.rb +597 -0
  132. data/lib/runeblog.rb +773 -0
  133. data/lib/runeblog_version.rb +50 -0
  134. data/lib/view.rb +69 -0
  135. data/runeblog.gemspec +42 -0
  136. data/test/austin.rb +158 -0
  137. data/test/fakeimage.jpg +0 -0
  138. data/test/general_test.rb +304 -0
  139. data/test/make_blog.rb +196 -0
  140. data/test/test +3 -0
  141. metadata +242 -0
@@ -0,0 +1,201 @@
1
+
2
+ # Reopening...
3
+
4
+ make_exception(:CantOpen, "Can't open '$1'")
5
+ make_exception(:CantDelete, "Can't open '$1'")
6
+ make_exception(:InternalError, "Glitch: $1 got arg '$2'")
7
+ make_exception(:CantCopy, "Can't copy $1 to $2")
8
+
9
+
10
+ module RuneBlog::REPL
11
+ Patterns =
12
+ {"help" => :cmd_help,
13
+ "h" => :cmd_help,
14
+ "version" => :cmd_version,
15
+ "v" => :cmd_version,
16
+ "list views" => :cmd_list_views,
17
+ "lsv" => :cmd_list_views,
18
+ "clear" => :cmd_clear,
19
+
20
+ "new view $name" => :cmd_new_view,
21
+
22
+ "tags" => :cmd_tags,
23
+ "import" => :cmd_import,
24
+
25
+ "new post" => :cmd_new_post,
26
+ "p" => :cmd_new_post,
27
+ "post" => :cmd_new_post,
28
+
29
+ "change view $name" => :cmd_change_view,
30
+ "cv $name" => :cmd_change_view,
31
+ "cv" => :cmd_change_view, # 0-arity must come second
32
+
33
+ "config" => :cmd_config,
34
+ "manage $widget" => :cmd_manage,
35
+
36
+ "legacy" => :cmd_legacy,
37
+
38
+ "list posts" => :cmd_list_posts,
39
+ "lsp" => :cmd_list_posts,
40
+
41
+ "list drafts" => :cmd_list_drafts,
42
+ "lsd" => :cmd_list_drafts,
43
+
44
+ "list assets" => :cmd_list_assets,
45
+ "lsa" => :cmd_list_assets,
46
+
47
+ "pages" => :cmd_pages,
48
+
49
+ "delete >postid" => :cmd_remove_post,
50
+ "undel $postid" => :cmd_undelete_post,
51
+
52
+ "edit $postid" => :cmd_edit_post,
53
+ "ed $postid" => :cmd_edit_post,
54
+ "e $postid" => :cmd_edit_post,
55
+
56
+ "preview" => :cmd_preview,
57
+
58
+ "browse" => :cmd_browse,
59
+
60
+ "rebuild" => :cmd_rebuild,
61
+
62
+ "publish" => :cmd_publish,
63
+
64
+ "ssh" => :cmd_ssh,
65
+
66
+ "q" => :cmd_quit,
67
+ "quit" => :cmd_quit
68
+ }
69
+
70
+ Abbr = {
71
+ "h" => :cmd_help,
72
+ "v" => :cmd_version,
73
+ "lsv" => :cmd_list_views,
74
+ "p" => :cmd_new_post,
75
+ "cv $name" => :cmd_change_view,
76
+ "cv" => :cmd_change_view, # 0-arity must come second
77
+ "lsp" => :cmd_list_posts,
78
+ "lsd" => :cmd_list_drafts,
79
+ "list assets" => :cmd_list_assets,
80
+ "lsa" => :cmd_list_assets,
81
+ "rm $postid" => :cmd_remove_post,
82
+ "ed $postid" => :cmd_edit_post,
83
+ "e $postid" => :cmd_edit_post,
84
+ "q" => :cmd_quit
85
+ }
86
+
87
+ Regexes = {}
88
+ Patterns.each_pair do |pat, meth|
89
+ rx = "^" + pat
90
+ rx.gsub!(/ /, " +")
91
+ rx.gsub!(/\$(\w+) */) { " *(?<#{$1}>\\w+)" }
92
+ # How to handle multiple optional args?
93
+ rx.sub!(/>(\w+)$/) { "(.+)" }
94
+ rx << "$"
95
+ rx = Regexp.new(rx)
96
+ Regexes[rx] = meth
97
+ end
98
+
99
+ def self.choose_method(cmd)
100
+ cmd = cmd.strip
101
+ found = nil
102
+ params = nil
103
+ Regexes.each_pair do |rx, meth|
104
+ m = cmd.match(rx)
105
+ result = m ? m.to_a : nil
106
+ next unless result
107
+ found = meth
108
+ params = m[1]
109
+ end
110
+ meth = found || :cmd_INVALID
111
+ params = cmd if meth == :cmd_INVALID
112
+ [meth, params]
113
+ end
114
+
115
+ def ask(prompt, meth = :to_s)
116
+ print prompt
117
+ gets.chomp.send(meth)
118
+ end
119
+
120
+ def ask!(prompt, meth = :to_s)
121
+ ask(fx(prompt, :bold), meth)
122
+ end
123
+
124
+ def reset_output(initial = "")
125
+ @out ||= ""
126
+ @out.replace initial
127
+ end
128
+
129
+ def flush_output(initial = "")
130
+ CantOpen
131
+ @out ||= ""
132
+ puts @out
133
+ reset_output
134
+ end
135
+
136
+ def output(str) # \n and indent
137
+ @out ||= ""
138
+ @out << " " + str.to_s
139
+ end
140
+
141
+ def outstr(str) # indent
142
+ @out ||= ""
143
+ @out << str
144
+ end
145
+
146
+ def output!(str) # \n and indent
147
+ @out ||= ""
148
+ @out << " " + str
149
+ end
150
+
151
+ def output_newline(n = 1)
152
+ @out ||= ""
153
+ n.times { @out << "\n" }
154
+ end
155
+
156
+ def check_empty(arg)
157
+ raise InternalError(caller[0], arg.inspect) unless arg.nil?
158
+ end
159
+
160
+ def get_integer(arg)
161
+ Integer(arg)
162
+ rescue
163
+ raise ArgumentError, "'#{arg}' is not an integer"
164
+ end
165
+
166
+ def check_file_exists(file)
167
+ raise FileNotFound(file) unless File.exist?(file)
168
+ end
169
+
170
+ def error_cant_delete(files)
171
+ case files
172
+ when String
173
+ raise CantDelete(files)
174
+ when Array
175
+ raise CantDelete(files.join("\n"))
176
+ end
177
+ end
178
+
179
+ def colored_slug(slug)
180
+ slug[0..3] + slug[4..-1]
181
+ end
182
+
183
+ def tags_for_view(vname = @blog.view)
184
+ Dir.chdir(vname) do
185
+ fname = "tagpool"
186
+ if File.exist?(fname)
187
+ tags = File.readlines(fname).map(&:chomp)
188
+ else
189
+ tags = []
190
+ end
191
+ end
192
+ tags.sort
193
+ end
194
+
195
+ def all_tags
196
+ all = []
197
+ @blog.views.each {|view| all.append(*tags_for_view(view)) }
198
+ all.sort + ["NEW TAG"]
199
+ end
200
+
201
+ end
@@ -0,0 +1,825 @@
1
+ require 'ostruct'
2
+ require 'pp'
3
+ require 'date'
4
+ require 'find'
5
+
6
+ require 'runeblog'
7
+ require 'pathmagic'
8
+ require 'processing'
9
+
10
+ # top = Livetext::Path + "/../plugin/liveblog/"
11
+ # eval(File.read("#{top}/testing.rb"))
12
+
13
+
14
+ def init_liveblog # FIXME - a lot of this logic sucks
15
+ dir = Dir.pwd.sub(/\.blogs.*/, "")
16
+ @blog = nil
17
+ Dir.chdir(dir) { @blog = RuneBlog.new }
18
+ @root = @blog.root
19
+ @view = @blog.view
20
+ @view_name = @blog.view.name unless @view.nil?
21
+ @vdir = @blog.view.dir rescue "NONAME"
22
+ @version = RuneBlog::VERSION
23
+ @theme = @vdir/:themes/:standard
24
+ rescue
25
+ raise "Only works inside a blog repo"
26
+ end
27
+
28
+ ##################
29
+ # "dot" commands
30
+ ##################
31
+
32
+ def dropcap
33
+ # Bad form: adds another HEAD
34
+ text = _data
35
+ _out " "
36
+ letter = text[0]
37
+ remain = text[1..-1]
38
+ _out %[<div class='mydrop'>#{letter}</div>]
39
+ _out %[<div style="padding-top: 1px">#{remain}]
40
+ end
41
+
42
+ def post
43
+ @meta = OpenStruct.new
44
+ @meta.num = _args[0]
45
+ _out " <!-- Post number #{@meta.num} -->\n "
46
+ end
47
+
48
+ def _got_python?
49
+ # Dumb - fix later - check up front as needed
50
+ # Should also check for praw lib
51
+ str = `which python3`
52
+ str.length > 0
53
+ end
54
+
55
+ def _reddit_post_url(vdir, title, url)
56
+ _got_python?
57
+ tmpfile = "/tmp/reddit-post-url.txt"
58
+ File.open(tmpfile, "w") do |tmp|
59
+ tmp.puts "[Post] " + title
60
+ tmp.puts url
61
+ end
62
+ rid = nil
63
+ Dir.chdir(vdir/:config) { rid = `python3 reddit/reddit_post_url.py` }
64
+ system("rm #{tmpfile}")
65
+ rid # returns reddit id
66
+ end
67
+
68
+ def post_trailer
69
+ perma = _var("publish.proto") + "://" + _var("publish.server") +
70
+ "/" + _var("publish.path") + "/" + _var("post.aslug") +
71
+ ".html"
72
+ tags = _var("post.tags")
73
+ taglist = tags.empty? ? "" : "Tags: #{tags}"
74
+
75
+ reddit_enabled = @blog.features["reddit"] rescue nil
76
+ reddit_txt = ""
77
+ if reddit_enabled
78
+ vdir = @blog.root/:views/@blog.view
79
+ nslug = "#{_var("post.num")}-#{_var("post.aslug")}"
80
+ rid_file = vdir/:posts/nslug/"reddit.id"
81
+ rid = File.exist?(rid_file) ? File.read(rid_file).chomp : nil
82
+ if rid.nil?
83
+ title = _var("title")
84
+ rid = _reddit_post_url(vdir, title, perma)
85
+ dump(rid, rid_file)
86
+ end
87
+ reddit_txt = <<~HTML
88
+ <hr>
89
+ <script src='https://redditjs.com/post.js'
90
+ data-url="#{rid}" data-width=800 ></script>
91
+ HTML
92
+ # damned syntax highlighting </>
93
+ end
94
+ _out <<~HTML
95
+ <table width=100%><tr>
96
+ <td width=10%><a style="text-decoration: none" href="javascript:history.go(-1)">[Back]</a></td>
97
+ <td width=10%><a style="text-decoration: none" href="#{perma}"> [permalink] </a></td>
98
+ <td width=80% align=right><font size=-3>#{taglist}</font></td></tr></table>
99
+ #{reddit_txt}
100
+ HTML
101
+ end
102
+
103
+ def faq
104
+ @faq_count ||= 0
105
+ _out "<br>" if @faq_count == 0
106
+ @faq_count += 1
107
+ ques = _data.chomp
108
+ ans = _body_text
109
+ id = "faq#@faq_count"
110
+ _out %[&nbsp;<a data-toggle="collapse" href="##{id}" role="button" aria-expanded="false" aria-controls="collapseExample"><font size=+3>&#8964;</font></a>]
111
+ _out %[&nbsp;<b>#{ques}</b>]
112
+ _out %[<div class="collapse" id="#{id}"><br><font size=+1>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#{ans}</font></div>\n]
113
+ _out "<br>" unless @faq_count == 1
114
+ _optional_blank_line
115
+ end
116
+
117
+ def backlink
118
+ _out %[<br><a href="javascript:history.go(-1)">[Back]</a>]
119
+ end
120
+
121
+ def code
122
+ lines = _body_text
123
+ _out "<font size=+1><pre>\n#{lines}\n</pre></font>"
124
+ end
125
+
126
+ def _read_navbar_data
127
+ vdir = @blog.root/:views/@blog.view
128
+ dir = vdir/"themes/standard/banner/navbar/"
129
+ datafile = dir/"list.data"
130
+ _get_data(datafile)
131
+ end
132
+
133
+ def banner
134
+ count = 0
135
+ bg = "white" # outside loop
136
+ wide = nil
137
+ high = 250
138
+ str2 = ""
139
+ navbar = nil
140
+ vdir = @blog.root/:views/@blog.view
141
+ lines = _body.to_a
142
+
143
+ lines.each do |line|
144
+ count += 1
145
+ tag, *data = line.split
146
+ data ||= []
147
+ case tag
148
+ when "width"; wide = data[0]
149
+ when "height"; high = data[0]
150
+ when "bgcolor"; bg = data[0] || "white"
151
+ when "image"
152
+ image = data[0] || "banner.jpg"
153
+ image = "banner"/image
154
+ wide = data[0]
155
+ width = wide ? "width=#{wide}" : ""
156
+ str2 << " <td><img src=#{image} #{width} height=#{high}></img></td>" + "\n"
157
+ when "svg_title"
158
+ stuff, hash = _svg_title(*data)
159
+ wide = hash["width"]
160
+ str2 << " <td width=#{wide}>#{stuff}</td>" + "\n"
161
+ when "text"
162
+ data[0] ||= "top.html"
163
+ file = "banner"/data[0]
164
+ if ! File.exist?(file)
165
+ src = file.sub(/html$/, "lt3")
166
+ if File.exist?(src)
167
+ preprocess src: src, dst: file, call: ".nopara" # , vars: @blog.view.globals
168
+ else
169
+ raise "Neither #{file} nor #{src} found"
170
+ end
171
+ end
172
+ str2 << "<td>" + File.read(file) + "</td>" + "\n"
173
+ when "navbar"
174
+ navbar = _make_navbar # horiz is default
175
+ when "vnavbar"
176
+ navbar = _make_navbar(:vert)
177
+ when "break"
178
+ str2 << " </tr>\n <tr>" + "\n"
179
+ else
180
+ str2 << " '#{tag}' isn't known" + "\n"
181
+ end
182
+ end
183
+ _out <<~HTML
184
+ <table width=100% bgcolor=#{bg}>
185
+ <tr>
186
+ #{str2}
187
+ </tr>
188
+ </table>
189
+ HTML
190
+ _out navbar if navbar
191
+ rescue => err
192
+ STDERR.puts "err = #{err}"
193
+ STDERR.puts err.backtrace.join("\n")
194
+ gets
195
+ end
196
+
197
+ def _svg_title(*args)
198
+ width = "95%"
199
+ height = 90
200
+ bgcolor = "black"
201
+ style = nil
202
+ size = ""
203
+ font = "sans-serif"
204
+ color = "white"
205
+ xy = "5,5"
206
+ align = "center"
207
+ style2 = nil
208
+ size2 = ""
209
+ font2 = "sans-serif"
210
+ color2 = "white"
211
+ xy2 = "5,5"
212
+ align2 = "center"
213
+
214
+ e = args.each
215
+ hash = {} # TODO get rid of hash??
216
+
217
+ valid = %w[width height bgcolor style size font color xy
218
+ align style2 size2 font2 color2 xy2 align2]
219
+ os = OpenStruct.new
220
+ loop do
221
+ arg = e.next
222
+ arg = arg.chop
223
+ raise "Don't know '#{arg}'" unless valid.include?(arg)
224
+ os.send(arg+"=", e.next)
225
+ end
226
+ x, y = xy.split(",")
227
+ x2, y2 = xy2.split(",")
228
+ names = %w[x y x2 y2] + valid
229
+ names.each {|name| hash[name] = os.send(name) }
230
+ result = <<~HTML
231
+ <svg width="#{width}" height="#{height}"
232
+ viewBox="0 0 #{width} #{height}">
233
+ <defs>
234
+ <linearGradient id="grad1" x1="100%" y1="100%" x2="0%" y2="100%">
235
+ <stop offset="0%" style="stop-color:rgb(198,198,228);stop-opacity:1" />
236
+ <stop offset="100%" style="stop-color:rgb(30,30,50);stop-opacity:1" />
237
+ </linearGradient>
238
+ </defs>
239
+ <style>
240
+ .title { font: #{style} #{size} #{font}; fill: #{color} }
241
+ .subtitle { font: #{style2} #{size2} #{font2}; fill: #{color2} }
242
+ </style>
243
+ <rect x="10" y="10" rx="10" ry="10" width="#{width}" height="#{height}" fill="url(#grad1)"/>
244
+ <text text-anchor="#{align}" x="#{x}" y="#{y}" class="title">#{Livetext::Vars["view.title"]} </text>
245
+ <text text-anchor="#{align2}" x="#{x2}" y="#{y2}" class="subtitle">#{Livetext::Vars["view.subtitle"]} </text>
246
+ </svg>
247
+ <!-- ^ how does syntax highlighting get messed up? </svg> -->
248
+ HTML
249
+ [result, hash]
250
+ end
251
+
252
+ def quote
253
+ _passthru "<blockquote>"
254
+ _passthru _body.join(" ")
255
+ _passthru "</blockquote>"
256
+ _optional_blank_line
257
+ end
258
+
259
+ def categories # does nothing right now
260
+ end
261
+
262
+ def style
263
+ fname = _args[0]
264
+ _passthru %[<link rel="stylesheet" href="???/etc/#{fname}')">]
265
+ end
266
+
267
+ # Move elsewhere later!
268
+
269
+ def h1; _passthru "<h1>#{@_data}</h1>"; end
270
+ def h2; _passthru "<h2>#{@_data}</h2>"; end
271
+ def h3; _passthru "<h3>#{@_data}</h3>"; end
272
+ def h4; _passthru "<h4>#{@_data}</h4>"; end
273
+ def h5; _passthru "<h5>#{@_data}</h5>"; end
274
+ def h6; _passthru "<h6>#{@_data}</h6>"; end
275
+
276
+ def hr; _passthru "<hr>"; end
277
+
278
+ def nlist
279
+ _out "<ol>"
280
+ _body {|line| _out "<li>#{line}</li>" }
281
+ _out "</ol>"
282
+ _optional_blank_line
283
+ end
284
+
285
+ def list
286
+ _out "<ul>"
287
+ _body {|line| _out "<li>#{line}</li>" }
288
+ _out "</ul>"
289
+ _optional_blank_line
290
+ end
291
+
292
+ def list!
293
+ _out "<ul>"
294
+ lines = _body.each
295
+ loop do
296
+ line = lines.next
297
+ line = _format(line)
298
+ if line[0] == " "
299
+ _out line
300
+ else
301
+ _out "<li>#{line}</li>"
302
+ end
303
+ end
304
+ _out "</ul>"
305
+ _optional_blank_line
306
+ end
307
+
308
+ ### inset
309
+
310
+ def inset
311
+ lines = _body
312
+ box = ""
313
+ output = []
314
+ lines.each do |line|
315
+ line = line
316
+ case line[0]
317
+ when "/" # Only into inset
318
+ line[0] = ' '
319
+ box << line
320
+ line.replace(" ")
321
+ when "|" # Into inset and body
322
+ line[0] = ' '
323
+ box << line
324
+ output << line
325
+ else # Only into body
326
+ output << line
327
+ end
328
+ end
329
+ lr = _args.first
330
+ wide = _args[1] || "25"
331
+ stuff = "<div style='float:#{lr}; width: #{wide}%; padding:8px; padding-right:12px'>"
332
+ stuff << '<b><i>' + box + '</i></b></div>'
333
+ _out "</p>" # kludge!! nopara
334
+ 0.upto(2) {|i| _passthru output[i] }
335
+ _passthru stuff
336
+ 3.upto(output.length-1) {|i| _passthru output[i] }
337
+ _out "<p>" # kludge!! para
338
+ _optional_blank_line
339
+ end
340
+
341
+ def title
342
+ raise "'post' was not called" unless @meta
343
+ title = @_data.chomp
344
+ @meta.title = title
345
+ setvar :title, title
346
+ # FIXME refactor -- just output variables for a template
347
+ _optional_blank_line
348
+ end
349
+
350
+ def pubdate
351
+ raise "'post' was not called" unless @meta
352
+ _debug "data = #@_data"
353
+ # Check for discrepancy?
354
+ match = /(\d{4}).(\d{2}).(\d{2})/.match @_data
355
+ junk, y, m, d = match.to_a
356
+ y, m, d = y.to_i, m.to_i, d.to_i
357
+ @meta.date = ::Date.new(y, m, d)
358
+ @meta.pubdate = "%04d-%02d-%02d" % [y, m, d]
359
+ _optional_blank_line
360
+ end
361
+
362
+ def image # primitive so far
363
+ _debug "img: huh? <img src=#{_args.first}></img>"
364
+ fname = _args.first
365
+ path = :assets/fname
366
+ _out "<img src=#{path}></img>"
367
+ _optional_blank_line
368
+ end
369
+
370
+ def tags
371
+ raise "'post' was not called" unless @meta
372
+ _debug "args = #{_args}"
373
+ @meta.tags = _args.dup || []
374
+ _optional_blank_line
375
+ end
376
+
377
+ def views
378
+ raise "'post' was not called" unless @meta
379
+ _debug "data = #{_args}"
380
+ @meta.views = _args.dup
381
+ _optional_blank_line
382
+ end
383
+
384
+ def pin
385
+ raise "'post' was not called" unless @meta
386
+ _debug "data = #{_args}" # verify only valid views?
387
+ pinned = @_args
388
+ @meta.pinned = pinned
389
+ pinned.each do |pinview|
390
+ dir = @blog.root/:views/pinview/"themes/standard/widgets/pinned/"
391
+ datafile = dir/"list.data"
392
+ pins = _get_data?(datafile)
393
+ pins << "#{@meta.num} #{@meta.title}\n"
394
+ pins.uniq!
395
+ File.open(datafile, "w") {|out| pins.each {|pin| out.puts pin } }
396
+ end
397
+ _optional_blank_line
398
+ rescue => err
399
+ STDERR.puts "err = #{err}"
400
+ STDERR.puts err.backtrace.join("\n")
401
+ gets
402
+ end
403
+
404
+ def write_post
405
+ raise "'post' was not called" unless @meta
406
+ @meta.views = @meta.views.join(" ") if @meta.views.is_a? Array
407
+ @meta.tags = @meta.tags.join(" ") if @meta.tags.is_a? Array
408
+ _write_metadata
409
+ rescue => err
410
+ puts "err = #{err}"
411
+ puts err.backtrace.join("\n")
412
+ end
413
+
414
+ def teaser
415
+ raise "'post' was not called" unless @meta
416
+ text = _body_text
417
+ @meta.teaser = text
418
+ setvar :teaser, @meta.teaser
419
+ if _args[0] == "dropcap" # FIXME doesn't work yet!
420
+ letter, remain = text[0], text[1..-1]
421
+ _out %[<div class='mydrop'>#{letter}</div>]
422
+ _out %[<div style="padding-top: 1px">#{remain}] + "\n"
423
+ else
424
+ _out @meta.teaser + "\n"
425
+ end
426
+ end
427
+
428
+ def finalize
429
+ return unless @meta
430
+ return @meta if @blog.nil?
431
+
432
+ @slug = @blog.make_slug(@meta)
433
+ slug_dir = @slug
434
+ @postdir = @blog.view.dir/:posts/slug_dir
435
+ write_post
436
+ @meta
437
+ end
438
+
439
+ def head # Does NOT output <head> tags
440
+ args = _args
441
+ args.each do |inc|
442
+ self.data = inc
443
+ _include
444
+ end
445
+ # Depends on vars: title, desc, host
446
+ defaults = {}
447
+ defaults = { "charset" => %[<meta charset="utf-8">],
448
+ "http-equiv" => %[<meta http-equiv="X-UA-Compatible" content="IE=edge">],
449
+ "title" => %[<title>\n #{_var("view.title")} | #{_var("view.subtitle")}\n </title>],
450
+ "generator" => %[<meta name="generator" content="Runeblog v #@version">],
451
+ "og:title" => %[<meta property="og:title" content="#{_var("view.title")}">],
452
+ "og:locale" => %[<meta property="og:locale" content="#{_var(:locale)}">],
453
+ "description" => %[<meta name="description" content="#{_var("view.subtitle")}">],
454
+ "og:description" => %[<meta property="og:description" content="#{_var("view.subtitle")}">],
455
+ "linkc" => %[<link rel="canonical" href="#{_var(:host)}">],
456
+ "og:url" => %[<meta property="og:url" content="#{_var(:host)}">],
457
+ "og:site_name" => %[<meta property="og:site_name" content="#{_var("view.title")}">],
458
+ # "style" => %[<link rel="stylesheet" href="etc/blog.css">],
459
+ # ^ FIXME
460
+ "feed" => %[<link type="application/atom+xml" rel="alternate" href="#{_var(:host)}/feed.xml" title="#{_var("view.title")}">],
461
+ "favicon" => %[<link rel="shortcut icon" type="image/x-icon" href="etc/favicon.ico">\n <link rel="apple-touch-icon" href="etc/favicon.ico">]
462
+ }
463
+ result = {}
464
+ lines = _body
465
+ lines.each do |line|
466
+ line.chomp
467
+ word, remain = line.split(" ", 2)
468
+ case word
469
+ when "viewport"
470
+ result["viewport"] = %[<meta name="viewport" content="#{remain}">]
471
+ when "script" # FIXME this is broken
472
+ file = remain
473
+ text = File.read(file)
474
+ result["script"] = Livetext.new.transform(text)
475
+ when "style"
476
+ result["style"] = %[<link rel="stylesheet" href="etc/#{remain}">]
477
+ # Later: allow other overrides
478
+ when ""; break
479
+ else
480
+ if defaults[word]
481
+ result[word] = %[<meta property="#{word}" content="#{remain}">]
482
+ else
483
+ puts "Unknown tag '#{word}'"
484
+ end
485
+ end
486
+ end
487
+ hash = defaults.dup.update(result) # FIXME collisions?
488
+
489
+ hash.each_value {|x| _out " " + x }
490
+ end
491
+
492
+ ########## newer stuff...
493
+
494
+ def meta
495
+ args = _args
496
+ enum = args.each
497
+ str = "<meta"
498
+ arg = enum.next
499
+ loop do
500
+ if arg.end_with?(":")
501
+ str << " " << arg[0..-2] << "="
502
+ a2 = enum.next
503
+ str << %["#{a2}"]
504
+ else
505
+ STDERR.puts "=== meta error?"
506
+ end
507
+ arg = enum.next
508
+ end
509
+ str << ">"
510
+ _out str
511
+ end
512
+
513
+ def recent_posts # side-effect
514
+ _out <<-HTML
515
+ <div class="col-lg-9 col-md-9 col-sm-9 col-xs-12">
516
+ <iframe id="main" style="width: 70vw; height: 100vh; position: relative;"
517
+ src='recent.html' width=100% frameborder="0" allowfullscreen>
518
+ </iframe>
519
+ </div>
520
+ HTML
521
+ end
522
+
523
+ def _make_class_name(app)
524
+ if app =~ /[-_]/
525
+ words = app.split(/[-_]/)
526
+ name = words.map(&:capitalize).join
527
+ else
528
+ name = app.capitalize
529
+ end
530
+ return name
531
+ end
532
+
533
+ def _load_local(widget)
534
+ Dir.chdir("widgets/#{widget}") do
535
+ rclass = _make_class_name(widget)
536
+ found = (require("./#{widget}") if File.exist?("#{widget}.rb"))
537
+ code = found ? ::RuneBlog::Widget.class_eval(rclass) : nil
538
+ code
539
+ end
540
+ rescue => err
541
+ STDERR.puts err.to_s
542
+ STDERR.puts err.backtrace.join("\n")
543
+ sleep 6; RubyText.stop
544
+ exit
545
+ end
546
+
547
+ def _handle_standard_widget(tag)
548
+ wtag = :widgets/tag
549
+ code = _load_local(tag)
550
+ if code
551
+ Dir.chdir(wtag) do
552
+ widget = code.new(@blog)
553
+ widget.build
554
+ end
555
+ end
556
+ end
557
+
558
+ def sidebar
559
+ _debug "--- handling sidebar\r"
560
+ if _args.include? "off"
561
+ _body { } # iterate, do nothing
562
+ return
563
+ end
564
+
565
+ _out %[<div class="col-lg-3 col-md-3 col-sm-3 col-xs-12">]
566
+
567
+ standard = %w[pinned pages links news]
568
+
569
+ _body do |token|
570
+ tag = token.chomp.strip.downcase
571
+ wtag = :widgets/tag
572
+ raise "Can't find #{wtag}" unless Dir.exist?(wtag)
573
+ tcard = "#{tag}-card.html"
574
+
575
+ case
576
+ when standard.include?(tag)
577
+ _handle_standard_widget(tag)
578
+ when tag == "ad"
579
+ num = rand(1..4)
580
+ img = "widgets/ad/ad#{num}.png"
581
+ src, dst = img, @root/:views/@view_name/"remote/widgets/ad/"
582
+ system!("cp #{src} #{dst}")
583
+ File.open(wtag/"vars.lt3", "w") {|f| f.puts ".set ad.image = #{img}" }
584
+ preprocess cwd: wtag, src: tag, dst: tcard, call: ".nopara",
585
+ force: true # , debug: true # , deps: depend
586
+ end
587
+
588
+ _include_file wtag/tcard
589
+ end
590
+ _out %[</div>]
591
+ rescue => err
592
+ puts "err = #{err}"
593
+ puts err.backtrace.join("\n")
594
+ sleep 6; RubyText.stop
595
+ exit
596
+ end
597
+
598
+ def stylesheet
599
+ lines = _body
600
+ url = lines[0]
601
+ integ = lines[1]
602
+ cross = lines[2] || "anonymous"
603
+ _out %[<link rel="stylesheet" href="#{url}" integrity="#{integ}" crossorigin="#{cross}"></link>]
604
+ end
605
+
606
+ def script
607
+ lines = _body
608
+ url = lines[0]
609
+ integ = lines[1]
610
+ cross = lines[2] || "anonymous"
611
+ _out %[<script src="#{url}" integrity="#{integ}" crossorigin="#{cross}"></script>]
612
+ end
613
+
614
+ $Dot = self # Clunky! for dot commands called from Functions class
615
+
616
+ # Find a better way to do this?
617
+
618
+ class Livetext::Functions
619
+
620
+ def br(n="1")
621
+ # Thought: Maybe make a way for functions to "simply" call the
622
+ # dot command of the same name?? Is this trivial??
623
+ n = n.empty? ? 1 : n.to_i
624
+ "<br>"*n
625
+ end
626
+
627
+ def h1(param); "<h1>#{param}</h1>"; end
628
+ def h2(param); "<h2>#{param}</h2>"; end
629
+ def h3(param); "<h3>#{param}</h3>"; end
630
+ def h4(param); "<h4>#{param}</h4>"; end
631
+ def h5(param); "<h5>#{param}</h5>"; end
632
+ def h6(param); "<h6>#{param}</h6>"; end
633
+
634
+ def hr(param=nil)
635
+ $Dot.hr
636
+ end
637
+
638
+ def image(param)
639
+ "<img src='#{param}'></img>"
640
+ end
641
+
642
+ end
643
+
644
+ ###### experimental...
645
+
646
+ class Livetext::Functions
647
+ def _var(name)
648
+ ::Livetext::Vars[name] || "[:#{name} is undefined]"
649
+ end
650
+ end
651
+
652
+ ###
653
+
654
+
655
+ def tag_cloud
656
+ title = _data
657
+ title = "Tag Cloud" if title.empty?
658
+ open = <<-HTML
659
+ <div class="card mb-3">
660
+ <div class="card-body">
661
+ <h5 class="card-title">
662
+ <button type="button" class="btn btn-primary" data-toggle="collapse" data-target="#tag-cloud">+</button>
663
+ #{title}
664
+ </h5>
665
+ <div class="collapse" id="tag-cloud">
666
+ HTML
667
+ _out open
668
+ _body do |line|
669
+ line.chomp!
670
+ url, classname, cdata = line.split(",", 3)
671
+ main = _main(url)
672
+ _out %[<a #{main} class="#{classname}">#{cdata}</a>]
673
+ end
674
+ close = %[ </div>\n </div>\n </div>]
675
+ _out close
676
+ end
677
+
678
+ def vnavbar
679
+ str = _make_navbar(:vert)
680
+ end
681
+
682
+ def hnavbar
683
+ str = _make_navbar # horiz is default
684
+ end
685
+
686
+ def navbar
687
+ str = _make_navbar # horiz is default
688
+ end
689
+
690
+ def _make_navbar(orient = :horiz)
691
+ vdir = @root/:views/@blog.view
692
+ title = _var("view.title")
693
+
694
+ if orient == :horiz
695
+ name = "navbar.html"
696
+ li1, li2 = "", ""
697
+ extra = "navbar-expand-lg"
698
+ list1 = list2 = ""
699
+ else
700
+ name = "vnavbar.html"
701
+ li1, li2 = '<li class="nav-item">', "</li>"
702
+ extra = ""
703
+ list1, list2 = '<l class="navbar-nav mr-auto">', "</ul>"
704
+ end
705
+
706
+ start = <<-HTML
707
+ <table><tr><td>
708
+ <nav class="navbar #{extra} navbar-light bg-light">
709
+ #{list1}
710
+ HTML
711
+ finish = <<-HTML
712
+ #{list2}
713
+ </nav>
714
+ </td></tr></table>
715
+ HTML
716
+
717
+ html_file = @blog.root/:views/@blog.view/"themes/standard/banner/navbar"/name
718
+ output = File.new(html_file, "w")
719
+ output.puts start
720
+ lines = _read_navbar_data
721
+ lines = ["index Home"] + lines unless _args.include?("nohome")
722
+ lines.each do |line|
723
+ basename, cdata = line.chomp.strip.split(" ", 2)
724
+ full = :banner/:navbar/basename+".html"
725
+ href_main = _main(full)
726
+ if basename == "index" # special case
727
+ output.puts %[#{li1} <a class="nav-link" href="index.html">#{cdata}<span class="sr-only">(current)</span></a> #{li2}]
728
+ else
729
+ dir = @blog.root/:views/@blog.view/"themes/standard/banner/navbar"
730
+ dest = vdir/"remote/banner/navbar"/basename+".html"
731
+ preprocess cwd: dir, src: basename, dst: dest, call: ".nopara" # , debug: true
732
+ output.puts %[#{li1} <a class="nav-link" #{href_main}>#{cdata}</a> #{li2}]
733
+ end
734
+ end
735
+ output.puts finish
736
+ output.close
737
+ return File.read(html_file)
738
+ end
739
+
740
+
741
+ ##################
742
+ # helper methods
743
+ ##################
744
+
745
+ def _html_body(file, css = nil)
746
+ file.puts "<html>"
747
+ if css
748
+ file.puts " <head>"
749
+ file.puts " <style>\n#{css}\n </style>"
750
+ file.puts " </head>"
751
+ end
752
+ file.puts " <body>"
753
+ yield
754
+ file.puts " </body>\n</html>"
755
+ end
756
+
757
+ def _errout(*args)
758
+ ::STDERR.puts *args
759
+ end
760
+
761
+ def _passthru(line)
762
+ return if line.nil?
763
+ line = _format(line)
764
+ _out line + "\n"
765
+ _out "<p>" if line.empty? && ! @_nopara
766
+ end
767
+
768
+ def _passthru_noline(line)
769
+ return if line.nil?
770
+ line = _format(line)
771
+ _out line
772
+ _out "<p>" if line.empty? && ! @_nopara
773
+ end
774
+
775
+ def _write_metadata
776
+ File.write("teaser.txt", @meta.teaser)
777
+ fields = [:num, :title, :date, :pubdate, :views, :tags, :pinned]
778
+ fname2 = "metadata.txt"
779
+ f2 = File.open(fname2, "w") do |f2|
780
+ fields.each {|fld| f2.puts "#{fld}: #{@meta.send(fld)}" }
781
+ end
782
+ end
783
+
784
+ def _post_lookup(postid) # side-effect
785
+ # .. = templates, ../.. = views/thisview
786
+ slug = title = date = teaser_text = nil
787
+
788
+ dir_posts = @vdir/:posts
789
+ posts = Dir.entries(dir_posts).grep(/^\d\d\d\d/).map {|x| dir_posts/x }
790
+ posts.select! {|x| File.directory?(x) }
791
+
792
+ post = posts.select {|x| File.basename(x).to_i == postid }
793
+ raise "Error: More than one post #{postid}" if post.size > 1
794
+ postdir = post.first
795
+ vp = RuneBlog::ViewPost.new(@blog.view, postdir)
796
+ vp
797
+ end
798
+
799
+ def _card_generic(card_title:, middle:, extra: "")
800
+ front = <<-HTML
801
+ <div class="card #{extra} mb-3">
802
+ <div class="card-body">
803
+ <h5 class="card-title">#{card_title}</h5>
804
+ HTML
805
+
806
+ tail = <<-HTML
807
+ </div>
808
+ </div>
809
+ HTML
810
+ text = front + middle + tail
811
+ _out text + "\n "
812
+ end
813
+
814
+ def _var(name) # FIXME scope issue!
815
+ ::Livetext::Vars[name] || "[:#{name} is undefined]"
816
+ end
817
+
818
+ def _main(url)
819
+ %[href="javascript: void(0)" onclick="javascript:open_main('#{url}')"]
820
+ end
821
+
822
+ def _blank(url)
823
+ %[href='#{url}' target='blank']
824
+ end
825
+