rearchitect as a gem

embedding Ruby in C did not go so well
This commit is contained in:
Josh Holtrop 2018-02-12 20:13:44 -05:00
parent 9188bb42e2
commit 432bd7458e
19 changed files with 136 additions and 559 deletions

11
.gitignore vendored
View File

@ -1,3 +1,8 @@
/.lock-waf*
/.waf*
/build/
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/

4
Gemfile Normal file
View File

@ -0,0 +1,4 @@
source 'https://rubygems.org'
# Specify your gem's dependencies in svi.gemspec
gemspec

20
Gemfile.lock Normal file
View 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
View 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.

View File

@ -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
View 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).

1
Rakefile Normal file
View File

@ -0,0 +1 @@
require "bundler/gem_tasks"

2
configure vendored
View File

@ -1,2 +0,0 @@
#!/bin/sh
exec ./waf configure "$@"

6
lib/svi.rb Normal file
View 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
View File

@ -0,0 +1,3 @@
module Svi
VERSION = "0.1.0"
end

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -1,4 +0,0 @@
module Yawpa
# gem version
VERSION = "1.3.0"
end

View File

@ -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
View 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

169
waf vendored

File diff suppressed because one or more lines are too long

19
wscript
View File

@ -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)