You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by xu...@apache.org on 2023/03/23 16:00:25 UTC

[incubator-opendal] branch main updated: feat(bindings/ruby): support read and write (#1734)

This is an automated email from the ASF dual-hosted git repository.

xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new 14cbb0f1 feat(bindings/ruby): support read and write (#1734)
14cbb0f1 is described below

commit 14cbb0f187cfc42ad0626d767628075e67afad44
Author: Chojan Shang <ps...@apache.org>
AuthorDate: Fri Mar 24 00:00:19 2023 +0800

    feat(bindings/ruby): support read and write (#1734)
    
    * feat(bindings/ruby): support read and write
    
    Signed-off-by: Chojan Shang <ps...@outlook.com>
    
    * chore(bindings/ruby): make bdd more clear
    
    Signed-off-by: Chojan Shang <ps...@outlook.com>
    
    * refactor(bindings/ruby): use string to replace u8 array
    
    Signed-off-by: Chojan Shang <ps...@outlook.com>
    
    * chore(bindings/ruby): remove hello_opendal
    
    Signed-off-by: Chojan Shang <ps...@outlook.com>
    
    * fix(bindings/ruby): revert to u8 array
    
    Signed-off-by: Chojan Shang <ps...@outlook.com>
    
    * fix(bindings/ruby): try to use RString
    
    Signed-off-by: Chojan Shang <ps...@outlook.com>
    
    ---------
    
    Signed-off-by: Chojan Shang <ps...@outlook.com>
---
 Cargo.lock                           |   1 +
 bindings/ruby/Cargo.toml             |   2 +-
 bindings/ruby/Gemfile                |   3 -
 bindings/ruby/README.md              |   2 +-
 bindings/ruby/lib/opendal.rb         |   2 +-
 bindings/ruby/src/lib.rs             | 103 ++++++++++++++++++++++++++++++++---
 bindings/ruby/tests/opendal_test.rb  |  24 --------
 bindings/ruby/tests/steps/binding.rb |  28 +++++-----
 bindings/ruby/tests/test_helper.rb   |  23 --------
 9 files changed, 115 insertions(+), 73 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 0fb64c49..0cdbdbc1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1784,6 +1784,7 @@ version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f3fda351681130b40996b3e40592565b5e44ef1a677c45ea3768ed223b5918a5"
 dependencies = [
+ "bytes",
  "magnus-macros",
  "rb-sys",
  "rb-sys-env",
diff --git a/bindings/ruby/Cargo.toml b/bindings/ruby/Cargo.toml
index 63d7a762..99f3b677 100644
--- a/bindings/ruby/Cargo.toml
+++ b/bindings/ruby/Cargo.toml
@@ -32,4 +32,4 @@ name = "opendal_ruby"
 
 [dependencies]
 opendal = { version = "0.30", path = "../../core" }
-magnus = "0.5"
\ No newline at end of file
+magnus = { version = "0.5" , features = ["bytes-crate"] }
\ No newline at end of file
diff --git a/bindings/ruby/Gemfile b/bindings/ruby/Gemfile
index 096e4143..60d03032 100644
--- a/bindings/ruby/Gemfile
+++ b/bindings/ruby/Gemfile
@@ -24,9 +24,6 @@ gemspec
 
 gem "rake", "~> 13.0"
 
-gem "minitest", "~> 5.10"
-gem "minitest-reporters", "~> 1.1"
 gem "cucumber", "~> 8.0"
-gem "color_pound_spec_reporter", "~> 0.0.6"
 
 gem "standard", "~> 1.3"
diff --git a/bindings/ruby/README.md b/bindings/ruby/README.md
index 15db8fb2..50561bfa 100644
--- a/bindings/ruby/README.md
+++ b/bindings/ruby/README.md
@@ -7,7 +7,7 @@ This crate intends to build a native ruby binding.
 Install gems:
 
 ```shell
-bundle install
+bundle
 ```
 
 Build bindings:
diff --git a/bindings/ruby/lib/opendal.rb b/bindings/ruby/lib/opendal.rb
index 0e477faa..28bd9813 100644
--- a/bindings/ruby/lib/opendal.rb
+++ b/bindings/ruby/lib/opendal.rb
@@ -17,5 +17,5 @@
 
 # frozen_string_literal: true
 
-require "opendal_ruby/version"
+require_relative "opendal_ruby/version"
 require_relative "opendal_ruby/opendal_ruby"
diff --git a/bindings/ruby/src/lib.rs b/bindings/ruby/src/lib.rs
index e3f1c3a1..59ee073e 100644
--- a/bindings/ruby/src/lib.rs
+++ b/bindings/ruby/src/lib.rs
@@ -15,16 +15,105 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use magnus::{define_global_function, function, Error};
-use opendal::{services::Memory, Operator};
+use std::{collections::HashMap, str::FromStr};
 
-fn hello_opendal() {
-    let op = Operator::new(Memory::default()).unwrap().finish();
-    println!("{op:?}")
+use magnus::{
+    class, define_class, error::Result, exception, function, method, prelude::*, Error, RString,
+};
+use opendal as od;
+
+fn build_operator(scheme: od::Scheme, map: HashMap<String, String>) -> Result<od::Operator> {
+    use od::services::*;
+
+    let op = match scheme {
+        od::Scheme::Azblob => od::Operator::from_map::<Azblob>(map)
+            .map_err(format_magnus_error)?
+            .finish(),
+        od::Scheme::Azdfs => od::Operator::from_map::<Azdfs>(map)
+            .map_err(format_magnus_error)?
+            .finish(),
+        od::Scheme::Fs => od::Operator::from_map::<Fs>(map)
+            .map_err(format_magnus_error)?
+            .finish(),
+        od::Scheme::Gcs => od::Operator::from_map::<Gcs>(map)
+            .map_err(format_magnus_error)?
+            .finish(),
+        od::Scheme::Ghac => od::Operator::from_map::<Ghac>(map)
+            .map_err(format_magnus_error)?
+            .finish(),
+        od::Scheme::Http => od::Operator::from_map::<Http>(map)
+            .map_err(format_magnus_error)?
+            .finish(),
+        od::Scheme::Ipmfs => od::Operator::from_map::<Ipmfs>(map)
+            .map_err(format_magnus_error)?
+            .finish(),
+        od::Scheme::Memory => od::Operator::from_map::<Memory>(map)
+            .map_err(format_magnus_error)?
+            .finish(),
+        od::Scheme::Obs => od::Operator::from_map::<Obs>(map)
+            .map_err(format_magnus_error)?
+            .finish(),
+        od::Scheme::Oss => od::Operator::from_map::<Oss>(map)
+            .map_err(format_magnus_error)?
+            .finish(),
+        od::Scheme::S3 => od::Operator::from_map::<S3>(map)
+            .map_err(format_magnus_error)?
+            .finish(),
+        od::Scheme::Webdav => od::Operator::from_map::<Webdav>(map)
+            .map_err(format_magnus_error)?
+            .finish(),
+        od::Scheme::Webhdfs => od::Operator::from_map::<Webhdfs>(map)
+            .map_err(format_magnus_error)?
+            .finish(),
+        _ => {
+            return Err(format_magnus_error(od::Error::new(
+                od::ErrorKind::Unexpected,
+                "not supported scheme",
+            )))
+        }
+    };
+
+    Ok(op)
+}
+
+#[magnus::wrap(class = "Operator", free_immediately, size)]
+#[derive(Clone, Debug)]
+pub struct Operator(od::BlockingOperator);
+
+impl Operator {
+    pub fn new(scheme: String, options: Option<HashMap<String, String>>) -> Result<Self> {
+        let scheme = od::Scheme::from_str(&scheme)
+            .map_err(|err| {
+                od::Error::new(od::ErrorKind::Unexpected, "unsupported scheme").set_source(err)
+            })
+            .map_err(format_magnus_error)?;
+        let options = options.unwrap_or_default();
+        Ok(Operator(build_operator(scheme, options)?.blocking()))
+    }
+
+    /// Read the whole path into string.
+    pub fn read(&self, path: String) -> Result<RString> {
+        let bytes = self.0.read(&path).map_err(format_magnus_error)?;
+        Ok(RString::from_slice(&bytes))
+    }
+
+    /// Write string into given path.
+    pub fn write(&self, path: String, bs: RString) -> Result<()> {
+        self.0
+            .write(&path, bs.to_bytes())
+            .map_err(format_magnus_error)
+    }
+}
+
+fn format_magnus_error(err: od::Error) -> Error {
+    Error::new(exception::runtime_error(), err.to_string())
 }
 
 #[magnus::init]
-fn init() -> Result<(), Error> {
-    define_global_function("hello_opendal", function!(hello_opendal, 0));
+fn init() -> Result<()> {
+    let class = define_class("Operator", class::object())?;
+    class.define_singleton_method("new", function!(Operator::new, 2))?;
+    class.define_method("read", method!(Operator::read, 1))?;
+    class.define_method("write", method!(Operator::write, 2))?;
     Ok(())
 }
diff --git a/bindings/ruby/tests/opendal_test.rb b/bindings/ruby/tests/opendal_test.rb
deleted file mode 100644
index 1b5c2ac6..00000000
--- a/bindings/ruby/tests/opendal_test.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-require_relative "test_helper"
-
-class OpenDALTest < Minitest::Test
-  def test_hello_opendal
-    hello_opendal
-  end
-end
diff --git a/bindings/ruby/tests/steps/binding.rb b/bindings/ruby/tests/steps/binding.rb
index adaa886e..d4498625 100644
--- a/bindings/ruby/tests/steps/binding.rb
+++ b/bindings/ruby/tests/steps/binding.rb
@@ -15,50 +15,52 @@
 # specific language governing permissions and limitations
 # under the License.
 
+require_relative "../../lib/opendal"
+
 Given("A new OpenDAL Blocking Operator") do
-  pending # Write code here that turns the phrase above into concrete actions
+  @op = Operator.new("memory", nil)
 end
 
-When("Blocking write path {string} with content {string}") do |string, string2|
-  pending # Write code here that turns the phrase above into concrete actions
+When("Blocking write path {string} with content {string}") do |path, content|
+  @op.write(path, content)
 end
 
-Then("The blocking file {string} should exist") do |string|
+Then("The blocking file {string} should exist") do |path|
   pending # Write code here that turns the phrase above into concrete actions
 end
 
-Then("The blocking file {string} entry mode must be file") do |string|
+Then("The blocking file {string} entry mode must be file") do |path|
   pending # Write code here that turns the phrase above into concrete actions
 end
 
-Then("The blocking file {string} content length must be {string}") do |string, string2|
+Then("The blocking file {string} content length must be {string}") do |path, length|
   pending # Write code here that turns the phrase above into concrete actions
 end
 
-Then("The blocking file {string} must have content {string}") do |string, string2|
-  pending # Write code here that turns the phrase above into concrete actions
+Then("The blocking file {string} must have content {string}") do |path, content|
+  @op.read(path) == content
 end
 
 Given("A new OpenDAL Async Operator") do
   pending # Write code here that turns the phrase above into concrete actions
 end
 
-When("Async write path {string} with content {string}") do |string, string2|
+When("Async write path {string} with content {string}") do |path, content|
   pending # Write code here that turns the phrase above into concrete actions
 end
 
-Then("The async file {string} should exist") do |string|
+Then("The async file {string} should exist") do |path|
   pending # Write code here that turns the phrase above into concrete actions
 end
 
-Then("The async file {string} entry mode must be file") do |string|
+Then("The async file {string} entry mode must be file") do |path|
   pending # Write code here that turns the phrase above into concrete actions
 end
 
-Then("The async file {string} content length must be {string}") do |string, string2|
+Then("The async file {string} content length must be {string}") do |path, length|
   pending # Write code here that turns the phrase above into concrete actions
 end
 
-Then("The async file {string} must have content {string}") do |string, string2|
+Then("The async file {string} must have content {string}") do |path, content|
   pending # Write code here that turns the phrase above into concrete actions
 end
diff --git a/bindings/ruby/tests/test_helper.rb b/bindings/ruby/tests/test_helper.rb
deleted file mode 100644
index 63a525f7..00000000
--- a/bindings/ruby/tests/test_helper.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
-require_relative "../lib/opendal"
-
-require "minitest/autorun"
-require "color_pound_spec_reporter"
-Minitest::Reporters.use! [ColorPoundSpecReporter.new]