runeblog 0.3.02

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 (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
+