rearchitect as a gem
embedding Ruby in C did not go so well
This commit is contained in:
parent
9188bb42e2
commit
432bd7458e
11
.gitignore
vendored
11
.gitignore
vendored
@ -1,3 +1,8 @@
|
||||
/.lock-waf*
|
||||
/.waf*
|
||||
/build/
|
||||
/.bundle/
|
||||
/.yardoc
|
||||
/_yardoc/
|
||||
/coverage/
|
||||
/doc/
|
||||
/pkg/
|
||||
/spec/reports/
|
||||
/tmp/
|
||||
|
4
Gemfile
Normal file
4
Gemfile
Normal file
@ -0,0 +1,4 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
# Specify your gem's dependencies in svi.gemspec
|
||||
gemspec
|
20
Gemfile.lock
Normal file
20
Gemfile.lock
Normal file
@ -0,0 +1,20 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
svi (0.1.0)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
rake (10.4.2)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
bundler (~> 1.10)
|
||||
rake (~> 10.0)
|
||||
svi!
|
||||
|
||||
BUNDLED WITH
|
||||
1.10.6
|
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Josh Holtrop
|
||||
|
||||
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.
|
19
Makefile
19
Makefile
@ -1,19 +0,0 @@
|
||||
.PHONY: build
|
||||
build:
|
||||
./waf "$@"
|
||||
|
||||
.PHONY: distclean
|
||||
distclean:
|
||||
./waf "$@"
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
./waf "$@"
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
./waf "$@"
|
||||
|
||||
.PHONY: uninstall
|
||||
uninstall:
|
||||
./waf "$@"
|
41
README.md
Normal file
41
README.md
Normal file
@ -0,0 +1,41 @@
|
||||
# Svi
|
||||
|
||||
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/svi`. To experiment with that code, run `bin/console` for an interactive prompt.
|
||||
|
||||
TODO: Delete this and the text above, and describe your gem
|
||||
|
||||
## Installation
|
||||
|
||||
Add this line to your application's Gemfile:
|
||||
|
||||
```ruby
|
||||
gem 'svi'
|
||||
```
|
||||
|
||||
And then execute:
|
||||
|
||||
$ bundle
|
||||
|
||||
Or install it yourself as:
|
||||
|
||||
$ gem install svi
|
||||
|
||||
## Usage
|
||||
|
||||
TODO: Write usage instructions here
|
||||
|
||||
## Development
|
||||
|
||||
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
||||
|
||||
To 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).
|
||||
|
||||
## Contributing
|
||||
|
||||
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/svi.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
||||
|
6
lib/svi.rb
Normal file
6
lib/svi.rb
Normal file
@ -0,0 +1,6 @@
|
||||
require_relative "svi/svn_runner"
|
||||
require_relative "svi/version"
|
||||
|
||||
module Svi
|
||||
# Your code goes here...
|
||||
end
|
3
lib/svi/version.rb
Normal file
3
lib/svi/version.rb
Normal file
@ -0,0 +1,3 @@
|
||||
module Svi
|
||||
VERSION = "0.1.0"
|
||||
end
|
@ -1,9 +0,0 @@
|
||||
begin
|
||||
load "#{$SHARE_DIR}/lib/svi.rb"
|
||||
rescue Interrupt => e
|
||||
rescue Exception => e
|
||||
$stderr.puts "#{e.class}: #{e}"
|
||||
e.backtrace.each do |entry|
|
||||
$stderr.puts "> #{entry}"
|
||||
end
|
||||
end
|
@ -1,12 +0,0 @@
|
||||
$LOAD_PATH.unshift("#{$SHARE_DIR}/lib")
|
||||
require "yawpa"
|
||||
require_relative "svi/svn_runner"
|
||||
|
||||
module Svi
|
||||
class << self
|
||||
def run(args)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Svi.run($ARGS)
|
@ -1,202 +0,0 @@
|
||||
require "yawpa/version"
|
||||
|
||||
# Yet Another Way to Parse Arguments is an argument-parsing library for Ruby.
|
||||
#
|
||||
# Yawpa does not try to provide a fancy DSL.
|
||||
# It does not require you to define a class or inherit from a class.
|
||||
# it just provides a simple functional interface for parsing options,
|
||||
# supporting subcommands and arbitrary numbers of arguments for each option.
|
||||
#
|
||||
# Features:
|
||||
# - POSIX or non-POSIX mode (supports subcommands using POSIX mode)
|
||||
# - Options can require an arbitrary number of parameters
|
||||
# - Options can be defined with a range specifying the allowed number of
|
||||
# parameters
|
||||
module Yawpa
|
||||
|
||||
# Exception class raised when an unknown option is observed.
|
||||
class ArgumentParsingException < Exception; end
|
||||
|
||||
class << self
|
||||
|
||||
# Parse input parameters looking for options according to rules given in
|
||||
# flags.
|
||||
# Syntax:
|
||||
# opts, args = parse(params, options, flags = {})
|
||||
#
|
||||
# An ArgumentParsingException will be raised if an unknown option is
|
||||
# observed or insufficient arguments are present for an option.
|
||||
#
|
||||
# Example +options+:
|
||||
#
|
||||
# {
|
||||
# version: nil,
|
||||
# verbose: {short: 'v'},
|
||||
# server: {nargs: (1..2)},
|
||||
# username: {nargs: 1},
|
||||
# password: {nargs: 1},
|
||||
# color: :boolean,
|
||||
# }
|
||||
#
|
||||
# The keys of the +options+ Hash can be either strings or symbols.
|
||||
#
|
||||
#
|
||||
# @param params [Array]
|
||||
# List of program parameters to parse.
|
||||
# @param options [Hash]
|
||||
# Hash containing the long option names as keys, and values containing
|
||||
# special flags for the options as values (examples above).
|
||||
# Possible values:
|
||||
# +nil+:: No special flags for this option (equivalent to +{}+)
|
||||
# +:boolean+::
|
||||
# The option is a toggleable boolean option (equivalent to
|
||||
# +{boolean: true}+)
|
||||
# Hash::
|
||||
# Possible option flags:
|
||||
# - +:short+: specify a short option letter to associate with the long
|
||||
# option
|
||||
# - +:nargs+: specify an exact number or range of possible numbers of
|
||||
# arguments to the option
|
||||
# - +:boolean+: if true, specify that the option is a toggleable
|
||||
# boolean option and allow a prefix of "no" to turn it off.
|
||||
# @param flags [Hash]
|
||||
# Optional flags dictating how {.parse} should do its job.
|
||||
# @option flags [Boolean] :posix_order
|
||||
# Stop processing parameters when a non-option argument is seen.
|
||||
# Set this to +true+ if you want to implement subcommands.
|
||||
#
|
||||
# @return [Array]
|
||||
# Two-element array containing +opts+ and +args+ return values.
|
||||
# +opts+::
|
||||
# The returned +opts+ value will be a Hash with the observed
|
||||
# options as keys and any option arguments as values.
|
||||
# +args+::
|
||||
# The returned +args+ will be an Array of the unprocessed
|
||||
# parameters (if +:posix_order+ was passed in +flags+, this array might
|
||||
# contain further options that were not processed after observing a
|
||||
# non-option parameters).
|
||||
def parse(params, options, flags = {})
|
||||
options = _massage_options(options)
|
||||
opts = {}
|
||||
args = []
|
||||
i = 0
|
||||
while i < params.length
|
||||
param = params[i]
|
||||
if param =~ /^--([^=]+)(?:=(.+))?$/
|
||||
param_name, val = $1, $2
|
||||
bool_val = true
|
||||
if options[param_name].nil?
|
||||
if param_name =~ /^no(.*)$/
|
||||
test_param_name = $1
|
||||
if options[test_param_name]
|
||||
param_name = test_param_name
|
||||
bool_val = false
|
||||
end
|
||||
end
|
||||
end
|
||||
opt_config = options[param_name]
|
||||
raise ArgumentParsingException.new("Unknown option '#{param_name}'") unless opt_config
|
||||
param_key = opt_config[:key]
|
||||
if opt_config[:boolean]
|
||||
opts[param_key] = bool_val
|
||||
elsif opt_config[:nargs].last == 0
|
||||
opts[param_key] = true
|
||||
else
|
||||
opts[param_key] = []
|
||||
i += _gather(opt_config[:nargs], i + 1, params, val, param_key, opts[param_key])
|
||||
end
|
||||
elsif param =~ /^-(.+)$/
|
||||
short_flags = $1
|
||||
short_idx = 0
|
||||
while short_idx < short_flags.length
|
||||
opt_config = _find_opt_config_by_short_name(options, short_flags[short_idx])
|
||||
if opt_config.nil?
|
||||
raise ArgumentParsingException.new("Unknown option '-#{short_flags[short_idx]}'")
|
||||
end
|
||||
param_key = opt_config[:key]
|
||||
if opt_config[:nargs].last == 0
|
||||
opts[param_key] = true
|
||||
else
|
||||
opts[param_key] = []
|
||||
i += _gather(opt_config[:nargs],
|
||||
i + 1,
|
||||
params,
|
||||
short_flags[short_idx + 1, short_flags.length],
|
||||
param_key,
|
||||
opts[param_key])
|
||||
break
|
||||
end
|
||||
short_idx += 1
|
||||
end
|
||||
elsif flags[:posix_order]
|
||||
args = params[i, params.length].map(&:dup)
|
||||
break
|
||||
else
|
||||
args << params[i].dup
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
# Condense 1-element arrays of option values to just the element itself
|
||||
opts.each_key do |k|
|
||||
if opts[k].is_a?(Array) and opts[k].length == 1
|
||||
opts[k] = opts[k].first
|
||||
end
|
||||
end
|
||||
|
||||
return [opts, args]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Internal helper method to gather arguments for an option
|
||||
def _gather(nargs, start_idx, params, initial, param_key, result)
|
||||
n_gathered = 0
|
||||
if initial and initial != ''
|
||||
result << initial
|
||||
n_gathered += 1
|
||||
end
|
||||
num_indices_used = 0
|
||||
index = start_idx
|
||||
while n_gathered < nargs.last and
|
||||
index < params.length and
|
||||
params[index][0] != '-' do
|
||||
result << params[index].dup
|
||||
index += 1
|
||||
num_indices_used += 1
|
||||
n_gathered += 1
|
||||
end
|
||||
if n_gathered < nargs.first
|
||||
raise ArgumentParsingException.new("Not enough arguments supplied for option '#{param_key}'")
|
||||
end
|
||||
num_indices_used
|
||||
end
|
||||
|
||||
# Internal helper method to format the options in a consistent format
|
||||
def _massage_options(options)
|
||||
{}.tap do |newopts|
|
||||
options.each_pair do |k, v|
|
||||
v = {} if v.nil?
|
||||
v = {boolean: true} if v == :boolean
|
||||
newkey = k.to_s
|
||||
newopts[newkey] = {key: k}
|
||||
nargs = v[:nargs] || 0
|
||||
nargs = (nargs..nargs) if nargs.is_a?(Integer)
|
||||
newopts[newkey][:nargs] = nargs
|
||||
newopts[newkey][:short] = v[:short] || ''
|
||||
newopts[newkey][:boolean] = v[:boolean]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Internal helper method to find an option configuration by short name
|
||||
def _find_opt_config_by_short_name(options, short_name)
|
||||
options.each_pair do |k, v|
|
||||
return v if v[:short] == short_name
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
@ -1,4 +0,0 @@
|
||||
module Yawpa
|
||||
# gem version
|
||||
VERSION = "1.3.0"
|
||||
end
|
120
src/main.cc
120
src/main.cc
@ -1,120 +0,0 @@
|
||||
#include <ruby.h>
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
VALUE svi_ruby_protect_eval_string_rescue(VALUE exception, VALUE exception_object)
|
||||
{
|
||||
*(bool *)exception = true;
|
||||
fprintf(stderr, "Unhandled exception: %s\n",
|
||||
rb_obj_classname(exception_object));
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
VALUE svi_ruby_protect_eval_string(const char * ruby_expression, bool * exception)
|
||||
{
|
||||
*exception = false;
|
||||
return rb_rescue((VALUE(*)(...))rb_eval_string, (VALUE)ruby_expression,
|
||||
(VALUE(*)(...))svi_ruby_protect_eval_string_rescue, (VALUE)exception);
|
||||
}
|
||||
|
||||
static char * read_file(const char * filename)
|
||||
{
|
||||
int fd = ::open(filename, O_RDONLY, 0);
|
||||
if (fd < 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
off_t size = ::lseek(fd, 0, SEEK_END);
|
||||
if (size <= 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
::lseek(fd, 0, SEEK_SET);
|
||||
char * buffer = new char[size];
|
||||
off_t n_bytes_read = 0u;
|
||||
for (;;)
|
||||
{
|
||||
off_t rd_size = ::read(fd, &buffer[n_bytes_read], size - n_bytes_read);
|
||||
if (rd_size <= 0)
|
||||
break;
|
||||
n_bytes_read += rd_size;
|
||||
if (n_bytes_read >= size)
|
||||
break;
|
||||
}
|
||||
::close(fd);
|
||||
if (n_bytes_read == size)
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
delete[] buffer;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_exe_path()
|
||||
{
|
||||
pid_t pid = getpid();
|
||||
char proc_path[25];
|
||||
sprintf(proc_path, "/proc/%d/exe", pid);
|
||||
char exe_path[100];
|
||||
ssize_t n = readlink(proc_path, exe_path, sizeof(exe_path));
|
||||
if ((n > 0) && (n < (ssize_t)sizeof(exe_path)))
|
||||
{
|
||||
return exe_path;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static std::string share_dir;
|
||||
|
||||
const char * read_bootstrap_file()
|
||||
{
|
||||
auto exe_path = get_exe_path();
|
||||
size_t index = exe_path.rfind('/');
|
||||
if ((index != std::string::npos) && (index > 0u))
|
||||
{
|
||||
index = exe_path.rfind('/', index - 1u);
|
||||
if (index != std::string::npos)
|
||||
{
|
||||
share_dir = std::string(exe_path, 0u, index) + "/share/svi";
|
||||
char * bootstrap = read_file((share_dir + "/lib/bootstrap.rb").c_str());
|
||||
if (bootstrap != nullptr)
|
||||
{
|
||||
return bootstrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
share_dir = SHARE_DIR;
|
||||
return read_file(SHARE_DIR "/lib/bootstrap.rb");
|
||||
}
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
bool exception = false;
|
||||
const char * ruby_startup = read_bootstrap_file();
|
||||
if (ruby_startup == nullptr)
|
||||
{
|
||||
fprintf(stderr, "Could not read Ruby startup file\n");
|
||||
return 1;
|
||||
}
|
||||
{
|
||||
RUBY_INIT_STACK;
|
||||
ruby_init();
|
||||
rb_gv_set("$SHARE_DIR", rb_str_new(share_dir.c_str(), share_dir.size()));
|
||||
VALUE argv_ruby_value = rb_ary_new();
|
||||
rb_gv_set("$ARGS", argv_ruby_value);
|
||||
for (int i = 0; i < argc; i++)
|
||||
{
|
||||
rb_ary_push(argv_ruby_value, rb_str_new_cstr(argv[i]));
|
||||
}
|
||||
svi_ruby_protect_eval_string(ruby_startup, &exception);
|
||||
}
|
||||
delete[] ruby_startup;
|
||||
return exception ? 1 : 0;
|
||||
}
|
32
svi.gemspec
Normal file
32
svi.gemspec
Normal file
@ -0,0 +1,32 @@
|
||||
# coding: utf-8
|
||||
lib = File.expand_path('../lib', __FILE__)
|
||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||
require 'svi/version'
|
||||
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = "svi"
|
||||
spec.version = Svi::VERSION
|
||||
spec.authors = ["Josh Holtrop"]
|
||||
spec.email = ["jholtrop@gmail.com"]
|
||||
|
||||
spec.summary = %q{TODO: Write a short summary, because Rubygems requires one.}
|
||||
spec.description = %q{TODO: Write a longer description or delete this line.}
|
||||
spec.homepage = "TODO: Put your gem's website or public repo URL here."
|
||||
spec.license = "MIT"
|
||||
|
||||
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
||||
# delete this section to allow pushing this gem to any host.
|
||||
if spec.respond_to?(:metadata)
|
||||
spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
|
||||
else
|
||||
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
||||
end
|
||||
|
||||
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
||||
spec.bindir = "exe"
|
||||
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||
spec.require_paths = ["lib"]
|
||||
|
||||
spec.add_development_dependency "bundler", "~> 1.10"
|
||||
spec.add_development_dependency "rake", "~> 10.0"
|
||||
end
|
19
wscript
19
wscript
@ -1,19 +0,0 @@
|
||||
import re
|
||||
|
||||
def options(opt):
|
||||
opt.load("compiler_cxx")
|
||||
|
||||
def configure(conf):
|
||||
conf.load("compiler_cxx")
|
||||
conf.define("SHARE_DIR", re.sub(r'/$', "", conf.options.prefix) + "/share/svi")
|
||||
conf.check_cfg(package = "ruby", args = "--cflags --libs", uselib_store = "ruby")
|
||||
|
||||
def build(bld):
|
||||
bld(features = "cxx cxxprogram",
|
||||
target = "svi",
|
||||
source = bld.path.ant_glob("src/**/*.cc"),
|
||||
cxxflags = ["-Wall", "-std=gnu++14"],
|
||||
uselib = ["ruby"])
|
||||
share_dir = bld.path.find_dir("share/svi")
|
||||
bld.install_files("${PREFIX}/share/svi", share_dir.ant_glob("**/*"),
|
||||
cwd = share_dir, relative_trick = True)
|
Loading…
x
Reference in New Issue
Block a user