Scaffolding Setup
From the starting point of Developing Drafts, I have a setup where I can begin working on new writing ideas and additions to the website without having to predetermine their place in a hierarchy or container. This also means that there’s no difference from a writing and editing perspective between working on a series or a single item. Which is what I want.
What I don’t have yet is a way to turn those drafts into published web pages with unique URLs which fits in seamlessly with managing the existing content archive.
End to end sketch
If I sketch out the CLI actions and workflow for posting a draft, I want it to go something like the following:
Create action
$ maetl --help
maetl [ content-type ] [options]
-f, --file Filename to create or convert. If no file type is given, references the directory
-d, --id Identifier of the content type, referencing the directory
-i, --import Local path or URL to import into the given content type
Content type subcommands:
draft Creates a new writing project in the local DRAFTS_PATH
entry Converts a draft or imports a path into a notebook entry on maetl.net
collection Converts a draft or imports a path into a new collection type on maetl.net
Examples
$ maetl draft -f new-ideas.md
$ maetl draft -d design-method-docs
$ maetl draft -d redesign -f burn-the-notebooks.md
$ maetl draft -i https://maetl.net/draft-template
$ maetl draft --import ~/Desktop/some_messy_hacked_up_artifact
$ maetl entry -d new-ideas
$ maetl collection -d redesign
$ maetl collection -i $DRAFTS_PATH/redesign
This would be far too much or too weird for a lot of people, but for me, is a fairly small and concise bit of command line scaffolding that will be easy to remember and repeat.
It’s not a lot of effort to build but the critical point is that by taking a punt on worrying about the archive problems, there are only two places in this script that would need to be updated if I prevaricate, waver, or change my mind on how notebooks, blogs, pages, etc are structured:
- entry / entries
- collection / collections
The entry
and collection
converters could be adapted to work with static site generators like Eleventy, Hugo, Jekyll etc, or customised to whatever odd system I want to put in place in future. So it provides a bit of slippage to encourage the fast production of writing and draft files without necessarily needing to mess around with YAML config, Markdown front matter, JSON, etc.
The path of least resistance
I already have a whole bunch of Ruby scripts set up within the maetl.net project so adding more to this is a minor detail, really. In the past I often used Rake tasks to manage this kind of thing, but more recently I have been using libraries like tty-toolkit and trying to keep everything as modular as possible in different files so it’s easier to rename or delete (trying new things out in self-contained files that are easy to delete later helps prevent legacy mess from accumulating).
The first step is to get the skeleton of the script going, a big chunk of which is copying over details from sketch above.
#!/usr/bin/env ruby
require "pathname"
require "tty-option"
require "fileutils"
module Maetl
DRAFTS_PATH = Pathname.new(ENV['DRAFTS_PATH'])
class Script
include TTY::Option
include FileUtils
argument :content_type
option :file do
short "-f"
long "--file path"
desc "Filename to create or convert. If no file type is given, references the directory"
end
option :dir do
short "-d"
long "--dir path"
desc "Identifier of the content type, referencing the directory"
end
option :import do
short "-i"
long "--import path"
desc "Local path or URL to import into the given content type"
end
flag :help do
short "-h"
long "--help"
desc "Print usage"
end
def run
parse
if params[:help]
print help
exit
else
case params[:content_type].to_sym
when :draft then create_draft
when :entry then prepare_entry
when :collection then prepare_collection
else
raise ArgumentError.new("Missing content type")
end
end
end
private
def create_draft
pp params.to_h
end
def prepare_entry
pp params.to_h
end
def prepare_collection
pp params.to_h
end
end
end
script = Maetl::Script.new
script.run
Now that the basic skeleton works, the very first thing I need to get started is to follow the patterns established in Developing Drafts for creating starter files for new bursts of writing.
I’ll start out by only supporting the single file use case:
option :file do
convert :pathname
short "-f"
long "--file path"
desc "Filename to create or convert. If no file type is given, references the directory"
end
def create_draft
file = params[:file]
new_draft_dir = DRAFTS_PATH.join(file.basename(file.extname))
new_file_path = new_draft_dir.join(file.basename)
mkdir(new_draft_dir)
File.write(new_file_path, file.basename(file.extname).to_s.gsub("-", " "))
end
And to test that it all works:
$ maetl draft -f how-to-shutdown-the-suez-canal.md
All this does is create a new directory in drafts then write a text file into it with the text how to shutdown the suez canal
.
No problems getting that working. It’s rough, with no error handling or checking but this isn’t difficult to handle later on.
Now, a bit of rewriting and clean up to properly support different combinations of -dir
and --file
flags:
def first_sentence(identity)
identity.basename(identity.extname).to_s.gsub("-", " ")
end
def draft_opts
if params[:file] && params[:dir]
new_dir = DRAFTS_PATH.join(params[:dir].basename)
return [new_dir, new_dir.join(params[:file].basename)]
end
if params[:dir]
new_dir = DRAFTS_PATH.join(params[:dir].basename)
return [new_dir, new_dir.join("#{params[:dir].basename}.txt")]
end
if params[:file]
new_dir = DRAFTS_PATH.join(params[:file].basename(params[:file].extname))
return [new_dir, new_dir.join(params[:file].basename)]
end
raise ArgumentError.new("Must provide --file or --dir option")
end
def create_draft
new_dir, new_file = draft_opts
mkdir(new_dir)
File.write(new_file, first_sentence(new_file))
end
In future, I’d like to change this to generate a random path and randomised draft content if the flags aren’t provided and remove the errors being raised but this isn’t helpful right now.
The following tests will ensure all branches are working:
$ maetl draft -d suez -f big-ship-crash.txt
$ maetl draft -d supply-chain-shocks
$ maetl draft -f cargo-ship-memes.md
The main thing I’ve achieved here is set up this CLI script as a metaphorical vice or clamp to force myself to make a decision about how to prepare files and directories to feed into the site publishing structure. I’ve put myself into the position of having to do it now in code, as I still haven’t come to a conclusion about how to design URLs and navigation.
One final little tweak is to open the new draft in a text writing app. Here I’ve used WriteRoom but without the -a
flag it would use the default assigned application for that file type.
def create_draft
new_dir, new_file = draft_opts
mkdir(new_dir)
File.write(new_file, first_sentence(new_file))
IO.popen("open -a WriteRoom #{new_file}")
end