feat(python): better struct access

Co-authored-by: alemi <me@alemi.dev>
This commit is contained in:
zaaarf 2024-10-16 02:46:20 +02:00
parent e1e09cb20e
commit 3326217058
No known key found for this signature in database
GPG key ID: 102E445F4C3F829B
7 changed files with 144 additions and 29 deletions

7
Cargo.lock generated
View file

@ -703,6 +703,12 @@ version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
[[package]]
name = "inventory"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767"
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.13.0" version = "0.13.0"
@ -1269,6 +1275,7 @@ checksum = "3d922163ba1f79c04bc49073ba7b32fd5a8d3b76a87c955921234b8e77333c51"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"indoc", "indoc",
"inventory",
"libc", "libc",
"memoffset", "memoffset",
"once_cell", "once_cell",

View file

@ -51,7 +51,7 @@ napi = { version = "2.16", features = ["full"], optional = true }
napi-derive = { version="2.16", optional = true} napi-derive = { version="2.16", optional = true}
# glue (python) # glue (python)
pyo3 = { version = "0.22", features = ["extension-module"], optional = true} pyo3 = { version = "0.22", features = ["extension-module", "multiple-pymethods"], optional = true}
# extra # extra
async-trait = { version = "0.1", optional = true } async-trait = { version = "0.1", optional = true }

View file

