summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2017-01-25 11:23:21 +0100
committerGitHub <noreply@github.com>2017-01-25 11:23:21 +0100
commit1e3193ebb2028478aa26efd1b69697cddf00914f (patch)
treed1504a620dc7065f387467162d75276b311ab56c
parent636bfbb768f23c92d581ab660fcaa88927c859b1 (diff)
parent4804cf36ce9a45a0300d5e78850cf55f6b8b2c2c (diff)
downloadimag-1e3193ebb2028478aa26efd1b69697cddf00914f.zip
imag-1e3193ebb2028478aa26efd1b69697cddf00914f.tar.gz
Merge pull request #847 from matthiasbeyer/imag-ruby
Imag ruby
-rw-r--r--Cargo.toml1
-rw-r--r--Makefile3
-rw-r--r--libimagruby/.gitignore3
-rw-r--r--libimagruby/Cargo.toml41
-rw-r--r--libimagruby/Gemfile4
-rw-r--r--libimagruby/Makefile20
-rw-r--r--libimagruby/README.md106
-rw-r--r--libimagruby/Rakefile2
-rw-r--r--libimagruby/imag.gemspec26
-rw-r--r--libimagruby/lib/imag.rb96
-rw-r--r--libimagruby/lib/imag/version.rb3
-rw-r--r--libimagruby/src/cache.rs42
-rw-r--r--libimagruby/src/entry.rs282
-rw-r--r--libimagruby/src/imag.rs148
-rw-r--r--libimagruby/src/lib.rs53
-rw-r--r--libimagruby/src/ruby_utils.rs210
-rw-r--r--libimagruby/src/store.rs559
-rw-r--r--libimagruby/src/storeid.rs154
-rw-r--r--libimagruby/src/toml_utils.rs65
-rw-r--r--libimagruby/src/util.rs102
-rw-r--r--libimagruby/test/test_entries.rb27
-rw-r--r--libimagruby/test/test_ruby.rb58
-rw-r--r--libimagruby/test/test_store.rb16
23 files changed, 2021 insertions, 0 deletions
diff --git a/Cargo.toml b/Cargo.toml
index b12e816..2a64e19 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -36,6 +36,7 @@ members = [
"libimagnotes",
"libimagref",
"libimagrt",
+ "libimagruby",
"libimagstore",
"libimagstorestdhook",
"libimagtimeui",
diff --git a/Makefile b/Makefile
index 4a4e751..e47c0c1 100644
--- a/Makefile
+++ b/Makefile
@@ -67,6 +67,9 @@ lib: $(LIB_TARGETS)
lib-test: $(LIB_TARGETS_TEST)
+lib-imag-ruby-test:
+ @$(MAKE) -C libimagruby
+
test: bin-test lib-test
install: $(INSTALL_TARGETS) imag-bin-install
diff --git a/libimagruby/.gitignore b/libimagruby/.gitignore
new file mode 100644
index 0000000..2f7c731
--- /dev/null
+++ b/libimagruby/.gitignore
@@ -0,0 +1,3 @@
+.bundle
+Gemfile.lock
+vendor/bundle
diff --git a/libimagruby/Cargo.toml b/libimagruby/Cargo.toml
new file mode 100644
index 0000000..2d98a08
--- /dev/null
+++ b/libimagruby/Cargo.toml
@@ -0,0 +1,41 @@
+[package]
+name = "libimagruby"
+version = "0.1.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+[lib]
+crate-type = ["dylib"]
+
+[dependencies]
+ruru = "0.9"
+lazy_static = "0.2"
+log = "0.3"
+env_logger = "0.3"
+toml = "0.2"
+uuid = { version = "0.3", features = ["v4"] }
+
+[dependencies.libimagerror]
+path = "../libimagerror"
+
+[dependencies.libimagrt]
+path = "../libimagrt"
+
+[dependencies.libimagstore]
+path = "../libimagstore"
+
+[dependencies.libimagstorestdhook]
+path = "../libimagstorestdhook"
+
+[dependencies.libimagutil]
+path = "../libimagutil"
+
diff --git a/libimagruby/Gemfile b/libimagruby/Gemfile
new file mode 100644
index 0000000..aadb347
--- /dev/null
+++ b/libimagruby/Gemfile
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+source "https://rubygems.org"
+
+gemspec
diff --git a/libimagruby/Makefile b/libimagruby/Makefile
new file mode 100644
index 0000000..f7ed82d
--- /dev/null
+++ b/libimagruby/Makefile
@@ -0,0 +1,20 @@
+ECHO=$(shell which echo) -e
+RUBY=$(shell which ruby)
+RUBY_TESTS=$(shell find ./test -maxdepth 1 -name "*.rb" -type f)
+RUBY_TEST_TARGETS=$(foreach x,$(subst ,,$(RUBY_TESTS)),$(x))
+
+all: lib
+
+lib:
+ $(MAKE) -C .. libimagruby
+
+lib-release:
+ $(MAKE) -C .. libimagruby-release
+
+test: lib $(RUBY_TEST_TARGETS)
+
+$(RUBY_TEST_TARGETS): %: lib .FORCE
+ @$(ECHO) "\t[TEST ]:\t$@"
+ @$(RUBY) $(subst -test,,$@)
+
+.FORCE:
diff --git a/libimagruby/README.md b/libimagruby/README.md
new file mode 100644
index 0000000..2f761fb
--- /dev/null
+++ b/libimagruby/README.md
@@ -0,0 +1,106 @@
+# imag-ruby
+
+A Ruby gem for scripting imag modules.
+
+## How does this work?
+
+Well, as we have some problems with lifetimes here, we have a fairly complex
+codebase in this crate.
+
+### The Problem
+
+The Problem is, that `libimagstore::store::FileLockEntry<'a>` has a lifetime. If
+we would wrap this object into a ruru wrapper and pass to the Ruby code, we
+couldn't guarantee anymore that the lifetime holds.
+
+The problem is simple, you see...
+
+### The solution?
+
+Never pass anything to the Ruby code.
+
+Yes, exactly. The Ruby code only sees 'handles'. It never actually gets the
+`Store` object either.
+We move the `Store` Object into a `Cache` object (actually, the Ruby code could
+have multiple `Store` objects to work with this way) and return a `StoreHandle`
+to the Ruby code (which is a UUID underneath).
+
+Also, the Ruby code never actually touches a `FileLockEntry` - it only gets a
+Handle for each `FileLockEntry` - which is a tuple of the `StoreHandle` and the
+`libimagstore::storeid::StoreId` for the Entry.
+
+Each operation on a `FileLockEntry` is then wrapped by this very library. Each
+time `FileLockEntry` is touched, this library fetches the appropriate `Store`
+object from the static `Cache`, then fetches the `FileLockEntry` object from it,
+does the operation and then drops the object (which implies that the actual
+`FileLockEntry` is `update()`d!).
+
+### The Hell?
+
+Yes, I know this is a lot of overhead. But what are we talking about here? This
+is Ruby code we're talking about here, so speed is not our concern.
+
+You could argue this is a hell of complexity introduced in this library and yes
+it is.
+If there are bugs (and I bet there are) they would be complex as hell.
+But that's it... if you have a better approach, please file a PR.
+
+## Tests?
+
+We have tests Ruby scripts in `./test`, they are not executed by travis-ci, as
+we need Ruby `2.3.0` for this and travis has `2.2.0` as latest version.
+But I hope we get it in travis soonish.
+
+## Ruby gem?
+
+This crate will contain both the Rust bindings for imag using `ruru` and a bunch
+of wrapper code for the actual `imag` gem.
+
+We are not there yet, though.
+
+### Why another layer of indirection?
+
+As "ruru" does not yet support modules (which is sad btw) we would end up with
+functions for all the things.
+
+E.G.: `imag_runtime_setup()` instead of `Imag::Runtime::setup()`
+
+I want to add a Ruby gem to wrap these things.
+
+So basically a piece of Ruby which uses `imag.rb` (the Rust gem) to build
+`imag` as a gem which then exports a fine module system.
+
+### Ideas for module system:
+
+```text
+Imag (Module)
+ Runtime (Class)
+ Store (Class)
+ Entry (Class)
+ EntryHeader (Class)
+ EntryContent (Class (inherits from String))
+ StoreId (Class)
+```
+
+I would name the types the same as in the Rust codebase, to avoid confusion.
+Only exception would be the `Entry` class, which would be a `FileLockEntry`
+underneath.
+
+If we adapt `libimagentrytag` and the other `libimagentry*`
+libraries, we would extend this type.
+
+## More plans
+
+I want to pull these libraries into the Ruby bindings:
+
+* libimagentryedit
+* libimagentryfilter
+* libimagentrylink
+* libimagentrylist
+* libimagentrymarkdown
+* libimagentrytag
+* libimagentryview
+
+Which all provide functions on top of `libimagstore::store::{FileLock,}Entry`,
+so we will implement them on `Imag::Entry`.
+
diff --git a/libimagruby/Rakefile b/libimagruby/Rakefile
new file mode 100644
index 0000000..43022f7
--- /dev/null
+++ b/libimagruby/Rakefile
@@ -0,0 +1,2 @@
+require "bundler/gem_tasks"
+task :default => :spec
diff --git a/libimagruby/imag.gemspec b/libimagruby/imag.gemspec
new file mode 100644
index 0000000..c606691
--- /dev/null
+++ b/libimagruby/imag.gemspec
@@ -0,0 +1,26 @@
+# coding: utf-8
+lib = File.expand_path('../lib', __FILE__)
+$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
+require 'imag/version'
+
+Gem::Specification.new do |spec|
+ spec.name = "imag"
+ spec.version = Imag::VERSION
+ spec.authors = ["Matthias Beyer"]
+ spec.email = ["mail@beyermatthias.de"]
+
+ spec.summary = %q{A Ruby gem to script imag.}
+ spec.description = %q{A Ruby gem to script imag, the personal information management suite for the commandline}
+ spec.homepage = "http://imag-pim.org"
+
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
+ f.match(%r{^(test|spec|features)/})
+ end
+
+ spec.bindir = "exe"
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
+ spec.require_paths = ["lib"]
+
+ spec.add_development_dependency "bundler", "~> 1.13"
+ spec.add_development_dependency "rake", "~> 10.0"
+end
diff --git a/libimagruby/lib/imag.rb b/libimagruby/lib/imag.rb
new file mode 100644
index 0000000..b8c9111
--- /dev/null
+++ b/libimagruby/lib/imag.rb
@@ -0,0 +1,96 @@
+#!/usr/bin/env ruby
+
+module Imag
+
+ IMAG_INIT_FN_NAME = 'imag_ruby_initialize'
+
+ def self.setup binary_path
+ require binary_path
+
+ self.core_setup
+ self.classes_setup
+ end
+
+ module Logger
+
+ def self.init debug, verbose, color
+ RImag.init_logger debug, verbose, color
+ end
+
+ def self.trace msg
+ RImag.trace msg
+ end
+
+ def self.dbg msg
+ RImag.dbg msg
+ end
+
+ def self.debug msg
+ RImag.debug msg
+ end
+
+ def self.info msg
+ RImag.info msg
+ end
+
+ def self.warn msg
+ RImag.warn msg
+ end
+
+ def self.error msg
+ RImag.error msg
+ end
+
+ end
+
+ private
+
+ def self.class_names
+ [
+ :StoreId ,
+ :StoreHandle ,
+ :FileLockEntryHandle ,
+ :EntryHeader ,
+ :EntryContent ,
+ ]
+ end
+
+ def self.core_setup
+ self.class_names.map {|n| [n, "R#{n}".to_sym ] }.each do |elem|
+ Imag.const_set elem.first, Kernel.const_get(elem.last)
+ end
+ end
+
+ def self.classes_setup
+ self.class_storeid_setup
+ end
+
+ def self.class_storeid_setup
+ Imag::StoreId.class_exec do
+ def to_s
+ self.to_str
+ end
+ end
+ end
+
+end
+
+if __FILE__ == $0
+ puts "Running some tests..."
+ puts "I hope you passed the library object as first argument..."
+ begin
+ Imag.setup ARGV.first
+ rescue Exception => e
+ puts "Seems not to be the case... or something else went wrong..."
+ puts e
+ exit 1
+ end
+
+ Imag::Logger.init true, true, true
+ Imag::Logger.info "The Logger should work now"
+
+ Imag::Logger.info "Lets see whether we have properly setup StoreId"
+ Imag::Logger.info Imag::StoreId::new_baseless("baselessId").to_s
+ Imag::Logger.info "Seems good."
+end
+
diff --git a/libimagruby/lib/imag/version.rb b/libimagruby/lib/imag/version.rb
new file mode 100644
index 0000000..c2a2473
--- /dev/null
+++ b/libimagruby/lib/imag/version.rb
@@ -0,0 +1,3 @@
+module Imag
+ VERSION = "0.1.0"
+end
diff --git a/libimagruby/src/cache.rs b/libimagruby/src/cache.rs
new file mode 100644
index 0000000..94c2b86
--- /dev/null
+++ b/libimagruby/src/cache.rs
@@ -0,0 +1,42 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+use std::collections::BTreeMap;
+use std::sync::Arc;
+use std::sync::Mutex;
+
+use uuid::Uuid;
+
+use libimagstore::store::Store;
+
+#[derive(Clone, Debug, Ord, Hash, Eq, PartialOrd, PartialEq)]
+pub struct StoreHandle(Uuid);
+
+impl StoreHandle {
+ pub fn new() -> StoreHandle {
+ StoreHandle(Uuid::new_v4())
+ }
+}
+
+lazy_static! {
+ pub static ref RUBY_STORE_CACHE: Arc<Mutex<BTreeMap<StoreHandle, Store>>> = {
+ Arc::new(Mutex::new(BTreeMap::new()))
+ };
+}
+
diff --git a/libimagruby/src/entry.rs b/libimagruby/src/entry.rs
new file mode 100644
index 0000000..4614fea
--- /dev/null
+++ b/libimagruby/src/entry.rs
@@ -0,0 +1,282 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+use std::collections::BTreeMap;
+use std::error::Error;
+use std::ops::Deref;
+use std::ops::DerefMut;
+
+use ruru::{Class, Object, AnyObject, Boolean, RString, VM, Hash, NilClass, VerifiedObject};
+use uuid::Uuid;
+
+use libimagstore::store::FileLockEntry as FLE;
+use libimagstore::store::EntryHeader;
+use libimagstore::store::EntryContent;
+use libimagstore::store::Entry;
+use libimagstore::storeid::StoreId;
+
+use ruby_utils::IntoToml;
+use toml_utils::IntoRuby;
+use util::Wrap;
+use util::Unwrap;
+use cache::RUBY_STORE_CACHE;
+use cache::StoreHandle;
+
+pub struct FileLockEntryHandle(StoreHandle, StoreId);
+
+impl FileLockEntryHandle {
+ pub fn new(sh: StoreHandle, id: StoreId) -> FileLockEntryHandle {
+ FileLockEntryHandle(sh, id)
+ }
+
+ pub fn store_handle(&self) -> &StoreHandle {
+ &self.0
+ }
+
+ pub fn fle_handle(&self) -> &StoreId {
+ &self.1
+ }
+}
+
+wrappable_struct!(FileLockEntryHandle, FileLockEntryWrapper, FLE_WRAPPER);
+class!(RFileLockEntryHandle);
+impl_wrap!(FileLockEntryHandle => FLE_WRAPPER);
+impl_unwrap!(RFileLockEntryHandle => FileLockEntryHandle => FLE_WRAPPER);
+impl_verified_object!(RFileLockEntryHandle);
+
+/// Helper macro for operating on RUBY_STORE_CACHE object
+///
+/// This helps us calling operations on FileLockEntry objects.
+///
+/// What I do here: Fetch the Store object from the cache, fetch the appropriate FileLockEntry and
+/// call the operation on it.
+///
+/// This could be improved with another cache, so not the store is cached but the FileLockEntry
+/// only, but then we run into lifetime problems with the Store and its FileLockEntry objects.
+/// Feel free to fix this, but for now, this is a workable solution.
+///
+#[macro_export]
+macro_rules! call_on_fle_from_store {
+ ($itself:ident ($wrapper:ident) -> $name:ident -> $operation:block) => {{
+ let handle = $itself.get_data(&*$wrapper);
+ let store_handle = handle.store_handle();
+ call_on_store_by_handle! {
+ store_handle named store inside {
+ match store.get(handle.fle_handle().clone()) {
+ Ok(Some(mut $name)) => {
+ $operation
+ },
+ Ok(None) => {
+ VM::raise(Class::from_existing("RuntimeError"), "Obj does not exist");
+ NilClass::new().to_any_object()
+ },
+ Err(e) => {
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ NilClass::new().to_any_object()
+ },
+ }
+ }
+ }
+ }};
+ ($itself:ident ($wrapper:ident) -> $name:ident -> $operation: block on fail return $ex:expr) => {{
+ let handle = $itself.get_data(&*$wrapper);
+ let store_handle = handle.store_handle();
+ call_on_store_by_handle! {
+ store_handle named store inside {
+ match store.get(handle.fle_handle().clone()) {
+ Ok(Some(mut $name)) => {
+ $operation
+ },
+ Ok(None) => {
+ VM::raise(Class::from_existing("RuntimeError"), "Obj does not exist");
+ $ex
+ },
+ Err(e) => {
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ $ex
+ },
+ }
+ }
+ }
+ }};
+}
+
+
+methods!(
+ RFileLockEntryHandle,
+ itself,
+
+ fn r_get_location() -> AnyObject {
+ call_on_fle_from_store!(itself (FLE_WRAPPER) -> fle -> { fle.get_location().clone().wrap() })
+ }
+
+ fn r_get_header() -> AnyObject {
+ call_on_fle_from_store!(itself (FLE_WRAPPER) -> fle -> { fle.get_header().clone().wrap() })
+ }
+
+ fn r_set_header(hdr: Hash) -> NilClass {
+ use ruby_utils::IntoToml;
+ use toml_utils::IntoRuby;
+ use toml::Value;
+
+ let entryheader = match typecheck!(hdr or return NilClass::new()).into_toml() {
+ Value::Table(t) => EntryHeader::from(t),
+ _ => {
+ let ec = Class::from_existing("RuntimeError");
+ VM::raise(ec, "Something weird happened. Hash seems to be not a Hash");
+ return NilClass::new();
+ },
+ };
+
+ call_on_fle_from_store!(itself (FLE_WRAPPER) -> fle -> {
+ *fle.get_header_mut() = entryheader;
+ NilClass::new().to_any_object()
+ });
+
+ NilClass::new()
+ }
+
+ fn r_get_content() -> AnyObject {
+ call_on_fle_from_store!(itself (FLE_WRAPPER) -> fle -> {
+ fle.get_content().clone().wrap()
+ } on fail return NilClass::new().to_any_object())
+ }
+
+ fn r_set_content(ctt: RString) -> NilClass {
+ use ruby_utils::IntoToml;
+ use toml_utils::IntoRuby;
+ use toml::Value;
+
+ let content = match typecheck!(ctt).into_toml() {
+ Value::String(s) => s,
+ _ => {
+ let ec = Class::from_existing("RuntimeError");
+ VM::raise(ec, "Something weird happened. String seems to be not a String");
+ return NilClass::new();
+ },
+ };
+
+ call_on_fle_from_store!(itself (FLE_WRAPPER) -> fle -> {
+ *fle.get_content_mut() = content;
+ NilClass::new().to_any_object()
+ });
+
+ NilClass::new()
+ }
+
+);
+
+wrappable_struct!(EntryHeader, EntryHeaderWrapper, ENTRY_HEADER_WRAPPER);
+class!(REntryHeader);
+impl_wrap!(EntryHeader => ENTRY_HEADER_WRAPPER);
+impl_unwrap!(REntryHeader => EntryHeader => ENTRY_HEADER_WRAPPER);
+impl_verified_object!(REntryHeader);
+
+methods!(
+ REntryHeader,
+ itself,
+
+ fn r_entry_header_new() -> AnyObject {
+ EntryHeader::new().wrap()
+ }
+
+ fn r_entry_header_insert(spec: RString, obj: AnyObject) -> Boolean {
+ let spec = typecheck!(spec or return Boolean::new(false)).to_string();
+ let obj = obj.unwrap(); // possibly not safe... TODO
+
+ match itself.get_data(&*ENTRY_HEADER_WRAPPER).insert(&spec, obj.into_toml()) {
+ Ok(b) => Boolean::new(b),
+ Err(e) => {
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ Boolean::new(false)
+ }
+ }
+ }
+
+ fn r_entry_header_set(spec: RString, obj: AnyObject) -> AnyObject {
+ use ruru::NilClass;
+
+ let spec = typecheck!(spec or return any Boolean::new(false)).to_string();
+ let obj = obj.unwrap(); // possibly not safe... TODO
+
+ match itself.get_data(&*ENTRY_HEADER_WRAPPER).set(&spec, obj.into_toml()) {
+ Ok(Some(v)) => v.into_ruby(),
+ Ok(None) => NilClass::new().to_any_object(),
+ Err(e) => {
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ return Boolean::new(false).to_any_object();
+ }
+ }
+ }
+
+ fn r_entry_header_get(spec: RString) -> AnyObject {
+ use ruru::NilClass;
+
+ let spec = typecheck!(spec or return any Boolean::new(false)).to_string();
+
+ match itself.get_data(&*ENTRY_HEADER_WRAPPER).read(&spec) {
+ Ok(Some(v)) => v.into_ruby(),
+ Ok(None) => NilClass::new().to_any_object(),
+ Err(e) => {
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ return Boolean::new(false).to_any_object();
+ }
+ }
+ }
+
+);
+
+wrappable_struct!(EntryContent, EntryContentWrapper, ENTRY_CONTENT_WRAPPER);
+class!(REntryContent);
+impl_wrap!(EntryContent => ENTRY_CONTENT_WRAPPER);
+impl_unwrap!(REntryContent => EntryContent => ENTRY_CONTENT_WRAPPER);
+
+wrappable_struct!(Entry, EntryWrapper, ENTRY_WRAPPER);
+class!(REntry);
+impl_unwrap!(REntry => Entry => ENTRY_WRAPPER);
+
+pub fn setup_filelockentry() -> Class {
+ let mut class = Class::new("RFileLockEntryHandle", None);
+ class.define(|itself| {
+ itself.def("location", r_get_location);
+ itself.def("header" , r_get_header);
+ itself.def("header=" , r_set_header);
+ itself.def("content" , r_get_content);
+ itself.def("content=", r_set_content);
+ });
+ class
+}
+
+pub fn setup_entryheader() -> Class {
+ let mut class = Class::new("REntryHeader", None);
+ class.define(|itself| {
+ itself.def("insert", r_entry_header_insert);
+ itself.def("set" , r_entry_header_set);
+ itself.def("[]=" , r_entry_header_set);
+ itself.def("read" , r_entry_header_get);
+ itself.def("[]" , r_entry_header_get);
+ });
+ class
+}
+
+pub fn setup_entrycontent() -> Class {
+ let string = Class::from_existing("String");
+ let mut class = Class::new("REntryContent", Some(&string));
+ class
+}
diff --git a/libimagruby/src/imag.rs b/libimagruby/src/imag.rs
new file mode 100644
index 0000000..a59610b
--- /dev/null
+++ b/libimagruby/src/imag.rs
@@ -0,0 +1,148 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+
+use std::error::Error;
+
+use ruru::{Class, Boolean, RString, NilClass, VM, Object};
+
+use libimagrt::logger::ImagLogger;
+
+class!(RImag);
+
+methods!(
+ RImag,
+ itself,
+
+ fn r_initialize_logger(debug: Boolean, verbose: Boolean, colored: Boolean) -> NilClass {
+ use std::env::var as env_var;
+ use env_logger;
+ use log;
+ use log::{Log, LogLevel, LogRecord, LogMetadata};
+ use log::LogLevelFilter;
+
+ let debug = match debug {
+ Ok(d) => d.to_bool(),
+ Err(ref e) => {
+ VM::raise(e.to_exception(), e.description());
+ return NilClass::new();
+ },
+ };
+
+ let verbose = match verbose {
+ Ok(v) => v.to_bool(),
+ Err(ref e) => {
+ VM::raise(e.to_exception(), e.description());
+ return NilClass::new();
+ },
+ };
+
+ let colored = match colored {
+ Ok(c) => c.to_bool(),
+ Err(ref e) => {
+ VM::raise(e.to_exception(), e.description());
+ return NilClass::new();
+ },
+ };
+
+ if env_var("IMAG_LOG_ENV").is_ok() {
+ env_logger::init().unwrap();
+ } else {
+ let lvl = if debug {
+ LogLevelFilter::Debug
+ } else if verbose {
+ LogLevelFilter::Info
+ } else {
+ LogLevelFilter::Warn
+ };
+
+ log::set_logger(|max_log_lvl| {
+ max_log_lvl.set(lvl);
+ debug!("Init logger with {}", lvl);
+ let lgr = ImagLogger::new(lvl.to_log_level().unwrap())
+ .with_color(colored)
+ .with_prefix("[imag][ruby]".to_owned())
+ .with_dbg_file_and_line(false);
+ Box::new(lgr)
+ })
+ .map_err(|_| {
+ panic!("Could not setup logger");
+ })
+ .ok();
+ }
+
+ NilClass::new()
+ }
+
+ fn r_log_trace(l: RString) -> NilClass {
+ match l {
+ Err(ref e) => VM::raise(e.to_exception(), e.description()),
+ Ok(s) => trace!("{}", s.to_string()),
+ }
+ NilClass::new()
+ }
+
+ fn r_log_debug(l: RString) -> NilClass {
+ match l {
+ Err(ref e) => VM::raise(e.to_exception(), e.description()),
+ Ok(s) => debug!("{}", s.to_string()),
+ }
+ NilClass::new()
+ }
+
+ fn r_log_info(l: RString) -> NilClass {
+ match l {
+ Err(ref e) => VM::raise(e.to_exception(), e.description()),
+ Ok(s) => info!("{}", s.to_string()),
+ }
+ NilClass::new()
+ }
+
+ fn r_log_warn(l: RString) -> NilClass {
+ match l {
+ Err(ref e) => VM::raise(e.to_exception(), e.description()),
+ Ok(s) => warn!("{}", s.to_string()),
+ }
+ NilClass::new()
+ }
+
+ fn r_log_error(l: RString) -> NilClass {
+ match l {
+ Err(ref e) => VM::raise(e.to_exception(), e.description()),
+ Ok(s) => error!("{}", s.to_string()),
+ }
+ NilClass::new()
+ }
+
+);
+
+pub fn setup() -> Class {
+ let mut class = Class::new("RImag", None);
+ class.define(|itself| {
+ itself.def_self("init_logger", r_initialize_logger);
+ itself.def_self("trace", r_log_trace);
+ itself.def_self("dbg", r_log_debug);
+ itself.def_self("debug", r_log_debug);
+ itself.def_self("info", r_log_info);
+ itself.def_self("warn", r_log_warn);
+ itself.def_self("error", r_log_error);
+ });
+ class
+}
+
diff --git a/libimagruby/src/lib.rs b/libimagruby/src/lib.rs
new file mode 100644
index 0000000..13d933b
--- /dev/null
+++ b/libimagruby/src/lib.rs
@@ -0,0 +1,53 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+#[macro_use] extern crate ruru;
+#[macro_use] extern crate lazy_static;
+#[macro_use] extern crate log;
+extern crate env_logger;
+extern crate toml;
+extern crate uuid;
+
+#[macro_use] extern crate libimagerror;
+extern crate libimagstore;
+extern crate libimagstorestdhook;
+extern crate libimagrt;
+#[macro_use] extern crate libimagutil;
+
+#[macro_use] mod util;
+#[macro_use] pub mod store;
+mod cache;
+
+pub mod entry;
+pub mod imag;
+pub mod ruby_utils;
+pub mod storeid;
+pub mod toml_utils;
+
+#[no_mangle]
+#[allow(non_snake_case)]
+pub extern fn Init_liblibimagruby() {
+ self::store::setup();
+ self::storeid::setup();
+ self::entry::setup_filelockentry();
+ self::entry::setup_entryheader();
+ self::entry::setup_entrycontent();
+ self::imag::setup();
+}
+
diff --git a/libimagruby/src/ruby_utils.rs b/libimagruby/src/ruby_utils.rs
new file mode 100644
index 0000000..b40f684
--- /dev/null
+++ b/libimagruby/src/ruby_utils.rs
@@ -0,0 +1,210 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+// Ruby -> Toml translation primitives
+
+use std::collections::BTreeMap;
+
+use ruru::{Object, AnyObject, Class, RString, Fixnum, Float, Symbol, Hash, Array, VM};
+use ruru::types::ValueType;
+use toml::Value;
+
+
+pub trait AsToml : Sized {
+ fn as_toml(&self) -> Value;
+}
+
+pub trait IntoToml : AsToml {
+ fn into_toml(self) -> Value {
+ self.as_toml()
+ }
+}
+impl<T: AsToml> IntoToml for T { }
+
+impl AsToml for AnyObject {
+
+ fn as_toml(&self) -> Value {
+ match self.value().ty() {
+ ValueType::None => {
+ Value::Boolean(false)
+ },
+ ValueType::Object => {
+ let rte = Class::from_existing("TypeError");
+ VM::raise(rte, "Cannot translate type '' to fit into TOML");
+ Value::Boolean(false)
+ },
+ ValueType::Class => {
+ let rte = Class::from_existing("TypeError");
+ VM::raise(rte, "Cannot translate type '' to fit into TOML");
+ Value::Boolean(false)
+ },
+ ValueType::Module => {
+ let rte = Class::from_existing("TypeError");
+ VM::raise(rte, "Cannot translate type '' to fit into TOML");
+ Value::Boolean(false)
+ },
+ ValueType::Float => self.try_convert_to::<Float>().unwrap().as_toml(),
+ ValueType::RString => self.try_convert_to::<RString>().unwrap().as_toml(),
+ ValueType::Regexp => {
+ let rte = Class::from_existing("TypeError");
+ VM::raise(rte, "Cannot translate type '' to fit into TOML");
+ Value::Boolean(false)
+ },
+ ValueType::Array => self.try_convert_to::<Array>().unwrap().as_toml(),
+ ValueType::Hash => self.try_convert_to::<Hash>().unwrap().as_toml(),
+ ValueType::Struct => {
+ let rte = Class::from_existing("TypeError");
+ VM::raise(rte, "Cannot translate type '' to fit into TOML");
+ Value::Boolean(false)
+ },
+ ValueType::Bignum => {
+ let rte = Class::from_existing("TypeError");
+ VM::raise(rte, "Cannot translate type '' to fit into TOML");
+ Value::Boolean(false)
+ },
+ ValueType::File => {
+ let rte = Class::from_existing("TypeError");
+ VM::raise(rte, "Cannot translate type '' to fit into TOML");
+ Value::Boolean(false)
+ },
+ ValueType::Data => {
+ let rte = Class::from_existing("TypeError");
+ VM::raise(rte, "Cannot translate type '' to fit into TOML");
+ Value::Boolean(false)
+ },
+ ValueType::Match => {
+ let rte = Class::from_existing("TypeError");
+ VM::raise(rte, "Cannot translate type '' to fit into TOML");
+ Value::Boolean(false)
+ },
+ ValueType::Complex => {
+ let rte = Class::from_existing("TypeError");
+ VM::raise(rte, "Cannot translate type '' to fit into TOML");
+ Value::Boolean(false)
+ },
+ ValueType::Rational => {
+ let rte = Class::from_existing("TypeError");
+ VM::raise(rte, "Cannot translate type '' to fit into TOML");
+ Value::Boolean(false)
+ },
+ ValueType::Nil => Value::Boolean(false),
+ ValueType::True => Value::Boolean(true),
+ ValueType::False => Value::Boolean(false),
+ ValueType::Symbol => self.try_convert_to::<Symbol>().unwrap().as_toml(),
+ ValueType::Fixnum => self.try_convert_to::<Fixnum>().unwrap().as_toml(),
+ ValueType::Undef => {
+ let rte = Class::from_existing("TypeError");
+ VM::raise(rte, "Cannot translate type '' to fit into TOML");
+ Value::Boolean(false)
+ },
+ ValueType::Node => {
+ let rte = Class::from_existing("TypeError");
+ VM::raise(rte, "Cannot translate type '' to fit into TOML");
+ Value::Boolean(false)
+ },
+ ValueType::IClass => {
+ let rte = Class::from_existing("TypeError");
+ VM::raise(rte, "Cannot translate type '' to fit into TOML");
+ Value::Boolean(false)
+ },
+ ValueType::Zombie => {
+ let rte = Class::from_existing("TypeError");
+ VM::raise(rte, "Cannot translate type '' to fit into TOML");
+ Value::Boolean(false)
+ },
+ ValueType::Mask => {
+ let rte = Class::from_existing("TypeError");
+ VM::raise(rte, "Cannot translate type '' to fit into TOML");
+ Value::Boolean(false)
+ },
+ }
+ }
+
+}
+
+impl AsToml for Hash {
+
+ fn as_toml(&self) -> Value {
+ let mut btm = BTreeMap::new();
+ self.try_convert_to::<Hash>()
+ .unwrap()
+ .each(|key, value| {
+ let key = match key.as_toml() {
+ Value::String(s) => s,
+ _ => {
+ let rte = Class::from_existing("TypeError");
+ VM::raise(rte, "Can only have String or Symbol as Key for TOML maps");
+ String::new()
+ }
+ };
+ let value = value.as_toml();
+ btm.insert(key, value);
+ });
+ Value::Table(btm)
+ }
+
+}
+
+impl AsToml for Array {
+
+ fn as_toml(&self) -> Value {
+ let vals = self
+ .try_convert_to::<Array>()
+ .unwrap()
+ .into_iter()
+ .map(|v| v.as_toml())
+ .collect::<Vec<Value>>();
+
+ Value::Array(vals)
+ }
+
+}
+
+impl AsToml for RString {
+
+ fn as_toml(&self) -> Value {
+ Value::String(self.try_convert_to::<RString>().unwrap().to_string())
+ }
+
+}
+
+impl AsToml for Float {
+
+ fn as_toml(&self) -> Value {
+ Value::Float(self.try_convert_to::<Float>().unwrap().to_f64())
+ }
+
+}
+
+impl AsToml for Symbol {
+
+ fn as_toml(&self) -> Value {
+ Value::String(self.try_convert_to::<Symbol>().unwrap().to_string())
+ }
+
+}
+
+impl AsToml for Fixnum {
+
+ fn as_toml(&self) -> Value {
+ Value::Integer(self.try_convert_to::<Fixnum>().unwrap().to_i64())
+ }
+
+}
+
diff --git a/libimagruby/src/store.rs b/libimagruby/src/store.rs
new file mode 100644
index 0000000..4c3a49a
--- /dev/null
+++ b/libimagruby/src/store.rs
@@ -0,0 +1,559 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+use libimagstore::store::Store;
+use libimagerror::trace::trace_error;
+use std::error::Error;
+use std::ops::Deref;
+use std::ops::DerefMut;
+
+use ruru::{Class, Object, AnyObject, Boolean, RString, VM, Hash, NilClass, VerifiedObject};
+
+use ruby_utils::IntoToml;
+use toml_utils::IntoRuby;
+use util::Wrap;
+use util::Unwrap;
+
+use storeid::RStoreId;
+use entry::RFileLockEntryHandle;
+use cache::StoreHandle;
+
+wrappable_struct!(StoreHandle, StoreWrapper, STORE_WRAPPER);
+class!(RStoreHandle);
+impl_wrap!(StoreHandle => STORE_WRAPPER);
+impl_unwrap!(RStoreHandle => StoreHandle => STORE_WRAPPER);
+impl_verified_object!(RStoreHandle);
+
+macro_rules! call_on_store_by_handle {
+ {
+ $store_handle:ident named $name:ident inside $operation:block
+ }=> {{
+ call_on_store_by_handle! {
+ $store_handle
+ named $name
+ inside $operation
+ on fail return NilClass::new().to_any_object()
+ }
+ }};
+
+ {
+ $store_handle:ident named $name:ident inside $operation:block on fail return $ex:expr
+ } => {{
+ use cache::RUBY_STORE_CACHE;
+
+ let arc = RUBY_STORE_CACHE.clone();
+ {
+ let lock = arc.lock();
+ match lock {
+ Ok(mut hm) => {
+ match hm.get($store_handle) {
+ Some($name) => { $operation },
+ None => {
+ VM::raise(Class::from_existing("RuntimeError"),
+ "Tried to operate on non-existing object");
+ $ex
+ }
+ }
+ },
+ Err(e) => {
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ $ex
+ }
+ }
+ }
+ }};
+}
+
+macro_rules! call_on_store {
+ {
+ $store_name:ident <- $itself:ident wrapped inside $wrapper:ident,
+ $fle_name:ident <- fetch $fle_handle_name:ident
+ operation $operation:block
+ } => {
+ call_on_store! {
+ $store_name <- $itself wrapped inside $wrapper,
+ $fle_name <- fetch $fle_handle_name,
+ operation $operation,
+ on fail return NilClass::new()
+ }
+ };
+
+ {
+ $store_name:ident <- $itself:ident wrapped inside $wrapper:ident,
+ $fle_name:ident <- fetch $fle_handle_name:ident,
+ operation $operation:block,
+ on fail return $fail_expr:expr
+ } => {
+ let handle = $itself.get_data(&*$wrapper);
+ call_on_store_by_handle! {
+ handle named $store_name inside {
+ let $fle_name = match $store_name.get($fle_handle_name) {
+ Ok(Some(fle)) => fle,
+ Ok(None) => {
+ VM::raise(Class::from_existing("RuntimeError"), "Obj does not exist");
+ return $fail_expr
+ },
+ Err(e) => {
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ return $fail_expr
+ },
+ };
+ $operation
+ }
+ on fail return $fail_expr
+ }
+ };
+
+ {
+ $store_name:ident <- $itself:ident wrapped inside $wrapper:ident,
+ operation $operation:block,
+ on fail return $fail_expr:expr
+ } => {
+ let handle = $itself.get_data(&*$wrapper);
+ call_on_store_by_handle! {
+ handle named $store_name inside $operation on fail return $fail_expr
+ }
+ };
+
+ {
+ $store_name:ident <- $itself:ident wrapped inside $wrapper:ident,
+ operation $block
+ } => {
+ let handle = $itself.get_data(&*$wrapper);
+ call_on_store_by_handle! { handle named $name inside $operation }
+ };
+}
+
+methods!(
+ RStoreHandle,
+ itself,
+
+ // Build a new Store object, return a handle to it.
+ //
+ // This function takes a boolean whether the store should include debugging functionality
+ // (namingly the debug hooks) and a runtimepath, where the store lifes.
+ // It then builds a Store object (raising errors on failure and returning Nil) and a handle for
+ // it.
+ // It puts the store object and the handle in the cache and returns the handle as object to the
+ // Ruby code.
+ //
+ // # Returns
+ //
+ // Nil on failure (including raising an error)
+ // StoreHandle on success
+ //
+ fn new(store_debugging: Boolean, rtp: RString) -> AnyObject {
+ use std::path::PathBuf;
+ use libimagerror::into::IntoError;
+ use libimagerror::trace::trace_error;
+ use libimagerror::trace::trace_error_dbg;
+ use libimagerror::trace::trace_error_exit;
+ use libimagrt::configuration::ConfigErrorKind;
+ use libimagrt::configuration::Configuration;
+ use libimagrt::error::RuntimeErrorKind;
+ use libimagstore::error::StoreErrorKind;
+ use libimagstore::hook::Hook;
+ use libimagstore::hook::position::HookPosition as HP;
+ use libimagstorestdhook::debug::DebugHook;
+ use libimagstorestdhook::vcs::git::delete::DeleteHook as GitDeleteHook;
+ use libimagstorestdhook::vcs::git::store_unload::StoreUnloadHook as GitStoreUnloadHook;
+ use libimagstorestdhook::vcs::git::update::UpdateHook as GitUpdateHook;
+
+ use cache::RUBY_STORE_CACHE;
+
+ let store_debugging = typecheck!(store_debugging or return any NilClass::new()).to_bool();
+ let rtp = PathBuf::from(typecheck!(rtp or return any NilClass::new()).to_string());
+
+ if !rtp.exists() || !rtp.is_dir() {
+ VM::raise(Class::from_existing("RuntimeError"), "Runtimepath not a directory");
+ return NilClass::new().to_any_object();
+ }
+
+ let store_config = match Configuration::new(&rtp) {
+ Ok(mut cfg) => cfg.store_config().cloned(),
+ Err(e) => if e.err_type() != ConfigErrorKind::NoConfigFileFound {
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ return NilClass::new().to_any_object();
+ } else {
+ warn!("No config file found.");
+ warn!("Continuing without configuration file");
+ None
+ },
+ };
+
+ let storepath = {
+ let mut spath = rtp.clone();
+ spath.push("store");
+ spath
+ };
+
+ let store = Store::new(storepath.clone(), store_config).map(|mut store| {
+ // If we are debugging, generate hooks for all positions
+ if store_debugging {
+ let hooks : Vec<(Box<Hook>, &str, HP)> = vec![
+ (Box::new(DebugHook::new(HP::PreCreate)) , "debug", HP::PreCreate),
+ (Box::new(DebugHook::new(HP::PostCreate)) , "debug", HP::PostCreate),
+ (Box::new(DebugHook::new(HP::PreRetrieve)) , "debug", HP::PreRetrieve),
+ (Box::new(DebugHook::new(HP::PostRetrieve)) , "debug", HP::PostRetrieve),
+ (Box::new(DebugHook::new(HP::PreUpdate)) , "debug", HP::PreUpdate),
+ (Box::new(DebugHook::new(HP::PostUpdate)) , "debug", HP::PostUpdate),
+ (Box::new(DebugHook::new(HP::PreDelete)) , "debug", HP::PreDelete),
+ (Box::new(DebugHook::new(HP::PostDelete)) , "debug", HP::PostDelete),
+ ];
+
+ // If hook registration fails, trace the error and warn, but continue.
+ for (hook, aspectname, position) in hooks {
+ if let Err(e) = store.register_hook(position, &String::from(aspectname), hook) {
+ if e.err_type() == StoreErrorKind::HookRegisterError {
+ trace_error_dbg(&e);
+ warn!("Registering debug hook with store failed");
+ } else {
+ trace_error(&e);
+ };
+ }
+ }
+ }
+
+ let sp = storepath;
+
+ let hooks : Vec<(Box<Hook>, &str, HP)> = vec![
+ (Box::new(GitDeleteHook::new(sp.clone(), HP::PostDelete)), "vcs", HP::PostDelete),
+ (Box::new(GitUpdateHook::new(sp.clone(), HP::PostUpdate)), "vcs", HP::PostUpdate),
+ (Box::new(GitStoreUnloadHook::new(sp)), "vcs", HP::StoreUnload),
+ ];
+
+ for (hook, aspectname, position) in hooks {
+ if let Err(e) = store.register_hook(position, &String::from(aspectname), hook) {
+ if e.err_type() == StoreErrorKind::HookRegisterError {
+ trace_error_dbg(&e);
+ warn!("Registering git hook with store failed");
+ } else {
+ trace_error(&e);
+ };
+ }
+ }
+
+ store
+ });
+
+ let store = match store {
+ Ok(s) => s,
+ Err(e) => {
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ return NilClass::new().to_any_object();
+ },
+ };
+
+ let store_handle = StoreHandle::new();
+
+ let arc = RUBY_STORE_CACHE.clone();
+ {
+ let lock = arc.lock();
+ match lock {
+ Ok(mut hm) => {
+ hm.insert(store_handle.clone(), store);
+ return store_handle.wrap().to_any_object();
+ },
+ Err(e) => {
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ return NilClass::new().to_any_object();
+ }
+ }
+ }
+
+ }
+
+
+ // Create an FileLockEntry in the store
+ //
+ // # Returns:
+ //
+ // On success: A RFileLockEntry
+ // On failure: Nil
+ // On error: Nil + Exception
+ //
+ fn create(id: RStoreId) -> AnyObject {
+ use entry::FileLockEntryHandle;
+ let sid = typecheck!(id or return any NilClass::new()).unwrap().clone();
+
+ call_on_store! {
+ store <- itself wrapped inside STORE_WRAPPER,
+ operation {
+ match store.create(sid.clone()) {
+ Err(e) => {
+ trace_error(&e);
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ NilClass::new().to_any_object()
+ },
+ Ok(entry) => {
+ // Take the location (StoreId) of the entry (we know it exists... so this
+ // is fine) and wrap it into a RFileLockEntry which is then returned to the
+ // user (as handle)
+ let sid = entry.get_location().clone();
+ let store_handle = itself.get_data(&*STORE_WRAPPER).clone();
+ FileLockEntryHandle::new(store_handle, sid).wrap()
+ },
+ }
+ },
+ on fail return NilClass::new().to_any_object()
+ }
+ }
+
+ // Retrieve an FileLockEntry from the store
+ //
+ // # Returns:
+ //
+ // On success: A RFileLockEntry
+ // On error: Nil + Exception
+ //
+ fn retrieve(id: RStoreId) -> AnyObject {
+ use entry::FileLockEntryHandle;
+ let sid = typecheck!(id or return any NilClass::new()).unwrap().clone();
+
+ call_on_store! {
+ store <- itself wrapped inside STORE_WRAPPER,
+ operation {
+ match store.retrieve(sid.clone()) {
+ Err(e) => {
+ trace_error(&e);
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ NilClass::new().to_any_object()
+ },
+ Ok(entry) => {
+ // Take the location (StoreId) of the entry (we know it exists... so this
+ // is fine) and wrap it into a RFileLockEntry which is then returned to the
+ // user (as handle)
+ let sid = entry.get_location().clone();
+ let store_handle = itself.get_data(&*STORE_WRAPPER).clone();
+ FileLockEntryHandle::new(store_handle, sid).wrap()
+ },
+ }
+ },
+ on fail return NilClass::new().to_any_object()
+ }
+ }
+
+ // Get an FileLockEntry from the store
+ //
+ // # Returns:
+ //
+ // On success, if there is some: A RFileLockEntry
+ // On success, if there is none: Nil
+ // On error: Nil + Exception
+ //
+ fn get(sid: RStoreId) -> AnyObject {
+ use entry::FileLockEntryHandle;
+ let sid = typecheck!(sid or return any NilClass::new()).unwrap().clone();
+
+ call_on_store! {
+ store <- itself wrapped inside STORE_WRAPPER,
+ operation {
+ match store.get(sid.clone()) {
+ Err(e) => {
+ trace_error(&e);
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ NilClass::new().to_any_object()
+ },
+ Ok(None) => NilClass::new().to_any_object(),
+ Ok(Some(entry)) => {
+ // Take the location (StoreId) of the entry (we know it exists... so this
+ // is fine) and wrap it into a RFileLockEntry which is then returned to the
+ // user (as handle)
+ let sid = entry.get_location().clone();
+ let store_handle = itself.get_data(&*STORE_WRAPPER).clone();
+ FileLockEntryHandle::new(store_handle, sid).wrap()
+ },
+ }
+ },
+ on fail return NilClass::new().to_any_object()
+ }
+ }
+
+ // Get all FileLockEntry of a module from the store
+ //
+ // # Returns:
+ //
+ // On success: A Array[RFileLockEntry]
+ // On error: Nil + Exception
+ //
+ fn retrieve_for_module(name: RString) -> AnyObject {
+ unimplemented!()
+ }
+
+ // Update a FileLockEntry in the store
+ //
+ // # Returns:
+ //
+ // On success: Nil
+ // On error: Nil + Exception
+ //
+ fn update(fle: RFileLockEntryHandle) -> NilClass {
+ let fle = typecheck!(fle).unwrap().fle_handle().clone();
+
+ call_on_store! {
+ store <- itself wrapped inside STORE_WRAPPER,
+ real_fle <- fetch fle,
+ operation {
+ if let Err(e) = store.update(real_fle) {
+ trace_error(&e);
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ }
+ NilClass::new()
+ },
+ on fail return NilClass::new()
+ }
+ }
+
+ // Delete a FileLockEntry from the store
+ //
+ // # Returns:
+ //
+ // On success: Nil
+ // On error: Nil + Exception
+ //
+ fn delete(sid: RStoreId) -> NilClass {
+ let sid = typecheck!(sid).unwrap().clone();
+
+ call_on_store! {
+ store <- itself wrapped inside STORE_WRAPPER,
+ operation {
+ if let Err(e) = store.delete(sid) {
+ trace_error(&e);
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ }
+ NilClass::new()
+ },
+ on fail return NilClass::new()
+ }
+ }
+
+ // Save a FileLockEntry in a new path inside the store, keep the RFileLockEntry
+ //
+ // # Returns:
+ //
+ // On success: Nil
+ // On error: Nil + Exception
+ //
+ fn save_to(fle: RFileLockEntryHandle, sid: RStoreId) -> NilClass {
+ let fle = typecheck!(fle).unwrap().fle_handle().clone();
+ let sid = typecheck!(sid).unwrap().clone();
+
+ call_on_store! {
+ store <- itself wrapped inside STORE_WRAPPER,
+ real_fle <- fetch fle,
+ operation {
+ if let Err(e) = store.save_to(&real_fle, sid) {
+ trace_error(&e);
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ }
+ NilClass::new()
+ },
+ on fail return NilClass::new()
+ }
+ }
+
+ // Save a FileLockEntry in a new path inside the store, move the RFileLockEntry
+ //
+ // # Returns:
+ //
+ // On success: Nil
+ // On error: Nil + Exception
+ //
+ fn save_as(fle: RFileLockEntryHandle, sid: RStoreId) -> NilClass {
+ let fle = typecheck!(fle).unwrap().fle_handle().clone();
+ let sid = typecheck!(sid).unwrap().clone();
+
+ call_on_store! {
+ store <- itself wrapped inside STORE_WRAPPER,
+ real_fle <- fetch fle,
+ operation {
+ if let Err(e) = store.save_as(real_fle, sid) {
+ trace_error(&e);
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ }
+ NilClass::new()
+ },
+ on fail return NilClass::new()
+ }
+ }
+
+ // Move one entry in the store to another place, by its ID
+ //
+ // # Returns:
+ //
+ // On success: Nil
+ // On error: Nil + Exception
+ //
+ fn move_by_id(old: RStoreId, nw: RStoreId) -> NilClass {
+ let old = typecheck!(old).unwrap().clone();
+ let nw = typecheck!(nw).unwrap().clone();
+
+ call_on_store! {
+ store <- itself wrapped inside STORE_WRAPPER,
+ operation {
+ if let Err(e) = store.move_by_id(old, nw) {
+ trace_error(&e);
+ VM::raise(Class::from_existing("RuntimeError"), e.description());
+ }
+ NilClass::new()
+ },
+ on fail return NilClass::new()
+ }
+ }
+
+ // Get the path of the store object
+ //
+ // # Returns:
+ //
+ // A RString
+ //
+ fn path() -> RString {
+ call_on_store! {
+ store <- itself wrapped inside STORE_WRAPPER,
+ operation {
+ store.path()
+ .clone()
+ .to_str()
+ .map(RString::new)
+ .unwrap_or(RString::new(""))
+ },
+ on fail return RString::new("")
+ }
+ }
+
+);
+
+pub fn setup() -> Class {
+ let mut class = Class::new("RStoreHandle", None);
+ class.define(|itself| {
+ itself.def_self("new" , new);
+ itself.def("create" , create);
+ itself.def("retrieve" , retrieve);
+ itself.def("get" , get);
+ itself.def("retrieve_for_module" , retrieve_for_module);
+ itself.def("update" , update);
+ itself.def("delete" , delete);
+ itself.def("save_to" , save_to);
+ itself.def("save_as" , save_as);
+ itself.def("move_by_id" , move_by_id);
+ itself.def("path" , path);
+ });
+ class
+}
+
diff --git a/libimagruby/src/storeid.rs b/libimagruby/src/storeid.rs
new file mode 100644
index 0000000..a820064
--- /dev/null
+++ b/libimagruby/src/storeid.rs
@@ -0,0 +1,154 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+use std::path::PathBuf;
+
+use ruru::{Class, Object, AnyObject, Boolean, RString, NilClass, VerifiedObject};
+
+use libimagstore::storeid::StoreId;
+use util::Unwrap;
+use util::Wrap;
+
+wrappable_struct!(StoreId, StoreIdWrapper, STOREID_WRAPPER);
+class!(RStoreId);
+impl_wrap!(StoreId => STOREID_WRAPPER);
+impl_unwrap!(RStoreId => StoreId => STOREID_WRAPPER);
+impl_verified_object!(RStoreId);
+
+
+methods!(
+ RStoreId,
+ itself,
+
+ fn r_storeid_new(base: RString, id: RString) -> AnyObject {
+ let base = match base.map(|b| b.to_string()).map(PathBuf::from) {
+ Ok(base) => base,
+ Err(e) => {
+ // TODO: Exception!
+ error!("Building StoreId object failed: {:?}", e);
+ return AnyObject::from(NilClass::new().value());
+ },
+ };
+
+ let id = match id.map(|id| id.to_string()).map(PathBuf::from) {
+ Ok(id) => id,
+ Err(e) => {
+ // TODO: Exception!
+ error!("Building StoreId object failed: {:?}", e);
+ return AnyObject::from(NilClass::new().value());
+ },
+ };
+
+ match StoreId::new(Some(base), id) {
+ Ok(sid) => Class::from_existing("RStoreId").wrap_data(sid, &*STOREID_WRAPPER),
+ Err(e) => {
+ // TODO: Exception!
+ error!("Building StoreId object failed: {:?}", e);
+ return AnyObject::from(NilClass::new().value());
+ },
+ }
+ }
+
+ fn r_storeid_new_baseless(id: RString) -> AnyObject {
+ let id = match id.map(|id| id.to_string()).map(PathBuf::from) {
+ Ok(id) => id,
+ Err(e) => {
+ // TODO: Exception!
+ error!("Building StoreId object failed: {:?}", e);
+ return AnyObject::from(NilClass::new().value());
+ },
+ };
+
+ match StoreId::new(None, id) {
+ Ok(sid) => Class::from_existing("RStoreId").wrap_data(sid, &*STOREID_WRAPPER),
+ Err(e) => {
+ // TODO: Exception!
+ error!("Building StoreId object failed: {:?}", e);
+ return AnyObject::from(NilClass::new().value());
+ },
+ }
+ }
+
+ fn r_storeid_without_base() -> RStoreId {
+ let withoutbase : StoreId = itself.get_data(&*STOREID_WRAPPER).clone().without_base();
+ Class::from_existing("RStoreId").wrap_data(withoutbase, &*STOREID_WRAPPER)
+ }
+
+ fn r_storeid_with_base(base: RString) -> AnyObject {
+ let base : PathBuf = match base.map(|b| b.to_string()).map(PathBuf::from) {
+ Ok(pb) => pb,
+ Err(e) => {
+ // TODO: Exception!
+ error!("Error: {:?}", e);
+ return AnyObject::from(NilClass::new().value());
+ },
+ };
+ let withoutbase : StoreId = itself.get_data(&*STOREID_WRAPPER).clone().with_base(base);
+ Class::from_existing("RStoreId").wrap_data(withoutbase, &*STOREID_WRAPPER)
+ }
+
+ fn r_storeid_into_pathbuf() -> AnyObject {
+ itself.get_data(&*STOREID_WRAPPER)
+ .clone()
+ .into_pathbuf()
+ // TODO: No unwraps
+ .map(|pb| pb.to_str().map(String::from).unwrap())
+ .as_ref()
+ .map(|s| AnyObject::from(RString::new(s).value()))
+ // TODO: Exception!
+ .unwrap_or(AnyObject::from(NilClass::new().value()))
+ }
+
+ fn r_storeid_exists() -> Boolean {
+ Boolean::new(itself.get_data(&*STOREID_WRAPPER).exists())
+ }
+
+ fn r_storeid_to_str() -> AnyObject {
+ itself.get_data(&*STOREID_WRAPPER)
+ .to_str()
+ .as_ref()
+ .map(|s| AnyObject::from(RString::new(s).value()))
+ // TODO: Exception!
+ .unwrap_or(AnyObject::from(NilClass::new().value()))
+ }
+
+ fn r_storeid_local() -> RString {
+ let local = itself.get_data(&*STOREID_WRAPPER).local();
+ let local = local.to_str().unwrap(); // TODO: No unwraps
+ RString::new(local)
+ }
+
+);
+
+pub fn setup() -> Class {
+ let mut class = Class::new("RStoreId", None);
+ class.define(|itself| {
+ itself.def_self("new" , r_storeid_new);
+ itself.def_self("new_baseless" , r_storeid_new_baseless);
+
+ itself.def("without_base" , r_storeid_without_base);
+ itself.def("with_base" , r_storeid_with_base);
+ itself.def("into_pathbuf" , r_storeid_into_pathbuf);
+ itself.def("exists" , r_storeid_exists);
+ itself.def("to_str" , r_storeid_to_str);
+ itself.def("local" , r_storeid_local);
+ });
+ class
+}
+
diff --git a/libimagruby/src/toml_utils.rs b/libimagruby/src/toml_utils.rs
new file mode 100644
index 0000000..85df9dd
--- /dev/null
+++ b/libimagruby/src/toml_utils.rs
@@ -0,0 +1,65 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+// Toml -> Ruby translation primitives
+
+use ruru::{Object, AnyObject, RString, Fixnum, Float, Boolean, Hash, Array};
+use toml::Value;
+
+pub trait AsRuby : Sized {
+ fn as_ruby(&self) -> AnyObject;
+}
+
+pub trait IntoRuby : AsRuby {
+ fn into_ruby(self) -> AnyObject {
+ self.as_ruby()
+ }
+}
+impl<T: AsRuby> IntoRuby for T { }
+
+impl AsRuby for Value {
+
+ fn as_ruby(&self) -> AnyObject {
+ match *self {
+ Value::String(ref s) => RString::new(&s).to_any_object(),
+ Value::Integer(i) => Fixnum::new(i).to_any_object(),
+ Value::Float(f) => Float::new(f).to_any_object(),
+ Value::Boolean(b) => Boolean::new(b).to_any_object(),
+ Value::Datetime(ref s) => RString::new(&s).to_any_object(),
+ Value::Array(ref a) => {
+ let mut arr = Array::new();
+ for obj in a.into_iter().map(AsRuby::as_ruby) {
+ arr.push(obj);
+ }
+ arr.to_any_object()
+ },
+ Value::Table(ref t) => {
+ let mut h = Hash::new();
+ for (k, v) in t.into_iter() {
+ let key = RString::new(k).to_any_object();
+ let v = v.as_ruby();
+ h.store(key, v);
+ }
+ h.to_any_object()
+ },
+ }
+ }
+
+}
+
diff --git a/libimagruby/src/util.rs b/libimagruby/src/util.rs
new file mode 100644
index 0000000..85813f2
--- /dev/null
+++ b/libimagruby/src/util.rs
@@ -0,0 +1,102 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+use ruru::AnyObject;
+pub trait Wrap {
+ fn wrap(self) -> AnyObject;
+}
+
+#[macro_export]
+macro_rules! impl_wrap {
+ ($target:ty => $wrapper:path) => {
+ impl Wrap for $target {
+ fn wrap(self) -> AnyObject {
+ Class::from_existing(concat!("R", stringify!($target)))
+ .wrap_data(self, &*($wrapper))
+ }
+ }
+ }
+}
+
+pub trait Unwrap {
+ type Target;
+ fn unwrap<'a>(&'a self) -> &'a mut Self::Target;
+}
+
+#[macro_export]
+macro_rules! impl_unwrap {
+ ($from:ty => $to:ty => $wrapper:path) => {
+ impl Unwrap for $from {
+ type Target = $to;
+ fn unwrap<'a>(&'a self) -> &'a mut $to {
+ self.get_data(&*($wrapper))
+ }
+ }
+ }
+}
+
+#[macro_export]
+macro_rules! impl_verified_object {
+ ($objname: ty) => {
+ impl VerifiedObject for $objname {
+ fn is_correct_type<T: Object>(object: &T) -> bool {
+ object.class() == Class::from_existing(stringify!($objname))
+ }
+
+ fn error_message() -> &'static str {
+ concat!("Not a ", stringify!($objname), " object")
+ }
+ }
+ };
+}
+
+/// Helper macro to simplify type checking in the ruby-interfacing functions.
+///
+/// # Return
+///
+/// If called with only the object to check, this returns NIL after raising an exception.
+/// If called with more arguments, the other things will be returned.
+/// E.G.:
+///
+/// ```ignore
+/// let obj1 = typecheck!(obj1); // returns `obj` or raises exception
+///
+/// // returns `obj` or raises exception and returns AnyObject (Boolean -> false):
+/// let obj2 = typecheck!(obj2 or return any Boolean::new(false));
+///
+/// // returns `obj` or raises excpetion and returns Boolean -> false
+/// let obj3 = typecheck!(obj3 or return Boolean::new(false));
+/// ```
+///
+#[macro_export]
+macro_rules! typecheck {
+ ($obj: ident) => { typecheck!($obj or return NilClass::new()) };
+ ($obj: ident or return any $els: expr) => { typecheck!($obj or return $els.to_any_object()) };
+ ($obj: ident or return $els: expr) => {
+ match $obj {
+ Ok(o) => o,
+ Err(e) => {
+ VM::raise(e.to_exception(), e.description());
+ return $els
+ },
+ }
+ };
+
+}
+
diff --git a/libimagruby/test/test_entries.rb b/libimagruby/test/test_entries.rb
new file mode 100644
index 0000000..f10dc64
--- /dev/null
+++ b/libimagruby/test/test_entries.rb
@@ -0,0 +1,27 @@
+#!/usr/bin/env ruby
+
+require "../target/debug/liblibimagruby.so"
+
+color = true
+verbose = true
+debug = false
+
+RImag.init_logger debug, verbose, color
+
+store_handle = RStoreHandle::new(false, "/tmp/store")
+id = RStoreId::new_baseless("test")
+test_handle = store_handle.retrieve(id)
+puts "Header: #{test_handle.header.to_s}"
+puts "Content: '#{test_handle.content}'"
+
+test_handle.content = "Foo"
+test_handle.header = {
+ "imag" => {
+ "links" => [],
+ "version" => "0.2.0"
+ },
+ "example" => {
+ "test" => "foo"
+ }
+}
+
diff --git a/libimagruby/test/test_ruby.rb b/libimagruby/test/test_ruby.rb
new file mode 100644
index 0000000..9b08859
--- /dev/null
+++ b/libimagruby/test/test_ruby.rb
@@ -0,0 +1,58 @@
+#!/usr/bin/env ruby
+
+require "../target/debug/liblibimagruby.so"
+
+color = true
+verbose = true
+debug = true
+
+RImag.init_logger debug, verbose, color
+
+RImag.trace "Trace-Hello from Ruby"
+RImag.dbg "Debug-Hello from Ruby"
+RImag.debug "Debug-Hello from Ruby"
+RImag.info "Info-Hello from Ruby"
+RImag.warn "Warn-Hello from Ruby"
+RImag.error "Error-Hello from Ruby"
+
+def works name, b
+ if b
+ RImag.info "Works: #{name}"
+ else
+ RImag.error "Fails: #{name}"
+ end
+end
+
+def has_instance_method klass, meth
+ works "#{klass}.instance_methods.include? #{meth}",
+ (klass.instance_methods.include? meth)
+end
+
+puts "---"
+
+works "RStoreId.new_baseless", (not RStoreId.new_baseless("test").nil?)
+
+works "RStoreHandle.respond_to? :new", (RStoreHandle.respond_to? :new)
+
+has_instance_method RStoreHandle, :create
+has_instance_method RStoreHandle, :get
+has_instance_method RStoreHandle, :retrieve
+has_instance_method RStoreHandle, :delete
+has_instance_method RStoreHandle, :update
+has_instance_method RStoreHandle, :move_by_id
+has_instance_method RStoreHandle, :save_as
+has_instance_method RStoreHandle, :save_to
+
+has_instance_method RFileLockEntryHandle, :content
+has_instance_method RFileLockEntryHandle, :content=
+has_instance_method RFileLockEntryHandle, :header
+has_instance_method RFileLockEntryHandle, :header=
+
+has_instance_method REntryHeader, :read
+has_instance_method REntryHeader, :[]
+has_instance_method REntryHeader, :set
+has_instance_method REntryHeader, :[]=
+has_instance_method REntryHeader, :insert
+
+works "REntryContent.superclass == String", (REntryContent.superclass == String)
+
diff --git a/libimagruby/test/test_store.rb b/libimagruby/test/test_store.rb
new file mode 100644
index 0000000..aed0017
--- /dev/null
+++ b/libimagruby/test/test_store.rb
@@ -0,0 +1,16 @@
+#!/usr/bin/env ruby
+
+require "../target/debug/liblibimagruby.so"
+
+color = true
+verbose = true
+debug = true
+
+RImag.init_logger debug, verbose, color
+
+store_handle = RStoreHandle::new(false, "/tmp/store")
+id = RStoreId::new_baseless("test")
+test_handle = store_handle.create(id)
+
+RImag.info "Created #{test_handle.location.to_str} from Ruby"
+