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 |