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/opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new 5e074cc49b feat(bindings/d): add D bindings support (#5181)
5e074cc49b is described below

commit 5e074cc49bc7662c790d88fb9be31e7c67899d64
Author: Matheus C. França <[email protected]>
AuthorDate: Tue Oct 22 10:05:41 2024 -0300

    feat(bindings/d): add D bindings support (#5181)
    
    D bindings
    * `std.paralellism` support added (write/read/list)
    * CI/CD test
    * rename `is_exists` - ref.: https://github.com/apache/opendal/pull/5199
    
    Signed-off-by: Matheus C. França  <[email protected]>
---
 .devcontainer/post_create.sh          |   3 +
 .gitattributes                        |   1 +
 .github/workflows/ci_bindings_d.yml   |  73 +++++++++++
 .typos.toml                           |   2 +
 README.md                             |   2 +
 bindings/README.md                    |   1 +
 bindings/d/.gitignore                 |  20 +++
 bindings/d/CONTRIBUTING.md            |  58 +++++++++
 bindings/d/DEPENDENCIES.md            |   4 +
 bindings/d/DEPENDENCIES.rust.tsv      |   0
 bindings/d/README.md                  |  24 ++++
 bindings/d/build.d                    |  42 +++++++
 bindings/d/dscanner.ini               | 105 ++++++++++++++++
 bindings/d/dub.json                   |  37 ++++++
 bindings/d/source/opendal/opendal_c.c |  23 ++++
 bindings/d/source/opendal/operator.d  | 229 ++++++++++++++++++++++++++++++++++
 bindings/d/source/opendal/package.d   | 123 ++++++++++++++++++
 bindings/d/test/dub.json              |  14 +++
 bindings/d/test/source/bdd.d          |  97 ++++++++++++++
 19 files changed, 858 insertions(+)

diff --git a/.devcontainer/post_create.sh b/.devcontainer/post_create.sh
index cbc54dfeab..987da1a8c5 100644
--- a/.devcontainer/post_create.sh
+++ b/.devcontainer/post_create.sh
@@ -69,3 +69,6 @@ opam install -y dune ounit2 ocamlformat
 
 # Setup for Cpp binding
 sudo apt install -y ninja-build
+
+# Setup for D binding
+sudo apt install -y dmd dub
diff --git a/.gitattributes b/.gitattributes
index 51806da035..cf8f7fa77c 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -8,6 +8,7 @@ integrations export-ignore
 
 bindings/c export-ignore
 bindings/cpp export-ignore
+bindings/d export-ignore
 bindings/dotnet export-ignore
 bindings/go export-ignore
 bindings/haskell export-ignore
diff --git a/.github/workflows/ci_bindings_d.yml 
b/.github/workflows/ci_bindings_d.yml
new file mode 100644
index 0000000000..9835868b59
--- /dev/null
+++ b/.github/workflows/ci_bindings_d.yml
@@ -0,0 +1,73 @@
+# 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.
+
+name: Bindings D CI
+
+on:
+  push:
+    branches:
+      - main
+    tags:
+      - "*"
+  pull_request:
+    branches:
+      - main
+    paths:
+      - "core/**"
+      - "bindings/c/**"
+      - "bindings/d/**"
+      - ".github/workflows/ci_bindings_d.yml"
+  workflow_dispatch:
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
+  cancel-in-progress: true
+
+permissions:
+  contents: read
+
+jobs:
+  test:
+    strategy:
+      matrix:
+      # dmd: base (self-hosting) compiler (frontend & backend)
+      # ldc2/ldmd2: (dmd-frontend + LLVM backend) - recommended for MacOS ARM64
+        dlang: ["ldc-latest", "dmd-latest"]
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: dlang-community/setup-dlang@v2
+        with:
+          compiler: ${{ matrix.dlang }}
+
+      - name: Setup Rust toolchain
+        uses: ./.github/actions/setup
+
+      - name: Build D binding
+        working-directory: bindings/d
+        run: dub build
+
+      - name: Check diff
+        run: git diff --exit-code
+
+      - name: Check
+        working-directory: bindings/d
+        run: dub lint
+
+      - name: Run tests
+        working-directory: bindings/d
+        run: dub test && cd test && dub
diff --git a/.typos.toml b/.typos.toml
index 9c58e5d2d9..7d504be990 100644
--- a/.typos.toml
+++ b/.typos.toml
@@ -36,4 +36,6 @@ extend-exclude = [
   # Generated pnpm locks.
   "website/pnpm-lock.yaml",
   "CHANGELOG.md",
+  # dscanner config
+  "bindings/d/dscanner.ini",
 ]
diff --git a/README.md b/README.md
index 4e74c654ea..d721fa888b 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,7 @@ OpenDAL offers a unified data access layer, empowering users 
to seamlessly and e
 | [Rust Core]       | [![Rust Core Image]][Rust Core Link]             | 
[![Docs Release]][Rust Core Release Docs] [![Docs Dev]][Rust Core Dev Docs]     
  |
 | [C Binding]       | -                                                | 
[![Docs Dev]][C Binding Dev Docs]                                               
  |
 | [Cpp Binding]     | -                                                | 
[![Docs Dev]][Cpp Binding Dev Docs]                                             
  |
+| [D Binding]   | -                                                | -         
                                                                        |
 | [Dotnet Binding]  | -                                                | -     
                                                                            |
 | [Go Binding]      | [![Go Binding Image]][Go Binding Link]           | 
[![Docs Release]][Go Release Docs]                                         |
 | [Haskell Binding] | -                                                | -     
                                                                            |
@@ -38,6 +39,7 @@ OpenDAL offers a unified data access layer, empowering users 
to seamlessly and e
 [C Binding Dev Docs]: https://opendal.apache.org/docs/c/
 [Cpp Binding]: bindings/cpp/README.md
 [Cpp Binding Dev Docs]: https://opendal.apache.org/docs/cpp/
+[D Binding]: bindings/d/README.md
 [Dotnet Binding]: bindings/dotnet/README.md
 [Go Binding]: bindings/go/README.md
 [Go Binding Image]: 
https://badge.fury.io/go/github.com%2Fapache%2Fopendal%2Fbindings%2Fgo.svg
diff --git a/bindings/README.md b/bindings/README.md
index 21d32c7d5e..2ad662e693 100644
--- a/bindings/README.md
+++ b/bindings/README.md
@@ -13,6 +13,7 @@ This folder contains the bindings for OpenDAL. Currently, we 
support the followi
 * [C](c/README.md)
 * [C++](cpp/README.md)
 * [C#](dotnet/README.md)
+* [D](d/README.md)
 * [Go](go/README.md)
 * [Haskell](haskell/README.md)
 * [Lua](lua/README.md)
diff --git a/bindings/d/.gitignore b/bindings/d/.gitignore
new file mode 100644
index 0000000000..3acdc490d3
--- /dev/null
+++ b/bindings/d/.gitignore
@@ -0,0 +1,20 @@
+.dub
+docs.json
+__dummy.html
+docs/
+/d
+d.so
+d.dylib
+d.dll
+d.a
+d.lib
+d-test-*
+*.exe
+*.pdb
+*.o
+*.obj
+*.lst
+*.h
+*.a
+*.ninja_log
+tests*
diff --git a/bindings/d/CONTRIBUTING.md b/bindings/d/CONTRIBUTING.md
new file mode 100644
index 0000000000..c64e596ad5
--- /dev/null
+++ b/bindings/d/CONTRIBUTING.md
@@ -0,0 +1,58 @@
+# Contributing
+
+- [Contributing](#contributing)
+  - [Setup](#setup)
+    - [Using a dev container environment](#using-a-dev-container-environment)
+    - [Bring your own toolbox](#bring-your-own-toolbox)
+  - [Build](#build)
+  - [Test](#test)
+
+## Setup
+
+### Using a dev container environment
+
+OpenDAL provides a pre-configured [dev container](https://containers.dev/) 
that could be used in [GitHub 
Codespaces](https://github.com/features/codespaces), 
[VSCode](https://code.visualstudio.com/), 
[JetBrains](https://www.jetbrains.com/remote-development/gateway/), 
[JupyterLab](https://jupyterlab.readthedocs.io/en/stable/). Please pick up your 
favourite runtime environment.
+
+The fastest way is:
+
+[![Open in GitHub 
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/apache/opendal?quickstart=1&machine=standardLinux32gb)
+
+### Bring your own toolbox
+
+To build OpenDAL D binding locally, you need:
+
+- [dmd/ldc/gdc](https://dlang.org/download)
+
+
+## Build
+
+First, build the C bindings:
+
+```shell
+dub build -b release
+```
+
+> **Note**:
+>
+> - `dub build` adds the header file `opendal.h` under `../c/include`
+> - The library is under `../../target/debug` or `../../target/release` after 
building.
+
+## Test
+
+To build and run the tests.
+
+```shell
+$ dub test
+  Generating test runner configuration 'opendal-test-unittest' for 'unittest' 
(library).
+    Starting Performing "unittest" build using /usr/bin/ldc2 for x86_64.
+    Building opendal ~master: building configuration [opendal-test-unittest]
+   Pre-build Running commands
+    Finished `release` profile [optimized] target(s) in 0.08s
+Cargo build completed successfully
+     Linking opendal-test-unittest
+     Running opendal-test-unittest 
+Basic Operator creation and write test passed
+1 modules passed unittests
+```
+
+
diff --git a/bindings/d/DEPENDENCIES.md b/bindings/d/DEPENDENCIES.md
new file mode 100644
index 0000000000..9909723208
--- /dev/null
+++ b/bindings/d/DEPENDENCIES.md
@@ -0,0 +1,4 @@
+# Dependencies
+
+OpenDAL D Binding is based on the C Binding.
+There are no extra runtime dependencies except those conveyed from C Binding.
diff --git a/bindings/d/DEPENDENCIES.rust.tsv b/bindings/d/DEPENDENCIES.rust.tsv
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/bindings/d/README.md b/bindings/d/README.md
new file mode 100644
index 0000000000..bb1a9e40fd
--- /dev/null
+++ b/bindings/d/README.md
@@ -0,0 +1,24 @@
+# Apache OpenDAL™ D Binding (WIP)
+
+![](https://img.shields.io/badge/status-unreleased-red)
+
+![](https://github.com/apache/opendal/assets/5351546/87bbf6e5-f19e-449a-b368-3e283016c887)
+
+## Build
+
+To compile OpenDAL from source code, you need:
+
+- [dmd/ldc/gdc](https://dlang.org/download)
+
+```bash
+# build libopendal_c (underneath call make -C ../c)
+dub build -b release
+# build and run unit tests
+dub test
+```
+
+## License and Trademarks
+
+Licensed under the Apache License, Version 2.0: 
http://www.apache.org/licenses/LICENSE-2.0
+
+Apache OpenDAL, OpenDAL, and Apache are either registered trademarks or 
trademarks of the Apache Software Foundation.
diff --git a/bindings/d/build.d b/bindings/d/build.d
new file mode 100644
index 0000000000..9b5387a804
--- /dev/null
+++ b/bindings/d/build.d
@@ -0,0 +1,42 @@
+module build;
+import std.stdio: writeln;
+import std.path: buildPath;
+import std.process: spawnShell, wait;
+import std.exception;
+import std.file: copy, mkdir, exists;
+import std.conv: to;
+
+version (Windows)
+    enum staticlib = "opendal_c.lib";
+else
+    enum staticlib = "libopendal_c.a";
+
+void main (string[] args)
+{
+    bool isRelease = args.length > 1 && args[1] == "release";
+    string buildType = isRelease ? "release" : "debug";
+
+    // Run cargo build
+    auto cargoCmd = "cargo build --manifest-path " ~ buildPath(
+        "..", "c", "Cargo.toml") ~ (isRelease ? " --release" : "");
+
+    auto status = wait(spawnShell(cargoCmd));
+    if (status != 0)
+    {
+        throw new Exception("Cargo build failed with status: " ~ 
status.to!string);
+    }
+    else
+    {
+        writeln("Cargo build completed successfully");
+    }
+
+    // Get opendal.h
+    copy(buildPath("..", "c", "include", "opendal.h"), buildPath("source", 
"opendal", "opendal.h"));
+
+    // Get libopendal_c.a
+    auto libPath = buildPath("..", "c", "target", buildType, staticlib);
+    writeln("Copying ", libPath, " to ", buildPath("lib", staticlib));
+    if (!exists("lib"))
+        mkdir ("lib");
+    copy(libPath, buildPath("lib", staticlib));
+}
diff --git a/bindings/d/dscanner.ini b/bindings/d/dscanner.ini
new file mode 100644
index 0000000000..48472d548c
--- /dev/null
+++ b/bindings/d/dscanner.ini
@@ -0,0 +1,105 @@
+; Configure which static analysis checks are enabled
+[analysis.config.StaticAnalysisConfig]
+; Check variable, class, struct, interface, union, and function names against
+; the Phobos style guide
+style_check="disabled"
+; Check for array literals that cause unnecessary allocation
+enum_array_literal_check="enabled"
+; Check for poor exception handling practices
+exception_check="enabled"
+; Check for use of the deprecated 'delete' keyword
+delete_check="enabled"
+; Check for use of the deprecated floating point operators
+float_operator_check="enabled"
+; Check underscores to improve number constant readability
+number_style_check="enabled"
+; Checks that opEquals, opCmp, toHash, and toString are either const, immutable
+; , or inout.
+object_const_check="enabled"
+; Checks for .. expressions where the left side is larger than the right.
+backwards_range_check="enabled"
+; Checks for if statements whose 'then' block is the same as the 'else' block
+if_else_same_check="enabled"
+; Checks for some problems with constructors
+constructor_check="enabled"
+; Checks for unused function parameters
+unused_parameter_check="disabled"
+; Checks for unused variables
+unused_variable_check="enabled"
+; Checks for unused labels
+unused_label_check="enabled"
+; Checks for duplicate attributes
+duplicate_attribute="enabled"
+; Checks that opEquals and toHash are both defined or neither are defined
+opequals_tohash_check="disabled"
+; Checks for subtraction from .length properties
+length_subtraction_check="disabled"
+; Checks for methods or properties whose names conflict with built-in propertie
+; s
+builtin_property_names_check="enabled"
+; Checks for confusing code in inline asm statements
+asm_style_check="enabled"
+; Checks for confusing logical operator precedence
+logical_precedence_check="disabled"
+; Checks for undocumented public declarations
+undocumented_declaration_check="disabled"
+; Checks for poor placement of function attributes
+function_attribute_check="enabled"
+; Checks for use of the comma operator
+comma_expression_check="enabled"
+; Checks for local imports that are too broad
+local_import_check="disabled"
+; Checks for variables that could be declared immutable
+could_be_immutable_check="disabled"
+; Checks for redundant expressions in if statements
+redundant_if_check="enabled"
+; Checks for redundant parenthesis
+redundant_parens_check="disabled"
+; Checks for mismatched argument and parameter names
+mismatched_args_check="disabled"
+; Checks for labels with the same name as variables
+label_var_same_name_check="disabled"
+; Checks for lines longer than 120 characters
+long_line_check="disabled"
+; Checks for assignment to auto-ref function parameters
+auto_ref_assignment_check="disabled"
+; Checks for incorrect infinite range definitions
+incorrect_infinite_range_check="enabled"
+; Checks for asserts that are always true
+useless_assert_check="enabled"
+; Check for uses of the old-style alias syntax
+alias_syntax_check="enabled"
+; Checks for else if that should be else static if
+static_if_else_check="enabled"
+; Check for unclear lambda syntax
+lambda_return_check="enabled"
+; Check for auto function without return statement
+auto_function_check="enabled"
+; Check for sortedness of imports
+imports_sortedness="disabled"
+; Check for explicitly annotated unittests
+explicitly_annotated_unittests="disabled"
+; Check for properly documented public functions (Returns, Params)
+properly_documented_public_functions="disabled"
+; Check for useless usage of the final attribute
+final_attribute_check="enabled"
+; Check for virtual calls in the class constructors
+vcall_in_ctor="enabled"
+; Check for useless user defined initializers
+useless_initializer="disabled"
+; Check allman brace style
+allman_braces_check="disabled"
+; Check for redundant attributes
+redundant_attributes_check="disabled"
+; Check for public declarations without a documented unittest
+has_public_example="disabled"
+; Check for asserts without an explanatory message
+assert_without_msg="disabled"
+; Check indent of if constraints
+if_constraints_indent="enabled"
+; Check for @trusted applied to a bigger scope than a single function
+trust_too_much="disabled"
+; Check for redundant storage classes on variable declarations")
+redundant_storage_classes="enabled"
+; Check for unused function return values
+unused_result="disabled"
\ No newline at end of file
diff --git a/bindings/d/dub.json b/bindings/d/dub.json
new file mode 100644
index 0000000000..668fc63f16
--- /dev/null
+++ b/bindings/d/dub.json
@@ -0,0 +1,37 @@
+{
+       "name": "opendal",
+       "license": "Apache-2.0",
+       "description": "Apache OpenDAL D bindings",
+       "excludedSourceFiles": ["source/opendal/package.d"],
+       "toolchainRequirements": {
+               "frontend": ">=2.105.3"
+       },
+       "buildTypes": {
+               "debug": {
+                       "buildOptions": ["debugMode", "debugInfo"]
+               },
+               "release": {
+                       "buildOptions": ["releaseMode", "optimize", "inline"]
+               }
+       },
+       "preBuildCommands": ["\"$DC\" -run $PACKAGE_DIR/build.d -- release"],
+       "configurations": [
+               {
+                       "name": "opendal",
+                       "targetType": "library",
+                       "targetName": "opendal",
+                       "sourceFiles": ["source/opendal/package.d"],
+                       "targetPath": "lib",
+                       "lflags-posix": ["-L$PACKAGE_DIR/lib"],
+                       "lflags-windows": ["/LIBPATH:$PACKAGE_DIR\\lib"],
+                       "libs": ["opendal_c"]
+               },
+               {
+                       "name": "unittest",
+                       "sourceFiles": ["source/opendal/package.d"],
+                       "lflags-posix": ["-L$PACKAGE_DIR/lib"],
+                       "lflags-windows": ["/LIBPATH:$PACKAGE_DIR\\lib"],
+                       "libs": ["opendal_c"]
+               }
+       ]
+}
\ No newline at end of file
diff --git a/bindings/d/source/opendal/opendal_c.c 
b/bindings/d/source/opendal/opendal_c.c
new file mode 100644
index 0000000000..4f0d59e888
--- /dev/null
+++ b/bindings/d/source/opendal/opendal_c.c
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+/// OpenDAL - ImportC generate D bindings
+#pragma attribute(push, nogc, nothrow)
+#include "opendal.h"
+#pragma attribute(pop)
\ No newline at end of file
diff --git a/bindings/d/source/opendal/operator.d 
b/bindings/d/source/opendal/operator.d
new file mode 100644
index 0000000000..a3fe616fbf
--- /dev/null
+++ b/bindings/d/source/opendal/operator.d
@@ -0,0 +1,229 @@
+/*
+ * 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.
+ */
+
+module opendal.operator;
+
+import std.string: toStringz;
+import std.exception: enforce;
+import std.conv: to;
+import std.parallelism: task, TaskPool;
+
+/// OpenDAL-C binding for D. (unsafe/@system)
+private import opendal.opendal_c;
+
+struct Operator
+{
+    private opendal_operator* op;
+    private TaskPool taskPool;
+    private bool enabledParallelism;
+
+    this(string scheme, OperatorOptions options, bool useParallel = false) 
@trusted
+    {
+        auto result = opendal_operator_new(scheme.toStringz, options.options);
+        enforce(result.op !is null, "Failed to create Operator");
+        enforce(result.error is null, "Error in Operator");
+        op = result.op;
+        enabledParallelism = useParallel;
+
+        if (enabledParallelism)
+            taskPool = new TaskPool();
+    }
+
+    void write(string path, ubyte[] data) @trusted
+    {
+        opendal_bytes bytes = opendal_bytes(data.ptr, data.length, 
data.length);
+        auto error = opendal_operator_write(op, path.toStringz, &bytes);
+        enforce(error is null, "Error writing data");
+    }
+
+    void writeParallel(string path, ubyte[] data) @safe
+    {
+        auto t = task!((Operator* op, string p, ubyte[] d) { op.write(p, d); 
})(&this, path, data);
+        taskPool.put(t);
+        t.yieldForce();
+    }
+
+    ubyte[] readParallel(string path) @trusted
+    {
+        auto t = task!((Operator* op, string p) { return op.read(p); })(&this, 
path);
+        taskPool.put(t);
+        return t.yieldForce();
+    }
+
+    Entry[] listParallel(string path) @trusted
+    {
+        auto t = task!((Operator* op, string p) { return op.list(p); })(&this, 
path);
+        taskPool.put(t);
+        return t.yieldForce();
+    }
+
+    ubyte[] read(string path) @trusted
+    {
+        auto result = opendal_operator_read(op, path.toStringz);
+        enforce(result.error is null, "Error reading data");
+        scope (exit)
+            opendal_bytes_free(&result.data);
+        return result.data.data[0 .. result.data.len].dup;
+    }
+
+    void remove(string path) @trusted
+    {
+        auto error = opendal_operator_delete(op, path.toStringz);
+        enforce(error is null, "Error deleting object");
+    }
+
+    bool exists(string path) @trusted
+    {
+        auto result = opendal_operator_exists(op, path.toStringz);
+        enforce(result.error is null, "Error checking existence");
+        return result.exists;
+    }
+
+    Metadata stat(string path) @trusted
+    {
+        auto result = opendal_operator_stat(op, path.toStringz);
+        enforce(result.error is null, "Error getting metadata");
+        return Metadata(result.meta);
+    }
+
+    Entry[] list(string path) @trusted
+    {
+        auto result = opendal_operator_list(op, path.toStringz);
+        enforce(result.error is null, "Error listing objects");
+
+        Entry[] entries;
+        while (true)
+        {
+            auto next = opendal_lister_next(result.lister);
+            if (next.entry is null)
+                break;
+            entries ~= Entry(next.entry);
+        }
+        return entries;
+    }
+
+    void createDir(string path) @trusted
+    {
+        auto error = opendal_operator_create_dir(op, path.toStringz);
+        enforce(error is null, "Error creating directory");
+    }
+
+    void rename(string src, string dest) @trusted
+    {
+        auto error = opendal_operator_rename(op, src.toStringz, 
dest.toStringz);
+        enforce(error is null, "Error renaming object");
+    }
+
+    void copy(string src, string dest) @trusted
+    {
+        auto error = opendal_operator_copy(op, src.toStringz, dest.toStringz);
+        enforce(error is null, "Error copying object");
+    }
+
+    ~this() @trusted
+    {
+        if (op !is null)
+            opendal_operator_free(op);
+        if (enabledParallelism)
+            taskPool.stop();
+    }
+}
+
+class OperatorOptions
+{
+    private opendal_operator_options* options;
+
+    this() @trusted
+    {
+        options = opendal_operator_options_new();
+    }
+
+    void set(string key, string value) @trusted
+    {
+        opendal_operator_options_set(options, key.toStringz, value.toStringz);
+    }
+
+    ~this() @trusted
+    {
+        if (options !is null)
+            opendal_operator_options_free(options);
+    }
+}
+
+struct Metadata
+{
+    private opendal_metadata* meta;
+
+    this(scope opendal_metadata* meta) @trusted pure
+    {
+        this.meta = meta;
+    }
+
+    ulong contentLength() @trusted
+    {
+        return opendal_metadata_content_length(meta);
+    }
+
+    bool isFile() @trusted
+    {
+        return opendal_metadata_is_file(meta);
+    }
+
+    bool isDir() @trusted
+    {
+        return opendal_metadata_is_dir(meta);
+    }
+
+    long lastModified() @trusted
+    {
+        return opendal_metadata_last_modified_ms(meta);
+    }
+
+    ~this() @trusted
+    {
+        if (meta !is null)
+            opendal_metadata_free(meta);
+    }
+}
+
+struct Entry
+{
+    private opendal_entry* entry;
+
+    this(opendal_entry* entry) pure @nogc
+    {
+        this.entry = entry;
+    }
+
+    string path()
+    {
+        return to!string(opendal_entry_path(entry));
+    }
+
+    string name()
+    {
+        return to!string(opendal_entry_name(entry));
+    }
+
+    ~this()
+    {
+        if (entry !is null)
+            opendal_entry_free(entry);
+    }
+}
diff --git a/bindings/d/source/opendal/package.d 
b/bindings/d/source/opendal/package.d
new file mode 100644
index 0000000000..523b1ed8c6
--- /dev/null
+++ b/bindings/d/source/opendal/package.d
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ */
+
+module opendal;
+
+public import opendal.operator;
+
+version (unittest)
+{
+       @("Test basic Operator creation")
+       @safe unittest
+       {
+               /* Initialize a operator for "memory" backend, with no options 
*/
+               OperatorOptions options = new OperatorOptions();
+               Operator op = Operator("memory", options);
+
+               /* Prepare some data to be written */
+               string data = "this_string_length_is_24";
+
+               /* Write this into path "/testpath" */
+               op.write("/testpath", cast(ubyte[])data.dup);
+
+               /* We can read it out, make sure the data is the same */
+               auto read_bytes = op.read("/testpath");
+               assert(read_bytes.length == 24);
+               assert(cast(string)read_bytes.idup == data);
+       }
+
+       @("Benchmark parallel and normal functions")
+       @safe unittest
+       {
+               import std.exception: assertNotThrown;
+               import std.file: tempDir;
+               import std.path: buildPath;
+               import std.datetime.stopwatch: StopWatch;
+               import std.stdio: writeln;
+
+               auto options = new OperatorOptions();
+               options.set("root", tempDir);
+               auto op = Operator("fs", options, true);
+
+               auto testPath = buildPath(tempDir, "benchmark_test.txt");
+               auto testData = cast(ubyte[])"Benchmarking OpenDAL async and 
normal functions".dup;
+
+               // Benchmark write operations
+               StopWatch sw;
+
+               sw.start();
+               assertNotThrown(op.write(testPath, testData));
+               sw.stop();
+               auto normalWriteTime = sw.peek();
+
+               sw.reset();
+               sw.start();
+               assertNotThrown(op.writeParallel(testPath, testData));
+               sw.stop();
+               auto parallelWriteTime = sw.peek();
+
+               // Benchmark read operations
+               sw.reset();
+               sw.start();
+               auto normalReadData = op.read(testPath);
+               sw.stop();
+               auto normalReadTime = sw.peek();
+
+               sw.reset();
+               sw.start();
+               auto parallelReadData = op.readParallel(testPath);
+               sw.stop();
+               auto parallelReadTime = sw.peek();
+
+               // Benchmark list operations
+               sw.reset();
+               sw.start();
+               op.list(tempDir);
+               sw.stop();
+               auto normalListTime = sw.peek();
+
+               sw.reset();
+               sw.start();
+               op.listParallel(tempDir);
+               sw.stop();
+               auto parallelListTime = sw.peek();
+
+               // Print benchmark results
+               writeln("Write benchmark:");
+               writeln("  Normal:   ", normalWriteTime);
+               writeln("  Parallel: ", parallelWriteTime);
+
+               writeln("Read benchmark:");
+               writeln("  Normal:   ", normalReadTime);
+               writeln("  Parallel: ", parallelReadTime);
+
+               writeln("List benchmark:");
+               writeln("  Normal:   ", normalListTime);
+               writeln("  Parallel: ", parallelListTime);
+
+               // Verify data integrity
+               assert(normalReadData == testData);
+               assert(parallelReadData == testData);
+
+               // Clean up
+               op.remove(testPath);
+               assert(!op.exists(testPath));
+       }
+
+}
diff --git a/bindings/d/test/dub.json b/bindings/d/test/dub.json
new file mode 100644
index 0000000000..d21ccb1079
--- /dev/null
+++ b/bindings/d/test/dub.json
@@ -0,0 +1,14 @@
+{
+    "name": "tests",
+    "targetType": "executable",
+    "dflags": ["-preview=all"],
+    "dependencies": {"opendal":{"path": ".."}},
+    "buildTypes": {
+        "debug": {
+            "buildOptions": ["debugMode", "debugInfo"]
+        },
+        "release": {
+            "buildOptions": ["releaseMode", "optimize", "inline"]
+        }
+    }
+}
\ No newline at end of file
diff --git a/bindings/d/test/source/bdd.d b/bindings/d/test/source/bdd.d
new file mode 100644
index 0000000000..2658f40307
--- /dev/null
+++ b/bindings/d/test/source/bdd.d
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+module bdd;
+
+import opendal;
+import std.stdio: writeln;
+
+class OperatorContext
+{
+    Operator op;
+
+    this() @trusted
+    {
+        auto options = new OperatorOptions();
+        op = Operator("memory", options);
+    }
+}
+
+class WriteScenario
+{
+    OperatorContext context;
+    string data;
+    string path;
+
+    this(OperatorContext context) @trusted
+    {
+        this.context = context;
+    }
+
+    WriteScenario givenSomeData(string data)
+    {
+        this.data = data;
+        return this;
+    }
+
+    WriteScenario whenWritingToPath(string path)
+    {
+        this.path = path;
+        context.op.write(path, cast(ubyte[])data.dup);
+        return this;
+    }
+
+    void thenDataShouldBeReadable()
+    {
+        auto read_bytes = context.op.read(path);
+        assert(read_bytes.length == data.length, "Read data length does not 
match written data length");
+        assert(cast(string)read_bytes.idup == data, "Read data does not match 
written data");
+    }
+}
+
+void main() @safe
+{
+    auto context = new OperatorContext();
+
+    describe("Operator memory backend", {
+        it("should write and read data correctly", {
+            new WriteScenario(context)
+            .givenSomeData("this_string_length_is_24")
+            .whenWritingToPath("/testpath")
+            .thenDataShouldBeReadable();
+        });
+
+        it("should print the read data", {
+            auto read_bytes = context.op.read("/testpath");
+            writeln(cast(string)read_bytes.idup);
+        });
+    });
+}
+
+void describe(string description, void delegate() tests) @trusted
+{
+    writeln("Describe: ", description);
+    tests();
+}
+
+void it(string description, void delegate() test)
+{
+    writeln("  It ", description);
+    test();
+    writeln("    - Passed");
+}

Reply via email to