proc-to-ast-0.1.0/0000755000175000017500000000000013717265332015111 5ustar debbiecocoadebbiecocoaproc-to-ast-0.1.0/README.md0000644000175000017500000000231313717265332016367 0ustar debbiecocoadebbiecocoa# proc_to_ast [![Gem Version](https://badge.fury.io/rb/proc_to_ast.svg)](http://badge.fury.io/rb/proc_to_ast) [![Build Status](https://travis-ci.org/joker1007/proc_to_ast.svg?branch=master)](https://travis-ci.org/joker1007/proc_to_ast) Add `#to_ast` method to Proc. `#to_ast` convert Proc to `Parser::AST::Node`, using [parser](https://github.com/whitequark/parser "whitequark/parser") gem. ## Installation Add this line to your application's Gemfile: gem 'proc_to_ast' And then execute: $ bundle Or install it yourself as: $ gem install proc_to_ast ## Usage ```ruby require 'proc_to_ast' foo = proc { p(1 + 1) } foo.to_ast # => # (block # (send nil :proc) # (args) # (send nil :p # (send # (int 1) :+ # (int 1)))) foo.to_source # => "proc do\n p(1 + 1)\nend" foo.to_source(highlight: true) # => "proc \e[32mdo\e[0m\n p(\e[1;34m1\e[0m + \e[1;34m1\e[0m)\n\e[32mend\e[0m" ``` ## Contributing 1. Fork it ( https://github.com/[my-github-username]/proc_to_ast/fork ) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create a new Pull Request proc-to-ast-0.1.0/.gitignore0000644000175000017500000000027113717265332017101 0ustar debbiecocoadebbiecocoa*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp *.bundle *.so *.o *.a mkmf.log proc-to-ast-0.1.0/proc_to_ast.gemspec0000644000175000017500000000216213717265332020773 0ustar debbiecocoadebbiecocoa# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'proc_to_ast/version' Gem::Specification.new do |spec| spec.name = "proc_to_ast" spec.version = ProcToAst::VERSION spec.authors = ["joker1007"] spec.email = ["kakyoin.hierophant@gmail.com"] spec.summary = %q{Convert Proc object to AST::Node} spec.description = %q{Add #to_ast method to Proc. #to_ast converts Proc object to AST::Node.} spec.homepage = "https://github.com/joker1007/proc_to_ast" spec.license = "MIT" spec.files = `git ls-files -z`.split("\x0") spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"] spec.add_runtime_dependency "parser" spec.add_runtime_dependency "unparser" spec.add_runtime_dependency "coderay" spec.add_development_dependency "bundler", ">= 1.5" spec.add_development_dependency "rake" spec.add_development_dependency "rspec" spec.required_ruby_version = '>= 2.0.0' end proc-to-ast-0.1.0/.rspec0000644000175000017500000000003713717265332016226 0ustar debbiecocoadebbiecocoa--format documentation --color proc-to-ast-0.1.0/.travis.yml0000644000175000017500000000015113717265332017217 0ustar debbiecocoadebbiecocoalanguage: ruby sudo: false rvm: - 2.2.2 - 2.1 - 2.0 notifications: email: on_success: change proc-to-ast-0.1.0/lib/0000755000175000017500000000000013717265332015657 5ustar debbiecocoadebbiecocoaproc-to-ast-0.1.0/lib/proc_to_ast.rb0000644000175000017500000000510013717265332020514 0ustar debbiecocoadebbiecocoarequire "proc_to_ast/version" require 'parser/current' require 'unparser' require 'coderay' module ProcToAst class MultiMatchError < StandardError; end class Parser attr_reader :parser def initialize @parser = ::Parser::CurrentRuby.default_parser @parser.diagnostics.consumer = ->(diagnostic) {} # suppress error message end # Read file and try parsing # if success parse, find proc AST # # @param filename [String] reading file path # @param linenum [Integer] start line number # @return [Parser::AST::Node] Proc AST def parse(filename, linenum) @filename, @linenum = filename, linenum buf = [] File.open(filename, "rb").each_with_index do |line, index| next if index < linenum - 1 buf << line begin return do_parse(buf.join) rescue ::Parser::SyntaxError node = trim_and_retry(buf) return node if node end end fail(::Parser::SyntaxError, 'Unknown error') end private def do_parse(source) parser.reset source_buffer = ::Parser::Source::Buffer.new(@filename, @linenum) source_buffer.source = source node = parser.parse(source_buffer) block_nodes = traverse_node(node) if block_nodes.length == 1 block_nodes.first else raise ProcToAst::MultiMatchError end end # Remove tail comma and wrap dummy method, and retry parsing # For proc inner Array or Hash def trim_and_retry(buf) *lines, last = buf # For inner Array or Hash or Arguments list. lines << last.gsub(/,\s*$/, "") do_parse("a(#{lines.join})") # wrap dummy method rescue ::Parser::SyntaxError end def traverse_node(node) if node.type != :block node.children.flat_map { |child| if child.is_a?(AST::Node) traverse_node(child) end }.compact else [node] end end end end class Proc # @return [Parser::AST::Node] Proc AST def to_ast filename, linenum = source_location parser = ProcToAst::Parser.new parser.parse(filename, linenum) end # @param highlight [Boolean] enable output highlight # @return [String] proc source code def to_source(highlight: false) source = Unparser.unparse(to_ast) if highlight CodeRay.scan(source, :ruby).terminal else source end end def to_raw_source(highlight: false) source = to_ast.loc.expression.source if highlight CodeRay.scan(source, :ruby).terminal else source end end end proc-to-ast-0.1.0/lib/proc_to_ast/0000755000175000017500000000000013717265332020173 5ustar debbiecocoadebbiecocoaproc-to-ast-0.1.0/lib/proc_to_ast/version.rb0000644000175000017500000000005113717265332022201 0ustar debbiecocoadebbiecocoamodule ProcToAst VERSION = "0.1.0" end proc-to-ast-0.1.0/Gemfile0000644000175000017500000000014013717265332016377 0ustar debbiecocoadebbiecocoasource 'https://rubygems.org' # Specify your gem's dependencies in proc_to_ast.gemspec gemspec proc-to-ast-0.1.0/Rakefile0000644000175000017500000000016613717265332016561 0ustar debbiecocoadebbiecocoarequire "bundler/gem_tasks" require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) task :default => :spec proc-to-ast-0.1.0/spec/0000755000175000017500000000000013717265332016043 5ustar debbiecocoadebbiecocoaproc-to-ast-0.1.0/spec/spec_helper.rb0000644000175000017500000000012113717265332020653 0ustar debbiecocoadebbiecocoa$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'proc_to_ast' proc-to-ast-0.1.0/spec/proc_to_ast_spec.rb0000644000175000017500000000743013717265332021722 0ustar debbiecocoadebbiecocoarequire 'spec_helper' describe Proc do describe "#to_ast" do it "return Parser::AST::Node" do ast = -> { 1 + 1 }.to_ast expect(ast).to be_a(Parser::AST::Node) expect(ast.type).to eq(:block) end context "proc variation" do it "converts Kernel#proc" do pr = proc { p 1 } expect(pr.to_ast).to be_a(AST::Node) end it "converts Proc.new" do pr = Proc.new do |b| puts b end expect(pr.to_ast).to be_a(AST::Node) end it "long proc" do pr = Proc.new do |b| p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 p 1 end expect(pr.to_ast).to be_a(AST::Node) end it "converts block passing method" do def receive_block(&block) block.to_ast end block_pass = receive_block do |n| puts n [1, 2, 3].map do |i| i * 2 end end block_pass2 = receive_block { %w(a b c).map(&:upcase) } expect(block_pass).to be_a(AST::Node) expect(block_pass2).to be_a(AST::Node) end it "raise ProcToAst::MultiMatchError, when other block exists on same line " do _ = [1].map {|i| i * 2}; fuga = ->(a) { p a } expect{ fuga.to_ast }.to raise_error(ProcToAst::MultiMatchError) end it "inner array" do array = [ 1, -> { 2 }, [ -> { 3 }, ], proc do |a| [ a, ] end ] expect(array[1].to_ast).to be_a(AST::Node) expect(array[1].to_source).to eq("lambda do\n 2\nend") expect(array[2][0].to_source).to eq("lambda do\n 3\nend") expect(array[3].to_source).to eq("proc do |a|\n [a]\nend") end it "inner two dimension array" do array = [ [1, 2, -> { 3 }], [4, 5, -> { 6 }] ] expect(array[0][2].to_ast).to be_a(AST::Node) expect(array[1][2].to_ast).to be_a(AST::Node) end it "inner hash" do hash_oneline = {a: -> { 1 }} expect(hash_oneline[:a].to_ast).to be_a(AST::Node) expect(hash_oneline[:a].to_source).to eq("lambda do\n 1\nend") end it "inner multiline hash" do hash = { a: -> { 1 }, :b => -> { 2 }, c: -> { 3 }, d: -> { 4 } } expect(hash[:a].to_ast).to be_a(AST::Node) expect(hash[:a].to_source).to eq("lambda do\n 1\nend") expect(hash[:b].to_ast).to be_a(AST::Node) expect(hash[:b].to_source).to eq("lambda do\n 2\nend") expect(hash[:c].to_ast).to be_a(AST::Node) expect(hash[:c].to_source).to eq("lambda do\n 3\nend") expect(hash[:d].to_ast).to be_a(AST::Node) expect(hash[:d].to_source).to eq("lambda do\n 4\nend") end it "inner another hash" do hash = { a: 1, :b => -> { 2 }, } expect(hash[:b].to_ast).to be_a(AST::Node) expect(hash[:b].to_source).to eq("lambda do\n 2\nend") end end end describe "#to_source" do it "return source code string" do fuga = ->(a) { p a } expect(fuga.to_source).to eq("lambda do |a|\n p(a)\nend") expect(fuga.to_source(highlight: true)).to eq("lambda \e[32mdo\e[0m |a|\n p(a)\n\e[32mend\e[0m") end end describe "#to_raw_source" do it "return original source code" do pr = ->(a) { a + 1 } expect(pr.to_raw_source).to eq("->(a) { a + 1 }") end end end proc-to-ast-0.1.0/LICENSE.txt0000644000175000017500000000205213717265332016733 0ustar debbiecocoadebbiecocoaCopyright (c) 2014 joker1007 MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.