View on GitHub

scriv-git-pages

Writing in Scrivener for GitHub Pages

Better Links Top Post-Processing Built In

Creating the ToC

I think the remaining priorities for this little effort are the table of contents, and setting it up to be fully automated. By “fully”, I mean that issuing a compile should automatically run the Ruby script that creates our individual files, plugs in the links, and creates the table of contents. I probably will not go so far as to automate the git commit and push to GitHub. We’ll see.

Since creating the table of contents requires some file-mangling, I think I’ll start with that before automation, so that I don’t have to wipe out the files that are currently up on GitHub. They’re under Git, so I could get them back, but let’s just not destroy them. So, table of contents first.

The approximate plan is to replace my hand-made ToC with a marker, probably <—TOC—>, in the source file. Then the Ruby script can just split the index file on the marker, write out the first chunk, write out the ToC, then write out the second chunk. Should be really “easy”.

I guess first, I’ll make the ToC edit and recompile. For my own convenience, I’ll comment out the existing one, in case everything goes wrong. … OK, that’s done.

The main loops of the Ruby script look like this:

input = ARGF.read
chunks = input.split(SPLIT_MARKER)
reference_chunk = chunks.delete_at(-1)
filenumber = 0
chunks.each do | chunk |
  title = chunk.split("\n")[0]
  Titles[filenumber] = title[2..-3]
  filenumber += 1
end
filenumber = 0
chunks.each do |chunk|
  write_file(chunk, reference_chunk, filenumber, chunks.length - 1) unless chunk.length < 1
  filenumber += 1
end

(We should factor out those two loops, by the way, calling one get_titles and the other write_files or something. Maybe later.) Anyway, we’ve already pulled one chunk off the back of the list before proceeding. What if we pull the one off the front and process it? We could either write it out separately, or we could assemble a new chunk, with the ToC in it, and put it back. I’m inclined to the latter.

Why? Two reasons. First, I expect it involves less changing of existing code. Second, I’d have to duplicate at least the call to write_file, and possibly even create a new version of that function. I could be wrong. We’ll see what happens as I go along. First things first, I’ll pull out the chunk and edit it trivially and put it back.

Here’s what I’ve got now, the main and the add_toc function:

input = ARGF.read
chunks = input.split(SPLIT_MARKER)
reference_chunk = chunks.delete_at(-1)
toc_chunk = chunks.delete_at(0)
updated_toc = add_toc(toc_chunk)
chunks.insert(0, updated_toc)
filenumber = 0
chunks.each do | chunk |
  title = chunk.split("\n")[0]
  Titles[filenumber] = title[2..-3]
  filenumber += 1
end
filenumber = 0
chunks.each do |chunk|
  write_file(chunk, reference_chunk, filenumber, chunks.length - 1) unless chunk.length < 1
  filenumber += 1
end

def add_toc(chunk)
  halves = chunk.split(TOC_MARKER)
  puts "halves length %d" % halves.length
  return chunk if halves.length != 2
  return halves[0] + "\nHERE IT IS\n" + halves[1]
end

That’s doing exactly what I expected, removing my TOC flag, and inserting “HERE IT IS” where the ToC should go. There was one little glitch: when I first ran it, the add_toc function wasn’t finding the flag. Turns out, Scrivener, or MacOS in their zeal to be helpful, had converted – to —, the em-dash. Thanks, doll.

Now all I have to do is format the ToC and insert it. Life is good.

Silly boy! You can’t put the real ToC in until after you’ve created the Titles Hash. Here’s what I’ll try now.

input = ARGF.read
chunks = input.split(SPLIT_MARKER)
reference_chunk = chunks.delete_at(-1)
filenumber = 0
chunks.each do | chunk |
  title = chunk.split("\n")[0]
  Titles[filenumber] = title[2..-3]
  filenumber += 1
end
toc_chunk = chunks.delete_at(0)
updated_toc = add_toc(toc_chunk)
chunks.insert(0, updated_toc)
filenumber = 0
chunks.each do |chunk|
  write_file(chunk, reference_chunk, filenumber, chunks.length - 1) unless chunk.length < 1
  filenumber += 1
