svenfuchs/cl

View on GitHub
bin/examples

Summary

Maintainability
Test Coverage
#!/usr/bin/env ruby
$: << File.expand_path('../../lib', __FILE__)

require 'erb'

class Example < Struct.new(:src)
  attr_reader :actual, :expected, :num, :errors

  def initialize(*)
    super
    @num = 0
    @errors = []
  end

  def write
    $stderr.puts "writing #{target}"
    File.write(target, render)
  end

  private

    def target
      src.sub(%r(/_src), '').sub('.erb.rb', '')
    end

    def render
      ERB.new(File.read(src), nil, '-').result(binding)
    end

    def run(code)
      @actual = capture { eval(code) }.strip.sub('Example::', '') # ugh.
      code
    rescue Exception => e
      puts e.message, e.backtrace unless e.message == 'exit'
      @actual = @stdout.string.strip # Cl::Ui prints error messages to stdout? gotta fix that.
      code
    end

    def out(expected, opts = { short: false })
      @num += 1
      @expected = expected.rstrip
      errors << error unless actual.strip == expected.strip
      out = "# => #{actual}" if opts[:short]
      out || comment(['Output:', indent(actual)].join("\n\n"))
    end

    def indent(str)
      map_lines(str) { |line| line.empty? ? line : "  #{line}" }
    end

    def comment(str)
      map_lines(str) { |line| "# #{line}".strip }
    end

    def error
      red sq(<<-msg)
        Expected example output does not match its actual output in #{src}, example ##{num}.

        Expected:

        #{indent(expected)}

        Actual:

        #{indent(actual)}
      msg
    end

    def map_lines(str, &block)
      str.to_s.split("\n").map(&block).join("\n")
    end

    def capture
      stdout, $stdout = $stdout, StringIO.new
      @stdout = $stdout
      yield
      $stdout.string
    ensure
      $stdout = stdout
    end

    def sq(str)
      width = str =~ /( *)\S/ && $1.size
      str.lines.map { |line| line.gsub(/^ {#{width}}/, '') }.join
    end
end

def red(str)
  "\e[31m#{str}\e[0m"
end

def yellow(str)
  "\e[33m#{str}\e[0m"
end

skip = RUBY_VERSION < '2.4' ? %w(readme/basic readme/description) : []
glob = ARGV.any? ? "{#{ARGV.join(',')}}" : '**/*'
paths = Dir["examples/_src/#{glob}.erb.rb"].sort

status = paths.inject(true) do |status, path|
  if skip.any? { |skip| path.include?(skip) }
    $stderr.puts yellow("skipping #{path} on Ruby #{RUBY_VERSION}")
    next status
  end

  consts = Example.constants
  example = Example.new(path)
  example.write
  example.errors.each { |msg| $stderr.puts msg }

  Cl::Cmd.registry.objects.reject! { |key, _| key != :help } # ugh.
  consts = Example.constants - consts
  consts.each { |name| Example.send(:remove_const, name) }

  status && example.errors.empty?
end

status ? puts('Ok.') : abort('Failed.')