#! /usr/bin/ruby def quit(message, status=1) STDERR.puts(message) exit(status) end quit('usage: input [output]', 0) \ if ARGV.empty? \ or ARGV.length > 2 \ or %w[-h --help].any? { |flag| ARGV.include?(flag) } input, output = ARGV output = input.delete_suffix('.typ') + '-expanded.typ' if output.nil? quit('error: file does not exist') if not File.file?(input) @source = File.read(input) def show(location) eol = @source[location] == ?\n location -= 1 if eol left = (@source.rindex(?\n, location) || -1) + 1 right = (@source.index(?\n, location) || 0) - 1 line = @source[left..right].rstrip prev, post = '', '' if left > 1 prev_left = (@source.rindex(?\n, left - 2) || -1) + 1 prev = @source[prev_left..(left - 2)].rstrip end if right != -1 post_right = (@source.index(?\n, right + 2) || 0) - 1 post = @source[(right + 2)..post_right].rstrip end offset = 0 linenumber = 0 until offset.nil? or offset >= location offset = @source.index(?\n, offset) + 1 linenumber += 1 end linenums = [-1, 0, 1].map { |d| linenumber + d }.map(&:to_s) width = linenums.map(&:length).max() a, b, c = linenums.map { |n| n.rjust(width) } s = ' ' * width location += 1 if eol STDERR.puts("\e[2m#{s} ┃\e[22m") STDERR.puts("\e[2m#{a} ┃ #{prev}\e[22m") if not prev.empty? STDERR.puts("\e[2m#{b} ┃ #{line}\e[22m") STDERR.puts("\e[2m#{s} ┇\e[22m #{' ' * (location - left)}\e[31m^\e[39m") STDERR.puts("\e[2m#{c} ┃ #{post}\e[22m") if not post.empty? STDERR.puts("\e[2m#{s} ┃\e[22m") if not post.empty? end def error(message, location) STDERR.puts("error: #{message}") show(location) exit(1) end BREAKPOINTS = /(? ?], ?( => ?), } offset = 0 parts = [] while true func = @source.index('#slide', offset) break if func.nil? parts << @source[offset...func] open = @source.index('[', func + 6) error('missing content after #slide', func + 6) if open.nil? # error('unexpected characters before opening bracket', open) \ # if not @source[(func + 6)...open].strip.empty? mark = open + 1 stack = [[?], open]] while true match = BREAKPOINTS.match(@source, mark) error('unmatched opening delimiter', stack[-1]) if match.nil? case match.match(0) when ?[, ?( stack.push([PAIRS[match.match(0)], match.begin(0)]) when ?], ?) delim, open = stack.pop() if delim != match.match(0) STDERR.puts("error: mismatched delimiters") STDERR.puts("info: opening delimiter here:") show(open) STDERR.puts("info: closing delimiter here:") show(match.begin(0)) exit(1) end else parts << @source[func...match.begin(0)].gsub(PAUSE, '') parts << stack.reverse_each.map(&:first).join() + ?\n end mark = match.end(0) break if stack.empty? end parts << @source[func...mark] offset = mark end parts << @source[offset..] File.write(output, parts.join())