@ -13,7 +13,6 @@ import java.util.OptionalLong;
@ToString @ToString
@EqualsAndHashCode @EqualsAndHashCode
@RequiredArgsConstructor @RequiredArgsConstructor
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class TextChange { public class TextChange {
/** /**
* The starting position of the change. * The starting position of the change.

View file

@ -24,7 +24,7 @@ class Config:
port: Optional[int] port: Optional[int]
tls: Optional[bool] tls: Optional[bool]
def __new__(cls, *, username: str, password: str, **kwargs) -> Config: ... def __new__(cls, username: str, password: str, **kwargs) -> Config: ...
def init() -> Driver: ... def init() -> Driver: ...
def set_logger(logger_cb: Callable[[str], None], debug: bool) -> bool: ... def set_logger(logger_cb: Callable[[str], None], debug: bool) -> bool: ...

View file

@ -6,11 +6,13 @@ use uuid::Uuid;
/// Represents a service user /// Represents a service user
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pyclass)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub struct User { pub struct User {
/// User unique identifier, should never change. /// User unique identifier, should never change.
pub id: Uuid, pub id: Uuid,
/// User name, can change but should be unique. /// User name, can change but should be unique.
#[pyo3(get, set)]
pub name: String, pub name: String,
} }

View file

@ -80,10 +80,9 @@
//! import codemp //! import codemp
//! //!
//! # connect first, api.code.mp is managed by hexed.technology //! # connect first, api.code.mp is managed by hexed.technology
//! config = codemp.get_default_config() //! client = codemp.connect(
//! config.username = "mail@example.net" //! codemp.Config('mail@example.net', 'dont-use-this-password')
//! config.password = "dont-use-this-password" //! ).wait()
//! client = codemp.connect(config).wait()
//! //!
//! # create and join a workspace //! # create and join a workspace
//! client.create_workspace("some-workspace").wait() //! client.create_workspace("some-workspace").wait()
@ -94,14 +93,16 @@
//! buffer = workspace.attach_buffer("/my/file.txt").wait() //! buffer = workspace.attach_buffer("/my/file.txt").wait()
//! //!
//! # write `hello!` at the beginning of this buffer //! # write `hello!` at the beginning of this buffer
//! buffer.send( //! buffer.send(codemp.TextChange(
//! 0, 0, "hello!" //! start=0, end=0,
//! ).wait() //! content="hello!"
//! )).wait()
//! //!
//! # wait for cursor movements //! # wait for cursor movements
//! while true: //! while true:
//! event = workspace.cursor().recv().wait() //! event = workspace.cursor().recv().wait()
//! print(f"user {event.user} moved on buffer {event.buffer}") //! print(f"user {event.user} moved on buffer {event.buffer}")
//!
//! ``` //! ```
//! //!
//! ## Lua //! ## Lua

View file

@ -3,14 +3,14 @@ pub mod controllers;
pub mod workspace; pub mod workspace;
use crate::{ use crate::{
api::{Config, Cursor, TextChange}, api::{BufferUpdate, Config, Cursor, Selection, TextChange, User},
buffer::Controller as BufferController, buffer::Controller as BufferController,
cursor::Controller as CursorController, cursor::Controller as CursorController,
Client, Workspace, Client, Workspace,
}; };
use pyo3::prelude::*;
use pyo3::{ use pyo3::{
prelude::*,
exceptions::{PyConnectionError, PyRuntimeError, PySystemError}, exceptions::{PyConnectionError, PyRuntimeError, PySystemError},
types::PyDict, types::PyDict,
}; };
@ -153,19 +153,28 @@ fn init() -> PyResult<Driver> {
Ok(Driver(Some(rt_stop_tx))) Ok(Driver(Some(rt_stop_tx)))
} }
#[pyfunction] #[pymethods]
fn get_default_config() -> crate::api::Config { impl User {
let mut conf = crate::api::Config::new("".to_string(), "".to_string()); #[getter]
conf.host = Some(conf.host().to_string()); fn get_id(&self) -> pyo3::PyResult<String> {
conf.port = Some(conf.port()); Ok(self.id.to_string())
conf.tls = Some(false); }
conf
#[setter]
fn set_id(&mut self, value: String) -> pyo3::PyResult<()> {
self.id = value.parse().map_err(|x: <uuid::Uuid as std::str::FromStr>::Err| pyo3::exceptions::PyRuntimeError::new_err(x.to_string()))?;
Ok(())
}
fn __str__(&self) -> String {
format!("{self:?}")
}
} }
#[pymethods] #[pymethods]
impl Config { impl Config {
#[new] #[new]
#[pyo3(signature = (*, username, password, **kwds))] #[pyo3(signature = (username, password, **kwds))]
pub fn pynew( pub fn pynew(
username: String, username: String,
password: String, password: String,
@ -176,7 +185,7 @@ impl Config {
let port = kwgs.get_item("port")?.and_then(|e| e.extract().ok()); let port = kwgs.get_item("port")?.and_then(|e| e.extract().ok());
let tls = kwgs.get_item("tls")?.and_then(|e| e.extract().ok()); let tls = kwgs.get_item("tls")?.and_then(|e| e.extract().ok());
Ok(Config { Ok(Self {
username, username,
password, password,
host, host,
@ -184,9 +193,109 @@ impl Config {
tls, tls,
}) })
} else { } else {
Ok(Config::new(username, password)) Ok(Self::new(username, password))
} }
} }
fn __str__(&self) -> String {
format!("{self:?}")
}
}
#[pymethods]
impl Cursor {
fn __str__(&self) -> String {
format!("{self:?}")
}
}
#[pymethods]
impl Selection {
#[new]
#[pyo3(signature = (**kwds))]
pub fn py_new(kwds: Option<&Bound<'_, PyDict>>) -> PyResult<Self> {
if let Some(kwds) = kwds {
let start_row = if let Some(e) = kwds.get_item("start_row")? {
e.extract()?
} else {
0
};
let start_col = if let Some(e) = kwds.get_item("start_col")? {
e.extract()?
} else {
0
};
let end_row = if let Some(e) = kwds.get_item("end_row")? {
e.extract()?
} else {
0
};
let end_col = if let Some(e) = kwds.get_item("end_col")? {
e.extract()?
} else {
0
};
let buffer = if let Some(e) = kwds.get_item("buffer")? {
e.extract()?
} else {
String::default()
};
Ok(Self { start_row, start_col, end_row, end_col, buffer })
} else {
Ok(Self::default())
}
}
fn __str__(&self) -> String {
format!("{self:?}")
}
}
#[pymethods]
impl BufferUpdate {
fn __str__(&self) -> String {
format!("{self:?}")
}
}
#[pymethods]
impl TextChange {
#[new]
#[pyo3(signature = (**kwds))]
pub fn py_new(kwds: Option<&Bound<'_, PyDict>>) -> PyResult<Self> {
if let Some(kwds) = kwds {
let start = if let Some(e) = kwds.get_item("start")? {
e.extract()?
} else {
0
};
let end = if let Some(e) = kwds.get_item("end")? {
e.extract()?
} else {
0
};
let content = if let Some(e) = kwds.get_item("content")? {
e.extract()?
} else {
String::default()
};
Ok(Self { start, end, content })
} else {
Ok(Self::default())
}
}
fn __str__(&self) -> String {
format!("{self:?}")
}
} }
#[pyfunction] #[pyfunction]
@ -254,27 +363,24 @@ impl From<crate::errors::ControllerError> for PyErr {
} }
} }
impl IntoPy<PyObject> for crate::api::User {
fn into_py(self, py: Python<'_>) -> PyObject {
self.id.to_string().into_py(py)
}
}
#[pymodule] #[pymodule]
fn codemp(m: &Bound<'_, PyModule>) -> PyResult<()> { fn codemp(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(version, m)?)?; m.add_function(wrap_pyfunction!(version, m)?)?;
m.add_function(wrap_pyfunction!(init, m)?)?; m.add_function(wrap_pyfunction!(init, m)?)?;
m.add_function(wrap_pyfunction!(get_default_config, m)?)?;
m.add_function(wrap_pyfunction!(connect, m)?)?; m.add_function(wrap_pyfunction!(connect, m)?)?;
m.add_function(wrap_pyfunction!(set_logger, m)?)?; m.add_function(wrap_pyfunction!(set_logger, m)?)?;
m.add_class::<Driver>()?; m.add_class::<Driver>()?;
m.add_class::<BufferUpdate>()?;
m.add_class::<TextChange>()?; m.add_class::<TextChange>()?;
m.add_class::<BufferController>()?; m.add_class::<BufferController>()?;
m.add_class::<Cursor>()?; m.add_class::<Cursor>()?;
m.add_class::<Selection>()?;
m.add_class::<CursorController>()?; m.add_class::<CursorController>()?;
m.add_class::<User>()?;
m.add_class::<Workspace>()?; m.add_class::<Workspace>()?;
m.add_class::<Client>()?; m.add_class::<Client>()?;
m.add_class::<Config>()?; m.add_class::<Config>()?;