Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ members = [
resolver = "2"

[workspace.dependencies]
pyo3 = { version = "0.27.0", features = ["extension-module"] }
pyo3 = { version = "0.27.1", features = ["extension-module"] }
quick-xml = "0.38.3"

# https://ohadravid.github.io/posts/2023-03-rusty-python
[profile.release]
debug = true # Debug symbols for profiler.
lto = true # Link-time optimization.
codegen-units = 1 # Slower compilation but faster code.
debug = true # Debug symbols for profiler.
lto = true # Link-time optimization.
codegen-units = 1 # Slower compilation but faster code.
1 change: 1 addition & 0 deletions crates/djc-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[package]
name = "djc-core"
description = "Singular Python API for Rust code used by django-components"
version = "1.1.0"
edition = "2021"

Expand Down
71 changes: 69 additions & 2 deletions crates/djc-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,76 @@
use djc_html_transformer::set_html_attributes;
use djc_html_transformer::{
set_html_attributes as set_html_attributes_rust, HtmlTransformerConfig,
};
use pyo3::exceptions::{PyValueError};
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyTuple};

/// A Python module implemented in Rust for high-performance transformations.
/// Singular Python API that brings togther all the other Rust crates.
#[pymodule]
fn djc_core(m: &Bound<'_, PyModule>) -> PyResult<()> {
// HTML transformer
m.add_function(wrap_pyfunction!(set_html_attributes, m)?)?;
Ok(())
}

/// Transform HTML by adding attributes to the elements.
///
/// Args:
/// html (str): The HTML string to transform. Can be a fragment or full document.
/// root_attributes (List[str]): List of attribute names to add to root elements only.
/// all_attributes (List[str]): List of attribute names to add to all elements.
/// check_end_names (bool, optional): Whether to validate matching of end tags. Defaults to false.
/// watch_on_attribute (str, optional): If set, captures which attributes were added to elements with this attribute.
///
/// Returns:
/// Tuple[str, Dict[str, List[str]]]: A tuple containing:
/// - The transformed HTML string
/// - A dictionary mapping captured attribute values to lists of attributes that were added
/// to those elements. Only returned if watch_on_attribute is set, otherwise empty dict.
///
/// Example:
/// >>> html = '<div data-id="123"><p>Hello</p></div>'
/// >>> html, captured = set_html_attributes(html, ['data-root-id'], ['data-v-123'], watch_on_attribute='data-id')
/// >>> print(captured)
/// {'123': ['data-root-id', 'data-v-123']}
///
/// Raises:
/// ValueError: If the HTML is malformed or cannot be parsed.
#[pyfunction]
#[pyo3(signature = (html, root_attributes, all_attributes, check_end_names=None, watch_on_attribute=None))]
#[pyo3(
text_signature = "(html, root_attributes, all_attributes, *, check_end_names=False, watch_on_attribute=None)"
)]
pub fn set_html_attributes(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here is the Python definition of set_html_attributes() function.

To actually expose it to Python, this Python function is added to the module's scope on line 12:

    m.add_function(wrap_pyfunction!(set_html_attributes, m)?)?;

py: Python,
html: &str,
root_attributes: Vec<String>,
all_attributes: Vec<String>,
check_end_names: Option<bool>,
watch_on_attribute: Option<String>,
) -> PyResult<Py<PyAny>> {
let config = HtmlTransformerConfig::new(
root_attributes,
all_attributes,
check_end_names.unwrap_or(false),
watch_on_attribute,
);

match set_html_attributes_rust(html, &config) {
Ok((html, captured)) => {
// Convert captured attributes to a Python dictionary
let captured_dict = PyDict::new(py);
for (id, attrs) in captured {
captured_dict.set_item(id, attrs)?;
}

// Convert items to Bound<PyAny> for the tuple
use pyo3::types::PyString;
let html_obj = PyString::new(py, &html).as_any().clone();
let dict_obj = captured_dict.as_any().clone();
let result = PyTuple::new(py, vec![html_obj, dict_obj])?;
Ok(result.into_any().unbind())
}
Err(e) => Err(PyValueError::new_err(e.to_string())),
}
}
2 changes: 1 addition & 1 deletion crates/djc-html-transformer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[package]
name = "djc-html-transformer"
description = "Apply attributes to HTML in a single pass"
version = "1.0.3"
edition = "2021"

[dependencies]
pyo3 = { workspace = true }
quick-xml = { workspace = true }
Loading
Loading