lib/opal/repl.rb
# frozen_string_literal: true require 'opal' require 'securerandom' module Opal class REPL HISTORY_PATH = File.expand_path('~/.opal-repl-history') def initialize begin require 'mini_racer' rescue LoadError abort 'opal-repl depends on mini_racer gem, which is not currently installed' end begin require 'readline' rescue LoadError abort 'opal-repl depends on readline, which is not currently available' end MiniRacer::Platform.set_flags! :harmony @history = File.exist?(HISTORY_PATH) end def run(filename = nil) load_opal load_file(filename) if filename load_history run_input_loop ensure dump_history end def load_file(filename) raise "file does not exist: #{filename}" unless File.exist? filename eval_ruby File.read(filename) end # A polyfill so that SecureRandom works in repl correctly. def random_bytes(bytes) ::SecureRandom.bytes(bytes).split('').map(&:ord) end def load_opal v8.attach('console.log', method(:puts).to_proc) v8.attach('console.warn', method(:warn).to_proc) v8.attach('crypto.randomBytes', method(:random_bytes).to_proc) v8.eval Opal::Builder.new.build('opal').to_s v8.attach('Opal.exit', method(:exit).to_proc) end def run_line(line) result = eval_ruby(line) puts "=> #{result}" end def run_input_loop # on SIGINT lets just return from the loop.. previous_trap = trap('SIGINT') { return } while (line = readline) run_line(line) end ensure trap('SIGINT', previous_trap || 'DEFAULT') end private def eval_ruby(code) builder = Opal::Builder.new builder.build_str(code, '(irb)', irb: true, const_missing: true) builder.processed[0...-1].each { |js_code| eval_js js_code.to_s } last_processed_file = builder.processed.last.to_s eval_js <<-JS var $_result = #{last_processed_file}; $_result.$inspect() JS rescue => e puts "#{e.message}\n\t#{e.backtrace.join("\n\t")}" end def eval_js(code) v8.eval(code) end def v8 @v8 ||= MiniRacer::Context.new end def readline Readline.readline '>> ', true end def load_history return unless @history File.read(HISTORY_PATH).lines.each { |line| Readline::HISTORY.push line.strip } end def dump_history return unless @history length = Readline::HISTORY.size > 1000 ? 1000 : Readline::HISTORY.size File.write(HISTORY_PATH, Readline::HISTORY.to_a[-length..-1].join("\n")) end end end