#!/usr/bin/env ruby
# Replay a sequence of commands, with fake typing, output printing, wait points, and all
# Because dead code on slides is stupid
# Supposed you have a file with commands:
#
# $ cat /tmp/demo.sh
# # A demo
# ls -la
# # ---
# # Let's create some file
# touch somefile
# # ---
# ls -la
#
# Then you can:
# $ replay /tmp/demo.sh
# Wait points are marked with "# wait" or "# ---". Use p, n or space and [ENTER] to continue. Blank lines are skipped
# Use --nowait options to skip wait points
# Use --verbose to print out comments
require 'optparse'
options = {}
OptionParser.new do |opts|
opts.banner = "\e[1mUsage: replay path/to/commands.sh\e[0m"
opts.on("-W", "--nowait", "Do not respect wait points") { |v| options[:nowait] = true }
opts.on("-f", "--fast", "Do not fake typing") { |v| options[:fast] = true }
opts.on("-v", "--verbose", "Print also comment lines") { |v| options[:verbose] = true }
end.parse!
class String
def each_char
self.split('').each { |c| yield c }
end unless method_defined?(:each_char)
def comment?
self =~ /^#\s{0,1}.+$/
end
def continue?
self =~ /^p$|^n$|^ $/
end
def finish?
self =~ /^!/
end
def wait?
self =~ /^#\s*wait$|^#\s*-+/
end
end
if ARGV.empty?
puts "\e[31;1m[!]\e[0m Usage: replay path/to/commands.sh"; exit(1)
end
begin
commands = ARGF.read
rescue Errno::ENOENT
puts "\e[31;1m[!]\e[0m Usage: replay path/to/commands.sh"; exit(1)
end
commands.each_line do |line|
next if line.chomp.empty? # Skip blank lines right away
unless line.wait? && !@finish && !options[:nowait]
next if line.comment? && !options[:verbose] # Skip comment lines unless verbose
unless line.comment?
STDOUT.print "\e[32;1m▹\e[0m \e[1m"
unless options[:fast]
line.each_char do |letter|
STDOUT.print letter
STDOUT.flush
sleep 0.05 unless line.comment?
end
else
STDOUT.print line
end
STDOUT.print "\e[0m"
if chdir = line[/^cd (.+)[ \n]$/, 1]; Dir.chdir(chdir); end # Follow `cd` commands
STDOUT.print `#{line}` # Execute the shell command
else
STDOUT.print "\e[1m#{line}\e[0m" unless line.wait?
end
else # ... process wait line
puts "\n\e[1mWaiting...\e[0m\n\n"
trap(:INT, proc { puts "\n"+('-'*80)+"\n\n"; exit } ) # Exit nicely from Ctrl+C
while input = STDIN.gets.chomp
if input.finish? # Finish all without waiting
puts "\n\e[1mFinishing all\e[0m\n"
@finish = true
break
end
break if input.continue? # Continue processing commands in queue...
STDOUT.print `#{input}` unless input.empty? # ... or execute input
end
end
end