require 'opal' require 'rack' module Opal class CLI attr_reader :options, :filename attr_reader :evals, :load_paths, :output, :requires class << self attr_accessor :stdout end def initialize options = nil options ||= {} @options = options @evals = options[:evals] || [] @requires = options[:requires] || [] @filename = options[:filename] @load_paths = options[:load_paths] || [] @output = options[:output] || self.class.stdout || $stdout raise ArgumentError if @evals.empty? and @filename.nil? end def run set_processor_options case when options[:sexp]; prepare_eval_code; show_sexp when options[:compile]; prepare_eval_code; show_compiled_source when options[:server]; prepare_eval_code; start_server else run_code end end # RUN CODE class PathFinder < Struct.new(:paths) def find(filename) full_path = nil _path = paths.find do |path| full_path = File.join(path, filename) File.exist? full_path end full_path or raise(ArgumentError, "file: #{filename} not found") end end def run_code Opal.paths.concat load_paths path_finder = PathFinder.new(Opal.paths) builder = Opal::Builder.new full_source = builder.build('opal') require 'pathname' requires.each do |path| path = Pathname(path) path = Pathname(path_finder.find(path)) unless path.absolute? full_source << builder.build_str(path.read, :file => path.to_s) end evals.each_with_index do |code, index| full_source << builder.build_str(code, :file => "(eval #{index+1})") end file = Pathname(filename.to_s) full_source << builder.build_str(file.read, :file => file.to_s) if file.exist? run_with_node(full_source) end def run_with_node(code) require 'open3' begin stdin, stdout, stderr = Open3.popen3('node') rescue Errno::ENOENT raise MissingNodeJS, 'Please install Node.js to be able to run Opal scripts.' end stdin.write code stdin.close [stdout, stderr].each do |io| str = io.read puts str unless str.empty? end end class MissingNodeJS < StandardError end def start_server require 'rack' require 'webrick' require 'logger' Rack::Server.start( :app => server, :Port => options[:port] || 3000, :AccessLog => [], :Logger => Logger.new($stdout) ) end def show_compiled_source if sprockets[filename] puts sprockets[filename].to_a.last elsif File.exist?(filename) puts Opal.compile File.read(filename), options else puts Opal.compile(filename, options) end end def show_sexp puts sexp.inspect end # PROCESSOR def set_processor_options processor_options.each do |option| key = option.to_sym next unless options.has_key? key Opal::Processor.send("#{option}=", options[key]) end end def map compiler = Opal::Compiler.new compiler.compile(filename, options) compiler.source_map end def source File.exist?(filename) ? File.read(filename) : filename end def processor_options %w[ method_missing_enabled arity_check_enabled const_missing_enabled dynamic_require_severity source_map_enabled irb_enabled ] end ## # SPROCKETS def sprockets server.sprockets end def server @server ||= Opal::Server.new do |s| load_paths.each do |path| s.append_path path end s.main = File.basename(filename, '.rb') end end ## # OUTPUT def puts *args output.puts *args end ## # EVALS def evals_source evals.inject('', &:<<) end def prepare_eval_code if evals.any? require 'tmpdir' path = File.join(Dir.mktmpdir,"opal-#{$$}.js.rb") File.open(path, 'w') do |tempfile| load_paths << File.dirname(path) tempfile.puts 'require "opal"' tempfile.puts evals_source end @filename = File.basename(path) end end ## # SOURCE def sexp Opal::Parser.new.parse(source) end end end