mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
Adjust Tag syntax to match TaskWarrior
This commit is contained in:
parent
2bacc05ced
commit
b95c146a7e
4 changed files with 55 additions and 22 deletions
|
@ -10,7 +10,8 @@ use nom::{
|
||||||
sequence::*,
|
sequence::*,
|
||||||
Err, IResult,
|
Err, IResult,
|
||||||
};
|
};
|
||||||
use taskchampion::{Status, Uuid};
|
use std::convert::TryFrom;
|
||||||
|
use taskchampion::{Status, Tag, Uuid};
|
||||||
|
|
||||||
/// A task identifier, as given in a filter command-line expression
|
/// A task identifier, as given in a filter command-line expression
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
@ -130,7 +131,10 @@ pub(super) fn plus_tag(input: &str) -> IResult<&str, &str> {
|
||||||
Ok(input.1)
|
Ok(input.1)
|
||||||
}
|
}
|
||||||
map_res(
|
map_res(
|
||||||
all_consuming(tuple((char('+'), recognize(pair(alpha1, alphanumeric0))))),
|
all_consuming(tuple((
|
||||||
|
char('+'),
|
||||||
|
recognize(verify(rest, |s: &str| Tag::try_from(s).is_ok())),
|
||||||
|
))),
|
||||||
to_tag,
|
to_tag,
|
||||||
)(input)
|
)(input)
|
||||||
}
|
}
|
||||||
|
@ -141,7 +145,10 @@ pub(super) fn minus_tag(input: &str) -> IResult<&str, &str> {
|
||||||
Ok(input.1)
|
Ok(input.1)
|
||||||
}
|
}
|
||||||
map_res(
|
map_res(
|
||||||
all_consuming(tuple((char('-'), recognize(pair(alpha1, alphanumeric0))))),
|
all_consuming(tuple((
|
||||||
|
char('-'),
|
||||||
|
recognize(verify(rest, |s: &str| Tag::try_from(s).is_ok())),
|
||||||
|
))),
|
||||||
to_tag,
|
to_tag,
|
||||||
)(input)
|
)(input)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
* [Using the Task Command](./using-task-command.md)
|
* [Using the Task Command](./using-task-command.md)
|
||||||
* [Configuration](./config-file.md)
|
* [Configuration](./config-file.md)
|
||||||
* [Reports](./reports.md)
|
* [Reports](./reports.md)
|
||||||
|
* [Tags](./tags.md)
|
||||||
* [Synchronization](./task-sync.md)
|
* [Synchronization](./task-sync.md)
|
||||||
* [Running the Sync Server](./running-sync-server.md)
|
* [Running the Sync Server](./running-sync-server.md)
|
||||||
* [Debugging](./debugging.md)
|
* [Debugging](./debugging.md)
|
||||||
|
|
12
docs/src/tags.md
Normal file
12
docs/src/tags.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# Tags
|
||||||
|
|
||||||
|
Each task has a collection of associated tags.
|
||||||
|
Tags are short words that categorize tasks, typically written with a leading `+`, such as `+next` or `+jobsearch`.
|
||||||
|
|
||||||
|
Tags are useful for filtering tasks in reports or on the command line.
|
||||||
|
For example, when it's time to continue the job search, `task +jobsearch` will show pending tasks with the `jobsearch` tag.
|
||||||
|
|
||||||
|
## Allowed Tags
|
||||||
|
|
||||||
|
Specifically, tags must be at least one character long and cannot contain whitespace or any of the characters `+-*/(<>^! %=~`.
|
||||||
|
The first character cannot be a digit, and `:` is not allowed after the first character.
|
|
@ -84,22 +84,35 @@ impl Status {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Tag is a newtype around a String that limits its values to valid tags.
|
/// A Tag is a newtype around a String that limits its values to valid tags.
|
||||||
|
///
|
||||||
|
/// Valid tags must not contain whitespace or any of the characters in [`INVALID_TAG_CHARACTERS`].
|
||||||
|
/// The first characters additionally cannot be a digit, and subsequent characters cannot be `:`.
|
||||||
|
/// This definition is based on [that of
|
||||||
|
/// TaskWarrior](https://github.com/GothenburgBitFactory/taskwarrior/blob/663c6575ceca5bd0135ae884879339dac89d3142/src/Lexer.cpp#L146-L164).
|
||||||
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
|
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
|
||||||
pub struct Tag(String);
|
pub struct Tag(String);
|
||||||
|
|
||||||
|
pub const INVALID_TAG_CHARACTERS: &str = "+-*/(<>^! %=~";
|
||||||
|
|
||||||
impl Tag {
|
impl Tag {
|
||||||
fn from_str(value: &str) -> Result<Tag, failure::Error> {
|
fn from_str(value: &str) -> Result<Tag, failure::Error> {
|
||||||
|
fn err(value: &str) -> Result<Tag, failure::Error> {
|
||||||
|
Err(format_err!("invalid tag {:?}", value))
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(c) = value.chars().next() {
|
if let Some(c) = value.chars().next() {
|
||||||
if !c.is_ascii_alphabetic() {
|
if c.is_whitespace() || c.is_ascii_digit() || INVALID_TAG_CHARACTERS.contains(c) {
|
||||||
return Err(format_err!("first character of a tag must be alphabetic"));
|
return err(value);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(format_err!("tags must have at least one character"));
|
return err(value);
|
||||||
}
|
}
|
||||||
if !value.chars().skip(1).all(|c| c.is_ascii_alphanumeric()) {
|
if !value
|
||||||
return Err(format_err!(
|
.chars()
|
||||||
"characters of a tag after the first must be alphanumeric"
|
.skip(1)
|
||||||
));
|
.all(|c| !(c.is_whitespace() || c == ':' || INVALID_TAG_CHARACTERS.contains(c)))
|
||||||
|
{
|
||||||
|
return err(value);
|
||||||
}
|
}
|
||||||
Ok(Self(String::from(value)))
|
Ok(Self(String::from(value)))
|
||||||
}
|
}
|
||||||
|
@ -383,23 +396,23 @@ mod test {
|
||||||
let tag: Tag = "abc".try_into().unwrap();
|
let tag: Tag = "abc".try_into().unwrap();
|
||||||
assert_eq!(tag, Tag("abc".to_owned()));
|
assert_eq!(tag, Tag("abc".to_owned()));
|
||||||
|
|
||||||
|
let tag: Tag = ":abc".try_into().unwrap();
|
||||||
|
assert_eq!(tag, Tag(":abc".to_owned()));
|
||||||
|
|
||||||
|
let tag: Tag = "a123_456".try_into().unwrap();
|
||||||
|
assert_eq!(tag, Tag("a123_456".to_owned()));
|
||||||
|
|
||||||
let tag: Result<Tag, _> = "".try_into();
|
let tag: Result<Tag, _> = "".try_into();
|
||||||
assert_eq!(
|
assert_eq!(tag.unwrap_err().to_string(), "invalid tag \"\"");
|
||||||
tag.unwrap_err().to_string(),
|
|
||||||
"tags must have at least one character"
|
let tag: Result<Tag, _> = "a:b".try_into();
|
||||||
);
|
assert_eq!(tag.unwrap_err().to_string(), "invalid tag \"a:b\"");
|
||||||
|
|
||||||
let tag: Result<Tag, _> = "999".try_into();
|
let tag: Result<Tag, _> = "999".try_into();
|
||||||
assert_eq!(
|
assert_eq!(tag.unwrap_err().to_string(), "invalid tag \"999\"");
|
||||||
tag.unwrap_err().to_string(),
|
|
||||||
"first character of a tag must be alphabetic"
|
|
||||||
);
|
|
||||||
|
|
||||||
let tag: Result<Tag, _> = "abc!!".try_into();
|
let tag: Result<Tag, _> = "abc!!".try_into();
|
||||||
assert_eq!(
|
assert_eq!(tag.unwrap_err().to_string(), "invalid tag \"abc!!\"");
|
||||||
tag.unwrap_err().to_string(),
|
|
||||||
"characters of a tag after the first must be alphanumeric"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue