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,773 @@
1
+ if ! defined?(Already_runeblog)
2
+
3
+ Already_runeblog = nil
4
+
5
+ require 'date'
6
+ require 'find'
7
+ require 'ostruct'
8
+
9
+ require 'logging'
10
+
11
+ require 'runeblog_version'
12
+ require 'helpers-blog'
13
+ require 'view'
14
+ require 'publish'
15
+ require 'post'
16
+
17
+ require 'pathmagic'
18
+
19
+ ###
20
+
21
+ class RuneBlog
22
+
23
+ DotDir = ".blogs"
24
+ ConfigFile = "config"
25
+ Themes = RuneBlog::Path/"../themes"
26
+
27
+ make_exception(:FileNotFound, "File $1 was not found")
28
+ make_exception(:BlogRepoAlreadyExists, "Blog repo $1 already exists")
29
+ make_exception(:CantAssignView, "$1 is not a view")
30
+ make_exception(:ViewAlreadyExists, "View $1 already exists")
31
+ make_exception(:DirAlreadyExists, "Directory $1 already exists")
32
+ make_exception(:CantCreateDir, "Can't create directory $1")
33
+ make_exception(:EditorProblem, "Could not edit $1")
34
+ make_exception(:NoSuchView, "No such view: $1")
35
+ make_exception(:NoBlogAccessor, "Runeblog.blog is not set")
36
+
37
+ class << self
38
+ attr_accessor :blog
39
+ include Helpers
40
+ end
41
+
42
+ attr_reader :views, :sequence
43
+ attr_accessor :root, :editor, :features
44
+ attr_accessor :view # overridden
45
+
46
+ attr_accessor :post_views, :post_tags, :dirty_views
47
+
48
+ include Helpers
49
+
50
+ class Default
51
+
52
+ # This will all become much more generic later.
53
+
54
+ def RuneBlog.post_template(num: 0, title: "No title", date: nil, view: "test_view",
55
+ teaser: "No teaser", body: "No body", tags: ["untagged"],
56
+ views: [], back: "javascript:history.go(-1)", home: "no url")
57
+ log!(enter: __method__, args: [num, title, date, view, teaser, body, tags, views, back, home], level: 3)
58
+ viewlist = (views + [view.to_s]).join(" ")
59
+ taglist = ".tags " + tags.join(" ")
60
+
61
+ <<~TEXT
62
+ .post #{num}
63
+
64
+ .title #{title}
65
+ .pubdate #{date}
66
+ .views #{viewlist}
67
+ #{taglist}
68
+
69
+ .teaser
70
+ #{teaser}
71
+ .end
72
+ #{body}
73
+ TEXT
74
+ end
75
+
76
+ end
77
+
78
+ def _tmp_error(err) # FIXME move to helpers
79
+ out = "/tmp/blog#{rand(100)}.txt"
80
+ File.open(out, "w") do |f|
81
+ f.puts err
82
+ f.puts err.backtrace.join("\n")
83
+ end
84
+ puts "Error: See #{out}"
85
+ end
86
+
87
+ def self.create_new_blog_repo(root_rel = ".blogs")
88
+ log!(enter: __method__, args: [root_rel])
89
+ raise ArgumentError unless root_rel.is_a?(String) && ! root_rel.empty?
90
+ self.blog = self # Weird. Like a singleton - dumbass circular dependency?
91
+ repo_root = Dir.pwd/root_rel
92
+ raise BlogRepoAlreadyExists if Dir.exist?(repo_root)
93
+ create_dirs(repo_root)
94
+ Dir.chdir(repo_root) do
95
+ create_dirs(:data, :config, :drafts, :views, :posts)
96
+ new_sequence
97
+ end
98
+ unless File.exist?(repo_root/"data/VIEW")
99
+ copy_data(:config, repo_root/:data)
100
+ end
101
+ copy_data(:extra, repo_root/:config)
102
+ write_repo_config(root: repo_root)
103
+ @blog = self.new
104
+ @blog
105
+ rescue => err
106
+ puts "Can't create blog repo: '#{repo_root}' - #{err}"
107
+ puts err.backtrace.join("\n")
108
+ end
109
+
110
+ def self.open(root_rel = ".blogs")
111
+ log!(enter: __method__, args: [root_rel])
112
+ self.blog = self # Weird. Like a singleton - dumbass circular dependency?
113
+ blog = self.new(root_rel)
114
+ rescue => err
115
+ _tmp_error(err)
116
+ end
117
+
118
+ def initialize(root_rel = ".blogs") # always assumes existing blog
119
+ log!(enter: "initialize", args: [root_rel])
120
+ self.class.blog = self # Weird. Like a singleton - dumbass circular dependency?
121
+
122
+ @root = Dir.pwd/root_rel
123
+ write_repo_config(root: @root) # ?? FIXME
124
+ get_repo_config
125
+ @views = retrieve_views
126
+ self.view = File.read(@root/"data/VIEW").chomp
127
+ md = Dir.pwd.match(%r[.*/views/(.*?)/])
128
+ if md
129
+ @view_name = md[1]
130
+ @view = str2view(@view_name)
131
+ end
132
+ @sequence = get_sequence
133
+ @post_views = []
134
+ @post_tags = []
135
+ end
136
+
137
+ def complete_file(name, vars, hash)
138
+ debugging = vars.nil?
139
+ return if hash.empty?
140
+ text = File.read(name)
141
+ if vars.nil? # FIXME dumbest hack ever?
142
+ vars = {}
143
+ hash.values.each {|val| vars[val] = val }
144
+ end
145
+
146
+ hash.each_pair {|key, var| text.gsub!(key, vars[var]) }
147
+ File.write(name, text)
148
+ end
149
+
150
+ def _generate_settings(view = nil)
151
+ vars = read_vars("#@root/data/universal.lt3")
152
+ hash = {/AUTHOR/ => "view.author",
153
+ /SITE/ => "view.site",
154
+ /FONT/ => "font.family",
155
+ /CHARSET/ => :charset,
156
+ /LOCALE/ => :locale}
157
+
158
+ # rubytext.txt - LATER
159
+ # complete_file(settings/"rubytext.txt", {}
160
+
161
+ if view
162
+ settings = @root/view/"settings"
163
+ ### ??? Where to get hash of view-specific vars?
164
+
165
+ # features.txt - handle specially
166
+ fname = settings/"features.txt"
167
+
168
+ # view.txt
169
+ complete_file(settings/"view.txt",
170
+ /AUTHOR/ => "view.author",
171
+ /TITLE/ => "view.title",
172
+ /SUBTITLE/ => "view.subtitle",
173
+ /SITE/ => "view.site")
174
+
175
+ # publish.txt
176
+ complete_file(settings/"publish.txt",
177
+ /USER/ => "publish.user",
178
+ /SERVER/ => "publish.server",
179
+ /DOCROOT/ => "publish.docroot",
180
+ /PATH/ => "publish.path",
181
+ /PROTO/ => "publish.proto")
182
+
183
+ # recent.txt - SKIP THIS?
184
+ complete_file(settings/"recent.txt", {})
185
+ end
186
+ end
187
+
188
+ def _generate_global
189
+ vars = read_vars("#@root/data/universal.lt3")
190
+ gfile = "#@root/data/global.lt3"
191
+ hash = {/AUTHOR/ => "univ.author",
192
+ /SITE/ => "univ.site",
193
+ /FONT/ => "font.family",
194
+ /CHARSET/ => :charset,
195
+ /LOCALE/ => :locale}
196
+ complete_file(gfile, vars, hash)
197
+ _generate_settings
198
+ end
199
+
200
+ def _deploy_local(dir)
201
+ log!(enter: __method__, args: [dir], level: 1)
202
+ Dir.chdir(dir) do
203
+ views = _retrieve_metadata(:views)
204
+ views.each do |v|
205
+ next unless _check_view?(v)
206
+ system!("cp *html #@root/views/#{v}/remote", show: true)
207
+ end
208
+ end
209
+ rescue => err
210
+ _tmp_error(err)
211
+ end
212
+
213
+ # FIXME reconcile with _get_draft data
214
+
215
+ def _retrieve_metadata(key)
216
+ key = key.to_s
217
+ lines = File.readlines("metadata.txt")
218
+ lines = lines.grep(/^#{key}: /)
219
+ case lines.size
220
+ when 0
221
+ result = nil # not found
222
+ when 1
223
+ front = "#{key}: "
224
+ n = front.size
225
+ str = lines.first.chomp[n..-1]
226
+ case key
227
+ when "views", "tags" # plurals
228
+ result = str.split
229
+ else
230
+ result = str
231
+ end
232
+ else
233
+ raise "Too many #{key} instances in metadata.txt!"
234
+ end
235
+ return result
236
+ rescue => err
237
+ _tmp_error(err)
238
+ end
239
+
240
+ def process_post(sourcefile)
241
+ log!(enter: __method__, args: [sourcefile], level: 2)
242
+ nslug = sourcefile.sub(/.lt3/, "")
243
+ dir = @root/:posts/nslug
244
+ create_dirs(dir)
245
+ # FIXME dependencies?
246
+ preprocess cwd: dir, src: @root/:drafts/sourcefile, dst: @root/:posts/sourcefile.sub(/.lt3/, ".html"), # ZZZ
247
+ mix: "liveblog" # , debug: true
248
+ _deploy_local(dir)
249
+ rescue => err
250
+ _tmp_error(err)
251
+ end
252
+
253
+ def inspect
254
+ log!(enter: __method__, level: 3)
255
+ str = "blog: "
256
+ ivars = ["@root", "@sequence"] # self.instance_variables
257
+ ivars.each do |iv|
258
+ val = self.instance_variable_get(iv)
259
+ str << "#{iv}: #{val} "
260
+ end
261
+ str
262
+ end
263
+
264
+ def view?(name)
265
+ log!(enter: __method__, args: [name], level: 3)
266
+ raise ArgumentError unless name.is_a?(String) && ! name.empty?
267
+ views.any? {|x| x.name == name }
268
+ end
269
+
270
+ def view(name = nil)
271
+ log!(enter: __method__, args: [name], level: 3)
272
+ raise ArgumentError unless name.nil? || (name.is_a?(String) && ! name.empty?)
273
+ name.nil? ? @view : str2view(name)
274
+ end
275
+
276
+ def str2view(str)
277
+ log!(enter: __method__, args: [str], level: 3)
278
+ raise ArgumentError unless str.is_a?(String) && ! str.empty?
279
+ @views.find {|x| x.name == str }
280
+ end
281
+
282
+ def _set_publisher
283
+ log!(enter: __method__, level: 3)
284
+ @view.publisher = RuneBlog::Publishing.new(@view.to_s) # FIXME refactor
285
+ rescue => err
286
+ _tmp_error(err)
287
+ end
288
+
289
+ def view=(arg)
290
+ log!(enter: __method__, args: [arg], level: 2)
291
+ case arg
292
+ when "[no view]"
293
+ # puts "Warning: No current view set"
294
+ @view = nil
295
+ when RuneBlog::View
296
+ @view = arg
297
+ _set_publisher
298
+ when String
299
+ new_view = str2view(arg)
300
+ raise NoSuchView(arg) if new_view.nil?
301
+ @view = new_view
302
+ _set_publisher
303
+ else
304
+ raise CantAssignView(arg.class.to_s)
305
+ end
306
+ rescue => err
307
+ _tmp_error(err)
308
+ end
309
+
310
+ def get_sequence
311
+ log!(enter: __method__, level: 3)
312
+ File.read(@root/"data/sequence").to_i
313
+ end
314
+
315
+ def next_sequence
316
+ log!(enter: __method__, level: 3)
317
+ @sequence += 1
318
+ dump(@sequence, @root/"data/sequence")
319
+ @sequence
320
+ end
321
+
322
+ def viewdir(v = nil) # delete?
323
+ log!(enter: __method__, args: [v], level: 3)
324
+ v ||= @view
325
+ v = str2view(v) if v.is_a?(String)
326
+ raise ArgumentError unless v.nil? || v.is_a?(RuneBlog::View)
327
+ return @root/:views/v
328
+ end
329
+
330
+ def self.exist?
331
+ log!(enter: __method__, level: 3)
332
+ Dir.exist?(DotDir)
333
+ end
334
+
335
+ def mark_last_published(str)
336
+ log!(enter: __method__, args: [str], level: 2)
337
+ dump(str, "#{self.view.dir}/last_published")
338
+ end
339
+
340
+ def add_view(view_name)
341
+ log!(enter: __method__, args: [view_name], level: 2)
342
+ view = RuneBlog::View.new(view_name)
343
+ self.view = view # current view
344
+ File.write(@root/"data/VIEW", view_name)
345
+ @views << view # all views
346
+ view
347
+ end
348
+
349
+ def make_empty_view_tree(view_name)
350
+ log!(enter: __method__, args: [view_name], level: 2)
351
+ Dir.chdir(@root) do
352
+ cmd = "cp -r #{RuneBlog::Path}/../empty_view views/#{view_name}"
353
+ system!(cmd)
354
+ end
355
+ rescue => err
356
+ _tmp_error(err)
357
+ end
358
+
359
+ def check_valid_new_view(view_name)
360
+ log!(enter: __method__, args: [view_name], level: 3)
361
+ raise ArgumentError unless view_name.is_a?(String)
362
+ raise ArgumentError if view_name.empty?
363
+ names = self.views.map(&:to_s)
364
+ bad = names.include?(view_name)
365
+ raise ViewAlreadyExists(view_name) if bad
366
+ vdir = @root/:views/view_name
367
+ raise DirAlreadyExists(view_name) if Dir.exist?(vdir)
368
+ return true # hm?
369
+ end
370
+
371
+ def create_view(view_name)
372
+ log!(enter: __method__, args: [view_name], level: 2)
373
+ make_empty_view_tree(view_name)
374
+ add_view(view_name)
375
+ mark_last_published("Initial creation")
376
+ system("cp #@root/data/global.lt3 #@root/views/#{view_name}/themes/standard/global.lt3")
377
+ rescue => err
378
+ _tmp_error(err)
379
+ end
380
+
381
+ def delete_view(name, force = false)
382
+ log!(enter: __method__, args: [name, force])
383
+ raise ArgumentError unless name.is_a?(String) && ! name.empty?
384
+ if force
385
+ vname = @root/:views/name
386
+ system!("rm -rf #{vname}")
387
+ @views -= [str2view(name)]
388
+ end
389
+ end
390
+
391
+ def view_files
392
+ log!(enter: __method__, level: 2)
393
+ vdir = self.view.dir
394
+ files = [vdir/"index.html"]
395
+ files += posts.map {|x| vdir/x }
396
+ files.reject! {|f| File.mtime(f) < File.mtime(vdir/:last_published) }
397
+ end
398
+
399
+ def post_lookup(postid) # side-effect?
400
+ log!(enter: __method__, args: [postid], level: 2)
401
+ slug = title = date = teaser_text = nil
402
+
403
+ dir_posts = @vdir/:posts
404
+ posts = Dir.entries(dir_posts).grep(/^\d\d\d\d/).map {|x| dir_posts/x }
405
+ posts.select! {|x| File.directory?(x) }
406
+
407
+ post = posts.select {|x| File.basename(x).to_i == postid }
408
+ raise "Error: More than one post #{postid}" if post.size > 1
409
+ postdir = post.first
410
+ vp = RuneBlog::ViewPost.new(self.view, postdir)
411
+ vp
412
+ rescue => err
413
+ _tmp_error(err)
414
+ end
415
+
416
+ def index_entry(slug)
417
+ log!(enter: __method__, args: [slug], level: 2)
418
+ id = slug.to_i
419
+ text = nil
420
+ @theme = @view.dir/"themes/standard"
421
+ post_entry_name = @theme/"blog/post_entry.lt3"
422
+ depend = [post_entry_name]
423
+ html = "/tmp/post_entry.html"
424
+ preprocess src: post_entry_name, dst: html,
425
+ call: ".nopara" # , deps: depend # , debug: true
426
+ @_post_entry = File.read(html)
427
+ vp = post_lookup(id)
428
+ nslug, aslug, title, date, teaser_text =
429
+ vp.nslug, vp.aslug, vp.title, vp.date, vp.teaser_text
430
+ path = vp.path
431
+ url = aslug + ".html"
432
+ date = ::Date.parse(date)
433
+ date = date.strftime("%B %e<br><div style='float: right'>%Y</div>")
434
+ text = interpolate(@_post_entry, binding)
435
+ text
436
+ rescue => err
437
+ _tmp_error(err)
438
+ end
439
+
440
+ def _sorted_posts
441
+ posts = nil
442
+ dir_posts = @vdir/:posts
443
+ entries = Dir.entries(dir_posts)
444
+ posts = entries.grep(/^\d\d\d\d/).map {|x| dir_posts/x }
445
+ posts.select! {|x| File.directory?(x) }
446
+ # directories that start with four digits
447
+ posts = posts.sort do |a, b|
448
+ ai = a.index(/\d\d\d\d-/)
449
+ bi = b.index(/\d\d\d\d-/)
450
+ na = a[ai..(ai+3)].to_i
451
+ nb = b[bi..(bi+3)].to_i
452
+ nb <=> na
453
+ end # sort descending
454
+ return posts[0..19] # return 20 at most
455
+ end
456
+
457
+ def collect_recent_posts(file)
458
+ log!(enter: __method__, args: [file], level: 3)
459
+ text = <<-HTML
460
+ <html>
461
+ <head><link rel="stylesheet" href="etc/blog.css"></head>
462
+ <body>
463
+ HTML
464
+ posts = _sorted_posts
465
+ if posts.size > 0
466
+ wanted = [8, posts.size].min # estimate how many we want?
467
+ enum = posts.each
468
+ entries = []
469
+ wanted.times do
470
+ postid = File.basename(enum.next)
471
+ postid = postid.to_i
472
+ entry = index_entry(postid)
473
+ entries << entry
474
+ text << entry
475
+ end
476
+ else
477
+ text << <<-HTML
478
+ <svg width="95%" height="75%" viewBox="0 0 95% 95%">
479
+ <style> .huge { font: italic 90px sans-serif; fill: white } </style>
480
+ <rect x="0" y="0" rx="50" ry="50" width="95%" height="95%" fill="lightblue"/>
481
+ <text x="120" y="250" class=huge>No posts</text>
482
+ <text x="120" y="350" class=huge>here yet</text>
483
+ </svg>
484
+ HTML
485
+ end
486
+ text << "</body></html>"
487
+ File.write(@vdir/:remote/file, text)
488
+ return posts.size
489
+ rescue => err
490
+ _tmp_error(err)
491
+ end
492
+
493
+ def create_new_post(title, testing = false, teaser: nil, body: nil,
494
+ pubdate: Time.now.strftime("%Y-%m-%d"), views: [])
495
+ log!(enter: __method__, args: [title, testing, teaser, body, views], level: 1, stderr: true)
496
+ meta = nil
497
+ views = views + [self.view.to_s]
498
+ views.uniq!
499
+ Dir.chdir(@root/"posts") do
500
+ post = Post.create(title: title, teaser: teaser, body: body, pubdate: pubdate, views: views)
501
+ post.edit unless testing
502
+ post.build
503
+ meta = post.meta
504
+ end
505
+ return meta.num
506
+ rescue => err
507
+ _tmp_error(err)
508
+ end
509
+
510
+ def import_legacy_post(file, oldfile, testing = false)
511
+ end
512
+
513
+ def posts
514
+ log!(enter: __method__, level: 3)
515
+ dir = self.view.dir/:posts
516
+ posts = Dir.entries(dir).grep(/^\d{4}/)
517
+ posts
518
+ end
519
+
520
+ def drafts
521
+ log!(enter: __method__, level: 3)
522
+ dir = @root/:drafts
523
+ drafts = Dir.entries(dir).grep(/^\d{4}.*/)
524
+ end
525
+
526
+ def change_view(view)
527
+ log!(enter: __method__, args: [view], level: 3)
528
+ raise ArgumentError unless view.is_a?(String) || view.is_a?(RuneBlog::View)
529
+ File.write(@root/"data/VIEW", view)
530
+ # write_repo_config
531
+ self.view = view # error checking?
532
+ end
533
+
534
+ def generate_index(view)
535
+ log!(enter: __method__, args: [view], pwd: true, dir: true)
536
+ raise ArgumentError unless view.is_a?(String) || view.is_a?(RuneBlog::View)
537
+ @vdir = @root/:views/view
538
+ num = collect_recent_posts("recent.html")
539
+ return num
540
+ rescue => err
541
+ _tmp_error(err)
542
+ end
543
+
544
+ def generate_view(view) # huh?
545
+ log!(enter: __method__, args: [view])
546
+ vdir = @root/:views/view
547
+ @theme = @root/:views/view/:themes/:standard
548
+ depend = [vdir/"remote/etc/blog.css.lt3", @theme/"global.lt3",
549
+ @theme/"blog/head.lt3",
550
+ # @theme/"navbar/navbar.lt3",
551
+ @theme/"blog/index.lt3"] # FIXME what about assets?
552
+ preprocess cwd: vdir/"themes/standard/etc", src: "blog.css.lt3",
553
+ copy: vdir/"remote/etc/", call: [".nopara"], strip: true
554
+ preprocess cwd: vdir/"themes/standard", deps: depend, force: true,
555
+ src: "blog/generate.lt3", dst: vdir/:remote/"index.html",
556
+ call: ".nopara"
557
+ copy!("#{vdir}/themes/standard/banner/*", "#{vdir}/remote/banner/") # includes navbar/
558
+ copy("#{vdir}/assets/*", "#{vdir}/remote/assets/")
559
+ # rebuild widgets?
560
+ copy_widget_html(view)
561
+ rescue => err
562
+ STDERR.puts err
563
+ STDERR.puts err.backtrace.join("\n")
564
+ # _tmp_error(err)
565
+ end
566
+
567
+ def _get_draft_data(num, sym)
568
+ tag = prefix(num)
569
+ front = @blog.root/:drafts/tag
570
+ files = Dir[front + "*"]
571
+ raise "No draft #{num} found" if files.empty?
572
+ raise "Too many files found for #{num}!" if files.size > 1
573
+ file = files.first
574
+ lines = File.readlines(file)
575
+ case sym
576
+ when :views
577
+ view_line = lines.grep(/^.views /)
578
+ raise "More than one .views call in #{draft}" if view_line.size > 1
579
+ raise "No .views call in #{draft}" if view_line.size < 1
580
+ view_line = view_line.first
581
+ views = view_line[7..-1].split
582
+ return views.uniq
583
+ else
584
+ raise "Unknown symbol #{sym.inspect}"
585
+ end
586
+ end
587
+
588
+ def _get_views(draft)
589
+ log!(enter: __method__, args: [draft], level: 2)
590
+ # FIXME dumb code
591
+ view_line = File.readlines(draft).grep(/^.views /)
592
+ raise "More than one .views call in #{draft}" if view_line.size > 1
593
+ raise "No .views call in #{draft}" if view_line.size < 1
594
+ view_line = view_line.first
595
+ views = view_line[7..-1].split
596
+ views.uniq
597
+ rescue => err
598
+ _tmp_error(err)
599
+ end
600
+
601
+ def _copy_get_dirs(draft, view)
602
+ log!(enter: __method__, args: [draft, view], level: 2)
603
+ fname = File.basename(draft)
604
+ noext = fname.sub(/.lt3$/, "")
605
+ vdir = @root/:views/view
606
+ dir = vdir/:posts/noext
607
+ Dir.mkdir(dir) unless Dir.exist?(dir)
608
+ system!("cp #{draft} #{dir}")
609
+ viewdir, slugdir, aslug = vdir, dir, noext[5..-1]
610
+ theme = viewdir/:themes/:standard
611
+ [noext, viewdir, slugdir, aslug, theme]
612
+ rescue => err
613
+ _tmp_error(err)
614
+ end
615
+
616
+ def _post_metadata(draft, pdraft)
617
+ log!(enter: __method__, args: [draft, pdraft], level: 2)
618
+ # FIXME store this somewhere
619
+ fname = File.basename(draft) # 0001-this-is-a-post.lt3
620
+ nslug = fname.sub(/.lt3$/, "") # 0001-this-is-a-post
621
+ aslug = nslug.sub(/\d\d\d\d-/, "") # this-is-a-post
622
+ pnum = nslug[0..3] # 0001
623
+ Dir.chdir(pdraft) do
624
+ excerpt = File.read("teaser.txt")
625
+ date = _retrieve_metadata(:date)
626
+ longdate = ::Date.parse(date).strftime("%B %e, %Y")
627
+ title = _retrieve_metadata(:title)
628
+ tags = _retrieve_metadata(:tags)
629
+ # FIXME simplify
630
+ vars = <<~LIVE
631
+ .set post.num = #{pnum}
632
+ .heredoc post.aslug
633
+ #{aslug}
634
+ .end
635
+ .heredoc title
636
+ #{title.chomp}
637
+ .end
638
+ .heredoc post.tags
639
+ #{tags.join(" ")}
640
+ .end
641
+ .heredoc teaser
642
+ #{excerpt.chomp}
643
+ .end
644
+ .heredoc longdate
645
+ #{longdate}
646
+ .end
647
+ LIVE
648
+ File.open(pdraft/"vars.lt3", "w") {|f| f.puts vars }
649
+ end
650
+ rescue => err
651
+ _tmp_error(err)
652
+ end
653
+
654
+ def copy_widget_html(view)
655
+ log!(enter: __method__, level: 2)
656
+ vdir = @root/:views/view
657
+ remote = vdir/:remote
658
+ wdir = vdir/:themes/:standard/:widgets
659
+ widgets = Dir[wdir/"*"].select {|w| File.directory?(w) }
660
+ widgets.each do |w|
661
+ dir = File.basename(w)
662
+ rem = w.sub(/themes.standard/, "remote")
663
+ create_dirs(rem)
664
+ files = Dir[w/"*"]
665
+ files = files.select {|x| x =~ /(html|css)$/ }
666
+ tag = File.basename(w)
667
+ files.each {|file| system!("cp #{file} #{rem}", show: (tag == "zzz")) }
668
+ end
669
+ rescue => err
670
+ _tmp_error(err)
671
+ end
672
+
673
+ def _handle_post(draft, view_name = self.view.to_s)
674
+ log!(enter: __method__, args: [draft, view_name], level: 2)
675
+ # break into separate methods?
676
+
677
+ return unless _check_view?(view_name)
678
+
679
+ fname = File.basename(draft) # 0001-this-is-a-post.lt3
680
+ nslug = fname.sub(/.lt3$/, "") # 0001-this-is-a-post
681
+ aslug = nslug.sub(/\d\d\d\d-/, "") # this-is-a-post
682
+ ahtml = aslug + ".html" # this-is-a-post.html
683
+ pdraft = @root/:posts/nslug
684
+ remote = @root/:views/view_name/:remote
685
+ @theme = @root/:views/view_name/:themes/:standard
686
+ # Step 1...
687
+ create_dirs(pdraft)
688
+ # FIXME dependencies?
689
+ preprocess cwd: pdraft, src: draft, dst: "guts.html",
690
+ mix: "liveblog" # , debug: true
691
+ _post_metadata(draft, pdraft)
692
+ # Step 2...
693
+ vposts = @root/:views/view_name/:posts
694
+ copy!(pdraft, vposts) # ??
695
+ # Step 3..
696
+ copy(pdraft/"guts.html", @theme/:post)
697
+ copy(pdraft/"vars.lt3", @theme/:post)
698
+ # Step 4...
699
+ # FIXME dependencies?
700
+ preprocess cwd: @theme/:post, src: "generate.lt3", force: true,
701
+ dst: remote/ahtml, copy: @theme/:post,
702
+ call: ".nopara" # , debug: true
703
+ copy_widget_html(view_name)
704
+ rescue => err
705
+ _tmp_error(err)
706
+ end
707
+
708
+ def _check_view?(view)
709
+ flag = self.view?(view)
710
+ puts " Warning: '#{view}' is not a view" unless flag
711
+ flag
712
+ end
713
+
714
+ def generate_post(draft, force = false)
715
+ log!(enter: __method__, args: [draft], level: 1)
716
+ views = _get_views(draft)
717
+ views.each {|view| _handle_post(draft, view) }
718
+ rescue => err
719
+ _tmp_error(err)
720
+ end
721
+
722
+ def remove_post(num)
723
+ log!(enter: __method__, args: [num], level: 1)
724
+ raise ArgumentError unless num.is_a?(Integer)
725
+ # FIXME update original draft .views
726
+ tag = prefix(num)
727
+ files = Find.find(self.view.dir).to_a
728
+ list = files.select {|x| File.directory?(x) and x =~ /#{tag}/ }
729
+ return nil if list.empty?
730
+ dest = list.map {|f| f.sub(/(?<num>\d{4}-)/, "_\\k<num>") }
731
+ list.each.with_index do |src, i|
732
+ cmd = "mv #{src} #{dest[i]} 2>/dev/null"
733
+ system!(cmd)
734
+ end
735
+ # FIXME - update index/etc
736
+ true
737
+ end
738
+
739
+ def undelete_post(num)
740
+ log!(enter: __method__, args: [num], level: 1)
741
+ raise ArgumentError unless num.is_a?(Integer)
742
+ files = Find.find(@root/:views).to_a
743
+ tag = prefix(num)
744
+ list = files.select {|x| File.directory?(x) and x =~ /_#{tag}/ }
745
+ return nil if list.empty?
746
+ dest = list.map {|f| f.sub(/_(?<num>\d{4}-)/, "\\k<num>") }
747
+ list.each.with_index do |src, i|
748
+ cmd = "mv #{src} #{dest[i]} 2>/dev/null"
749
+ system!(cmd)
750
+ end
751
+ # FIXME - update index/etc
752
+ true
753
+ end
754
+
755
+ def delete_draft(num)
756
+ log!(enter: __method__, args: [num], level: 1)
757
+ raise ArgumentError unless num.is_a?(Integer)
758
+ tag = prefix(num)
759
+ system!("rm -rf #@root/drafts/#{tag}-*")
760
+ end
761
+
762
+ def make_slug(meta)
763
+ log!(enter: __method__, args: [meta], level: 3)
764
+ raise ArgumentError unless meta.title.is_a?(String)
765
+ label = '%04d' % meta.num # FIXME can do better
766
+ slug0 = meta.title.downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '')
767
+ str = "#{label}-#{slug0}"
768
+ meta.slug = str
769
+ str
770
+ end
771
+ end
772
+
773
+ end