mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-08-21 07:43:08 +02:00
Merge branch 'main' into sqlstore
This commit is contained in:
commit
43ca0623b1
15 changed files with 176 additions and 22 deletions
0
.changelogs/.gitignore
vendored
Normal file
0
.changelogs/.gitignore
vendored
Normal file
7
.github/workflows/checks.yml
vendored
7
.github/workflows/checks.yml
vendored
|
@ -27,6 +27,13 @@ jobs:
|
|||
path: target
|
||||
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
# Fixed version for clippy lints. Bump this as necesary. It must not
|
||||
# be older than the MSRV in tests.yml.
|
||||
toolchain: "1.54"
|
||||
override: true
|
||||
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
with:
|
||||
command: check
|
||||
|
|
12
.github/workflows/tests.yml
vendored
12
.github/workflows/tests.yml
vendored
|
@ -9,15 +9,19 @@ on:
|
|||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
rust:
|
||||
- "1.47" # MSRV
|
||||
# MSRV; most not be higher than the clippy rust version in checks.yml
|
||||
- "1.47"
|
||||
- "stable"
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macOS-latest
|
||||
- windows-latest
|
||||
|
||||
name: "Test - Rust ${{ matrix.rust }}"
|
||||
name: "Test - Rust ${{ matrix.rust }} on ${{ matrix.os }}"
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
|
20
CHANGELOG.md
Normal file
20
CHANGELOG.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
Note: unreleased change log entries are kept in `.changelogs/` directory in repo root, and can be added with `./script/changelog.py add "Added thing for reason"
|
||||
|
||||
## 0.3.0 - 2021-01-11
|
||||
- Flexible named reports
|
||||
- Updates to the TaskChampion crate API
|
||||
- Usability improvements
|
||||
|
||||
## 0.2.0 - 2020-11-30
|
||||
|
||||
This release is the first "MVP" version of this tool. It can do basic task operations, and supports a synchronization. Major missing features are captured in issues, but briefly:
|
||||
|
||||
better command-line API, similar to TaskWarrior
|
||||
authentication of the replica / server protocol
|
||||
encryption of replica data before transmission to the server
|
||||
lots of task features (tags, annotations, dependencies, ..)
|
||||
lots of CLI features (filtering, modifying, ..)
|
|
@ -7,6 +7,10 @@ It also means that things are changing quickly, and lots of stuff is planned tha
|
|||
If you would like to work on TaskChampion, please contact the developers (via the issue tracker) before spending a lot of time working on a pull request.
|
||||
Doing so may save you some wasted time and frustration!
|
||||
|
||||
A good starting point might be one of the issues tagged with ["good first issue"][first].
|
||||
|
||||
[first]: https://github.com/taskchampion/taskchampion/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22
|
||||
|
||||
# Other Ways To Help
|
||||
|
||||
The best way to help this project to grow is to help spread awareness of it.
|
||||
|
@ -15,7 +19,6 @@ Tell your friends, post to social media, blog about it -- whatever works best!
|
|||
Other ideas;
|
||||
* Improve the documentation where it's unclear or lacking some information
|
||||
* Build and maintain tools that integrate with TaskChampion
|
||||
* Devise a nice TaskChampion logo
|
||||
|
||||
# Development Guide
|
||||
|
||||
|
@ -49,3 +52,14 @@ You may be able to limit the scope of what you need to understand to just one cr
|
|||
We expect contributors to follow the [GitHub Flow](https://guides.github.com/introduction/flow/).
|
||||
Aside from that, we have no particular requirements on pull requests.
|
||||
Make your patch, double-check that it's complete (tests? docs? documentation comments?), and make a new pull request.
|
||||
|
||||
Any non-trivial change (particularly those that change the behaviour of the application, or change the API) should be noted in the projects changelog.
|
||||
In order to manage this, changelog entries are stored as text files in the `.changelog/` directory at the repository root.
|
||||
|
||||
To add a new changelog entry, you can simply run `python3 ./script/changelog.py add "Fixed thingo to increase zorbloxification [Issue #2](http://example.com)`
|
||||
|
||||
This creates a file named `./changelogs/yyyy-mm-dd-branchname.md` (timestamp, current git branch) which contains a markdown snippet.
|
||||
|
||||
If you don't have a Python 3 intepreter installed, you can simply create this file manually. It should contain a list item like `- Fixed thingo [...]`
|
||||
|
||||
Periodically (probably just before release), these changelog entries are concatenated combined together and added into the `CHANGELOG.md` file.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Release process
|
||||
|
||||
1. Ensure the changelog is updated with everything from the `.changelogs` directory. `python3 ./script/changelog.py build` will output a Markdown snippet to include in `CHANGELOG.md` then `rm .changelog/*.txt`
|
||||
1. Run `git pull upstream main`
|
||||
1. Run `cargo test`
|
||||
1. Run `cargo clean && cargo clippy`
|
||||
|
|
|
@ -154,11 +154,18 @@ fn named_date<Tz: TimeZone>(
|
|||
move |input: &str| {
|
||||
let local_today = now.with_timezone(&local).date();
|
||||
let remaining = &input[input.len()..];
|
||||
let day_index = local_today.weekday().num_days_from_monday();
|
||||
match input {
|
||||
"yesterday" => Ok((remaining, local_today - Duration::days(1))),
|
||||
"today" => Ok((remaining, local_today)),
|
||||
"tomorrow" => Ok((remaining, local_today + Duration::days(1))),
|
||||
// TODO: lots more!
|
||||
"eod" => Ok((remaining,local_today + Duration::days(1))),
|
||||
"sod" => Ok((remaining,local_today)),
|
||||
"eow" => Ok((remaining,local_today + Duration::days((6-day_index).into()))),
|
||||
"eoww" => Ok((remaining,local_today + Duration::days((5-day_index).into()))),
|
||||
"sow" => Ok((remaining,local_today + Duration::days((6-day_index).into()))),
|
||||
"soww" => Ok((remaining,local_today + Duration::days((7-day_index).into()))),
|
||||
_ => Err(Err::Error(Error::new(input, ErrorKind::Tag))),
|
||||
}
|
||||
.map(|(rem, dt)| (rem, dt.and_hms(0, 0, 0).with_timezone(&Utc)))
|
||||
|
@ -301,6 +308,12 @@ mod test {
|
|||
#[case::today_from_evening(ldt(2021, 3, 1, 21, 30, 30), "today", ld(2021, 3, 1))]
|
||||
#[case::tomorrow(ld(2021, 3, 1), "tomorrow", ld(2021, 3, 2))]
|
||||
#[case::tomorow_from_evening(ldt(2021, 3, 1, 21, 30, 30), "tomorrow", ld(2021, 3, 2))]
|
||||
#[case::end_of_week(ld(2021,8,25,), "eow", ld(2021,8,29))]
|
||||
#[case::end_of_work_week(ld(2021,8,25), "eoww", ld(2021,8,28))]
|
||||
#[case::start_of_week(ld(2021,8,25), "sow", ld(2021,8,29))]
|
||||
#[case::start_of_work_week(ld(2021,8,25), "soww", ld(2021,8,30))]
|
||||
#[case::end_of_today(ld(2021,8,25), "eod", ld(2021,8,26))]
|
||||
#[case::start_of_today(ld(2021,8,25), "sod", ld(2021,8,25))]
|
||||
fn test_local_timestamp(
|
||||
#[case] now: Box<dyn Fn(FixedOffset) -> DateTime<Utc>>,
|
||||
#[values(*IST, *UTC_FO, *HST)] tz: FixedOffset,
|
||||
|
|
|
@ -125,7 +125,7 @@ fn get_server(settings: &Settings) -> anyhow::Result<Box<dyn Server>> {
|
|||
settings.server_origin.as_ref(),
|
||||
settings.encryption_secret.as_ref(),
|
||||
) {
|
||||
let client_key = Uuid::parse_str(&client_key)?;
|
||||
let client_key = Uuid::parse_str(client_key)?;
|
||||
|
||||
log::debug!("Using sync-server with origin {}", origin);
|
||||
log::debug!("Sync client ID: {}", client_key);
|
||||
|
|
|
@ -30,11 +30,11 @@ pub(super) fn apply_modification(
|
|||
}
|
||||
|
||||
for tag in modification.add_tags.iter() {
|
||||
task.add_tag(&tag)?;
|
||||
task.add_tag(tag)?;
|
||||
}
|
||||
|
||||
for tag in modification.remove_tags.iter() {
|
||||
task.remove_tag(&tag)?;
|
||||
task.remove_tag(tag)?;
|
||||
}
|
||||
|
||||
if let Some(wait) = modification.wait {
|
||||
|
|
|
@ -31,8 +31,7 @@ For the public TaskChampion Rust API, see the `taskchampion` crate.
|
|||
|
||||
*/
|
||||
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
use std::string::FromUtf8Error;
|
||||
use std::ffi::OsString;
|
||||
|
||||
// NOTE: it's important that this 'mod' comes first so that the macros can be used in other modules
|
||||
mod macros;
|
||||
|
@ -63,8 +62,8 @@ pub fn main() -> Result<(), Error> {
|
|||
// parse the command line into a vector of &str, failing if
|
||||
// there are invalid utf-8 sequences.
|
||||
let argv: Vec<String> = std::env::args_os()
|
||||
.map(|oss| String::from_utf8(oss.into_vec()))
|
||||
.collect::<Result<_, FromUtf8Error>>()
|
||||
.map(|oss| oss.into_string())
|
||||
.collect::<Result<_, OsString>>()
|
||||
.map_err(|_| Error::for_arguments("arguments must be valid utf-8"))?;
|
||||
let argv: Vec<&str> = argv.iter().map(|s| s.as_ref()).collect();
|
||||
|
||||
|
|
|
@ -130,7 +130,7 @@ impl TryFrom<&toml::Value> for Report {
|
|||
.map(|(i, v)| {
|
||||
v.as_str()
|
||||
.ok_or_else(|| anyhow!(".filter[{}]: not a string", i))
|
||||
.and_then(|s| Condition::parse_str(&s))
|
||||
.and_then(|s| Condition::parse_str(s))
|
||||
.map_err(|e| anyhow!(".filter[{}]: {}", i, e))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?,
|
||||
|
|
|
@ -97,7 +97,7 @@ impl Settings {
|
|||
"server_dir",
|
||||
"reports",
|
||||
];
|
||||
let table = table_with_keys(&config_toml, &table_keys)?;
|
||||
let table = table_with_keys(config_toml, &table_keys)?;
|
||||
|
||||
fn get_str_cfg<F: FnOnce(String)>(
|
||||
table: &Table,
|
||||
|
@ -184,10 +184,24 @@ impl Settings {
|
|||
.ok_or_else(|| anyhow!("Could not determine config file name"))?
|
||||
};
|
||||
|
||||
let mut document = fs::read_to_string(filename.clone())
|
||||
let exists = filename.exists();
|
||||
|
||||
// try to create the parent directory if the file does not exist
|
||||
if !exists {
|
||||
if let Some(dir) = filename.parent() {
|
||||
fs::create_dir_all(dir)?;
|
||||
}
|
||||
}
|
||||
|
||||
// start with the existing document, or a blank document
|
||||
let mut document = if exists {
|
||||
fs::read_to_string(filename.clone())
|
||||
.context("Could not read existing configuration file")?
|
||||
.parse::<Document>()
|
||||
.context("Could not parse existing configuration file")?;
|
||||
.context("Could not parse existing configuration file")?
|
||||
} else {
|
||||
Document::new()
|
||||
};
|
||||
|
||||
// set the value as the correct type
|
||||
match key {
|
||||
|
|
BIN
docs/src/images/name_timestamp.png
Normal file
BIN
docs/src/images/name_timestamp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8 KiB |
|
@ -28,3 +28,21 @@ Some of the units allow an adjectival form, such as `daily` or `annually`; this
|
|||
|
||||
[ISO 8601 standard durations](https://en.wikipedia.org/wiki/ISO_8601#Durations) are also allowed.
|
||||
While the standard does not specify the length of "P1Y" or "P1M", Taskchampion treats those as 365 and 30 days, respectively.
|
||||
|
||||
|
||||
## Named Timestamps
|
||||
|
||||
Some commonly used named timestamps
|
||||
|
||||
* `today` Start of today
|
||||
* `yesterday` Start of yesterday
|
||||
* `tomorrow` Start of tomorrow
|
||||
* `sod` Start of today
|
||||
* `eod` End of today
|
||||
* `sow` Start of the next week
|
||||
* `eow` End of the week
|
||||
* `eoww` End of work week
|
||||
* `soww` Start of the next work week
|
||||
|
||||
|
||||

|
||||
|
|
64
scripts/changelog.py
Executable file
64
scripts/changelog.py
Executable file
|
@ -0,0 +1,64 @@
|
|||
#!/usr/bin/env python3
|
||||
import os
|
||||
import argparse
|
||||
import datetime
|
||||
import subprocess
|
||||
from typing import List
|
||||
|
||||
def ymd():
|
||||
return datetime.datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
def git_current_branch() -> str :
|
||||
out = subprocess.check_output(["git", "branch", "--show-current"])
|
||||
return out.strip().decode("utf-8")
|
||||
|
||||
def get_dir() -> str:
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
return os.path.join(
|
||||
here,
|
||||
"../.changelogs")
|
||||
|
||||
def get_changefiles() -> List[str]:
|
||||
changedir = get_dir()
|
||||
changefiles = []
|
||||
for f in os.listdir(changedir):
|
||||
if f.endswith(".md") and not f.startswith("."):
|
||||
changefiles.append(os.path.join(changedir, f))
|
||||
|
||||
return changefiles
|
||||
|
||||
def cmd_add(args):
|
||||
text = args.text.strip()
|
||||
if not text.startswith("- "):
|
||||
text = "- %s" % text
|
||||
|
||||
timestamp = ymd()
|
||||
branchname = git_current_branch()
|
||||
fname = os.path.join(get_dir(), "%s-%s.md" % (timestamp, branchname))
|
||||
with open(fname, "a") as f:
|
||||
f.write(text)
|
||||
f.write("\n")
|
||||
|
||||
def cmd_build(args):
|
||||
print("## x.y.z - %s" % (ymd()))
|
||||
for e in get_changefiles():
|
||||
print(open(e).read().strip())
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers(title='Sub commands', dest='command')
|
||||
subparsers.required = True
|
||||
|
||||
parser_add = subparsers.add_parser('add')
|
||||
parser_add.add_argument("text")
|
||||
parser_add.set_defaults(func=cmd_add)
|
||||
|
||||
parser_build = subparsers.add_parser('build')
|
||||
parser_build.set_defaults(func=cmd_build)
|
||||
|
||||
args = parser.parse_args()
|
||||
args.func(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Add table
Add a link
Reference in a new issue