end

That works! Here’s what add_toc looks like now:

def add_toc(chunk)
  halves = chunk.split(TOC_MARKER)
  puts "halves length %d" % halves.length
  return chunk if halves.length != 2
  return halves[0] + table_of_contents + halves[1]
end

def table_of_contents
  toc = ""
  Titles.each_pair do | number, title |
    toc += "* [" + title + "](" + make_file_name(number) + ")\n"  
  end
  return toc
end

That could be cleaner. I’ll do a little cleanup and then push this baby.

First, I improve this:

toc += "* [" + title + "](" + make_file_name(number) + “)\n”

to this:

toc += "* [%s](%s)\n" % [title, make_file_name(number)]

That’s a bit more like Ruby. Now I want to factor out all the loops and other chunks, using the Composed Method pattern, which essentially asks that a method either do things, or call things, but not both.

That goes swimmingly (which, I gather from extensive reading, means “well”), and now the main looks like this:

input = ARGF.read
chunks = input.split(SPLIT_MARKER)
reference_chunk = chunks.delete_at(-1)
record_titles(chunks)
update_table_of_contents(chunks)
write_files(chunks, reference_chunk)

That’s nearly good, in my opinion. Below is the whole script, in case you want to read it or use it as it stands. I’m going to go remove the saved ToC from the top of the booklet, generate, and ship.

Next step, maybe: work on running the script right out of Scrivener. I think this is nearly ready to go.

#!/usr/bin/env ruby -wU
require 'tempfile'

SPLIT_MARKER = "----\n\n"
TOC_MARKER = "<!--TOC-->\n"
Titles = {}

def add_toc(chunk)
  halves = chunk.split(TOC_MARKER)
  puts "halves length %d" % halves.length
  return chunk if halves.length != 2
  return halves[0] + table_of_contents + halves[1]
end

def make_file_name(filenumber)
  return "index.md" if filenumber == 0
  return sprintf("%02d.md", filenumber)
end

def make_link_line(filenumber, max_length)
  link = ""
  # if ( filenumber == 1 ) 
    # link += "[Prev](index.html) "
  if filenumber > 0
    link += "[%s](%02d.html) | " % [Titles[filenumber - 1], filenumber - 1]
  end
  link += "[Top](index.html) | "
  link += "[%s](%02d.html)" % [Titles[filenumber + 1], filenumber + 1] unless filenumber >= max_length
  return link
end

def record_titles(chunks)
  filenumber = 0
  chunks.each do | chunk |
    title = chunk.split("\n")[0]
    Titles[filenumber] = title[2..-3]
    filenumber += 1
  end
end

def table_of_contents
  toc = ""
  Titles.each_pair do | number, title |
    # toc += "* [" + title + "](" + make_file_name(number) + ")\n" 
    toc += "* [%s](%s)\n" % [title, make_file_name(number)] 
  end
  return toc
end

def update_table_of_contents(chunks)
  toc_chunk = chunks.delete_at(0)
  updated_toc = add_toc(toc_chunk)
  chunks.insert(0, updated_toc)
end

def write_file(chunk, reference_chunk, filenumber, max_length)
  filename = make_file_name(filenumber)
  title = Titles[filenumber]
  puts filename + ": " + title
  tf = File.new(filename, "w")
  tf.print chunk
  tf.puts
  tf.puts
  tf.puts make_link_line(filenumber, max_length)
  tf.puts
  tf.puts
  tf.print reference_chunk
  tf.puts
  tf.puts
  tf.close
end

def write_files(chunks, reference_chunk)
  filenumber = 0
  chunks.each do |chunk|
    write_file(chunk, reference_chunk, filenumber, chunks.length - 1) unless chunk.length < 1
    filenumber += 1
  end
end

input = ARGF.read
chunks = input.split(SPLIT_MARKER)
reference_chunk = chunks.delete_at(-1)
record_titles(chunks)
update_table_of_contents(chunks)
write_files(chunks, reference_chunk)
Better Links Top Post-Processing Built In