diff --git a/bb_postindex.rb b/bb_postindex.rb new file mode 100644 index 0000000..d835b2f --- /dev/null +++ b/bb_postindex.rb @@ -0,0 +1,203 @@ +#!/usr/bin/ruby + +# BB_POSTINDEX.RB makes a neat post collection index for Xenforo2 forum +# posts. Write the post summary text followed by the post URL in +# any (raw) text editor, with a single newline separating the summary +# from the URL, but as many blank lines as you want separating each +# post summary. Optionally, prefix the summary with specific commands +# to force different output formatting: +# - !extra categorizes the comment as an extra to be placed at +# the end of the post collection for the day, +# - !daystart specifies that the following posts should be placed +# inside spoiler tags given by the specified day (see example). +# - !dayend similarly signals the end of the spoiler tags. +# +# Example: So the text files I write to sort posts look like: +# +# !daystart Saturday, 15 February 2020 +# +# Oh, snap! Things just got real ((@UserName) post) +# https://blahblahblah/page-12#post-3456789 +# +# !extra (@WinnerPoster) made an interesting comment +# https://blahblahblah/page-13#post-4214133 +# +# !dayend +# +# Then running this script with formats the posts with hyperlinks +# to the posts, @'s for the credited users, and puts everything +# behind spoiler tags separating the days. Within each day, the +# normal posts are formatted with bullet points and a sans serif +# font for whimsy, and the extra posts appear in a separate section +# "Informative takes and extras:" at the end of the spoiler. + +# For whimsy, use Arial san serif font for the main post list. +LIST_FORMAT_FONT = "arial" + +# Use text macros to dictate the format of the post collection index. +# !daystart begins a spoiler tag for a given day +DAY_START = "!daystart" + +# !dayend ends the spoiler tag for a given day +DAY_END = "!dayend" + +# !extra specifies that the post is an informative extra, to appear +# separately in a list at the end +EXTRA = "!extra" + + +# A class to represent an INDEXEDPOST with a given summary, user, +# and post hyperlink. Optionally, the post may be specified as +# an 'extra', in which case no list-style bullet point and sans +# serif formatting is applied. +# +# Example: +# # Create a new indexed post and print it in default list format +# post1 = IndexedPost.new("List post (@User post)", "@User", "www.blah#p-1") +# puts post1 #=> [FONT=arial][URL='www.blah#p-1']○ List post ([/URL] +# # @User[URL='www.blah#p-1'] post)[/URL][/FONT] +# +# # Create a new indexed post and print it in extra format +# post2 = IndexedPost.new("@User's extra post", "@User", "www.blah", true) +# puts post2 #=> @User[URL='www.blah']'s extra post[/URL] +# # And in list form: +# puts post2.list_format #=> [FONT=arial][URL='www.blah']○ [/URL] +# #@User[URL='www.blah']'s extra post[/URL][/FONT] +# +class IndexedPost + + # Create a new indexed post with the given SUMMARY, USER and + # POST_HYPERLINK. If EXTRA is true, formats the post as an + # informative extra without any explicit list formatting. + def initialize(summary, user, post_hyperlink, extra=false) + @summary = summary + @user = user + @post_hyperlink = post_hyperlink + @extra = extra + end + + # Return a string representation for this post in extra format, + # i.e. in the regular font with just the hyperlink and no + # additional formatting. + def extra_format() + text = @summary.split(@user).map do |part| + if (part.length > 0) + "[URL='" + @post_hyperlink + "']" + part + "[/URL]" + end + end + text.join(" " + @user + " ") + end + + # Return a string representation for this post in list format, + # i.e. in sans serif font with an ASCII bullet point prepended. + # TODO: These methods are very similar. Refactor to get rid of the + # code duplication at some point later? + def list_format() + text = ("○ " + @summary).split(@user).map do |part| + if (part.length > 0) + "[URL='" + @post_hyperlink + "']" + part + "[/URL]" + end + end + "[FONT=" + LIST_FORMAT_FONT + "]" + text.join(" " + @user + " ") + "[/FONT]" + end + + # Return the printable string representation for this indexed post. + def to_s() + if (@extra) + self.extra_format + else + self.list_format + end + end + +end + + +# Assume that the file full of posts is the first command line arg +if (ARGV.length < 1) + raise(ArgumentError, "No post file specified in command line args") +else + posts_file = File.open(ARGV.join("")) +end + +# Read lines from the post collection file (ignoring blank lines), and +# generate the index structure. +list_posts = [] +extra_posts = [] +day_active = false + +line = posts_file.gets.strip + +while (line) + # Skip any preceding white space or blank lines + line.strip! + if (not line.empty?) + case + when line.start_with?(DAY_START) + # Start the spoiler tag and refresh the post lists + puts "[SPOILER=\"" + line.split(DAY_START)[1].strip! + "\"]" + list_posts = [] + extra_posts = [] + day_active = true + + when line.start_with?(DAY_END) + # Print all of the collected list posts + list_posts.map { |post| puts post.list_format } + + # Print all of the collected extra posts in a separate section + puts "\nInformative takes and extras:" + extra_posts.map { |post| puts post.extra_format } + + # Finally, end the spoiler tag + puts "[/SPOILER]" + day_active = false + + else + # If the line begins with !extra, it's an extra/informative post + extra = line.start_with?(EXTRA) + if (extra) + line = line.split(EXTRA)[1].strip + end + + # Parse the user name (assumed to be between a pair of parentheses + # for simplicity) + user_with_parens = line.scan(/\(@.+\)(?=['\s])/)[0].strip + user = user_with_parens[1..-2] + + # And remove the extra parentheses in the formatted summary + summary = line.gsub(user_with_parens, user) + + # Grab the next line too for the hyperlink + post_hyperlink = posts_file.gets.strip + + # Instantiate a new post index and add it to the list. + indexed_post = IndexedPost.new(summary, user, post_hyperlink, extra) + if (extra) + extra_posts << indexed_post + else + list_posts << indexed_post + end + end + end + + # Read in the next line + line = posts_file.gets +end + +# Done reading! Close the collected posts file. +posts_file.close() + +# Check: at the end here, if we still have collected posts for the day, +# print them all now and close the spoiler tag. +if (day_active) + # TODO: Duplicate code from the case. Refactor? + # Print all of the collected list posts + list_posts.map { |post| puts post.list_format } + + # Print all of the collected extra posts in a separate section + puts "\nInformative takes and extras:" + extra_posts.map { |post| puts post.extra_format } + + # Finally, end the spoiler tag + puts "[/SPOILER]" +end \ No newline at end of file