From 57cf3f022a636dbb9ef7143435ebe16d4ed34366 Mon Sep 17 00:00:00 2001 From: Simon Parri Date: Mon, 1 May 2023 14:54:29 -0500 Subject: Initialize repository --- .gitignore | 1 + README.org | 56 ++++++++++++++++++++++++++++++++++++ steeplejack | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 .gitignore create mode 100644 README.org create mode 100755 steeplejack diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b25c15b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*~ diff --git a/README.org b/README.org new file mode 100644 index 0000000..e71b44a --- /dev/null +++ b/README.org @@ -0,0 +1,56 @@ +* Steeplejack +A generic scaffolding system. +** About +#+begin_quote +A steeplejack is a craftsman who scales buildings, chimneys, and +church steeples to carry out repairs or maintenance. + +Steeplejacks erect ladders on church spires, industrial chimneys, +cooling towers, bell towers, clock towers, or any other high +structure. +#+end_quote + +Like a steeplejack, ~steeplejack~ is a diligent worker that can erect +scaffolding for any project you need. +** Usage +~steeplejack~ can be taught how to erect scaffolding by setting the +=STEEPLEJACK_DIR= (defaults to ~~/.steeplejack~)environment variable and +putting template directories there. A template directory contains a +~_scaffold.yml~ and various ~.erb~ files. ~_scaffold.yml~ should at least +contain a =params= or =parameters= property. It should be a list of +property names. Each parameter will be read when the scaffold is +erected. ~_scaffold.yml~ can also contain an =erb_options= parameter +that, when present, is used to set the =trim_mode= for ERB. See the [[https://docs.ruby-lang.org/en/master/ERB.html][ERB +documentation]] for details. + +When the scaffold is erected, each ~.erb~ file in the scaffold’s +directory is evaluated, and the output is written to the corresponding +file in the target directory. For example, let’s take a scaffold +directory ~scaf~. We will deploy it to ~targ~. Let’s say that ~scaf~ has +three files in it: ++ ~scaf/_scaffold.yml~ ++ ~scaf/a.erb~ ++ ~scaf/b.erb~ +Let’s also say that ~_scaffold.yml~ contains the text +#+begin_src yaml + properties: + - foo + - bar +#+end_src +When deploying ~scaf~ to ~targ~, you will be prompted for =foo= and =bar=. +Then ~scaf/a.erb~ and ~scaf/b.erb~ will be evaluated, and the results +written to ~targ/a~ and ~targ/b~. +** Configuration +When ~steeplejack~ starts up, it loads ~$STEEPLEJACK_DIR/init.rb~. The +most notable thing to have ~init.rb~ is ~add_alias~. An example: +#+begin_src ruby + add_alias sc: :scaffold, + ls: :list, + i: :info +#+end_src + +Note that ~add_alias~ is not the same as ~alias~. ~alias~ is a built-in +Ruby feature, whereas ~add_alias~ is ~steeplejack~-specific; using ~alias~ +will not make the alias available as a ~steeplejack~ subcommand. +** License +~steeplejack~ is published under the [[http://gnu.org/licenses/gpl-3.0.html][GPLv3]] (or any later version). diff --git a/steeplejack b/steeplejack new file mode 100755 index 0000000..fde9259 --- /dev/null +++ b/steeplejack @@ -0,0 +1,94 @@ +#!/usr/bin/ruby + +%w[erb yaml readline fileutils] + .map &(method :require) + +STEEPLEJACK_DIR = File.expand_path(ENV["STEEPLEJACK_DIR"]&.chomp("/") || "~/.steeplejack") +STEEPLEJACK_RC = "#{STEEPLEJACK_DIR}/init.rb" + +def erb(from:, to:, params:, options:) + File.write to, ERB.new(File.read(from), trim_mode: options).result_with_hash(params) +end + +def get_params(params) + Hash[params.zip params.map {|p| Readline.readline p+": "}] +end + +$commands = { + scaffold: "Deploy a scaffold", + list: "List available scaffolds", + info: "Show info on scaffold", + help: "Show this help" +} + +def add_alias(hash) + hash.each do |from, to| + Kernel.alias_method from, to + $commands[from] = "Alias for #{to}" + end +end + +def scaffold(name, dest) + puts "### Deploying \"#{name}\" to \"#{dest}\" ###" + + orig = "#{STEEPLEJACK_DIR}/#{name}" + yaml = "#{orig}/_scaffold.yml" + recipe = YAML.load_file yaml + + files = Dir.chdir(orig) do + Dir["**/**.erb"].map {|s| s.chomp ".erb" } + end + + # We want to check the files as soon as possible + files.each do |file| + raise "File exists: #{dest}/#{file}" if File.exist? "#{dest}/#{file}" + end + + params = get_params (recipe["parameters"] || recipe["params"]) + options = recipe["erb_options"] + + FileUtils.mkdir_p dest + + files.each do |file| + puts "Writing \"#{file}\"" + erb from: "#{orig}/#{file}.erb", to: "#{dest}/#{file}", + params: params, options: options + end + + puts "\"#{name}\" deployed successfully" +end + +def list + if File.exist? STEEPLEJACK_DIR + (Dir.children(STEEPLEJACK_DIR) + .filter &(File.method :directory?)) + .map {|p| p.chomp("/").split("/")[-1] } + .each &(method :puts) + end +end + +def info(name) + puts File.read "#{STEEPLEJACK_DIR}/#{name}/_scaffold.yml" +end + +def help + puts "#{$0}: generic scaffolding" + puts "usage: #{$0} SUBCOMMAND ARGS" + puts + puts "Available subcommands:" + $commands.each {|n, d| puts " #{n}: #{d || "Not documented"}" } +end + +def main + load STEEPLEJACK_RC if File.exist? STEEPLEJACK_RC + sub = ARGV[0] + if ["--help", "-h", nil].include? sub + help + elsif $commands.keys.include? sub.to_sym + send sub, *ARGV[1..-1] + else + raise "No subcommand \"#{sub}\"" + end +end + +main if $0 == __FILE__ -- cgit v1.2.3