DiTo97 / alphacodings

base26 and base52 encodings
MIT License
1 stars 0 forks source link

support dashu library in rust #21

Open DiTo97 opened 3 weeks ago

DiTo97 commented 3 weeks ago

To run the code in Python after building the package with maturin, you can follow these steps:

  1. Build the package:

    maturin develop
  2. Import and use the module in Python:

    import rust_module
    
    # Example usage
    encoded = rust_module.base26_encode("Hello, World!")
    print(f"Encoded: {encoded}")
    
    decoded = rust_module.base26_decode(encoded)
    print(f"Decoded: {decoded}")

Regarding separating source code from bindings, it's indeed possible and often a good practice to keep the core logic separate from the bindings. This way, the core logic can be reused in different contexts. Here's how you can structure your Rust project:

  1. Create a library for the core logic:

    • In src/lib.rs:

      use dashu::integer::IBig;
      use std::collections::VecDeque;
      
      pub fn base256_int_to_string(number: &IBig) -> Result<String, String> {
       let bytes = number.to_bytes_be();
       String::from_utf8(bytes).map_err(|e| e.to_string())
      }
      
      pub fn string_to_base256_int(string: &str) -> IBig {
       let bytes = string.as_bytes();
       IBig::from_bytes_be(bytes)
      }
      
      const ENCODING: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
      
      pub fn base26_encode(string: &str) -> Result<String, String> {
       let number = string_to_base256_int(string);
       let mut number = number;
       let mut coding = VecDeque::new();
      
       while number > IBig::from(0) {
           let (quotient, remainder) = number.div_rem(IBig::from(26));
           coding.push_front(ENCODING.chars().nth(remainder.to_usize().unwrap()).unwrap());
           number = quotient;
       }
      
       Ok(coding.iter().collect())
      }
      
      pub fn base26_decode(string: &str) -> Result<String, String> {
       let mut number = IBig::from(0);
      
       for character in string.chars() {
           let value = ENCODING.find(character).ok_or_else(|| "Invalid character".to_string())?;
           number = number * IBig::from(26) + IBig::from(value);
       }
      
       base256_int_to_string(&number)
      }
  2. Create a separate file for the Python bindings:

    • In src/python_bindings.rs:

      use pyo3::prelude::*;
      use pyo3::wrap_pyfunction;
      use crate::{base256_int_to_string, string_to_base256_int, base26_encode, base26_decode};
      use dashu::integer::IBig;
      
      #[pyfunction]
      fn py_base256_int_to_string(number: &str) -> PyResult<String> {
       let number = IBig::from_str_radix(number, 10).map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?;
       base256_int_to_string(&number).map_err(|e| pyo3::exceptions::PyValueError::new_err(e))
      }
      
      #[pyfunction]
      fn py_string_to_base256_int(string: &str) -> PyResult<String> {
       let number = string_to_base256_int(string);
       Ok(number.to_string())
      }
      
      #[pyfunction]
      fn py_base26_encode(string: &str) -> PyResult<String> {
       base26_encode(string).map_err(|e| pyo3::exceptions::PyValueError::new_err(e))
      }
      
      #[pyfunction]
      fn py_base26_decode(string: &str) -> PyResult<String> {
       base26_decode(string).map_err(|e| pyo3::exceptions::PyValueError::new_err(e))
      }
      
      #[pymodule]
      fn rust_module(py: Python, m: &PyModule) -> PyResult<()> {
       m.add_function(wrap_pyfunction!(py_base256_int_to_string, m)?)?;
       m.add_function(wrap_pyfunction!(py_string_to_base256_int, m)?)?;
       m.add_function(wrap_pyfunction!(py_base26_encode, m)?)?;
       m.add_function(wrap_pyfunction!(py_base26_decode, m)?)?;
       Ok(())
      }
  3. Modify Cargo.toml to include the new module:

    [lib]
    name = "your_crate_name"
    crate-type = ["cdylib", "rlib"]
    
    [dependencies]
    dashu = "0.1"
    pyo3 = { version = "0.18", features = ["extension-module"] }
  4. Build the package with maturin:

    maturin develop

Now, you can use the Python module as shown earlier. This approach keeps your core logic in a separate library, making it reusable in other contexts, such as a standalone Rust application or another language's bindings.

DiTo97 commented 3 weeks ago

Here's the final repository structure along with the contents of each configuration file:

Final Repository Structure

my_dashu_lib/
├── .github/
│   └── workflows/
│       └── ci.yml
├── python_package/
│   ├── my_dashu_lib/
│   │   └── __init__.py
│   └── setup.py
├── src/
│   ├── bindings.rs
│   ├── calculations.rs
│   └── lib.rs
├── Cargo.toml
└── README.md

Configuration Files

Cargo.toml

[package]
name = "my_dashu_lib"
version = "0.1.0"
edition = "2021"

[dependencies]
dashu = "0.4"  # Adjust the version as needed
pyo3 = { version = "0.16", features = ["extension-module"] }

src/lib.rs

pub mod calculations;
pub mod bindings;

src/calculations.rs

use dashu::integer::IBig;

pub fn add_big_integers(a: &str, b: &str) -> IBig {
    let a: IBig = a.parse().unwrap();
    let b: IBig = b.parse().unwrap();
    a + b
}

src/bindings.rs

use pyo3::prelude::*;
use crate::calculations::add_big_integers;

#[pyfunction]
fn add(a: &str, b: &str) -> String {
    add_big_integers(a, b).to_string()
}

#[pymodule]
fn my_dashu_lib(py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(add, m)?)?;
    Ok(())
}

python_package/setup.py

from setuptools import setup
from setuptools_rust import RustExtension

setup(
    name="my_dashu_lib",
    version="0.1.0",
    rust_extensions=[RustExtension("my_dashu_lib.my_dashu_lib", "Cargo.toml", binding="pyo3")],
    packages=["my_dashu_lib"],
    zip_safe=False,
)

python_package/my_dashu_lib/__init__.py

# This file can be left empty or used to initialize the package

.github/workflows/ci.yml

name: CI

on: [push, pull_request]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: '3.9'
    - name: Install Rust
      uses: actions-rs/toolchain@v1
      with:
        toolchain: stable
        override: true
        components: rustfmt
    - name: Install maturin
      run: pip install maturin
    - name: Build and test
      run: |
        maturin develop
        pytest

This structure and configuration should give you a working Rust library with Python bindings, a Python package setup, and a CI pipeline using GitHub Actions. Let me know if you need any more details or adjustments!