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::*,
|
||||
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
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
|
@ -130,7 +131,10 @@ pub(super) fn plus_tag(input: &str) -> IResult<&str, &str> {
|
|||
Ok(input.1)
|
||||
}
|
||||
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,
|
||||
)(input)
|
||||
}
|
||||
|
@ -141,7 +145,10 @@ pub(super) fn minus_tag(input: &str) -> IResult<&str, &str> {
|
|||
Ok(input.1)
|
||||
}
|
||||
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,
|
||||
)(input)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* [Using the Task Command](./using-task-command.md)
|
||||
* [Configuration](./config-file.md)
|
||||
* [Reports](./reports.md)
|
||||
* [Tags](./tags.md)
|
||||
* [Synchronization](./task-sync.md)
|
||||
* [Running the Sync Server](./running-sync-server.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.
|
||||
///
|
||||
/// 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)]
|
||||
pub struct Tag(String);
|
||||
|
||||
pub const INVALID_TAG_CHARACTERS: &str = "+-*/(<>^! %=~";
|
||||
|
||||
impl Tag {
|
||||
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 !c.is_ascii_alphabetic() {
|
||||
return Err(format_err!("first character of a tag must be alphabetic"));
|
||||
if c.is_whitespace() || c.is_ascii_digit() || INVALID_TAG_CHARACTERS.contains(c) {
|
||||
return err(value);
|
||||
}
|
||||
} 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()) {
|
||||
return Err(format_err!(
|
||||
"characters of a tag after the first must be alphanumeric"
|
||||
));
|
||||
if !value
|
||||
.chars()
|
||||
.skip(1)
|
||||
.all(|c| !(c.is_whitespace() || c == ':' || INVALID_TAG_CHARACTERS.contains(c)))
|
||||
{
|
||||
return err(value);
|
||||
}
|
||||
Ok(Self(String::from(value)))
|
||||
}
|
||||
|
@ -383,23 +396,23 @@ mod test {
|
|||
let tag: Tag = "abc".try_into().unwrap();
|
||||
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();
|
||||
assert_eq!(
|
||||
tag.unwrap_err().to_string(),
|
||||
"tags must have at least one character"
|
||||
);
|
||||
assert_eq!(tag.unwrap_err().to_string(), "invalid tag \"\"");
|
||||
|
||||
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();
|
||||
assert_eq!(
|
||||
tag.unwrap_err().to_string(),
|
||||
"first character of a tag must be alphabetic"
|
||||
);
|
||||
assert_eq!(tag.unwrap_err().to_string(), "invalid tag \"999\"");
|
||||
|
||||
let tag: Result<Tag, _> = "abc!!".try_into();
|
||||
assert_eq!(
|
||||
tag.unwrap_err().to_string(),
|
||||
"characters of a tag after the first must be alphanumeric"
|
||||
);
|
||||
assert_eq!(tag.unwrap_err().to_string(), "invalid tag \"abc!!\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue