Skip to content

Instantly share code, notes, and snippets.

@ahoward
Created August 16, 2018 18:19
Show Gist options
  • Save ahoward/ef408c76656f964ba22dfc053f6ff29b to your computer and use it in GitHub Desktop.
Save ahoward/ef408c76656f964ba22dfc053f6ff29b to your computer and use it in GitHub Desktop.
even though i author'd main.rb - which is a really complete cli tool, i 've been yearning for something simpler for writing small cli tools. eg. in a rails ./scripts/ directory...
#! /usr/bin/env ruby
require_relative '../lib/script.rb'
script {
help '
HELP!
'
run {
p [@mode, @argv, @options]
}
run :foo do
p [@mode, @argv, @options]
end
run :bar do
p [@mode, @argv, @options]
end
run 'foo-bar' do
p [@mode, @argv, @options]
end
}
#! /usr/bin/env ruby
-> do
# require_relative '../lib/script.rb'
script {
help <<-____
NAME
script.rb
SYNOPSIS
script.rb is an extremely minimal but complete command line script dsl and library
TL;DR;
#
~> cat a.rb
script {
}
~> ruby a.rb
NAME
#TODO
SYNOPSIS
#TODO
DESCRIPTION
#TODO
EXAMPLES
#TODO
#
~> cat a.rb
script {
help 'this is the help'
}
~> ruby a.rb
this is the help
#
~> cat a.rb
script {
run{ p 42 }
}
~> ruby a.rb
42
#
~> cat a.rb 1 2 3 k:v a=b x: y=
script {
run{
p @argv
p @options
}
}
~> ruby a.rb
['a', 'b', 'c']
{'k' => 'v', 'a' => 'b', 'x' => nil, 'y' => nil}
#
~> cat a.rb
script {
run{
p [@mode, @argv, @options]
}
run(:foo){
p [@mode, @argv, @options]
}
}
~> ruby a.rb a b c k:v
[nil, ['a', 'b', 'c'], {'k' => 'v'}]
~> ruby a.rb foo a b c k:v
['foo', ['a', 'b', 'c'], {'k' => 'v'}]
____
run {
p [@mode, @argv, @options]
}
run :foo do
p [@mode, @argv, @options]
end
run :bar do
p [@mode, @argv, @options]
end
}
end
BEGIN {
#
require 'json'
require 'base64'
require 'securerandom'
require 'openssl'
require 'uri'
require 'cgi'
require 'yaml'
#
def script(*args, &block)
Script.run!(*args, &block)
end
#
class Script
attr_accessor :env
attr_accessor :argv
attr_accessor :stdout
attr_accessor :stdin
attr_accessor :stderr
attr_accessor :help
def run!(env = ENV, argv = ARGV)
setup!(env, argv)
parse_command_line!
set_mode!
run_mode!
end
def setup!(env, argv)
@env = env.to_hash.dup
@argv = argv.map{|arg| arg.dup}
@stdout = $stdout.dup
@stdin = $stdin.dup
@stderr = $stderr.dup
@help = Util.unindent(DEFAULT_HELP)
end
DEFAULT_HELP = <<-__
NAME
#TODO
SYNOPSIS
#TODO
DESCRIPTION
#TODO
EXAMPLES
#TODO
__
def parse_command_line!
@options = Hash.new
re = %r`[:=]`
argv = []
head = []
tail = []
%w[ :: ].each do |stop|
if((i = @argv.index(stop)))
head = @argv.slice(0 ... i)
tail = @argv.slice((i + 1) ... @argv.size)
@argv = head
break
end
end
@argv.each do |arg|
if arg =~ re
key, val = arg.split(re).map{|kv| kv.strip}
@options[key] = val
else
argv.push(arg)
end
end
argv += tail
@argv.replace(argv)
end
def set_mode!
case
when respond_to?("run_#{ @argv[0] }!")
@mode = @argv.shift
else
@mode = nil
end
end
def run_mode!
if @mode
send("run_#{ @mode }!")
else
run
end
end
def run
run_help!
end
def run_help!
STDOUT.puts(@help)
end
module Util
def unindent(arg)
string = arg.to_s.dup
margin = nil
string.each_line do |line|
next if line =~ %r/^\s*$/
margin = line[%r/^\s*/] and break
end
string.gsub!(%r/^#{ margin }/, "") if margin
margin ? string : nil
end
extend Util
end
class DSL < ::Object
def DSL.evaluate(script, &block)
dsl = DSL.new(script)
dsl.instance_eval(&block)
script
end
def initialize(script)
@script = script
end
def help(help)
@script.help = Util.unindent(help)
end
def run(mode = nil, &block)
method_name =
if mode
"run_#{ mode }!"
else
"run"
end
@script.define_singleton_method(method_name, &block)
end
end
def Script.run!(*args, &block)
%w[ PIPE INT ].each{|signal| Signal.trap(signal, "EXIT")}
Script.new.tap do |script|
DSL.evaluate(script, &block)
script.run!
end
end
end
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment