[
  {
    "path": ".gitignore",
    "content": "/.bundle/\n/.yardoc\n/Gemfile.lock\n/_yardoc/\n/coverage/\n/doc/\n/pkg/\n/spec/reports/\n/tmp/\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: ruby\nrvm:\n  - 2.3.0\nbefore_install: gem install bundler -v 1.10.6\n"
  },
  {
    "path": "Gemfile",
    "content": "source 'https://rubygems.org'\n\n# Specify your gem's dependencies in yomikomu.gemspec\ngemspec\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Koichi Sasada\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Yomikomu\n\n* `yomikomu` gem enables to load compiled instruction sequences (bytecodes).\n* `kakidasu` command compiles and stores compiled instruction sequences (bytecodes).\n\nThis gem requires Ruby 2.3.0.\nMost of code is ported from `ruby/sample/iseq_load.rb`.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'yomikomu'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install yomikomu\n\n## Usage\n\n### Configuration\n\nYou can use the following configuration with environment variables.\n\n* YOMIKOMU_STORAGE (default: fs): choose storage type.\n  * 'fs' (default) stores binaries in the same directory (for examlple, `x.rb` will be compiled to `x.rb.yarb` in the same directory).\n  * 'fs2' stores binaries in specific directory.\n  * 'fsgz' stores files same as 'fs', but gz compressed.\n  * 'fs2gz' stores files same as 'fs2', but gz compressed.\n  * 'dbm' stores binaries using dbm.\n  * 'flatfile' stores binaries in one flat file. Updating is not supported. Use with YOMIKOMU_AUTO_COMPILE.\n* YOMIKOMU_STORAGE_DIR (default: \"~/.ruby_binaries\"): choose directory where binary files are stored.\n* YOMIKOMU_AUTO_COMPILE (default: false): if this value is `true`, then compile all required scripts implicitly.\n* YOMIKOMU_YOMIKOMANAI (default: not defined): disable all YOMIKOMU features.\n* YOMIKOMU_INFO (default: not defined): show some information.\n* YOMIKOMU_DEBUG (default: not defined): show more information.\n\n### Compile and store instruction sequences\n\nYou only need to use the following command.\n\n```\n$ kakidasu [file or dir] ...\n```\n\nCompile specified file or files (*.rb) in specified directory.\nIf no files or directories are specified, then use \"libdir\" of installed Ruby.\n\n### Load compiled binary\n\nYou only need to require `yomikomu`.\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/ko1/yomikomu.\n\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n\n"
  },
  {
    "path": "Rakefile",
    "content": "require \"bundler/gem_tasks\"\nrequire \"rake/testtask\"\n\nRake::TestTask.new(:test) do |t|\n  t.libs << \"test\"\n  t.libs << \"lib\"\n  t.test_files = FileList['test/**/*_test.rb']\nend\n\ntask :default => :test\n"
  },
  {
    "path": "bin/console",
    "content": "#!/usr/bin/env ruby\n\nrequire \"bundler/setup\"\nrequire \"yomikomu\"\n\n# You can add fixtures and/or initialization code here to make experimenting\n# with your gem easier. You can also use a different console, if you like.\n\n# (If you use this, don't forget to add pry to your Gemfile!)\n# require \"pry\"\n# Pry.start\n\nrequire \"irb\"\nIRB.start\n"
  },
  {
    "path": "bin/setup",
    "content": "#!/bin/bash\nset -euo pipefail\nIFS=$'\\n\\t'\n\nbundle install\n\n# Do any other automated setup that you need to do here\n"
  },
  {
    "path": "exe/kakidasu",
    "content": "#! /usr/bin/env ruby\n\n$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)\n\nrequire 'yomikomu'\nrequire 'rbconfig'\nrequire 'optparse'\n\nverbose = false\nexecutor = nil\n\nopts = OptionParser.new{|o|\n  o.on('--verify [original script]', 'verify mode'){\n    executor = Proc.new{|file|\n      raise \"TODO\"\n    }\n  }\n  o.on('--remove [original script]', 'remove specific compiled iseq'){\n    executor = Proc.new{|file|\n      ::Yomikomu.remove_compiled_iseq(file)\n    }\n  }\n  o.on('--remove-all', 'remove all compiled iseq'){\n    ::Yomikomu.remove_all_compiled_iseq\n  }\n  o.on('-v', '--verbose'){\n    $VERBOSE = true\n    ENV['YOMIKOMU_DEBUG'] = 'true'\n  }\n}\n\nexecutor = Proc.new{|file|\n  begin\n    ::Yomikomu.compile_and_store_iseq(file)\n  rescue SyntaxError => e\n    STDERR.puts e\n  end\n} unless executor # default\n\nopts.parse!(ARGV)\n\npaths = ARGV.empty? ? [RbConfig::CONFIG['libdir']] : ARGV\n\npaths.each{|path|\n  if File.directory?(path)\n    pattern = File.join(path, '**/*.rb')\n    Dir.glob(pattern){|file|\n      executor.call(file)\n    }\n  else\n    executor.call(path)\n  end\n}\n"
  },
  {
    "path": "lib/yomikomu/version.rb",
    "content": "module Yomikomu\n  VERSION = \"0.4.1\"\nend\n"
  },
  {
    "path": "lib/yomikomu.rb",
    "content": "# frozen_string_literal: true\nrequire \"yomikomu/version\"\n\nmodule Yomikomu\n  STATISTICS = Hash.new(0)\n\n  def self.prefix\n    unless yomu_dir = ENV['YOMIKOMU_STORAGE_DIR']\n      yomu_dir = File.expand_path(\"~/.ruby_binaries\")\n    end\n    Dir.mkdir(yomu_dir) unless File.exist?(yomu_dir)\n    \"#{yomu_dir}/cb.\"\n  end\n\n  YOMIKOMU_AUTO_COMPILE = ENV['YOMIKOMU_AUTO_COMPILE'] == 'true'\n  YOMIKOMU_USE_MMAP = ENV['YOMIKOMU_USE_MMAP'] == 'true'\n\n  def self.status\n    STDERR.puts \"[YOMIKOMU:INFO] (pid:#{Process.pid}) \" +\n                ::Yomikomu::STATISTICS.map{|k, v| \"#{k}: #{v}\"}.join(', ')\n  end\n\n  if $VERBOSE || ENV['YOMIKOMU_INFO'] == 'true'\n    def self.info\n      STDERR.puts \"[YOMIKOMU:INFO] (pid:#{Process.pid}) #{yield}\"\n    end\n    at_exit{\n      status\n    }\n  else\n    def self.info\n    end\n  end\n\n  if ENV['YOMIKOMU_DEBUG'] == 'true'\n    def self.debug\n      STDERR.puts \"[YOMIKOMU:DEBUG] (pid:#{Process.pid}) #{yield}\"\n    end\n  else\n    def self.debug\n    end\n  end\n\n  class NullStorage\n    def load_iseq fname; end\n    def compile_and_store_iseq fname; end\n    def remove_compiled_iseq fname; end\n  end\n\n  class BasicStorage\n    def initialize\n      require 'digest/sha1'\n    end\n\n    def load_iseq fname\n      iseq_key = iseq_key_name(fname)\n\n      if compiled_iseq_exist?(fname, iseq_key) && compiled_iseq_is_younger?(fname, iseq_key)\n        ::Yomikomu::STATISTICS[:loaded] += 1\n        ::Yomikomu.debug{ \"load #{fname} from #{iseq_key}\" }\n        binary = read_compiled_iseq(fname, iseq_key)\n        iseq = RubyVM::InstructionSequence.load_from_binary(binary)\n        # p [extra_data(iseq.path), RubyVM::InstructionSequence.load_from_binary_extra_data(binary)]\n        # raise unless extra_data(iseq.path) == RubyVM::InstructionSequence.load_from_binary_extra_data(binary)\n        iseq\n      elsif YOMIKOMU_AUTO_COMPILE\n        compile_and_store_iseq(fname, iseq_key)\n      else\n        ::Yomikomu::STATISTICS[:ignored] += 1\n        ::Yomikomu.debug{ \"ignored #{fname}\" }\n        nil\n      end\n    end\n\n    def extra_data fname\n      \"SHA-1:#{::Digest::SHA1.file(fname).digest}\"\n    end\n\n    def compile_and_store_iseq fname, iseq_key = iseq_key_name(fname = File.expand_path(fname))\n      ::Yomikomu.debug{ \"compile #{fname} into #{iseq_key}\" }\n      begin\n        iseq = RubyVM::InstructionSequence.compile_file(fname)\n        binary = iseq.to_binary(extra_data(fname))\n        write_compiled_iseq(fname, iseq_key, binary)\n        ::Yomikomu::STATISTICS[:compiled] += 1\n        iseq\n      rescue SyntaxError, RuntimeError => e\n        puts \"#{e}: #{fname}\"\n        nil\n      end\n    end\n\n    # def remove_compiled_iseq fname; nil; end # should implement at sub classes\n\n    private\n\n    def iseq_key_name fname\n      fname\n    end\n\n    # should implement at sub classes\n    # def compiled_iseq_younger? fname, iseq_key; end\n    # def compiled_iseq_exist? fname, iseq_key; end\n    # def read_compiled_file fname, iseq_key; end\n    # def write_compiled_file fname, iseq_key, binary; end\n  end\n\n  class FSStorage < BasicStorage\n    def initialize\n      super\n    end\n\n    def remove_compiled_iseq fname\n      iseq_key = iseq_key_name(fname)\n      if File.exist?(iseq_key)\n        Yomikomu.debug{ \"rm #{iseq_key}\" }\n        File.unlink(iseq_key)\n      end\n    end\n\n    private\n\n    def iseq_key_name fname\n      \"#{fname}.yarb\" # same directory\n    end\n\n    def compiled_iseq_exist? fname, iseq_key\n      File.exist?(iseq_key)\n    end\n\n    def compiled_iseq_is_younger? fname, iseq_key\n      File.mtime(iseq_key) >= File.mtime(fname)\n    end\n\n    def read_compiled_iseq fname, iseq_key\n      File.binread(iseq_key)\n    end\n\n    def write_compiled_iseq fname, iseq_key, binary\n      File.binwrite(iseq_key, binary)\n    end\n\n    def remove_all_compiled_iseq\n      raise \"unsupported\"\n    end\n  end\n\n  class FSSGZtorage < FSStorage\n\n  end\n\n  class FS2Storage < FSStorage\n    def initialize\n      super\n\n      require 'fileutils'\n      @dir = Yomikomu.prefix + \"files\"\n      unless File.directory?(@dir)\n        FileUtils.mkdir_p(@dir)\n      end\n    end\n\n    def iseq_key_name fname\n      File.join(@dir, fname.gsub(/[^A-Za-z0-9\\._-]/){|c| '%02x' % c.ord} + '.yarb') # special directory\n    end\n\n    def remove_all_compiled_iseq\n      Dir.glob(File.join(@dir, '**/*.yarb')){|path|\n        Yomikomu.debug{ \"rm #{path}\" }\n        FileUtils.rm(path)\n      }\n    end\n  end\n\n  module GZFileStorage\n    def initialize\n      require 'zlib'\n      super\n    end\n\n    def iseq_key_name fname\n      super + '.gz'\n    end\n\n    def read_compiled_iseq fname, iseq_key\n      Zlib::GzipReader.open(iseq_key){|f|\n        f.read\n      }\n    end\n\n    def write_compiled_iseq fname, iseq_key, binary\n      Zlib::GzipWriter.open(iseq_key){|f|\n        f.write(binary)\n      }\n    end\n  end\n\n  class FSGZStorage < FSStorage\n    include GZFileStorage\n  end\n\n  class FS2GZStorage < FS2Storage\n    include GZFileStorage\n  end\n\n  if YOMIKOMU_USE_MMAP\n    require 'mmapped_string'\n    Yomikomu.info{ \"[RUBY_YOMIKOMU] use mmap\" }\n\n    module MMapFile\n      def read_compiled_iseq fname, iseq_key\n        MmappedString.open(iseq_key)\n      end\n    end\n\n    class FSStorage\n      prepend MMapFile\n    end\n\n    class FS2Storage\n      prepend MMapFile\n    end\n  end\n\n  class DBMStorage < BasicStorage\n    def initialize\n      super\n      require 'dbm'\n      @db = DBM.open(Yomikomu.prefix + 'db')\n    end\n\n    def remove_compiled_iseq fname\n      @db.delete fname\n    end\n\n    private\n\n    def date_key_name fname\n      \"date.#{fname}\"\n    end\n\n    def iseq_key_name fname\n      \"body.#{fname}\"\n    end\n\n    def compiled_iseq_exist? fname, iseq_key\n      @db.has_key? iseq_key\n    end\n\n    def compiled_iseq_is_younger? fname, iseq_key\n      date_key = date_key_name(fname)\n      if @db.has_key? date_key\n        @db[date_key].to_i >= File.mtime(fname).to_i\n      end\n    end\n\n    def read_compiled_iseq fname, iseq_key\n      @db[iseq_key]\n    end\n\n    def write_compiled_iseq fname, iseq_key, binary\n      date_key = date_key_name(fname)\n      @db[iseq_key] = binary\n      @db[date_key] = Time.now.to_i\n    end\n  end\n\n  class FlatFileStorage < BasicStorage\n    def index_path\n      Yomikomu.prefix + 'ff_index'\n    end\n\n    def data_path\n      Yomikomu.prefix + 'ff_data'\n    end\n\n    def initialize\n      super\n      require 'fileutils'\n\n      @updated = false\n\n      if File.exist?(index_path)\n        open(index_path, 'rb'){|f| @index = Marshal.load(f)}\n      else\n        @index = {}\n        open(data_path, 'w'){} # touch\n      end\n\n      @data_file = open(data_path, 'a+b')\n\n      at_exit{\n        if @updated\n          open(index_path, 'wb'){|f| Marshal.dump(@index, f)}\n          Yomikomu.info{'FlatFile: update'}\n        end\n      }\n    end\n\n    def remove_compiled_iseq fname\n      raise 'unsupported'\n    end\n\n    private\n\n    def compiled_iseq_exist? fname, iseq_key\n      @index[iseq_key]\n    end\n\n    def compiled_iseq_is_younger? fname, iseq_key\n      offset, size, date = @index[iseq_key]\n      date.to_i >= File.mtime(fname).to_i\n    end\n\n    def read_compiled_iseq fname, iseq_key\n      offset, size, date = @index[iseq_key]\n      @data_file.pos = offset\n      binary = @data_file.read(size)\n      raise \"size is not match\" if binary.size != size\n      binary\n    end\n\n    def write_compiled_iseq fname, iseq_key, binary\n      raise \"compiled binary for #{fname} already exists. flatfile does not support overwrite.\" if compiled_iseq_exist?(fname, iseq_key)\n\n      @data_file.seek 0, IO::SEEK_END\n      offset = @data_file.tell\n      size = binary.size\n      date = Time.now.to_i\n      @data_file.write(binary)\n      @index[iseq_key] = [offset, size, date]\n\n      @updated = true\n    end\n  end\n\n  class FlatFileGZStorage < FlatFileStorage\n    def index_path\n      super + '_gz'\n    end\n\n    def data_path\n      super + '_gz'\n    end\n\n    def initialize\n      super\n      require 'zlib'\n    end\n\n    def read_compiled_iseq fname, iseq_key\n      binary = super\n      Zlib::Inflate.inflate(binary)\n    end\n\n    def write_compiled_iseq fname, iseq_key, binary\n      binary = Zlib::Deflate.deflate(binary)\n      super(fname, iseq_key, binary)\n    end\n  end\n\n  def self.compile_and_store_iseq fname\n    STORAGE.compile_and_store_iseq fname\n  end\n\n  def self.remove_compiled_iseq fname\n    STORAGE.remove_compiled_iseq fname\n  end\n\n  def self.remove_all_compiled_iseq\n    STORAGE.remove_all_compiled_iseq\n  end\n\n  def self.verify_compiled_iseq fname\n    STORAGE.verify_compiled_iseq fname\n  end\n\n  # select storage\n  STORAGE = case storage = ENV['YOMIKOMU_STORAGE']\n            when 'fs'\n              FSStorage.new\n            when 'fsgz'\n              FSGZStorage.new\n            when 'fs2'\n              FS2Storage.new\n            when 'fs2gz'\n              FS2GZStorage.new\n            when 'dbm'\n              DBMStorage.new\n            when 'flatfile'\n              FlatFileStorage.new\n            when 'flatfilegz'\n              FlatFileGZStorage.new\n            when 'null'\n              NullStorage.new\n            when nil\n              FSStorage.new\n            else\n              raise \"Unknown storage type: #{storage}\"\n            end\n\n  Yomikomu.info{ \"[RUBY_YOMIKOMU] use #{STORAGE.class}\" }\nend\n\nclass RubyVM::InstructionSequence\n  if ENV['YOMIKOMU_YOMIKOMANAI'] != 'true'\n    def self.load_iseq fname\n      ::Yomikomu::STORAGE.load_iseq(fname)\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_helper.rb",
    "content": "$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)\n\nrequire 'tmpdir'\n\ntmpdir = Dir.mktmpdir('yomikomu')\nENV['YOMIKOMU_STORAGE_DIR'] = tmpdir\nENV['YOMIKOMU_STORAGE'] = 'fs2'\nrequire 'yomikomu'\nrequire 'minitest/autorun'\n"
  },
  {
    "path": "test/x.rb",
    "content": "\r\ndef yomikomi_test_hello(name)\r\n  \"hello #{name}\"\r\nend\r\n"
  },
  {
    "path": "test/yomikomu_test.rb",
    "content": "require 'test_helper'\n\nclass YomikomuTest < Minitest::Test\n  def test_that_it_has_a_version_number\n    refute_nil ::Yomikomu::VERSION\n  end\n\n  def test_compile_file\n    assert_equal 0, ::Yomikomu::STATISTICS[:loaded]\n    assert_equal 0, ::Yomikomu::STATISTICS[:compiled]\n    ignored = ::Yomikomu::STATISTICS[:ignored]\n\n    Yomikomu::compile_and_store_iseq File.join(__dir__, 'x.rb')\n    assert_equal 1, ::Yomikomu::STATISTICS[:compiled]\n\n    load_file = File.join(__dir__, 'x.rb')\n\n    load load_file\n\n    assert_equal(\"hello world\", yomikomi_test_hello(\"world\"))\n    assert_equal 1, ::Yomikomu::STATISTICS[:loaded]\n    assert_equal 1, ::Yomikomu::STATISTICS[:compiled]\n\n    Yomikomu::remove_all_compiled_iseq\n    load load_file\n\n    assert_equal(\"hello world\", yomikomi_test_hello(\"world\"))\n    assert_equal 1, ::Yomikomu::STATISTICS[:loaded]\n    assert_equal 1, ::Yomikomu::STATISTICS[:compiled]\n\n    Yomikomu::compile_and_store_iseq File.join(__dir__, 'x.rb')\n    assert_equal 2, ::Yomikomu::STATISTICS[:compiled]\n\n    Yomikomu::remove_compiled_iseq load_file\n    load load_file\n\n    assert_equal(\"hello world\", yomikomi_test_hello(\"world\"))\n    assert_equal 1, ::Yomikomu::STATISTICS[:loaded]\n    assert_equal 2, ::Yomikomu::STATISTICS[:compiled]\n  end\nend\n"
  },
  {
    "path": "yomikomu.gemspec",
    "content": "# coding: utf-8\nlib = File.expand_path('../lib', __FILE__)\n$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)\nrequire 'yomikomu/version'\n\nGem::Specification.new do |spec|\n  spec.name          = \"yomikomu\"\n  spec.version       = Yomikomu::VERSION\n  spec.authors       = [\"Koichi Sasada\"]\n  spec.email         = [\"ko1@atdot.net\"]\n\n  spec.summary       = %q{Dump compiled iseq by binary (kakidasu) and load binary (yomidasu).}\n  spec.description   = %q{Dump compiled iseq by binary (kakidasu) and load binary (yomidasu).}\n  spec.homepage      = \"http://github.com/ko1/yomikomu\"\n  spec.license       = \"MIT\"\n\n  spec.files         = `git ls-files -z`.split(\"\\x0\").reject { |f| f.match(%r{^(test|spec|features)/}) }\n  spec.bindir        = \"exe\"\n  spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }\n  spec.require_paths = [\"lib\"]\n\n  spec.required_ruby_version = '>= 2.3.0'\n\n  spec.add_development_dependency \"bundler\", \"~> 1.10\"\n  spec.add_development_dependency \"rake\", \"~> 10.0\"\n  spec.add_development_dependency \"minitest\"\nend\n"
  }
]