Skip to content

Commit

Permalink
Update Tag API (#19)
Browse files Browse the repository at this point in the history
The `new` method allows users to construct tags pretty simply from
strings. And this isn't much trouble, but preserves the strong-typing
idea from Rust, requiring e.g., `t.add_tag(Tag("foo"))`.
  • Loading branch information
djmitche authored Jan 1, 2025
1 parent a044e26 commit d7ff2c3
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 12 deletions.
33 changes: 27 additions & 6 deletions src/task/tag.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
use pyo3::prelude::*;
use pyo3::{exceptions::PyValueError, prelude::*};
use taskchampion::Tag as TCTag;

/// TODO: following the api there currently is no way to construct the task by hand, not sure if this is
/// correct
#[pyclass]
pub struct Tag(pub(crate) TCTag);
pub struct Tag(TCTag);

#[pymethods]
impl Tag {
#[new]
pub fn new(tag: String) -> anyhow::Result<Self> {
Ok(Tag(tag.parse()?))
pub fn new(tag: String) -> PyResult<Self> {
Ok(Tag(tag
.parse()
.map_err(|_| PyValueError::new_err("Invalid tag"))?))
}

pub fn __repr__(&self) -> String {
format!("{:?}", self.0)
}

pub fn __str__(&self) -> String {
self.0.to_string()
}

pub fn is_synthetic(&self) -> bool {
self.0.is_synthetic()
}
Expand All @@ -21,6 +30,18 @@ impl Tag {
}
}

impl AsRef<TCTag> for Tag {
fn as_ref(&self) -> &TCTag {
&self.0
}
}

impl From<TCTag> for Tag {
fn from(value: TCTag) -> Self {
Tag(value)
}
}

impl From<Tag> for TCTag {
fn from(value: Tag) -> Self {
value.0
Expand Down
10 changes: 4 additions & 6 deletions src/task/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,18 +93,16 @@ impl Task {
///
/// Returns:
/// bool: if the task has a given tag
// TODO: Not very user friendly; User has to construct a Tag object and then pass is into here.
// Should probably use a string
pub fn has_tag(&self, tag: &Tag) -> bool {
self.0.has_tag(&tag.0)
self.0.has_tag(tag.as_ref())
}

/// Get task tags
///
/// Returns:
/// list[str]: list of tags
pub fn get_tags(&self) -> Vec<Tag> {
self.0.get_tags().map(Tag).collect()
self.0.get_tags().map(Tag::from).collect()
}
/// Get task annotations
///
Expand Down Expand Up @@ -280,15 +278,15 @@ impl Task {
pub fn add_tag(&mut self, tag: &Tag) -> anyhow::Result<Vec<Operation>> {
let mut ops: Vec<TCOperation> = Vec::new();

self.0.add_tag(&tag.0, &mut ops).expect("");
self.0.add_tag(tag.as_ref(), &mut ops).expect("");

Ok(ops.iter().map(|op| Operation(op.clone())).collect())
}

pub fn remove_tag(&mut self, tag: &Tag) -> anyhow::Result<Vec<Operation>> {
let mut ops: Vec<TCOperation> = Vec::new();

self.0.remove_tag(&tag.0, &mut ops).expect("");
self.0.remove_tag(tag.as_ref(), &mut ops).expect("");

Ok(ops.iter().map(|op| Operation(op.clone())).collect())
}
Expand Down
17 changes: 17 additions & 0 deletions tests/test_tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,23 @@ def synthetic_tag():
return Tag("UNBLOCKED")


def test_invalid():
with pytest.raises(ValueError):
Tag("-24098")
with pytest.raises(ValueError):
Tag("FOO")


def test_repr(user_tag, synthetic_tag):
assert repr(user_tag) == 'Tag(User("user_tag"))'
assert repr(synthetic_tag) == "Tag(Synthetic(Unblocked))"


def test_str(user_tag, synthetic_tag):
assert str(user_tag) == "user_tag"
assert str(synthetic_tag) == "UNBLOCKED"


def test_user_tag(user_tag: Tag):
assert user_tag.is_user()
assert not user_tag.is_synthetic()
Expand Down

0 comments on commit d7ff2c3

Please sign in to comment.