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,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