Compare commits

..

243 commits

Author SHA1 Message Date
dependabot[bot]
c594ecb58d
Bump sigstore/cosign-installer from 3.8.2 to 3.9.0 (#3902)
Some checks failed
checks / Check & Clippy (push) Has been cancelled
checks / Formatting (push) Has been cancelled
checks / Cargo Metadata (push) Has been cancelled
release-tests / check-tarball (push) Has been cancelled
tests / coverage (push) Has been cancelled
tests / Cargo Test (push) Has been cancelled
tests / tests (Mac OS 12.latest) (push) Has been cancelled
tests / tests (Mac OS 13.latest) (push) Has been cancelled
tests / tests (arch, Archlinux Base (Rolling), ubuntu-latest) (push) Has been cancelled
tests / tests (debiantesting, Debian Testing, ubuntu-latest) (push) Has been cancelled
tests / tests (fedora40, Fedora 40, ubuntu-latest) (push) Has been cancelled
tests / tests (fedora41, Fedora 41, ubuntu-latest) (push) Has been cancelled
tests / tests (opensuse, OpenSUSE Tumbleweed (Rolling), ubuntu-latest) (push) Has been cancelled
tests / tests (ubuntu2004, Ubuntu 20.04, ubuntu-latest) (push) Has been cancelled
tests / tests (ubuntu2204, Ubuntu 22.04, ubuntu-latest) (push) Has been cancelled
Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 3.8.2 to 3.9.0.
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/v3.8.2...v3.9.0)

---
updated-dependencies:
- dependency-name: sigstore/cosign-installer
  dependency-version: 3.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-23 10:18:30 -04:00
pre-commit-ci[bot]
baaf69202b
[pre-commit.ci] pre-commit autoupdate (#3897)
Some checks failed
checks / Check & Clippy (push) Has been cancelled
checks / Formatting (push) Has been cancelled
checks / Cargo Metadata (push) Has been cancelled
release-tests / check-tarball (push) Has been cancelled
tests / coverage (push) Has been cancelled
tests / Cargo Test (push) Has been cancelled
tests / tests (Mac OS 12.latest) (push) Has been cancelled
tests / tests (Mac OS 13.latest) (push) Has been cancelled
tests / tests (arch, Archlinux Base (Rolling), ubuntu-latest) (push) Has been cancelled
tests / tests (debiantesting, Debian Testing, ubuntu-latest) (push) Has been cancelled
tests / tests (fedora40, Fedora 40, ubuntu-latest) (push) Has been cancelled
tests / tests (fedora41, Fedora 41, ubuntu-latest) (push) Has been cancelled
tests / tests (opensuse, OpenSUSE Tumbleweed (Rolling), ubuntu-latest) (push) Has been cancelled
tests / tests (ubuntu2004, Ubuntu 20.04, ubuntu-latest) (push) Has been cancelled
tests / tests (ubuntu2204, Ubuntu 22.04, ubuntu-latest) (push) Has been cancelled
updates:
- [github.com/pre-commit/mirrors-clang-format: v20.1.5 → v20.1.6](https://github.com/pre-commit/mirrors-clang-format/compare/v20.1.5...v20.1.6)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-06-16 14:08:43 -04:00
pre-commit-ci[bot]
a949c698f9
[pre-commit.ci] pre-commit autoupdate (#3887)
Some checks failed
checks / Check & Clippy (push) Has been cancelled
checks / Formatting (push) Has been cancelled
checks / Cargo Metadata (push) Has been cancelled
release-tests / check-tarball (push) Has been cancelled
tests / coverage (push) Has been cancelled
tests / Cargo Test (push) Has been cancelled
tests / tests (Mac OS 12.latest) (push) Has been cancelled
tests / tests (Mac OS 13.latest) (push) Has been cancelled
tests / tests (arch, Archlinux Base (Rolling), ubuntu-latest) (push) Has been cancelled
tests / tests (debiantesting, Debian Testing, ubuntu-latest) (push) Has been cancelled
tests / tests (fedora40, Fedora 40, ubuntu-latest) (push) Has been cancelled
tests / tests (fedora41, Fedora 41, ubuntu-latest) (push) Has been cancelled
tests / tests (opensuse, OpenSUSE Tumbleweed (Rolling), ubuntu-latest) (push) Has been cancelled
tests / tests (ubuntu2004, Ubuntu 20.04, ubuntu-latest) (push) Has been cancelled
tests / tests (ubuntu2204, Ubuntu 22.04, ubuntu-latest) (push) Has been cancelled
updates:
- [github.com/pre-commit/mirrors-clang-format: v20.1.4 → v20.1.5](https://github.com/pre-commit/mirrors-clang-format/compare/v20.1.4...v20.1.5)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-06-14 17:34:59 +02:00
dependabot[bot]
ffa0d3e944
Bump src/taskchampion-cpp/corrosion from 00af456 to 4eccadd (#3893)
Bumps [src/taskchampion-cpp/corrosion](https://github.com/corrosion-rs/corrosion) from `00af456` to `4eccadd`.
- [Release notes](https://github.com/corrosion-rs/corrosion/releases)
- [Commits](00af456488...4eccadd678)

---
updated-dependencies:
- dependency-name: src/taskchampion-cpp/corrosion
  dependency-version: 4eccadd67819b427978ca540e0c31e6cce08f226
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-06 08:08:07 -04:00
dependabot[bot]
6d81c8cda0
Bump src/taskchampion-cpp/corrosion from 715c235 to 00af456 (#3891)
Bumps [src/taskchampion-cpp/corrosion](https://github.com/corrosion-rs/corrosion) from `715c235` to `00af456`.
- [Release notes](https://github.com/corrosion-rs/corrosion/releases)
- [Commits](715c235dae...00af456488)

---
updated-dependencies:
- dependency-name: src/taskchampion-cpp/corrosion
  dependency-version: 00af4564881e9fc031f6b3303c1d6d19ecfe00f3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-05 11:49:04 -04:00
dependabot[bot]
440d3f8c92
Bump src/libshared from 8ad3646 to 121f757 (#3890)
Bumps [src/libshared](https://github.com/GothenburgBitFactory/libshared) from `8ad3646` to `121f757`.
- [Commits](8ad3646209...121f757c3e)

---
updated-dependencies:
- dependency-name: src/libshared
  dependency-version: 121f757c3ec1b1f548f7835208b8c72d85d141a7
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-02 09:01:53 -04:00
dependabot[bot]
e5b69afee2
Bump docker/build-push-action from 6.16.0 to 6.18.0 (#3889)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.16.0 to 6.18.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.16.0...v6.18.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 6.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-02 09:01:38 -04:00
Dustin J. Mitchell
75d351afad
Do not auto-create .taskrc when stdout is not a TTY (#3888)
* Do not auto-create .taskrc when stdout is not a TTY

This avoids prompting or automatically creating such a file, both of
which are unexpected when performing command-line completion.

Fixes #3751.

* Test case for taskrc creation no longer works

A taskrc is only created when stdout is a tty, which would require
allocating a pty, which is very platform-dependent and definitely not
worth the trouble for this test.
2025-06-02 07:59:05 -04:00
Dustin J. Mitchell
f6824e90a1
Correctly handle undo with multiple tasks (#3886)
The `std::stringstream::clear` method does not, in fact, clear the
string -- it just resets some internal flags. Assigning a new
stringstream to the variable is not the most efficient way to do this,
but it's the clearest.
2025-05-26 13:07:35 -04:00
pre-commit-ci[bot]
89d84f0bdd
[pre-commit.ci] pre-commit autoupdate (#3878)
updates:
- [github.com/pre-commit/mirrors-clang-format: v20.1.3 → v20.1.4](https://github.com/pre-commit/mirrors-clang-format/compare/v20.1.3...v20.1.4)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-05-12 14:01:34 -04:00
Dustin J. Mitchell
4620b5fd25
Make tags.none: filter work again (#3877)
* Don't remove the legacy 'tags' property

* Simulate the `tags` DOM property

This refers to the deprecated "tags" property of tasks. This property
usually still exists, for compatibility with older versions, but should
not be relied on anymore.
2025-05-12 08:16:54 +02:00
Tobias Predel
6c60a8db84
Move timegm implementation to libshared (#3875)
Move `timegm` implementation to libshared
2025-05-09 20:26:21 -04:00
Dustin J. Mitchell
79eb38d582
Fix compiler warning about unused variable (#3873)
This was added to indicate that the return value of chdir was unused,
but newer compilers "see through" this and determine it to be unused.
The return value is not marked must-use, so just doing nothing with it
is sufficient.
2025-05-08 17:08:22 +00:00
Antoni Borowski
0e59a62ead
Fix #3571: Added detailed feedback for successful task synchronization (#3758)
* Fix #3571: Added detailed feedback for successful task synchronization

* Refactor sync logic and add verbose output for synchronization operations

* Give a count of local operations sent

---------

Co-authored-by: Antoni Borowski <antoniborowski11@gmail.com>
Co-authored-by: Dustin J. Mitchell <dustin@v.igoro.us>
2025-05-07 18:29:19 -04:00
pre-commit-ci[bot]
97bcc76ac1
[pre-commit.ci] pre-commit autoupdate (#3868)
updates:
- [github.com/pre-commit/mirrors-clang-format: v20.1.0 → v20.1.3](https://github.com/pre-commit/mirrors-clang-format/compare/v20.1.0...v20.1.3)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-05-05 16:55:43 +00:00
Felix Stupp
499f931f67
use cached urgency() for json exports (#3867) 2025-05-05 10:10:02 -04:00
dependabot[bot]
416c6d3ca4
Bump src/libshared from cb078b0 to 8ad3646 (#3864)
Bumps [src/libshared](https://github.com/GothenburgBitFactory/libshared) from `cb078b0` to `8ad3646`.
- [Commits](cb078b00c5...8ad3646209)

---
updated-dependencies:
- dependency-name: src/libshared
  dependency-version: 8ad3646209c8d2e7820c3cd59319a2be3b3d221e
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-05 07:56:13 -04:00
dependabot[bot]
36e5f8895d
Bump src/libshared from 2aa844c to cb078b0 (#3860)
Bumps [src/libshared](https://github.com/GothenburgBitFactory/libshared) from `2aa844c` to `cb078b0`.
- [Commits](2aa844cb9b...cb078b00c5)

---
updated-dependencies:
- dependency-name: src/libshared
  dependency-version: cb078b00c5201e116c9dfd7d6951d954473eaa8f
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-02 08:20:55 -04:00
Dustin J. Mitchell
b4e25fe42f
Do not decode a non-JSON value (#3859)
None of the other task modifications (modify, prepend, append) treat the
input as JSON, so this one shouldn't either. This works around
https://github.com/GothenburgBitFactory/libshared/issues/95
2025-04-30 19:22:44 -04:00
dependabot[bot]
7be313e91f
Bump docker/build-push-action from 6.15.0 to 6.16.0 (#3856)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.15.0 to 6.16.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.15.0...v6.16.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 6.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-28 10:54:21 -04:00
dependabot[bot]
36a449c935
Bump sigstore/cosign-installer from 3.8.1 to 3.8.2 (#3855)
Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 3.8.1 to 3.8.2.
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/v3.8.1...v3.8.2)

---
updated-dependencies:
- dependency-name: sigstore/cosign-installer
  dependency-version: 3.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-28 10:54:06 -04:00
Ram-Z
31829d61fc
Add uuid UDA type (#3827)
Mainly so that UDAs that refer to another task can be formated as
"short".
2025-04-20 20:51:38 -04:00
dependabot[bot]
bae37d9448
Bump src/taskchampion-cpp/corrosion from bf065b8 to 715c235 (#3853)
Bumps [src/taskchampion-cpp/corrosion](https://github.com/corrosion-rs/corrosion) from `bf065b8` to `715c235`.
- [Release notes](https://github.com/corrosion-rs/corrosion/releases)
- [Commits](bf065b89a3...715c235dae)

---
updated-dependencies:
- dependency-name: src/taskchampion-cpp/corrosion
  dependency-version: 715c235daef4b8ee67278f12256334ad3dd4c4ae
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-18 13:46:42 -04:00
dependabot[bot]
bfea0f6836
Bump src/taskchampion-cpp/corrosion from c484074 to bf065b8 (#3850)
Bumps [src/taskchampion-cpp/corrosion](https://github.com/corrosion-rs/corrosion) from `c484074` to `bf065b8`.
- [Release notes](https://github.com/corrosion-rs/corrosion/releases)
- [Commits](c4840742d2...bf065b89a3)

---
updated-dependencies:
- dependency-name: src/taskchampion-cpp/corrosion
  dependency-version: bf065b89a33c4d6ce16cae626e8f15f9914ce4dc
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-17 11:45:17 -04:00
Felix Schurk
2a64b5c880
change implementation from has (#3849)
Change from get(uuid, task) to get_task_data to always have a full uuid
match and to improve read performance for the diagnostics command.

See #3848 for details.
2025-04-16 13:49:14 +00:00
Nick Grimshaw
15bb71764e
Redact HTTP credentials from "Syncing…" message (#3846) (#3847) 2025-04-14 17:08:32 +00:00
Adrian Wilkins
5b70ce6be2
Add note about uninstallation (#3845)
* Add note about uninstallation

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-04-10 08:33:52 -04:00
dependabot[bot]
22608cb44e
Bump tokio from 1.44.0 to 1.44.2 (#3843)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.44.0 to 1.44.2.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.44.0...tokio-1.44.2)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.44.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-07 22:24:53 -04:00
Dustin J. Mitchell
f1cb656f75
Update tokio to 1.44.2 (#3842) 2025-04-08 00:40:06 +00:00
Dustin J. Mitchell
db23195f4d
Remove duplicate word (#3834)
* Remove duplicate word

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-04-01 22:42:42 +02:00
dependabot[bot]
4a464c13a8
Bump src/taskchampion-cpp/corrosion from fcd8b41 to c484074 (#3836)
Bumps [src/taskchampion-cpp/corrosion](https://github.com/corrosion-rs/corrosion) from `fcd8b41` to `c484074`.
- [Release notes](https://github.com/corrosion-rs/corrosion/releases)
- [Commits](fcd8b41981...c4840742d2)

---
updated-dependencies:
- dependency-name: src/taskchampion-cpp/corrosion
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-31 17:58:09 -04:00
Ram-Z
a3b44bdef5
Replace inacurate comment (#3829) 2025-03-24 19:07:27 -04:00
soerenschneider
bc16297274
docs: Add note to disable AWS lifecycle policies (#3828)
* docs: Add note to disable AWS lifecycle policies

* fix grammar

---------

Co-authored-by: Dustin J. Mitchell <dustin@v.igoro.us>
2025-03-21 12:26:31 +00:00
Felix Schurk
7bf3be2f07
Update dependabot.yml (#3825) 2025-03-19 19:47:52 -04:00
dependabot[bot]
768d45197b
Bump docker/login-action from 3.3.0 to 3.4.0 (#3821)
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.3.0 to 3.4.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3.3.0...v3.4.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-17 20:11:14 -04:00
pre-commit-ci[bot]
f9c17d9b5b
[pre-commit.ci] pre-commit autoupdate (#3822)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/pre-commit/mirrors-clang-format: v19.1.7 → v20.1.0](https://github.com/pre-commit/mirrors-clang-format/compare/v19.1.7...v20.1.0)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-03-17 19:50:44 -04:00
Dustin J. Mitchell
1f6e7de569
Release 3.4.1 (#3818) 2025-03-14 10:20:52 +01:00
Kalle Kietäväinen
2ee5fb287c
Fix suppressing news nag after reading the news (#3817)
The news nag suppression regressed again in 5c67d22. That commit
intended to remove the sponsorship outro from the news, but also removed
the bookkeeping that marks the news as read. This commit reverts that
part back to its previous state.
2025-03-13 20:01:16 -04:00
Dustin J. Mitchell
b792018c00
Updates after 3.4.0 (#3816)
* Cargo update
* Update submodules
2025-03-12 22:27:55 +00:00
Dustin J. Mitchell
063325b052
Release 3.4.0 (#3811) 2025-03-12 17:58:49 -04:00
Ram-Z
f73b42d23f
Allow dur/dur division (#3812)
It is perfectly valid to divide two durations, it is a ratio with no
unit.
2025-03-12 16:48:49 -04:00
Dustin J. Mitchell
5c67d22540 Remove sponsorship outro from 'task news' 2025-03-08 12:22:09 -05:00
Tobias Predel
5814526429
Remove headers (#3807) 2025-03-08 09:04:01 -05:00
Dustin J. Mitchell
0c9205aa17
Update ring (#3806) 2025-03-07 00:33:38 +00:00
Dustin J. Mitchell
74276b400c
Generate valid JSON even with invalid dates (#3801) 2025-03-06 16:59:14 +01:00
Dustin J. Mitchell
022650dbff
Make updates after releasing (#3803)
* add symlinks for ease of access
* Update things after making a release, to maximize testing time
2025-03-06 12:43:41 +00:00
Yong Li
5ec0f4ebc0
Update task.1.in (#3804)
Clearly this is an error. The example command should use foo.
2025-03-06 07:33:37 -05:00
Dustin J. Mitchell
3c12c0dfd0
Fix errors finding Rust toolchain (#3802)
* switch to dtolnay/toolchain
* Add .dockerignore to avoid copying target/ into images
* update corrosion
2025-03-06 06:59:04 -05:00
dependabot[bot]
bcb3f820ab
Bump docker/build-push-action from 6.14.0 to 6.15.0 (#3800)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.14.0 to 6.15.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.14.0...v6.15.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-03 16:30:31 -05:00
Tobias Predel
1567ea5c06
Remove header files out of CMakeLists (#3797)
It does not make any difference.
2025-03-03 12:14:39 -05:00
Dustin J. Mitchell
3bb71390a9
Ignore "target" in creating source_package (#3799)
Cargo creates this directory if run directly, but it shouldn't be in the
release tarball.
2025-03-03 17:43:20 +01:00
Tobias Predel
a3e0dada30
Remove unused includes (#3798) 2025-03-02 11:03:18 -05:00
Tobias Predel
81ca04fc8c
Declare in corresponding header files and dissolve main.h (#3796)
* Declare in corresponding header files and dissolve main.h

Apply include-what-you-use

* Remove further unncessary includes

* Incorporate review comment

* Do not declare static functions and variables in header

* Adapt test

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-03-01 14:47:42 -05:00
Tobias Predel
55c02f5420
Remove unused includes (#3795)
* Remove unused include in Variant.h

* Remove unused include in util.h

* Remove unused include directives in util.cpp

* Remove unused include directives in TF2.h

* Remove unused include directives in TF2.cpp

* Remove unused include directive in TDB2.h

* Remove unused include directives in TDB2.cpp

* Remove unused include directives in Task.h

* Remove unused include directive in rules.cpp

* Remove unused include directives in rules.cpp

* Remove unused include directives in nag.cpp

* Remove unused include directive in main.h

* Remove unused include directive in legacy.cpph

* Remove unused include directive in Hooks.cpp

* Remove unused include directive in Filter.h

* Remove unused include directive in Filter.cpp

* Remove unused include directive in feedback.cpp

* Remove unused include directive in Eval.cpp

* Remove unused include directives in dependency.cpp

* Remove unused include directivess in Context.cpp
2025-03-01 10:42:40 -05:00
dependabot[bot]
8e90dc1571
Bump sigstore/cosign-installer from 3.8.0 to 3.8.1 (#3790)
Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 3.8.0 to 3.8.1.
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/v3.8.0...v3.8.1)

---
updated-dependencies:
- dependency-name: sigstore/cosign-installer
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-24 09:30:15 -05:00
dependabot[bot]
284948d9f9
Bump docker/build-push-action from 6.13.0 to 6.14.0 (#3791)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.13.0 to 6.14.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.13.0...v6.14.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-24 09:30:06 -05:00
Dustin J. Mitchell
a701f8fc7d
Open Replica read-only when possible (#3776)
* Open Replica read-only when possible

Specifically, when either
 - the command is read-only; or
 - the command requires GC (including recurrence updates) but GC is disabled by config

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-02-14 10:25:19 +01:00
dependabot[bot]
a97deb0c84
Bump sigstore/cosign-installer from 3.7.0 to 3.8.0 (#3778)
Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 3.7.0 to 3.8.0.
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/v3.7.0...v3.8.0)

---
updated-dependencies:
- dependency-name: sigstore/cosign-installer
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-10 10:10:51 -05:00
Dustin J. Mitchell
d6658fe26f
Remove missed handleRecurrence/handleUntil calls (#3771)
These were missed in #3753.
2025-02-06 08:31:33 +01:00
Dustin J. Mitchell
7871617e9c
Only handleRecurrence/handleUntil when rc.gc=1 (#3772) 2025-02-06 08:30:41 +01:00
Dustin J. Mitchell
fdb7e5e020
Test for unusual task data (#3770)
* Test for unusual task data

A task might have any combination of keys and values, but Taskwarrior
often assumes that only valid values can occur, and crashes otherwise.
This is highly inconvenient, as it's often impossible to do anything
with the invalid task -- Taskwarrior just fails without modifying it.

So, this is the beginning of some testing for such invalid tasks, with
the goal of making Taskwarrior due something reasonable. In general, an
invalid attribute value is treated as if it was not set. This is not
exhaustive, and there are likely still bugs of this sort, but as we find
them we can fix and add regression tests to this script.

This introduces a new test-only binary that creates a "bare" task using
TaskChampion, avoiding Taskwarrior's efforts to not create "unusual"
tasks.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-02-05 08:20:35 -05:00
pre-commit-ci[bot]
e1fc283da5
[pre-commit.ci] pre-commit autoupdate (#3773)
updates:
- [github.com/psf/black: 24.10.0 → 25.1.0](https://github.com/psf/black/compare/24.10.0...25.1.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-02-03 13:13:57 -05:00
dependabot[bot]
244513fad7
Bump docker/build-push-action from 6.12.0 to 6.13.0 (#3766)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.12.0 to 6.13.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.12.0...v6.13.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-01 15:51:35 -05:00
Matthew
3ae7413ebd
Update task.md json lines end with \n (0xA) not \r (0xD) (#3752)
Update task.md to fix mistake in json format.  In the code the json lines end with \n (0xA) not \r (0xD)
2025-02-01 15:37:43 -05:00
dependabot[bot]
8d210b5263
Bump docker/build-push-action from 6.11.0 to 6.12.0 (#3761)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.11.0 to 6.12.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.11.0...v6.12.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-20 18:00:57 -05:00
pre-commit-ci[bot]
20417b311e
[pre-commit.ci] pre-commit autoupdate (#3762)
updates:
- [github.com/pre-commit/mirrors-clang-format: v19.1.6 → v19.1.7](https://github.com/pre-commit/mirrors-clang-format/compare/v19.1.6...v19.1.7)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-01-20 13:27:51 -05:00
Dustin J. Mitchell
aeeec16984
Handle 'until' and 'recur' simiar to handling of 'gc' (#3753)
This centralizes updates to recurrence and 'until' in Command, instead
of doing so in each individual command implementation.

This is preparatory to opening the TaskChampion replica in read-only
mode.
2025-01-18 02:20:41 -05:00
Dustin J. Mitchell
1c9dddcae7
Remove unused, undefined method TDB2::dump (#3754) 2025-01-13 08:33:06 -05:00
dependabot[bot]
ffcd1c0d79
Bump docker/build-push-action from 6.10.0 to 6.11.0 (#3755)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.10.0 to 6.11.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.10.0...v6.11.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-13 08:32:46 -05:00
Dustin J. Mitchell
2e5177aa7c
Update to TaskChampion 2.0.2 (#3746) 2025-01-06 13:29:19 -05:00
pre-commit-ci[bot]
ae3651fd3f
[pre-commit.ci] pre-commit autoupdate (#3748)
updates:
- [github.com/pre-commit/mirrors-clang-format: v19.1.5 → v19.1.6](https://github.com/pre-commit/mirrors-clang-format/compare/v19.1.5...v19.1.6)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-01-06 12:48:12 -05:00
Dustin J. Mitchell
ddeec3512a
Add more CMake options to INSTALL (#3743) 2025-01-01 13:29:17 -05:00
Karl
630585d7b4
Update git log in CMakeLists.txt to not include potential signatures (#3742) 2024-12-31 13:51:35 -05:00
jrmarino
9105985c7c
Add offline build notes to INSTALL (#3705) (#3740)
Document additional steps that have been successful for NixOS and Ravenports.
2024-12-31 17:28:48 +00:00
Tejada-Omar
3bf0200602
Consider news read if news.version > current version (#3734)
Avoids two installations of taskwarrior with differing versions from
constantly nagging and rewriting `news.version`
2024-12-23 11:34:51 -05:00
Kalle Kietäväinen
1b9353dccc
Fix suppressing news nag after reading the news (#3731) 2024-12-20 13:13:23 +01:00
Dustin J. Mitchell
1ee69ea214
Release 3.3.0 (#3729)
* Release 3.3.0

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-12-19 16:57:43 +01:00
Dustin J. Mitchell
dcbe916286
Make test hooks executable (#3728) 2024-12-17 19:08:48 -05:00
Dustin J. Mitchell
cc505e4881
Support importing Taskwarrior v2.x data files (#3724)
This should ease the pain of upgrading from v2.x to v3.x.
2024-12-17 01:24:45 +00:00
Dustin J. Mitchell
758ac8f850
Add support for sync to AWS (#3723)
This is closely modeled on support for sync to GCP (#3223), but with
different authentication options to mirror typical usage of AWS.
2024-12-17 01:08:50 +00:00
pre-commit-ci[bot]
ff325bc19e
[pre-commit.ci] pre-commit autoupdate (#3725)
updates:
- [github.com/pre-commit/mirrors-clang-format: v19.1.4 → v19.1.5](https://github.com/pre-commit/mirrors-clang-format/compare/v19.1.4...v19.1.5)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-12-16 12:59:12 -05:00
dependabot[bot]
ddae5c4ba9
Bump taskchampion from 0.9.0 to 1.0.0 (#3722)
* Bump taskchampion from 0.9.0 to 1.0.0

Bumps [taskchampion](https://github.com/GothenburgBitFactory/taskchampion) from 0.9.0 to 1.0.0.
- [Release notes](https://github.com/GothenburgBitFactory/taskchampion/releases)
- [Commits](https://github.com/GothenburgBitFactory/taskchampion/compare/v0.9.0...v1.0.0)

---
updated-dependencies:
- dependency-name: taskchampion
  dependency-type: direct:production
  update-type: version-update:semver-major
...

* Bump MSRV

* update url to address RUSTSEC-2024-0421

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dustin J. Mitchell <dustin@v.igoro.us>
2024-12-10 13:45:25 +00:00
Dustin J. Mitchell
4add839548
Add instructions for running against customized TaskChampion (#3719)
This is often useful when doing work that includes changes in both TC
and TW.
2024-12-09 12:05:09 +01:00
Dustin J. Mitchell
3ea726f2bb
Cargo update hashbrown (#3716) 2024-12-07 21:56:34 -05:00
Kursat Aktas
ce70a182c1
Introducing Taskwarrior Guru on Gurubase.io (#3689)
* Introducing Taskwarrior Guru on Gurubase.io

Signed-off-by: Kursat Aktas <kursat.ce@gmail.com>
2024-12-07 11:18:52 -05:00
dependabot[bot]
8de7ff52e7
Bump docker/build-push-action from 6.9.0 to 6.10.0 (#3715)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.9.0 to 6.10.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.9.0...v6.10.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 08:13:54 -05:00
David Tolnay
dfc36aefcf
Rely on cxx to enforce matching versions (#3713) 2024-12-01 17:12:05 -05:00
Dustin J. Mitchell
c2cb7f36a7
Include cxxbridge-cmd in Cargo.lock, check version consistency (#3712)
This adds cxxbridge-cmd to Cargo.lock per
https://github.com/dtolnay/cxx/issues/1407#issuecomment-2509136343

It adds an MSRV to `src/taskchampion-cpp/Cargo.toml` so that the
version of `Cargo.lock` is stil compatible with the MSRV.

It additionally adds a check of the Cargo metadata for all of the cxx*
versions agreeing, and for the MSRV's agreeing.
2024-12-01 15:22:26 +00:00
Dustin J. Mitchell
e5ab1bc7a5
Update libshared (#3711)
This brings in https://github.com/GothenburgBitFactory/libshared/pull/89
2024-11-29 09:35:21 -05:00
Dustin J. Mitchell
4797c4e17e
Check Datetime addition when performing recurrence (#3708) 2024-11-29 09:12:20 -05:00
Dustin J. Mitchell
0b286460b6
Update rustls to latest version (#3704) 2024-11-27 17:59:31 -05:00
Dustin J. Mitchell
a99b6084e8
Only nag to read news when there's news to read (#3699) 2024-11-25 17:48:06 -05:00
pre-commit-ci[bot]
5664182f5e
[pre-commit.ci] pre-commit autoupdate (#3701)
updates:
- [github.com/pre-commit/mirrors-clang-format: v19.1.3 → v19.1.4](https://github.com/pre-commit/mirrors-clang-format/compare/v19.1.3...v19.1.4)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-11-25 20:33:39 +01:00
Felix Schurk
68c63372c1
change fedora39 to fedora41 runner (#3698)
* change fedora39 to fedora41 runner

* update github workflow runner file
2024-11-25 05:54:43 +01:00
Dustin J. Mitchell
ed3667c19e
Revert "add cpp standard flag to cmake (#3684)"
This reverts commit 01ced3238e.
2024-11-22 22:47:29 -05:00
Thomas Lauf
caae3fa37b Use repository owner instead of actor to log into GitHub CR 2024-11-22 21:18:31 +01:00
geoffpaulsen
066bb3e331
Updating the URL for Migration Documentation (#3694) 2024-11-21 21:19:44 -05:00
Kalle Kietäväinen
98204b17a6
Set CMake C++ standard (#3688)
Instead of setting `-std` compiler flag directly, set the
`CMAKE_CXX_STANDARD` variable. This lets CMake know the required C++
standard and evaluate the final compiler flag correctly, taking into
account compile features set by `target_compile_features()`.

This change preserves the existing behavior, where compiler extensions
are disabled for other targets than Cygwin.
2024-11-17 15:18:28 -05:00
Dustin J. Mitchell
096f94d3d1
Use Signal instead of PGP to contact me securely (#3685) 2024-11-16 13:45:44 -05:00
Felix Schurk
01ced3238e
add cpp standard flag to cmake (#3684)
add DCMAKE_CXX_STANDARD flag

See documentation in CMake.
https://cmake.org/cmake/help/latest/prop_tgt/CXX_STANDARD.html
2024-11-15 16:32:05 -05:00
Chongyun Lee
8cc4c461d6
Fix compile with libc++ 18 (#3680) 2024-11-13 22:09:08 -05:00
Scott Mcdermott
3e8bda6a23
release 3.2.0 links wrong PR for weekstart change (#3681)
ChangeLog: fix typo in linked PR number for weekstart change

off by 1000
2024-11-13 22:04:25 -05:00
Dustin J. Mitchell
7a092bea03
Release v3.2.0 (#3679) 2024-11-12 14:52:22 -05:00
Dustin J. Mitchell
54a94bd18c
include bubblegum-256.theme in default .taskrc (#3673) 2024-11-08 13:10:04 +01:00
Dustin J. Mitchell
a2f9b92d6c
Better undo output (and remove undo.style config) (#3672) 2024-11-07 14:56:34 -05:00
Dustin J. Mitchell
dcc8a8cdde
bump libshared for bold 256color support (#3670)
In particular, commit 47a750c385.
2024-11-06 07:40:16 -05:00
Dustin J. Mitchell
c9967c20e2
Restore support for task info journal (#3671)
This support was removed before Taskwarrior-3.x, and is now restored,
including the original tests removed in
ddd367232e
2024-11-06 07:39:39 -05:00
Dustin J. Mitchell
7da23aee1c
Run cargo test and fix it (#3663)
run cargo test and fix it
2024-11-05 08:55:10 -05:00
Denis Zh.
5b1be95f7d
Add color.calendar.scheduled to no-color.theme (#3666)
* Add scheduled color setting for calendar report
* Add default color.calendar.scheduled to all themes
2024-11-05 08:54:49 -05:00
Denis Zh.
0ff7844732
Fix missing line in man task-color (#3665)
Escape leading single quote to prevent groff misinterpretation as a
control character.
2024-11-05 08:00:43 -05:00
pre-commit-ci[bot]
023e7958c9
[pre-commit.ci] pre-commit autoupdate (#3664)
updates:
- [github.com/pre-commit/mirrors-clang-format: v19.1.2 → v19.1.3](https://github.com/pre-commit/mirrors-clang-format/compare/v19.1.2...v19.1.3)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-11-04 16:42:35 -05:00
Dustin J. Mitchell
8184226319
Support ENABLE_TLS_NATIVE_ROOTS to use system TLS CAs (#3660) 2024-11-02 11:16:23 +01:00
Dustin J. Mitchell
94c95563ab
Upgrade to TaskChampion 0.9.0 (#3662)
See https://github.com/GothenburgBitFactory/taskchampion/releases/tag/v0.9.0
2024-10-31 12:59:49 +00:00
Dustin J. Mitchell
6ff900f3fc
Use Replica::pending_tasks (#3661)
This replaces a loop over _all_ tasks with one that fetches only pending
tasks, as determined by the working set.

This should be faster for task DB's with large numbers of completed
tasks, although on my medium-sized installation (~5000 total tasks) the
difference is negligible.
2024-10-30 21:49:04 -04:00
dependabot[bot]
8bad3cdcbc
Bump docker/build-push-action from 6.8.0 to 6.9.0 (#3642)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.8.0 to 6.9.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.8.0...v6.9.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-27 15:22:38 -04:00
pre-commit-ci[bot]
0bb32d188c
[pre-commit.ci] pre-commit autoupdate (#3657)
updates:
- [github.com/pre-commit/mirrors-clang-format: v19.1.1 → v19.1.2](https://github.com/pre-commit/mirrors-clang-format/compare/v19.1.1...v19.1.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-10-27 15:22:29 -04:00
dependabot[bot]
c3b850898f
Bump sigstore/cosign-installer from 3.6.0 to 3.7.0 (#3641)
Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 3.6.0 to 3.7.0.
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/v3.6.0...v3.7.0)

---
updated-dependencies:
- dependency-name: sigstore/cosign-installer
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-27 15:22:12 -04:00
Fredrik Lanker
af8c5d58c8
Limit the allowed epoch timestamps (#3651)
The code for parsing epoch timestamps when displaying tasks only
supports values between year 1980 and 9999. Previous to this change, it
was possible to set e.g., the due timestamp to a value outside of these
limits, which would make it impossible to later on show the task.

With this change, we only allow setting values within the same limits
used by the code for displaying tasks.
2024-10-23 19:18:21 -04:00
Dustin J. Mitchell
2db373d631
Update to TaskChampion 0.8.0 (#3648)
* Update to TaskChampion 0.8.0

* Cargo update
2024-10-22 19:37:47 -04:00
Dustin J. Mitchell
96c72f3e06
Issue warnings instead of errors for 'weird' tasks (#3646)
* Issue warnings instead of errors for 'weird' tasks

* Support more comprehensive checks when adding a task
2024-10-22 15:15:51 -04:00
Thomas Lauf
4bf6144daf
Add SECURITY.md (#3655) 2024-10-21 15:16:25 -04:00
Scott Mcdermott
3e20ad6f6f
Pass rc.weekstart to libshared for ISO8601 weeknum parsing if "monday" (#3654)
* libshared: bump for weekstart, epoch defines, eopww fix

mainly those visible changes, and miscellaneous others

see GothenburgBitFactory/taskwarrior#3623 (weekstart)
see GothenburgBitFactory/taskwarrior#3651 (epoch limit defines)
see GothenburgBitFactory/libshared#73 (eopww fix)

* Initialize libshared's weekstart from user's rc.weekstart config

This enables use of newer libshared code that can parse week numbers
according to ISO8601 instead of existing code which is always using
Sunday-based weeks.  To get ISO behavior, set rc.weekstart=monday.
Default is still Sunday / old algorithm, as before, since Sunday is in
the hardcoded default rcfile.

Weekstart does not yet fix week-relative shortcuts, which will still
always use Monday.

See #3623 for further details.
2024-10-19 16:00:50 -04:00
Dustin J. Mitchell
7bd3d1b892
Install uuid-dev in GitHub action (#3647) 2024-10-14 17:48:41 -04:00
pre-commit-ci[bot]
0bd3989bab
[pre-commit.ci] pre-commit autoupdate (#3650)
updates:
- [github.com/psf/black: 24.8.0 → 24.10.0](https://github.com/psf/black/compare/24.8.0...24.10.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-10-14 17:47:39 -04:00
Dustin J. Mitchell
26c383d615
Restore 'load' timer (#3635)
* Restore 'load' timer

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-10-10 07:33:02 +02:00
pre-commit-ci[bot]
a8b4bcdda8
[pre-commit.ci] pre-commit autoupdate (#3638)
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.6.0...v5.0.0)
- [github.com/pre-commit/mirrors-clang-format: v18.1.8 → v19.1.1](https://github.com/pre-commit/mirrors-clang-format/compare/v18.1.8...v19.1.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-10-07 14:36:44 -04:00
Dustin J. Mitchell
28628e5dca
Add newline in sync error message (#3603) 2024-10-03 18:32:13 -04:00
dependabot[bot]
ff2b1cb888
Bump docker/build-push-action from 6.7.0 to 6.8.0 (#3637)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.7.0 to 6.8.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.7.0...v6.8.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-30 08:59:26 -04:00
dependabot[bot]
cfe92ce845
Bump threeal/gcovr-action from 1.0.0 to 1.1.0 (#3636)
Bumps [threeal/gcovr-action](https://github.com/threeal/gcovr-action) from 1.0.0 to 1.1.0.
- [Release notes](https://github.com/threeal/gcovr-action/releases)
- [Commits](https://github.com/threeal/gcovr-action/compare/v1.0.0...v1.1.0)

---
updated-dependencies:
- dependency-name: threeal/gcovr-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-30 08:06:38 -04:00
Dustin J. Mitchell
c95dc9d149
Ignore SIGPIPE (#3627)
This replicates what the Rust runtime does, and matches what Rust code
expects, for example when writing to a socket which is no longer
connected to the remote end.
2024-09-22 18:57:46 -04:00
Gagan Nagaraj
d75ef7f197
Check if end date is being set to a pending task (#3622)
check if end date is being set to a pending task

-throw error if end date is being set to a pending task
- add test for the bug
2024-09-13 12:16:20 -04:00
Gagan Nagaraj
c00c0e941b
Throw error when task config write is unsuccessfully (#3620) 2024-09-11 10:20:22 -04:00
Gagan Nagaraj
6a24510473
Exclude attributes starting with tag_ (#3619)
* Exclude attributes starting with tag_

* Check only for tag_*
2024-09-09 08:12:19 -04:00
Tobias Predel
72f9cd91a5
Refine INSTALL file (#3615)
Update INSTALL file

CMake can also abstract the install procedure from the underlying Makefile
2024-09-02 18:22:33 -04:00
Dustin J. Mitchell
44d443a8d6
Update INSTALL file (#3606) 2024-09-02 12:53:50 +00:00
Dustin J. Mitchell
2e3badbf99
Add some instructions to the MSRV (#3604)
There is no easy way to determine the MSRV for TaskChampion, other than
somehow pulling the right version of the source and grepping for it. In
practice, if we update the `taskchampion` dependency to one that has a
higher MSRV, we'll get a build error and find this comment. And if we
get an error building Taskwarrior due to an old MSRV (for example if
something changes on `crates.io`) then we will also find this comment.

This also removes some superfluous dependency versions from the root
workspace. `src/taskchampion-cpp/Cargo.toml` specifies versions
directly.
2024-08-26 21:45:19 -04:00
dependabot[bot]
6cfbb16966
Bump docker/build-push-action from 6.6.1 to 6.7.0 (#3602)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.6.1 to 6.7.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.6.1...v6.7.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-19 09:22:41 -04:00
Dustin J. Mitchell
70632b088e
Do not count undo operations in the 'would be reverted..' message (#3598) 2024-08-14 08:35:34 -04:00
dependabot[bot]
d46e5eca58
Bump docker/build-push-action from 6.5.0 to 6.6.1 (#3595)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.5.0 to 6.6.1.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.5.0...v6.6.1)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-12 08:23:45 -04:00
dependabot[bot]
05da133eb6
Bump sigstore/cosign-installer from 3.5.0 to 3.6.0 (#3594)
Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 3.5.0 to 3.6.0.
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/v3.5.0...v3.6.0)

---
updated-dependencies:
- dependency-name: sigstore/cosign-installer
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-12 08:22:36 -04:00
Dustin J. Mitchell
c719cce4f1
Run all C++ tests from a single executable (#3582) 2024-08-12 00:20:17 +00:00
Dustin J. Mitchell
4ff63a7960
Use TaskChampion 0.7.0, now via cxx instead of hand-rolled FFI (#3588)
TC 0.7.0 introduces a new `TaskData` type that maps to Taskwarrior's
`Task` type more cleanly. It also introduces the idea of gathering lists
of operations and "committing" them to a replica.

A consequence of this change is that TaskChampion no longer
automatically maintains dependency information, so Taskwarrior must do
so, with its `TDB2::dependency_sync` method. This method does a very
similar thing to what TaskChampion had been doing, so this is a shift of
responsibility but not a major performance difference.

Cxx is .. not great. It is missing a lot of useful things that make a
general-purpose bridge impractical:

 - no support for trait objects
 - no support for `Option<T>` (https://github.com/dtolnay/cxx/issues/87)
 - no support for `Vec<Box<..>>`

As a result, some creativity is required in writing the bridge, for
example returning a `Vec<OptionTaskData>` from `all_task_data` to allow
individual `TaskData` values to be "taken" from the vector.

That said, Cxx is the current state-of-the-art, and does a good job of
ensuring memory safety, at the cost of some slightly awkward APIs.

Subsequent work can remove the "TDB2" layer and allow commands and other
parts of Taskwarrior to interface directly with the `Replica`.
2024-08-11 02:06:00 +00:00
Jan Christian Grünhage
0f96fd31bf
Update google-cloud-auth to drop ring@0.16.20 (#3591)
* Update google-cloud-auth to drop ring@0.16.20

ring@0.16.20 doesn't build on ppc and risc-v, and updating
google-cloud-auth pulls in a newer version of jsonwebtoken,
which in turn depends on a newer version of ring that we depend on
already either way.

This necessitated an MSRV bump to 1.73.0
2024-08-09 21:24:12 -04:00
Jan Christian Grünhage
3d30f2ac46
Optionally use system provided corrosion (#3590) 2024-08-09 13:05:19 -04:00
Dustin J. Mitchell
49e09a9783
Remove accidentally-included sqlite3 file (#3589) 2024-08-08 03:11:56 +00:00
Dustin J. Mitchell
17889a3f25
Actually run shell tests (#3583)
Two of these used EXPFAIL which, because nothing is interpreting the TAP
output, does not work. So, that functionality is removed, and the
expected-to-fail bits are commented out or removed.

There was a conditional on the filename in `bash_tap.sh` which caused it
to not actually do anything and just run the test as a simple shell
script. That, too, has been removed.
2024-08-07 00:44:12 +00:00
pre-commit-ci[bot]
c0b708d1f3
[pre-commit.ci] pre-commit autoupdate (#3587)
updates:
- [github.com/psf/black: 24.4.2 → 24.8.0](https://github.com/psf/black/compare/24.4.2...24.8.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-08-05 14:20:35 -04:00
Dustin J. Mitchell
5c6cc3e522
Release 3.1.0 (#3574) 2024-08-04 16:51:11 +00:00
Dustin J. Mitchell
160be69852
Update to docker compose v2 (#3577) 2024-08-04 16:37:18 +00:00
Dustin J. Mitchell
70714e8ca0
Merge pull request #3566 from felixschurk/add-clang-format
Add clang-format to enforce style guide
2024-07-29 17:45:36 -04:00
Felix Schurk
4d058a5c5a add .git-blame-ignore-revs file to hide formating changes in git blame 2024-07-29 22:36:03 +02:00
Felix Schurk
93356b39c3 add initial bulk run from pre-commit over all files 2024-07-29 22:34:51 +02:00
Felix Schurk
665aeeef61 reflect updated code-style and workflow in documentation 2024-07-29 22:33:17 +02:00
Felix Schurk
954d3f5058 add blank line between cmake.h header include to prevent sorting
* add required comment in the line below cmake.h include header
2024-07-29 22:33:17 +02:00
Felix Schurk
dfc3566796 add .cache from clang-format to .gitignore 2024-07-29 22:33:17 +02:00
Felix Schurk
a40ead9c60 add pre-commit-config.yaml file
Includes default tools, as well as clang-format and black.
2024-07-29 22:33:17 +02:00
Felix Schurk
5406772b66 add clang-format initial version 2024-07-29 22:33:16 +02:00
dependabot[bot]
234fac40c6
Bump docker/build-push-action from 6.4.1 to 6.5.0 (#3570)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.4.1 to 6.5.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.4.1...v6.5.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-29 08:50:15 -04:00
dependabot[bot]
9de339e087
Bump docker/login-action from 3.2.0 to 3.3.0 (#3569)
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.2.0 to 3.3.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3.2.0...v3.3.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-29 08:49:13 -04:00
Adrian Sadłocha
bb72ea6169
Remove encoding declaration in Python files (#3568)
As per
https://docs.python.org/3.11/reference/lexical_analysis.html#encoding-declarations,
the default encoding of Python files is UTF-8. In fact, it's been the
default encoding since Python 3.0 (released in 2008).
2024-07-26 23:36:23 -04:00
dependabot[bot]
3a07f70253
Bump docker/build-push-action from 6.3.0 to 6.4.1 (#3562)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.3.0 to 6.4.1.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.3.0...v6.4.1)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-26 23:35:18 -04:00
Adrian Sadłocha
9c49863795
Make task news nag configurable and deterministic (#3567)
This patch fixes #3497.
2024-07-26 20:30:54 -04:00
Adrian Sadłocha
9dde68f918
Remove unused variables in task history implementation (#3564) 2024-07-24 19:54:56 -04:00
Adrian Sadłocha
4dc3093b22
Update a comment to match the code (#3565) 2024-07-24 19:52:25 -04:00
Adrian Sadłocha
40ea3f2f54
Fix conversion from TCStatus::Unknown (#3561)
Before this patch, the messsage would be "unknown TCStatus 4294967295"
(i.e. `u32::MAX`) instead of "unknown TCStatus -1".
2024-07-20 06:46:06 -04:00
Adrian Sadłocha
7ea4baed77
Warn if an import contains multiple occurrences of the same UUID (#3560) 2024-07-19 22:27:16 -04:00
Dustin J. Mitchell
0650fe509f
Fix formatting in task-sync manpage (#3535) 2024-07-15 14:50:01 +02:00
Dustin J. Mitchell
7d79b9e516
Rename 'expiration.on-sync' to 'purge.on-sync' (#3556)
Taskwarrior uses "expire" to refer to deletion of tasks past their
"until" date, so let's use `purge` to link this semantically to the
`task purge` command.
2024-07-14 15:45:26 -04:00
Dustin J. Mitchell
1304d6361c
Restore 'task purge' functionality (#3540)
Co-authored-by: ryneeverett <ryneeverett@gmail.com>
2024-07-14 15:59:00 +00:00
Sebastian Carlos
e156efae7d
Note in taskrc(5) that "undo" configurations are not currently supported (#3518) 2024-07-13 22:54:35 -04:00
Dustin J. Mitchell
d4649dd210
Revert "Do not create recurring tasks before today (#3542)" (#3555)
This reverts commit 6d3519419e.
2024-07-13 19:12:49 -04:00
Dustin J. Mitchell
9db275fedb
Fix link in issue template (#3554) 2024-07-12 21:27:11 -04:00
Dustin J. Mitchell
c477b2b59c
Do not claim that Taskwarrior automatically syncs (#3537)
Taskwarrior only syncs when `task sync` is run.
2024-07-12 21:23:20 -04:00
Dustin J. Mitchell
213b9d3aee
Add support for task expiration (#3546) 2024-07-09 16:39:39 -04:00
Will R S Hansen
2bd609afe3
Export tasks in a deterministic order (#3549)
fix issue #3527
2024-07-09 03:22:14 +00:00
dependabot[bot]
61c9b48664
Bump docker/build-push-action from 6.2.0 to 6.3.0 (#3547)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.2.0 to 6.3.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.2.0...v6.3.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-08 07:49:05 -04:00
Felix Schurk
4a04275266
add faketime and change to apt-get in devcontainer (#3543)
We require faketime to be installed for running the tests, therefore its added.
Further for non-interactive usage apt-get is recommended, therefore the change.
2024-07-07 23:04:09 +02:00
koleesch
847c482c25
Remove duplicate check from task diag (#3545) 2024-07-07 13:19:54 -04:00
Dustin J. Mitchell
6d3519419e
Do not create recurring tasks before today (#3542)
Tasks can be due "today", as `task add foo due:today ..` is a common
form. However, recurrences before that are just not created.

This avoids a lengthy "hang" when recurrences are updated on an old task
database, as many tasks in the past are created.
2024-07-07 08:51:09 -04:00
Sebastian Carlos
d1a3573c5f
Replace "gc" with "rebuild" in man pages. (#3533)
Replace "gc" with "rebuild" in man page.

Also, explain IDs in the context of the working set of tasks.
2024-07-04 22:32:22 -04:00
Hector Dearman
fa5604ea8d
Relax sync.server.origin to allow paths (#3423) 2024-07-04 23:17:52 +00:00
Felix Schurk
85f52e3630
Update performance scripts (#3532)
* update performance script to work with out-of-source build
* update displayed messages and remove perf.rc file
* remove .gitignore in performance folder
2024-07-04 08:45:42 +02:00
Felix Schurk
eb22036f6b
Update to taskchampion 0.6 (#3531)
* update dependency to taskchampion 0.6
* change from origin to url in remote server
2024-07-04 08:45:11 +02:00
dependabot[bot]
9372c988fa
Bump docker/build-push-action from 6.0.0 to 6.2.0 (#3529)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.0.0 to 6.2.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.0.0...v6.2.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 09:37:08 -04:00
Sebastian Carlos
1b81813223
Clarify relation between the DUE tag and the rc.due setting in task(1) (#3526)
Also, clarify that DUE is based on a number of days since the current date.

Co-authored-by: Sebastian Carlos <sebastiancarlos@gmail.com>
2024-06-28 18:13:02 -04:00
Sebastian Carlos
b70d8ef574
Remove locking config (#3519)
This flag is no longer needed. It was used to control file locking on the *.data files.

Co-authored-by: Sebastian Carlos <sebastiancarlos@gmail.com>
2024-06-28 18:12:10 -04:00
Felix Schurk
5ab51271b0
increase timout to 10s and run on ubuntu-latest (#3523)
As identified in #3512 it seams as the problem was the internal timeout.
Closes #3507.
2024-06-28 18:11:53 -04:00
Sebastian Carlos
64609a0407
Consistently exclude WAITING tasks from reports which filter by status (#3525)
Consistently exclude WAITING tasks from reports which filter by status

If I understand correctly, the virtual status "waiting" is deprecated.
This is why some reports include the explicit "-WAITING" filter even
though it is not necessary: The idea is that being explicit will be
needed in the future when the "waiting" status is removed.

But I noticed that some reports do not include the "-WAITING" filter, so
I added it to all reports that filter by status.
2024-06-28 18:02:37 -04:00
Sebastian Carlos
750af261aa
Un-deprecate the non-1/0 boolean values (#3522)
As per https://github.com/GothenburgBitFactory/tw.org/issues/867

Co-authored-by: Sebastian Carlos <sebastiancarlos@gmail.com>
2024-06-26 22:29:26 -04:00
Dustin J. Mitchell
9cd1b96e1f
Remove support for the F4 format (#3494)
This was only still used in tests.
2024-06-26 07:33:46 +02:00
Dustin J. Mitchell
f1460013be
Include fewer deps (#3489)
Update chrono, and include fewer deps

Somewhere in the process of moving TaskChampion back to its own repo,
Taskwarrior's `Cargo.toml` ended up containing all of its dependencies,
unnecessarily.

This included an old version of `chrono` (addressed in
https://github.com/GothenburgBitFactory/taskchampion/pull/399). Removing
the old version requirement from `Cargo.toml` results in using a newer
version, addressing a (benign) security vulnerability.
2024-06-24 17:19:12 -04:00
Sebastian Carlos
910860ae1c
Properly tag monospaced text in man pages. (Improves HTML rendering) (#3509)
This would be immediately useful to improve the rendering of the man
pages in third-party websites such as manned.org and the arch man pagesk
- https://mankier.com/1/task
- https://man.archlinux.org/man/task.1

The regular console output, or websites which render the manpage in
monospaced fonts, will not be affected by this change.

This change could also help with eventually rendering the manpages in
the documentation website, and having a mix of monospaced and variable
width fonts.

To test the HTML output:
```bash
git clone git@github.com:jacksonp/manner.git # used by ManKier
./manner/manner.php task.1.in >| task.1.html && $BROWSER task.1.html
```

Co-authored-by: Sebastian Carlos <sebastiancarlos@gmail.com>
2024-06-24 14:00:12 -04:00
Sebastian Carlos
71becf0185
Remove mentions of legacy data files in the man pages (#3516)
Co-authored-by: Sebastian Carlos <sebastiancarlos@gmail.com>
2024-06-24 13:59:00 -04:00
Felix Schurk
572268606f
update branch in coverage status badge (#3515) 2024-06-24 17:31:53 +02:00
Dustin J. Mitchell
e3181aa8d4
Consider all news "major" (#3493)
This has the effect that `task news` will unconditionally update the
config with the new version once news has been shown (assuming the user
does not kill the process first).
2024-06-24 08:14:33 -04:00
Sebastian Carlos
e7ad31c1c2
Document the 'status' attribute in the man page (#3511)
Co-authored-by: Sebastian Carlos <sebastiancarlos@gmail.com>
2024-06-23 19:45:08 -04:00
Sebastian Carlos
1d59c210d2
Document 'modified' attribute in man page. (#3510)
Co-authored-by: Sebastian Carlos <sebastiancarlos@gmail.com>
2024-06-23 19:25:43 -04:00
Felix Schurk
e0e6ea7170
remove test certificates and link in python code (#3506)
With taskwarrior > 3.0 these are no longer required and thus can be
removed.
2024-06-22 19:52:43 -04:00
Adrian Galilea
bb8a105754
Add bubblegum-256.theme for improved legibility and contrast (#3376) (#3505) 2024-06-22 19:46:56 -04:00
Dustin J. Mitchell
261e07dc0d
Add a config.toml symlink (#3503)
This also uses `cargo xtask` in the action, to test this functionality
2024-06-21 18:18:16 -04:00
Dustin J. Mitchell
5f983a66af
Include version in default-generated .taskrc (#3500)
This will avoid new users being prompted with all the news since
2.5.0.
2024-06-21 08:32:09 -04:00
Felix Schurk
c44229dd32
create coverage actions (#3483)
* add coveralls coverage actions to tests
* using Ninja for Build
* exclude build directory in coverage report
* let distro tests run after successful coverage run
* add coveralls badge to readme

Closes #3413.
2024-06-21 12:31:45 +00:00
Hector Dearman
0119867223
Resolve a number of minor warnings (#3495) 2024-06-20 08:29:39 -04:00
Hector Dearman
210ec10132
Update cargo dependencies (#3496)
Run cargo update

The build was failing on nightly-aarch64-apple-darwin
(rustc 1.81.0-nightly (d8a38b000 2024-06-19))
due issues like:

error[E0635]: unknown feature `stdsimd`
  --> /Users/chromy/.cargo/registry/src/index.crates.io-6f17d22bba15001f/ahash-0.7.6/src/lib.rs:33:42
   |
33 | #![cfg_attr(feature = "stdsimd", feature(stdsimd))]
   |                                          ^^^^^^^

This was due to:
https://users.rust-lang.org/t/error-e0635-unknown-feature-stdsimd/106445
And resolved by moving to a newer version of ahash.

I confirmed the build still works on:
stable-aarch64-apple-darwin (rustc 1.78.0 (9b00956e5 2024-04-29))
1.70.0-aarch64-apple-darwin (rustc 1.70.0 (90c541806 2023-05-31))

after this change.

I had to manually downgrade google-cloud-auth to v0.13.0
v0.13.2 depends on jsonwebtoken v9.3.0 which drops support for rustc
1.70.0.
2024-06-20 08:28:02 -04:00
Dustin J. Mitchell
24f56b65a9
Only warn about .data files when showing reports (#3473)
* Only warn about .data files when showing reports

This avoids the warning appearing in shell completion, for example.

* Update src/commands/CmdCustom.cpp

Co-authored-by: ryneeverett <ryneeverett@gmail.com>

---------

Co-authored-by: ryneeverett <ryneeverett@gmail.com>
2024-06-19 11:17:14 +02:00
Dustin J. Mitchell
9788798189
Remove unnecessary cbindgen-related Makefile (#3486) 2024-06-19 02:54:46 +00:00
dependabot[bot]
dfab237830
Bump docker/build-push-action from 5.3.0 to 6.0.0 (#3492)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5.3.0 to 6.0.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v5.3.0...v6.0.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-17 12:02:51 +00:00
Dustin J. Mitchell
82e645b929
Fix builds with Rust 1.79 (#3487)
* use underscore in taskchampion-lib name

* update to corrosion 0.5.0
2024-06-17 10:15:20 +02:00
Dustin J. Mitchell
161463deec
Fix warning about unused method (#3488)
fix warning about unused method
2024-06-14 22:15:21 -04:00
Dustin J. Mitchell
bba010c307
Update the comms channels in README (#3484)
We don't update the bird-site anymore, libera.chaat is gone (or
un-monitored), and we prefer discussions over discord over reddit.
2024-06-14 19:37:47 -04:00
Dustin J. Mitchell
e9c6c6c846
Be resilient to a missing 'entry' in urgency (#3479)
Any combination of properties is possible - Taskwarrior should make
the best of the tasks it finds, rather than crashing.
2024-06-13 14:17:23 -04:00
dependabot[bot]
5821eda98e
Bump docker/login-action from 3.1.0 to 3.2.0 (#3474)
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.1.0 to 3.2.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3.1.0...v3.2.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-03 17:07:15 -04:00
Dustin J. Mitchell
99827de3dd
Add a release-check action to verify release tarballs have all the necessary parts (#3472) 2024-06-02 17:44:04 -04:00
dependabot[bot]
977a8f3853
Bump tokio from 1.37.0 to 1.38.0 (#3470)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.37.0 to 1.38.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.37.0...tokio-1.38.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-31 08:00:05 -04:00
Felix Schurk
236a5f0bf1
remove custom commands and custom scripts (#3468)
Fixes #3462.
2024-05-28 18:27:09 -04:00
Andonome
50052f5115
Placing limit in man 5 taskrc (#3467) 2024-05-27 15:50:59 -04:00
Andonome
d775923070
Let task show recognize limit in taskrc (#3466) 2024-05-27 13:50:46 -04:00
Dustin J. Mitchell
8a807af2ef
Add test that on-add returning 1 does not add task (#3457)
This test existed, but didn't notice that the task was actually added.
The bug itself was fixed in #3443.
2024-05-15 08:11:45 -04:00
Dustin J. Mitchell
aebbfaff98
Be resilient to different numbers of local changes (#3449)
With an explanation in the comments
2024-05-14 22:03:16 -04:00
Maarten Aertsen
fb16dbf7cf
Test modification of a task by an on-add hook (test case for #3416) (#3443)
* Add test case to cover https://github.com/GothenburgBitFactory/taskwarrior/issues/3416

* Add (builtin) on-add-modify test hook and use it

* TDB2::add() move hook invocation before save (#3416)
2024-05-14 21:47:43 -04:00
Dustin J. Mitchell
e4c33d1e1d
move .cargo/config to config.toml (#3454) 2024-05-14 01:51:23 +00:00
Dustin J. Mitchell
20583ddb7d
more specifics about docs updates (#3453) 2024-05-14 01:50:43 +00:00
Felix Schurk
82e0d53cdf
add ctest as test driver (#3446) 2024-05-10 01:20:54 +00:00
Felix Schurk
2361521449
remove stress_test file (#3447)
Close #3422
2024-05-08 21:33:17 -04:00
Joseph Coffa
60575a1967
Update task-sync(5) to include delete perms for GCP sync (#3442) 2024-05-05 23:24:41 -04:00
Dominik Rehák
0deeeb0a1d
CmdStart: Fix "make this task pending" note for completed tasks (#2769) 2024-05-05 20:04:18 -04:00
Philipp Oberdiek
9d9dde1065
Fix inherited urgency when parent and child have the same urgency (#2941)
Update condition for inheritance value hack

If the parent and child task have the same urgency the parent task also
needs the 0.01 extra urgency to be sorted above the child.
2024-05-05 20:02:12 -04:00
Steve Dondley
651ea36382
prevent task completion commands from triggering hooks #3131 (#3133) 2024-05-05 19:51:44 -04:00
Dustin J. Mitchell
8aa4758993
add targets for individual tests (#3431) 2024-05-03 10:06:29 -04:00
Dustin J. Mitchell
28a46880a2
Treat a nonzero exit status as a failure (#3430)
And fix the test cases that have been failing ,undetected
2024-05-03 13:58:09 +00:00
Christian Clauss
50cfbe8b63
Fix Python SyntaxError in tests/version.test.py (#3424)
% `ruff check`
```
error: Failed to parse test/version.test.py:92:54: Expected a statement
Error: test/version.test.py:92:54: E999 SyntaxError: Expected a statement
```
2024-05-03 00:30:02 +00:00
Felix Schurk
52dbecb515
remove .gitignore files and symbolic links/aliases (#3421)
* remove symbollic links in the src directory as they are no longer
  working with the out-of-source build
* remove .gitignore in the documentation (is build in build folder not
  needed)
* remove CMake folders as they are also no longer present in the source
  directory

Closes #3420.
2024-05-02 20:26:10 -04:00
Christian Clauss
b7551cbba6
Fix SyntaxWarning invalid escape sequence in Python code (#3433) 2024-05-03 00:22:33 +00:00
Dustin J. Mitchell
380c740ff0
remove pull req template (#3432) 2024-05-02 19:35:00 -04:00
Dustin J. Mitchell
94b3e301d1
Remove taskchampion source from this repo (#3427)
* move taskchampion-lib to src/tc/lib, remove the rest
* update references to taskchampion
* Use a top-level Cargo.toml so everything is consistent
* apply comments from ryneeverett
2024-05-02 02:45:11 +00:00
mattsmida
ef9613e2d6
Renaming test files according to their language (#3407) 2024-05-01 14:28:07 -04:00
Dustin J. Mitchell
43ca74549d
Include the whole error message in errors from Rust (#3415) 2024-04-30 14:54:42 -04:00
Felix Schurk
d093ce3d84
Fix test script paths (#3387)
* fix path to task executable in pyton tests

The current approach would copy the current files into the `build/test`
directory. Updating the paths according to the custom user setup.

By the copy I appended `.py` to have a clear visible distingtion which
ones are the python tests.

As soon as a source file in the normal directory is changed, it is
copied over and the corresponding file is updated.

From now on the python tests would need to get run in the according
build directory.

* reflect the current build instruction in PR template

* update paths and globing in run_all

* add line break for every cpp test

* remove .gitignore in test folder

As now all the auxillary files such as `all.log` as well as the
executables are present in the `build` directory there is no longer a
need to ignore them.

* update paths in python test scripts and enable deactivated

* remove .py extension when copy to build

Further remove glob pattern for `*.t.py` tests.

* remove accidentally added template.t from test files
2024-04-28 15:38:14 -04:00
sleepy_nols
7dba5e7695
update '.data' warning message to '*.data' for better readability (#3409)
TDB2: update '.data' warning to '*.data' for better readability, closes #3406
2024-04-28 15:24:42 -04:00
Felix Schurk
eaef05ee95
Update fedora 38 docker container to fedora 40 (#3396) 2024-04-24 08:31:36 -04:00
Dustin J. Mitchell
bc86a1e53f
Release 3.0.2 (#3394) 2024-04-23 00:18:38 +00:00
Dustin J. Mitchell
9b35ab37aa
Remove debug print (#3389) 2024-04-22 20:01:26 -04:00
Dustin J. Mitchell
a9995808ec
Update cmake support for git submodules (#3383) 2024-04-21 08:55:38 -04:00
740 changed files with 31900 additions and 53933 deletions

View file

@ -1,2 +0,0 @@
[alias]
xtask = "run --package xtask --"

5
.clang-format Normal file
View file

@ -0,0 +1,5 @@
---
Language: Cpp
BasedOnStyle: Google
ColumnLimit: 100
...

View file

@ -10,11 +10,11 @@ RUN if [ "${REINSTALL_CMAKE_VERSION_FROM_SOURCE}" != "none" ]; then \
fi \
&& rm -f /tmp/reinstall-cmake.sh
RUN sudo apt update && sudo apt install uuid-dev
RUN sudo apt-get update && sudo apt-get install uuid-dev faketime
# [Optional] Uncomment this section to install additional vcpkg ports.
# RUN su vscode -c "${VCPKG_ROOT}/vcpkg install <your-port-name-here>"
# [Optional] Uncomment this section to install additional packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
# && apt-get -y install --no-install-recommends <your-package-list-here>

1
.dockerignore Normal file
View file

@ -0,0 +1 @@
target

2
.git-blame-ignore-revs Normal file
View file

@ -0,0 +1,2 @@
# initial bulk run of formatting with pre-commit
93356b39c3086fdf8dd41d7357bb1c115ff69cb1

3
.github/CODEOWNERS vendored
View file

@ -1,3 +0,0 @@
taskchampion/* @dbr @djmitche
Cargo.toml @dbr @djmitche
Cargo.lock @dbr @djmitche

View file

@ -5,6 +5,11 @@ updates:
directory: "/"
schedule:
interval: "weekly"
# Enable version updates for git submodules
- package-ecosystem: "gitsubmodule"
directory: "/"
schedule:
interval: "daily"
# Enable updates for Rust packages
- package-ecosystem: "cargo"
directory: "/" # Location of package manifests

View file

@ -9,4 +9,4 @@
* Clearly describe the feature.
* Clearly state the use case. We are only interested in use cases, do not waste time with implementation details or suggested syntax.
* Please see our notes on [How to request a feature](https://taskwarrior.org/docs/features.html)
* Please see our notes on [How to request a feature](https://taskwarrior.org/docs/features/)

View file

@ -1,11 +0,0 @@
#### Description
Replace this text with a description of the PR.
#### Additional information...
- [ ] I changed C++ code or build infrastructure.
Please run the test suite and include the output of `cd test && ./problems`.
- [ ] I changed Rust code or build infrastructure.
Please run `cargo test` and address any failures before submitting.

View file

@ -15,22 +15,25 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
id: toolchain
with:
# If this version is old enough to cause errors, or older than the
# TaskChampion MSRV, bump it to the MSRV of the currently-required
# TaskChampion package; if necessary, bump that version as well.
toolchain: "1.81.0" # MSRV
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
key: ${{ runner.os }}-cargo-registry-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/toolchain@v1
with:
toolchain: "1.70.0" # MSRV
override: true
key: ${{ runner.os }}-cargo-build-target-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/cargo@v1.0.3
with:
@ -44,72 +47,34 @@ jobs:
args: --all-features
name: "Clippy Results"
mdbook:
runs-on: ubuntu-latest
name: "Documentation"
steps:
- uses: actions/checkout@v4
- name: Setup mdBook
uses: peaceiris/actions-mdbook@v2
with:
# if this changes, change it in .github/workflows/publish-docs.yml as well
mdbook-version: '0.4.10'
- run: mdbook test taskchampion/docs
- run: mdbook build taskchampion/docs
fmt:
runs-on: ubuntu-latest
name: "Formatting"
steps:
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
- uses: dtolnay/rust-toolchain@master
id: toolchain
with:
profile: minimal
components: rustfmt
toolchain: stable
override: true
toolchain: "stable"
components: "rustfmt"
- uses: actions-rs/cargo@v1.0.3
with:
command: fmt
args: --all -- --check
codegen:
cargo-metadata:
runs-on: ubuntu-latest
name: "codegen"
name: "Cargo Metadata"
steps:
- uses: actions/checkout@v4
- name: Cache cargo registry
uses: actions/cache@v4
- uses: dtolnay/rust-toolchain@master
id: toolchain
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
toolchain: "stable"
components: "rustfmt"
- name: Cache cargo build
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/toolchain@v1
with:
toolchain: "1.70.0" # MSRV
override: true
- uses: actions-rs/cargo@v1.0.3
with:
command: run
args: --package xtask -- codegen
- name: check for changes
run: |
if ! git diff; then
echo "Generated code not up-to-date;
run `cargo run --package xtask -- codegen` and commit the result";
exit 1;
fi
- name: "Check metadata"
run: ".github/workflows/metadata-check.sh"

View file

@ -23,31 +23,35 @@ jobs:
id-token: write
steps:
- name: Create lowercase repository name
run: |
GHCR_REPOSITORY="${{ github.repository_owner }}"
echo "REPOSITORY=${GHCR_REPOSITORY,,}" >> ${GITHUB_ENV}
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: "recursive"
- name: Install cosign
uses: sigstore/cosign-installer@v3.5.0
uses: sigstore/cosign-installer@v3.9.0
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@v3.1.0
uses: docker/login-action@v3.4.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Taskwarrior Docker image
id: build-and-push
uses: docker/build-push-action@v5.3.0
uses: docker/build-push-action@v6.18.0
with:
context: .
file: "./docker/task.dockerfile"
push: true
tags: ${{ env.REGISTRY }}/${{ github.actor }}/task:${{ github.ref_name }}
tags: ${{ env.REGISTRY }}/${{ env.REPOSITORY }}/task:${{ github.ref_name }}
- name: Sign the published Docker image
env:
COSIGN_EXPERIMENTAL: "true"
run: cosign sign ${{ env.REGISTRY }}/${{ github.actor }}/task@${{ steps.build-and-push.outputs.digest }}
run: cosign sign ${{ env.REGISTRY }}/${{ env.REPOSITORY }}/task@${{ steps.build-and-push.outputs.digest }}

32
.github/workflows/metadata-check.sh vendored Executable file
View file

@ -0,0 +1,32 @@
#! /bin/bash
# Check the 'cargo metadata' for various requirements
set -e
META=$(mktemp)
trap 'rm -rf -- "${META}"' EXIT
cargo metadata --locked --format-version 1 > "${META}"
get_msrv() {
local package="${1}"
jq -r '.packages[] | select(.name == "'"${package}"'") | .rust_version' "${META}"
}
check_msrv() {
local taskchampion_msrv=$(get_msrv taskchampion)
local taskchampion_lib_msrv=$(get_msrv taskchampion-lib)
echo "Found taskchampion MSRV ${taskchampion_msrv}"
echo "Found taskchampion-lib MSRV ${taskchampion_lib_msrv}"
if [ "${taskchampion_msrv}" != "${taskchampion_lib_msrv}" ]; then
echo "Those MSRVs should be the same (or taskchampion-lib should be greater, in which case adjust this script)"
exit 1
else
echo "✓ MSRVs are at the same version."
fi
}
check_msrv

View file

@ -1,31 +0,0 @@
name: docs
on:
push:
branches:
- develop
permissions:
contents: write
jobs:
mdbook-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup mdBook
uses: peaceiris/actions-mdbook@v2
with:
# if this changes, change it in .github/workflows/checks.yml as well
mdbook-version: '0.4.10'
- run: mdbook build taskchampion/docs
- name: Deploy
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./taskchampion/docs/book
destination_dir: taskchampion

28
.github/workflows/release-check.yaml vendored Normal file
View file

@ -0,0 +1,28 @@
name: release-tests
on: [push, pull_request]
jobs:
check-tarball:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
id: toolchain
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }}
- name: Install uuid-dev
run: sudo apt install uuid-dev
- name: make a release tarball and build from it
run: |
cmake -S. -Bbuild &&
make -Cbuild package_source &&
tar -xf build/task-*.tar.gz &&
cd task-*.*.* &&
cmake -S. -Bbuild &&
cmake --build build --target task_executable

View file

@ -1,83 +0,0 @@
name: tests - rust
on:
push:
branches:
- develop
pull_request:
types: [opened, reopened, synchronize]
jobs:
## Run the `taskchampion` crate's tests with various combinations of features.
features:
strategy:
matrix:
features:
- ""
- "server-sync"
name: "taskchampion ${{ matrix.features == '' && 'with no features' || format('with features {0}', matrix.features) }}"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ubuntu-latest-stable-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v4
with:
path: target
key: ubuntu-latest-stable-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: test
run: cargo test -p taskchampion --no-default-features --features "${{ matrix.features }}"
## Run all TaskChampion crate tests, using both the minimum supported rust version
## and the latest stable Rust.
test:
strategy:
matrix:
rust:
- "1.70.0" # MSRV
- "stable"
os:
- ubuntu-latest
- macOS-latest
- windows-latest
name: "rust ${{ matrix.rust }} on ${{ matrix.os }}"
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-${{ matrix.rust }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-${{ matrix.rust }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/toolchain@v1
with:
toolchain: "${{ matrix.rust }}"
override: true
- name: test
run: cargo test

View file

@ -1,77 +1,138 @@
## Run the Taskwarrior tests, using stable rust to build TaskChampion.
name: tests
on: [push, pull_request]
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- name: Install apt packages
run: sudo apt-get install -y build-essential cmake git uuid-dev faketime locales python3 curl gcovr ninja-build
- name: Check out this repository
uses: actions/checkout@v4.1.6
- name: Configure project
run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS=--coverage
- name: Build project
run: cmake --build build --target test_runner --target task_executable
- name: Test project
run: ctest --test-dir build -j 8 --output-on-failure
- name: Generate a code coverage report
uses: threeal/gcovr-action@v1.1.0
with:
coveralls-out: coverage.coveralls.json
excludes: |
build
- name: Sent to Coveralls
uses: coverallsapp/github-action@v2
with:
file: coverage.coveralls.json
# MacOS tests do not run in Docker, and use the actions-rs Rust installaction
tests-macos-12:
needs: coverage
name: tests (Mac OS 12.latest)
if: false # see #3242
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
id: toolchain
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-stable-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
key: ${{ runner.os }}-cargo-registry-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-stable-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/toolchain@v1
with:
toolchain: "stable"
override: true
key: ${{ runner.os }}-cargo-build-target-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }}
- name: Test MacOS
run: bash test/scripts/test_macos.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tests-macos-13:
needs: coverage
name: tests (Mac OS 13.latest)
if: false # see #3242
runs-on: macos-13
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
id: toolchain
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-stable-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
key: ${{ runner.os }}-cargo-registry-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-stable-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/toolchain@v1
with:
toolchain: "stable"
override: true
key: ${{ runner.os }}-cargo-build-target-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }}
- name: Test MacOS
run: bash test/scripts/test_macos.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
cargo-test:
runs-on: ubuntu-latest
name: "Cargo Test"
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
id: toolchain
with:
# If this version is old enough to cause errors, or older than the
# TaskChampion MSRV, bump it to the MSRV of the currently-required
# TaskChampion package; if necessary, bump that version as well.
toolchain: "1.81.0" # MSRV
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/cargo@v1.0.3
with:
command: test
tests:
needs: coverage
strategy:
fail-fast: false
matrix:
include:
- name: "Fedora 38"
- name: "Fedora 40"
runner: ubuntu-latest
dockerfile: fedora38
- name: "Fedora 39"
dockerfile: fedora40
- name: "Fedora 41"
runner: ubuntu-latest
dockerfile: fedora39
dockerfile: fedora41
- name: "Debian Testing"
runner: ubuntu-latest
dockerfile: debiantesting
@ -99,10 +160,10 @@ jobs:
GITHUB_USER: ${{ github.actor }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONTAINER: ${{ matrix.dockerfile }}
run: docker-compose build test-$CONTAINER
run: docker compose build test-${{ env.CONTAINER }}
- name: Test ${{ matrix.name }}
run: docker-compose run test-$CONTAINER
run: docker compose run test-${{ env.CONTAINER }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONTAINER: ${{ matrix.dockerfile }}

6
.gitignore vendored
View file

@ -1,14 +1,12 @@
cmake.h
commit.h
.cache
*~
.*.swp
Session.vim
package-config/osx/binary/task
/build*/
install_manifest.txt
_CPack_Packages
/*build*/
patches
*.exe
tutorials
.prove
/target/

4
.gitmodules vendored
View file

@ -1,6 +1,6 @@
[submodule "src/libshared"]
path = src/libshared
url = https://github.com/GothenburgBitFactory/libshared.git
[submodule "src/tc/corrosion"]
path = src/tc/corrosion
[submodule "src/taskchampion-cpp/corrosion"]
path = src/taskchampion-cpp/corrosion
url = https://github.com/corrosion-rs/corrosion.git

19
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,19 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v20.1.6
hooks:
- id: clang-format
types_or: [c++, c]
- repo: https://github.com/psf/black
rev: 25.1.0
hooks:
- id: black

View file

@ -1,9 +1,10 @@
cmake_minimum_required (VERSION 3.22)
enable_testing()
set (CMAKE_EXPORT_COMPILE_COMMANDS ON)
project (task
VERSION 3.0.1
VERSION 3.4.1
DESCRIPTION "Taskwarrior - a command-line TODO list manager"
HOMEPAGE_URL https://taskwarrior.org/)
@ -24,19 +25,19 @@ if (ENABLE_WASM)
set(CMAKE_EXECUTABLE_SUFFIX ".js")
endif (ENABLE_WASM)
message ("-- Looking for libshared")
if (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src)
message ("-- Found libshared")
message ("-- Looking for git submodules")
if (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src AND EXISTS ${CMAKE_SOURCE_DIR}/src/taskchampion-cpp/corrosion)
message ("-- Found git submodules")
else (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src)
message ("-- Cloning libshared")
message ("-- Cloning git submodules")
execute_process (COMMAND git submodule update --init
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
endif (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src)
endif (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src AND EXISTS ${CMAKE_SOURCE_DIR}/src/taskchampion-cpp/corrosion)
message ("-- Looking for SHA1 references")
if (EXISTS ${CMAKE_SOURCE_DIR}/.git/index)
set (HAVE_COMMIT true)
execute_process (COMMAND git log -1 --pretty=format:%h
execute_process (COMMAND git log -1 --pretty=format:%h --no-show-signature
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE COMMIT)
configure_file ( ${CMAKE_SOURCE_DIR}/commit.h.in
@ -66,7 +67,6 @@ SET (TASK_BINDIR bin CACHE STRING "Installation directory for the bi
# rust libs require these
set (TASK_LIBRARIES dl pthread)
check_function_exists (timegm HAVE_TIMEGM)
check_function_exists (get_current_dir_name HAVE_GET_CURRENT_DIR_NAME)
check_function_exists (wordexp HAVE_WORDEXP)
@ -141,43 +141,26 @@ configure_file (
add_subdirectory (src)
add_subdirectory (src/commands)
add_subdirectory (src/tc)
add_subdirectory (src/taskchampion-cpp)
add_subdirectory (src/columns)
add_subdirectory (doc)
add_subdirectory (scripts)
if (EXISTS ${CMAKE_SOURCE_DIR}/test)
add_subdirectory (test EXCLUDE_FROM_ALL)
endif (EXISTS ${CMAKE_SOURCE_DIR}/test)
if (EXISTS performance)
if (EXISTS ${CMAKE_SOURCE_DIR}/performance)
add_subdirectory (performance EXCLUDE_FROM_ALL)
endif (EXISTS performance)
endif (EXISTS ${CMAKE_SOURCE_DIR}/performance)
set (doc_FILES ChangeLog README.md INSTALL AUTHORS COPYING LICENSE)
foreach (doc_FILE ${doc_FILES})
install (FILES ${doc_FILE} DESTINATION ${TASK_DOCDIR})
endforeach (doc_FILE)
add_custom_command(OUTPUT run-review
COMMAND docker build -q --build-arg PR=$(PR) --build-arg LIBPR=$(LIBPR) -t taskwarrior-review:$(PR)s$(LIBPR) - < scripts/review-dockerfile
COMMAND docker run --rm --memory 1g --hostname pr-$(PR)s$(LIBPR) -it taskwarrior-review:$(PR)s$(LIBPR) bash || :
)
add_custom_target(review DEPENDS run-review)
add_custom_command(OUTPUT run-reproduce
COMMAND docker build -q --build-arg RELEASE=$(RELEASE) -t taskwarrior-reproduce:$(RELEASE) - < scripts/reproduce-dockerfile
COMMAND docker run --rm --memory 1g --hostname tw-$(RELEASE) -it taskwarrior-reproduce:$(RELEASE) bash || :
)
add_custom_target(reproduce DEPENDS run-reproduce)
add_custom_command(OUTPUT show-problems
COMMAND cd test && ./problems
)
add_custom_target(problems DEPENDS show-problems)
# ---
set (CPACK_SOURCE_GENERATOR "TGZ")
set (CPACK_SOURCE_PACKAGE_FILE_NAME ${PACKAGE_NAME}-${PACKAGE_VERSION})
set (CPACK_SOURCE_IGNORE_FILES "build" "test" "misc/*" "performance" "swp$" "src/lex$" "task-.*.tar.gz"
set (CPACK_SOURCE_IGNORE_FILES "build" "target" "test" "misc/*" "performance" "swp$" "src/lex$" "task-.*.tar.gz"
"commit.h" "cmake.h$" "\\\\.gitmodules" "src/libshared/\\\\.git" ".github/" ".*\\\\.gitignore$" "docker-compose.yml" "\\\\.git/")
include (CPack)

1
CONTRIBUTING.md Symbolic link
View file

@ -0,0 +1 @@
doc/devel/contrib/development.md

3187
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,42 +1,7 @@
[workspace]
members = [
"taskchampion/taskchampion",
"taskchampion/lib",
"taskchampion/integration-tests",
"taskchampion/xtask",
"src/taskchampion-cpp",
]
resolver = "2"
# src/tc/rust is just part of the TW build and not a public crate
exclude = [ "src/tc/rust" ]
# All Rust dependencies are defined here, and then referenced by the
# Cargo.toml's in the members with `foo.workspace = true`.
[workspace.dependencies]
anyhow = "1.0"
byteorder = "1.5"
cc = "1.0.73"
chrono = { version = "^0.4.22", features = ["serde"] }
ffizz-header = "0.5"
flate2 = "1"
google-cloud-storage = { version = "0.15.0", default-features = false, features = ["rustls-tls", "auth"] }
lazy_static = "1"
libc = "0.2.136"
log = "^0.4.17"
pretty_assertions = "1"
proptest = "^1.4.0"
ring = "0.17"
rstest = "0.17"
rusqlite = { version = "0.29", features = ["bundled"] }
serde_json = "^1.0"
serde = { version = "^1.0.147", features = ["derive"] }
strum = "0.25"
strum_macros = "0.25"
tempfile = "3"
tokio = { version = "1", features = ["rt-multi-thread"] }
thiserror = "1.0"
ureq = { version = "^2.9.0", features = ["tls"] }
uuid = { version = "^1.8.0", features = ["serde", "v4"] }
url = { version = "2" }

111
ChangeLog
View file

@ -1,5 +1,111 @@
------ current release ---------------------------
3.4.1 -
- The nagging to read `task news` has been fixed.
------ old releases ------------------------------
3.4.0 -
- Where possible, the task DB is now opened in read-only mode, which improves
performance. This is the case for reports (task lists) only when the `gc`
config is false.
- An updated version of corrosion fixes build errors trying to find the Rust
toolchain.
Thanks to the following people for contributions to this release:
- Dustin J. Mitchell
- Kalle Kietäväinen
- Karl
- Matthew
- Tejada-Omar
- Tobias Predel
- Yong Li
- jrmarino
3.3.0 -
- Sync now supports AWS S3 as a backend.
- A new `task import-v2` command allows importing Taskwarrior-2.x
data files directly.
Thanks to the following people for contributions to this release:
- Chongyun Lee
- David Tolnay
- Dustin J. Mitchell
- Felix Schurk
- geoffpaulsen
- Kalle Kietäväinen
- Kursat Aktas
- Scott Mcdermott
- Thomas Lauf
3.2.0 -
- Support for the journal in `task info` has been restored (#3671) and the
task info output no longer contains `tag_` values (#3619).
- The `rc.weekstart` value now affects calculation of week numbers in
expressions like `2013-W49` (#3654).
- Build-time flag `ENABLE_TLS_NATIVE_ROOTS` will cause `task sync` to use the
system TLS roots instead of its built-in roots to authenticate the server (#3660).
- The output from `task undo` is now more human-readable. The `undo.style`
configuraiton option, which has had no effect since 3.0.0, is now removed (3672).
- Fetching pending tasks is now more efficient (#3661).
Thanks to the following people for contributions to this release:
- Denis Zh.
- Dustin J. Mitchell
- Fredrik Lanker
- Gagan Nagaraj
- Jan Christian Grünhage
- Scott Mcdermott
- Thomas Lauf
- Tobias Predel
3.1.0 -
- Support for `task purge` has been restored, and new support added for automatically
expiring old tasks. (#3540, #3546, #3556)
- `task news` is now better behaved, and can be completely disabled.
- Multiple imports of the same UUID will now generate a warning. (#3560)
- The `sync.server.url` config replaces `sync.server.origin` and allows a URL
containing a path. (#3423)
- The new `bubblegum-256.theme` has improved legibility and contrast over
others. (#3505)
- Warnings regarding `.data` files are only show for reports. (#3473)
- Inherited urgency is correctly calculated to make parents more urgent than
children (#2941)
- Task completion commands no longer trigger hooks (#3133)
Thanks to the following people for contributions to this release:
- Adrian Galilea
- Adrian Sadłocha
- Andonome
- Christian Clauss
- Dominik Rehák
- Dustin J. Mitchell
- Felix Schurk
- Hector Dearman
- Joseph Coffa
- koleesch
- Maarten Aertsen
- mattsmida
- Philipp Oberdiek
- Sebastian Carlos
- sleepy_nols
- Steve Dondley
- Will R S Hansen
3.0.2 -
- Fix an accidentally-included debug print which polluted output of
reports with the Taskwarrior version (#3389)
3.0.1 -
- Fix an error in creation of the 3.0.0 tarball which caused builds to fail (#3302)
@ -8,8 +114,6 @@
- Fix incorrect task ID of 0 when using hooks (#3339)
- Issue a warning if .data files remain (#3321)
------ old releases ------------------------------
3.0.0 -
- [BREAKING CHANGE] the sync functionality has been rewritten entirely, and
@ -38,7 +142,7 @@
- Akash Shanmugaraj
- Andrew Savchenko
- Dathan Bennett
- Dathan Bennett
- Dustin Mitchell
- dbr/Ben
- Felix Schurk
@ -2779,4 +2883,3 @@ regular usage to determine which features were needed or unnecessary.]
- Usage.
------ start -----------------------------------

63
INSTALL
View file

@ -21,18 +21,20 @@ You will need a C++ compiler that supports full C++17, which includes:
You will need the following libraries:
- libuuid (not needed for OSX)
You will need a Rust toolchain of the Minimum Supported Rust Version (MSRV):
- rust 1.81.0
Basic Installation
------------------
Briefly, these shell commands will unpack, build and install Taskwarrior:
$ tar xzf task-X.Y.Z.tar.gz [1]
$ cd task-X.Y.Z [2]
$ cmake -DCMAKE_BUILD_TYPE=release . [3]
$ make [4]
$ sudo make install [5]
$ cd .. ; rm -r task-X.Y.Z [6]
$ tar xzf task-X.Y.Z.tar.gz [1]
$ cd task-X.Y.Z [2]
$ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release . [3]
$ cmake --build build [4]
$ sudo cmake --install build [5]
$ cd .. ; rm -r task-X.Y.Z [6] (see: Uninstallation)
These commands are explained below:
@ -87,6 +89,11 @@ get absolute installation directories:
CMAKE_INSTALL_PREFIX/TASK_MAN1DIR /usr/local/share/man/man1
CMAKE_INSTALL_PREFIX/TASK_MAN5DIR /usr/local/share/man/man5
The following variables control aspects of the build process:
SYSTEM_CORROSION - Use system provided corrosion instead of vendored version
ENABLE_TLS_NATIVE_ROOTS - Use the system's TLS root certificates
Uninstallation
--------------
@ -96,6 +103,13 @@ There is no uninstall option in CMake makefiles. This is a manual process.
To uninstall Taskwarrior, remove the files listed in the install_manifest.txt
file that was generated when you built Taskwarrior.
```sh
cd task-X.Y.Z
sudo xargs rm < build/install_manifest.txt
```
If you want to uninstall this way, you will need to omit step [6] above and
retain the source folder after installation.
Taskwarrior Build Notes
-----------------------
@ -108,6 +122,43 @@ If Taskwarrior will not build on your system, first take a look at the Operating
System notes below. If this doesn't help, then go to the Troubleshooting
section, which includes instructions on how to contact us for help.
Offline Build Notes
-------------------
It is common for packaging systems (e.g. NixOS, FreeBSD Ports Collection, pkgsrc, etc)
to disable networking during builds. This restriction requires all distribution files
to be prepositioned after checksum verification as a prerequisite for the build. The
following steps have been successful in allowing Taskwarrior to be built in this
environment:
1. Extract all crates in a known location, e.g. ${WRKDIR}/cargo-crates
This includes crates needed for corrosion (search for Cargo.lock files)
2. Create .cargo-checksum.json for each crate
For example:
printf '{"package":"%s","files":{}}' $(sha256 -q ${DISTDIR}/rayon-core-1.12.1.tar.gz) \
> ${WRKDIR}/cargo-crates/rayon-core-1.12.1/.cargo-checksum.json
3. Create a custom config.toml file
For example, ${WRKDIR}/.cargo/config.toml
[source.cargo]
directory = '${WRKDIR}/cargo-crates'
[source.crates-io]
replace-with = 'cargo'
4. After running cmake, configure cargo
For example:
cd ${WRKSRC} && ${SETENV} ${MAKE_ENV} ${CARGO_ENV} \
/usr/local/bin/cargo update \
--manifest-path ${WRKDIR}/.cargo/config.toml \
--verbose
5. Set CARGO_HOME in environment
For example
CARGO_HOME=${WRKDIR}/.cargo
The build and installation steps should be the same as a standard build
at this point.
Operating System Notes
----------------------

View file

@ -21,4 +21,3 @@ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -2,9 +2,11 @@
<img src="https://avatars.githubusercontent.com/u/36100920?s=200&u=24da05914c20c4ccfe8485310f7b83049407fa9a&v=4"></br>
[![GitHub Actions build status](https://github.com/GothenburgBitFactory/taskwarrior/workflows/tests/badge.svg?branch=develop)](https://github.com/GothenburgBitFactory/taskwarrior/actions)
[![Coverage Status](https://coveralls.io/repos/github/GothenburgBitFactory/taskwarrior/badge.svg?branch=develop)](https://coveralls.io/github/GothenburgBitFactory/taskwarrior?branch=develop)
[![Release](https://img.shields.io/github/v/release/GothenburgBitFactory/taskwarrior)](https://github.com/GothenburgBitFactory/taskwarrior/releases/latest)
[![Release date](https://img.shields.io/github/release-date/GothenburgBitFactory/taskwarrior)](https://github.com/GothenburgBitFactory/taskwarrior/releases/latest)
[![GitHub Sponsors](https://img.shields.io/github/sponsors/GothenburgBitFactory?color=green)](https://github.com/sponsors/GothenburgBitFactory/)
[![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20Taskwarrior%20Guru-006BFF)](https://gurubase.io/g/taskwarrior)
</br>
</div>
@ -38,11 +40,9 @@ The [online documentation](https://taskwarrior.org/docs), downloads, news and
more are available on our website, [taskwarrior.org](https://taskwarrior.org).
## Community
[![Twitter](https://img.shields.io/twitter/follow/taskwarrior?style=social)](https://twitter.com/taskwarrior)
[![Reddit](https://img.shields.io/reddit/subreddit-subscribers/taskwarrior?style=social)](https://reddit.com/r/taskwarrior/)
[![Libera.chat](https://img.shields.io/badge/IRC%20libera.chat-online-green)](https://web.libera.chat/#taskwarrior)
[![Discord](https://img.shields.io/discord/796949983734661191?label=discord)](https://discord.gg/eRXEHk8w62)
[![Github discussions](https://img.shields.io/github/discussions/GothenburgBitFactory/taskwarrior?label=GitHub%20discussions)](https://github.com/GothenburgBitFactory/taskwarrior/discussions)
[![Reddit](https://img.shields.io/reddit/subreddit-subscribers/taskwarrior?style=social)](https://reddit.com/r/taskwarrior/)
Taskwarrior has a lively community on many places on the internet.

1
RELEASING.md Symbolic link
View file

@ -0,0 +1 @@
doc/devel/contrib/releasing.md

13
SECURITY.md Normal file
View file

@ -0,0 +1,13 @@
# Security
To report a vulnerability, please contact Dustin via signal, [`djmitche.78`](https://signal.me/#eu/2T98jpkMAzvFL2wg3OkZnNrfhk1DFfu6eqkMEPqcAuCsLZPVk39A67rp4khmrMNF).
Initial response is expected within ~48h.
We kindly ask to follow the responsible disclosure model and refrain from sharing information until:
1. Vulnerabilities are patched in Taskwarrior + 60 days to coordinate with distributions.
2. 90 days since the vulnerability is disclosed to us.
We recognise the legitimacy of public interest and accept that security researchers can publish information after 90-days deadline unilaterally.
We will assist with obtaining CVE and acknowledge the vulnerabilities reported.

View file

@ -41,9 +41,6 @@
/* Found tm_gmtoff */
#cmakedefine HAVE_TM_GMTOFF
/* Found timegm */
#cmakedefine HAVE_TIMEGM
/* Found st.st_birthtime struct member */
#cmakedefine HAVE_ST_BIRTHTIME
@ -58,4 +55,3 @@
/* Undefine this to eliminate the execute command */
#define HAVE_EXECUTE 1

View file

@ -6,7 +6,8 @@ include (CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("-std=c++17" _HAS_CXX17)
if (_HAS_CXX17)
set (_CXX14_FLAGS "-std=c++17")
set (CMAKE_CXX_STANDARD 17)
set (CMAKE_CXX_EXTENSIONS OFF)
else (_HAS_CXX17)
message (FATAL_ERROR "C++17 support missing. Try upgrading your C++ compiler. If you have a good reason for using an outdated compiler, please let us know at support@gothenburgbitfactory.org.")
endif (_HAS_CXX17)
@ -32,7 +33,7 @@ elseif (${CMAKE_SYSTEM_NAME} STREQUAL "GNU")
set (GNUHURD true)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "CYGWIN")
set (CYGWIN true)
set (_CXX14_FLAGS "-std=gnu++17")
set (CMAKE_CXX_EXTENSIONS ON)
else (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
set (UNKNOWN true)
endif (${CMAKE_SYSTEM_NAME} MATCHES "Linux")

View file

@ -11,10 +11,6 @@ For all other documenation, see https://taskwarrior.org.
As of the 3.0 release, Taskwarrior uses TaskChampion to manage task data.
Find documentation of TaskChampion here:
* [TaskChampion README](../../taskchampion)
* [TaskChampion CONTRIBUTING guide](../../taskchampion/CONTRIBUTING.md)
* [TaskChampion Book](../../taskchampion/docs/src/SUMMARY.md)
* [TaskChampion Repository](https://github.com/GothenburgBitFactory/taskchampion/)
* [TaskChampion Book](https://gothenburgbitfactory.github.io/taskchampion/)
* [TaskChampion API Documentation](https://docs.rs/taskchampion)
TaskChampion will [become its own
project](https://github.com/GothenburgBitFactory/taskwarrior/issues/3209) soon.

View file

@ -1,30 +1,18 @@
# Coding Style
The coding style used for the Taskwarrior, Taskserver, and other codebases is deliberately kept simple and a little vague.
This is because there are many languages involved (C++, C, Python, sh, bash, HTML, troff and more), and specіfying those would be a major effort that detracts from the main focus which is improving the software.
The coding style used for the Taskwarrior is based on the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html), with small modifications in the line length.
Instead, the general guideline is simply this:
# Automatic formatting
Make all changes and additions such that they blend in perfectly with the surrounding code, so it looks like only one person worked on the source, and that person is rigidly consistent.
In order to have consistancy and automatic formatting [pre-commit](https://pre-commit.com) is used to apply [clang-format](https://clang.llvm.org/docs/ClangFormat.html) and [black](https://github.com/psf/black) are used.
In order to set them up locally please run:
```python
pip install pre-commit
pre-commit install
```
To be a little more explicit:
## C++
- All functionality in C++17 is allowed.
- Indent C++ code using two spaces, no tabs
- Surround operators and expression terms with a space.
This includes a space between a function name and its list of arguments.
- No cuddled braces
- Class names are capitalized, variable names are not
## Python
Follow [PEP8](https://www.python.org/dev/peps/pep-0008/) as much as possible.
For more information refer to the [quick-start of pre-commit](https://pre-commit.com/#quick-start).
The setup is also included in the CI, hence if one can not install it automatically then the CI will take care for it in the PR.
## Rust

View file

@ -1,21 +1,15 @@
# Developing Taskwarrior
The following describes the process for developing Taskwarrior. If you are only
changing TaskChampion (Rust code), you can simply treat it like any other Rust
project: modify the source under `taskchampion/` and use `cargo test` to run
the TaskChampion tests.
See the [TaskChampion CONTRIBUTING guide](../../../taskchampion/CONTRIBUTING.md) for more.
## Satisfy the Requirements:
* CMake 3.0 or later
* CMake 3.22 or later
* gcc 7.0 or later, clang 6.0 or later, or a compiler with full C++17 support
* libuuid (if not on macOS)
* Rust 1.64.0 or higher (hint: use https://rustup.rs/ instead of using your system's package manager)
## Install Optional Dependencies:
* python 3 (for running the test suite)
* pre-commit (for applying formatting changes locally)
* clangd or ccls (for C++ integration in many editors)
* rust-analyzer (for Rust integration in many editors)
@ -56,24 +50,57 @@ cmake --build build-clang
```
## Run the Test Suite:
First switch to the test directory:
For running the test suite [ctest](https://cmake.org/cmake/help/latest/manual/ctest.1.html) is used.
Before one can run the test suite the `task_executable` must be built.
After that also the `test_runner` target must be build, which can be done over:
```sh
cmake --build build --target test_runner
```
Again you may also use the `-j <number-of-jobs>` option for parallel builds.
Now one can invoke `ctest` to run the tests.
```sh
ctest --test-dir build
```
$ cd build/test
This would run all the test in serial and might take some time.
### Running tests in parallel
```sh
ctest --test-dir build -j <number-of-jobs>
```
Then you can run all tests, showing details, with
Further it is adviced to add the `--output-on-failure` option to `ctest`, to recieve a verbose output if a test is failing as well as the `--rerun-failed` flag, to invoke in subsequent runs only the failed ones.
### Running specific tests
For this case one can use the `-R <regex>` or `--tests-regex <regex>` option to run only the tests matching the regular expression.
Running only the `cpp` tests can then be achieved over
```sh
ctest --test-dir build -R cpp
```
$ make VERBOSE=1
or running the `variant_*` tests
```sh
ctest --test-dir build -R variant
```
Alternately, run the tests with the details hidden in `all.log`:
```
$ ./run_all
```
Either way, you can get a summary of any test failures with:
```
$ ./problems
### Repeating a test case
In order to find sporadic test failures the `--repeat` flag can be used.
```sh
ctest --test-dir build -R cpp --repeat-until-fail 10
```
There are more options to `ctest` such as `--progress`, allowing to have a less verbose output.
They can be found in the [ctest](https://cmake.org/cmake/help/latest/manual/ctest.1.html) man page.
Note that any development should be performed using a git clone, and the current development branch.
The source tarballs do not reflect HEAD, and do not contain the test suite.
Follow the [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow) for creating a pull request.
## Using a Custom Version of TaskChampion
To build against a different version of Taskchampion, modify the requirement in `src/taskchampion-cpp/Cargo.toml`.
To build from a local checkout, replace the version with a [path dependency](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-path-dependencies), giving the path to the directory containing TaskChampion's `Cargo.toml`:
```toml
taskchampion = { path = "path/to/taskchampion" }
```

View file

@ -11,7 +11,6 @@ To release Taskwarrior, follow this process:
- On `develop` after that PR merges, create a release tarball:
- `git clone . release-tarball`
- `cd release-tarball/`
- edit `Cargo.toml` to contain only `taskchampion` and `taskchampion-lib` in `members` (see https://github.com/GothenburgBitFactory/taskwarrior/issues/3294).
- `cmake -S. -Bbuild`
- `make -Cbuild package_source`
- copy build/task-*.tar.gz elsewhere and delete the `release-tarball` dir
@ -21,4 +20,10 @@ To release Taskwarrior, follow this process:
- Find the tag under https://github.com/GothenburgBitFactory/taskwarrior/tags and create a release from it
- Give it a clever title if you can think of one; refer to previous releases
- Include the tarball from earlier
- Add a new item in `content/news` on https://github.com/GothenburgBitFactory/tw.org
- Update https://github.com/GothenburgBitFactory/tw.org
- Add a new item in `content/news`
- Update `data/projects.json` with the latest version and a fake next version for "devel"
- Update `data/releases.json` with the new version, and copy the tarball into `content/download`.
- Update various things, in a new PR:
- `cargo update`
- `git submodule update --remote --merge`

View file

@ -1,42 +1,18 @@
# Rust and C++
Taskwarrior has historically been a C++ project, but as part of an [ongoing effort to replace its storage backend](https://github.com/GothenburgBitFactory/taskwarrior/issues/2770) it now includes a Rust library called TaskChampion.
To develop Taskwarrior, you will need both a [C++ compiler and tools](./development.md), and a [Rust toolchain](https://www.rust-lang.org/tools/install).
However, most tasks will only require you to be familiar with one of the two languages.
Taskwarrior has historically been a C++ project, but as of taskwarrior-3.0.0, the storage backend is now provided by a Rust library called TaskChampion.
## TaskChampion
TaskChampion implements storage and access to "replicas" containing a user's tasks.
It defines an abstract model for this data, and also provides a simple Rust API for manipulating replicas.
It also defines a method of synchronizing replicas and provides an implementation of that method in the form of a sync server.
TaskChampion provides a C interface via the `taskchampion-lib` crate.
Other applications, besides Taskwarrior, can use TaskChampion to manage tasks.
Applications written in Rust can use the `taskchampion` crate, while those in other languages may use the `taskchampion-lib` crate.
Taskwarrior is just one application using the TaskChampion interface.
You can build TaskChampion locally by simply running `cargo build` in the root of this repository.
The implementation, including more documentation, is in the [`rust`](../../rust) subdirectory.
## Taskwarrior's use of TaskChampion
Taskwarrior's interface to TaskChampion has a few layers:
* The skeletal Rust crate in [`src/tc/rust`](../../src/tc/rust) brings the symbols from `taskchampion-lib` under CMake's management.
The corresponding header file is included from [`taskchampion/lib`](../../taskchampion/lib).
All of these symbols are placed in the C++ namespace, `tc::ffi`.
* C++ wrappers for the types from `taskchampion-lib` are defined in [`src/tc`](../../src/tc), ensuring memory safety (with `unique_ptr`) and adding methods corresponding to the Rust API's methods.
The wrapper types are in the C++ namespace, `tc`.
## WARNING About Dependency Tracking
CMake cannot detect changes to Rust files in under the `taskchampion/` directory.
Running `make` after these files are changed will not incorporate the changes into the resulting executables.
To force re-compilation of the Rust dependencies:
```
rm -rf src/tc/rust/x86_64-unknown-linux-gnu/debug/libtc_rust.a
make
```
You may need to adjust the `x86_64-unknown-linux-gnu` part of that command depending on what system you are using for development.
Taskwarrior's interface to TaskChampion is in `src/taskchampion-cpp`.
This links to `taskchampion` as a Rust dependency, and uses [cxx](https://cxx.rs) to build a C++ API for it.
That API is defined, and documented, in `src/taskchampion-cpp/src/lib.rs`, and available in the `tc` namespace in C++ code.

View file

@ -227,14 +227,14 @@ An example is to make two reports share the same description:
$ task config -- report.ls.description rc.report.list.description
This sets the description for the `ls` report to be a reference to the description of the `list` report.
This sets the description for the `ls` report to be a reference to the description of the `list` report.
This reference is not evaluated when the entry is written, but is evaluated every time the value is read, thus providing late-bound behavior.
Then if the description of the `list` report changes, so does that of the `ls` report automatically.
## Implementation Details
These notes list a series of anticipated changes to the codebase.
These notes list a series of anticipated changes to the codebase.
- The `src/columns/Col*` objects will implement type-specific and attribute-specific DOM support.
DOM reference lookup will defer to the column objects first.

View file

@ -25,7 +25,7 @@ In brief: "MUST" (or "REQUIRED") means that the item is an absolute requirement
## General Format
The format is JSON, specifically a JSON object as a single line of text, terminated by a newline (U+000D).
The format is JSON, specifically a JSON object as a single line of text, terminated by a line feed (U+000A).
The JSON looks like this:
@ -466,4 +466,3 @@ Given that the configuration is not present in the JSON format of a task, any fi
This means that if a task contains a UDA, unless the meaning of it is understood, it MUST be preserved.
UDAs may have one of four types: string, numeric, date and duration.

4
doc/man/.gitignore vendored
View file

@ -1,4 +0,0 @@
task-color.5
task-sync.5
task.1
taskrc.5

View file

@ -8,14 +8,18 @@ It should be mentioned that Taskwarrior is aware of whether its output is going
to a terminal, or to a file or through a pipe. When Taskwarrior output goes to
a terminal, color is desirable, but consider the following command:
.nf
$ task list > file.txt
.fi
Do we really want all those color control codes in the file? Taskwarrior
assumes that you do not, and temporarily sets color to 'off' while generating
the output. This explains the output from the following command:
.nf
$ task show | grep '^color '
color off
.fi
it always returns 'off', no matter what the setting, because the output is being
sent to a pipe.
@ -23,20 +27,26 @@ sent to a pipe.
If you wanted those color codes, you can override this behavior by setting the
_forcecolor variable to on, like this:
.nf
$ task config _forcecolor on
$ task config | grep '^color '
color on
.fi
or by temporarily overriding it like this:
.nf
$ task rc._forcecolor=on config | grep '^color '
color on
.fi
.SH AVAILABLE COLORS
Taskwarrior has a 'color' command that will show all the colors it is capable of
displaying. Try this:
.nf
$ task color
.fi
The output cannot be replicated here in a man page, but you should see a set of
color samples. How many you see depends on your terminal program's ability to
@ -48,7 +58,9 @@ You should at least see the Basic colors and Effects - if you do, then you have
.SH 16-COLOR SUPPORT
The basic color support is provided through named colors:
.nf
black, red, blue, green, magenta, cyan, yellow, white
.fi
Foreground color (for text) is simply specified as one of the above colors, or
not specified at all to use the default terminal text color.
@ -56,37 +68,49 @@ not specified at all to use the default terminal text color.
Background color is specified by using the word 'on', and one of the above
colors. Some examples:
.nf
green # green text, default background color
green on yellow # green text, yellow background
on yellow # default text color, yellow background
.fi
These colors can be modified further, by making the foreground bold, or by
making the background bright. Some examples:
.nf
bold green
bold white on bright red
on bright cyan
.fi
The order of the words is not important, so the following are equivalent:
.nf
bold green
green bold
.fi
But the 'on' is important - colors before the 'on' are foreground, and colors
after 'on' are background.
There is an additional 'underline' attribute that may be used:
.nf
underline bold red on black
.fi
And an 'inverse' attribute:
.nf
inverse red
.fi
Taskwarrior has a command that helps you visualize these color combinations.
Try this:
.nf
$ task color underline bold red on black
.fi
You can use this command to see how the various color combinations work. You
will also see some sample colors displayed, like the ones above, in addition to
@ -103,11 +127,13 @@ Using 256 colors follows the same form, but the names are different, and some
colors can be referenced in different ways. First there is by color ordinal,
which is like this:
.nf
color0
color1
color2
...
color255
.fi
This gives you access to all 256 colors, but doesn't help you much. This range
is a combination of 8 basic colors (color0 - color7), then 8 brighter variations
@ -119,31 +145,43 @@ be addressed via RGB values from 0 to 5 for each component color. A value of 0
means none of this component color, and a value of 5 means the most intense
component color. For example, a bright red is specified as:
.nf
rgb500
.fi
And a darker red would be:
.nf
rgb300
.fi
Note that the three digits represent the three component values, so in this
example the 5, 0 and 0 represent red=5, green=0, blue=0. Combining intense red
with no green and no blue yields red. Similarly, blue and green are:
.nf
rgb005
rgb050
.fi
Another example - bright yellow - is a mix of bright red and bright green, but
no blue component, so bright yellow is addressed as:
.nf
rgb550
.fi
A soft pink would be addressed as:
.nf
rgb515
.fi
See if you agree, by running:
.nf
$ task color black on rgb515
.fi
You may notice that the large color block is represented as 6 squares. All
colors in the first square have a red value of 0. All colors in the 6th square
@ -163,7 +201,9 @@ will be disappointed, perhaps even appalled.
There is some limited color mapping - for example, if you were to specify this
combination:
.nf
red on gray3
.fi
you are mixing a 16-color and 256-color specification. Taskwarrior will map red
to color1, and proceed. Note that red and color1 are not quite the same tone.
@ -175,7 +215,9 @@ colors, but there is still underline available.
Taskwarrior will show examples of all defined colors used in your .taskrc, or
theme, if you run this command:
.nf
$ task color legend
.fi
This gives you an example of each of the colors, so you can see the effect,
without necessarily creating a set of tasks that meet each of the rule criteria.
@ -185,20 +227,26 @@ Taskwarrior supports colorization rules. These are configuration values that
specify a color, and the conditions under which that color is used. By example,
let us add a few tasks:
.nf
$ task add project:Home priority:H pay the bills (1)
$ task add project:Home clean the rug (2)
$ task add project:Garden clean out the garage (3)
.fi
We can add a color rule that uses a blue background for all tasks in the Home
project:
.nf
$ task config color.project.Home 'on blue'
.fi
We use quotes around 'on blue' because there are two words, but they represent
one value in the .taskrc file. Now suppose we wish to use a bold yellow text
color for all cleaning work:
.nf
$ task config color.keyword.clean 'bold yellow'
.fi
Now what happens to task 2, which belongs to project Home (blue background), and
is also a cleaning task (bold yellow foreground)? The colors are combined, and
@ -219,7 +267,9 @@ color blending.
The precedence for the color rules is determined by the configuration
variable 'rule.precedence.color', which by default contains:
.nf
deleted,completed,active,keyword.,tag.,project.,overdue,scheduled,due.today,due,blocked,blocking,recurring,tagged,uda.
.fi
These are just the color rules with the 'color.' prefix removed. The
rule 'color.deleted' has the highest precedence, and 'color.uda.' the lowest.
@ -228,7 +278,7 @@ The keyword rule shown here as 'keyword.' corresponds to a wildcard pattern,
meaning 'color.keyword.*', or in other words all the keyword rules.
There is also 'color.project.none', 'color.tag.none' and
'color.uda.priority.none' to specifically represent missing data.
\[aq]color.uda.priority.none' to specifically represent missing data.
.SH THEMES
Taskwarrior supports themes. What this really means is that with the ability to
@ -238,50 +288,38 @@ be included.
To get a good idea of what a color theme looks like, try adding this entry to
your .taskrc file:
.RS
include dark-256.theme
.RE
.nf
include dark-256.theme
.fi
You can use any of the standard Taskwarrior themes:
.RS
dark-16.theme
.br
dark-256.theme
.br
dark-blue-256.theme
.br
dark-gray-256.theme
.br
dark-green-256.theme
.br
dark-red-256.theme
.br
dark-violets-256.theme
.br
dark-yellow-green.theme
.br
light-16.theme
.br
light-256.theme
.br
solarized-dark-256.theme
.br
solarized-light-256.theme
.br
dark-default-16.theme
.br
dark-gray-blue-256.theme
.br
no-color.theme
.RE
.nf
dark-16.theme
dark-256.theme
dark-blue-256.theme
dark-gray-256.theme
dark-green-256.theme
dark-red-256.theme
dark-violets-256.theme
dark-yellow-green.theme
light-16.theme
light-256.theme
solarized-dark-256.theme
solarized-light-256.theme
dark-default-16.theme
dark-gray-blue-256.theme
no-color.theme
.fi
Bear in mind that if you are using a terminal with a dark background, you will
see better results using a dark theme.
You can also see how the theme will color the various tasks with the command:
.nf
$ task color legend
.fi
Better yet, create your own, and share it. We will gladly host the theme file
on <https://taskwarrior.org>.

View file

@ -30,12 +30,11 @@ the existing replica, and run `task sync`.
.SS When to Synchronize
Taskwarrior can perform a sync operation at every garbage collection (gc) run.
This is the default, and is appropriate for local synchronization.
For synchronization to a server, a better solution is to run
For synchronization to a server, a common solution is to run
.nf
$ task sync
.fi
periodically, such as via
.BR cron (8) .
@ -52,7 +51,9 @@ For most of these, you will need an encryption secret used to encrypt and
decrypt your tasks. This can be any secret string, and must match for all
replicas sharing tasks.
.nf
$ task config sync.encryption_secret <encryption_secret>
.fi
Tools such as
.BR pwgen (1)
@ -64,16 +65,22 @@ To synchronize your tasks to a sync server, you will need the following
information from the server administrator:
.br
- The server's URL ("origin", such as "https://tw.example.com")
- The server's URL (such as "https://tw.example.com/path")
.br
- A client ID ("client_id") identifying your tasks
Configure Taskwarrior with these details:
$ task config sync.server.origin <origin>
.nf
$ task config sync.server.url <url>
$ task config sync.server.client_id <client_id>
.fi
Note that the origin must include the scheme, such as 'http://' or 'https://'.
Note that the URL must include the scheme, such as 'http://' or 'https://'.
$ task config sync.server.origin <origin>
Is a deprecated synonym for "sync.server.url".
.SS Google Cloud Platform
@ -83,14 +90,18 @@ the bucket are adequate.
Authenticate to the project with:
.nf
$ gcloud config set project $PROJECT_NAME
$ gcloud auth application-default login
.fi
Then configure Taskwarrior with:
.nf
$ task config sync.gcp.bucket <bucket-name>
.fi
However you can bring your own service account credentials if your
However you can bring your own service account credentials if your
`application-default` is already being used by some other application
To begin, navigate to the "IAM and Admin" section in the Navigation Menu, then select "Roles."
@ -101,30 +112,111 @@ Provide an appropriate name and description for the new role.
Add permissions to your new role using the filter "Service:storage" (not the "Filter permissions by role" input box).
Select the following permissions:
- storage.buckets.create
- storage.buckets.get
- storage.buckets.create
- storage.buckets.get
- storage.buckets.update
- storage.objects.create
- storage.objects.delete
- storage.objects.get
- storage.objects.list
- storage.objects.update
Create your new role.
Create your new role.
On the left sidebar, navigate to "Service accounts."
On the left sidebar, navigate to "Service accounts."
On the top menu bar within the "Service accounts" section, click "CREATE SERVICE ACCOUNT."
Provide an appropriate name and description for the new service account.
Select the role you just created and complete the service account creation process.
Now, in the Service Account dashboard, click into the new service account and select "keys" on the top menu bar.
Click on "ADD KEY" to create and download a new key (a JSON key).
On the top menu bar within the "Service accounts" section, click "CREATE SERVICE ACCOUNT."
Provide an appropriate name and description for the new service account.
Select the role you just created and complete the service account creation process.
Now, in the Service Account dashboard, click into the new service account and select "keys" on the top menu bar.
Click on "ADD KEY" to create and download a new key (a JSON key).
Then configure Taskwarrior with:
.nf
$ task config sync.gcp.bucket <bucket-name>
$ task config sync.gcp.credential_path <absolute-path-to-downloaded-credentials>
$ task config sync.gcp.credential_path <absolute-path-to-downloaded-credentials>
.fi
.SS Amazon Web Services
To synchronize your tasks to AWS, select a region near you and use the AWS
console to create a new S3 bucket. The default settings for the bucket are
adequate. In particular, ensure that no lifecycle policies are enabled, as they
may automatically delete or transition objects, potentially impacting data
availability.
You will also need an AWS IAM user with the following policy, where BUCKETNAME
is the name of the bucket. The same user can be configured for multiple
Taskwarrior clients.
.nf
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "TaskChampion",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::BUCKETNAME",
"arn:aws:s3:::BUCKETNAME/*"
]
}
]
}
.fi
To create such a user, create a new policy in the IAM console, select the JSON
option in the policy editor, and paste the policy. Click "Next" and give the
policy a name such as "TaskwarriorSync". Next, create a new user, with a name
of your choosing, select "Attach Policies Directly", and then choose the
newly-created policy.
You will need access keys configured for the new user. Find the user in the
user list, open the "Security Credentials" tab, then click "Create access key"
and follow the steps.
At this point, you can choose how to provide those credentials to Taskwarrior.
The simplest is to include them in the Taskwarrior configuration:
.nf
$ task config sync.aws.region <region>
$ task config sync.aws.bucket <bucket-name>
$ task config sync.aws.access_key_id <access-key-id>
$ task config sync.aws.secret_access_key <secret-access-key>
.fi
Alternatively, you can set up an AWS CLI profile, using a profile name of your
choosing such as "taskwarrior-creds":
.nf
$ aws configure --profile taskwarrior-creds
.fi
Enter the access key ID and secret access key. The default region and format
are not important. Then configure Taskwarrior with:
.nf
$ task config sync.aws.region <region>
$ task config sync.aws.bucket <bucket-name>
$ task config sync.aws.profile taskwarrior-creds
.fi
To use AWS's default credential sources, such as environment variables, the
default profile, or an instance profile, set
.nf
$ task config sync.aws.region <region>
$ task config sync.aws.bucket <bucket-name>
$ task config sync.aws.default_credentials true
.fi
.SS Local Synchronization
@ -132,7 +224,9 @@ In order to take advantage of synchronization's side effect of saving disk
space without setting up a remote server, it is possible to sync tasks locally.
To configure local sync:
.nf
$ task config sync.local.server_dir /path/to/sync
.fi
The default configuration is to sync to a database in the task directory
("data.location").
@ -153,7 +247,7 @@ To add a new user to the server, invent a new client ID with a tool like
`uuidgen` or an online UUID generator. There is no need to configure the server
for this new client ID: the sync server will automatically create a new user
whenever presented with a new client ID. Supply the ID, along with the
origin, to the user for inclusion in their Taskwarrior config. The user should
URL, to the user for inclusion in their Taskwarrior config. The user should
invent their own "encryption_secret".
.SH AVOIDING DUPLICATE RECURRING TASKS
@ -161,11 +255,15 @@ invent their own "encryption_secret".
If you run multiple clients that sync to the same server, you will need to run
this command on your primary client (the one you use most often):
.nf
$ task config recurrence on
.fi
And on the other clients, run:
.nf
$ task config recurrence off
.fi
This protects you against the effects of a sync/duplication bug.
@ -184,7 +282,9 @@ modifying the same task on two machines, without an intervening sync.
Setup simply involves creating the directory and modifying your data.location
configuration variable like this:
.nf
$ task config data.location /path/to/shared/directory
.fi
Strengths:
.br

View file

@ -24,18 +24,24 @@ descriptors), project groups, etc.
The <filter> consists of zero or more search criteria that select tasks. For
example, to list all pending tasks belonging to the 'Home' project:
.nf
task project:Home list
.fi
You can specify multiple filter terms, each of which further restricts the
result:
.nf
task project:Home +weekend garden list
.fi
This example applies three filters: the 'Home' project, the 'weekend' tag, and
the description or annotations must contain the character sequence 'garden'.
In this example, 'garden' is translated internally to:
.nf
description.contains:garden
.fi
as a convenient shortcut. The 'contains' here is an attribute modifier, which
is used to exert more control over the filter than simply absence or presence.
@ -45,24 +51,30 @@ Note that a filter may have zero terms, which means that all tasks apply to the
command. This can be dangerous, and this special case is confirmed, and
cannot be overridden. For example, this command:
.nf
task modify +work
This command has no filter, and will modify all tasks. Are you sure? (yes/no)
.fi
will add the 'work' tag to all tasks, but only after confirmation.
More filter examples:
.nf
task <command> <mods>
task 28 <command> <mods>
task +weekend <command> <mods>
task +bills due.by:eom <command> <mods>
task project:Home due.before:today <command> <mods>
task ebeeab00-ccf8-464b-8b58-f7f2d606edfb <command> <mods>
.fi
By default filter elements are combined with an implicit 'and' operator,
but 'or' and 'xor' may also be used, provided parentheses are included:
.nf
task '( /[Cc]at|[Dd]og/ or /[0-9]+/ )' <command> <mods>
.fi
The parentheses isolate the logical term from any default command filter or
implicit report filter which would be combined with an implicit 'and'.
@ -71,10 +83,12 @@ A filter may target specific tasks using ID or UUID numbers. To specify
multiple tasks use one of these forms (space-separated list of ID numbers,
UUID numbers or ID ranges):
.nf
task 1 2 3 delete
task 1-3 info
task 1 2-5 19 modify pri:H
task 4-7 ebeeab00-ccf8-464b-8b58-f7f2d606edfb info
.fi
Note that it may be necessary to properly escape special characters as well as
quotes in order to avoid their special meanings in the shell. See also the
@ -85,11 +99,13 @@ section 'SPECIFYING DESCRIPTIONS' for more information.
The <mods> consist of zero or more changes to apply to the selected tasks, such
as:
.nf
task <filter> <command> project:Home
task <filter> <command> +weekend +garden due:tomorrow
task <filter> <command> Description/annotation text
task <filter> <command> /from/to/ <- replace first match
task <filter> <command> /from/to/g <- replace all matches
.fi
.SH SUBCOMMANDS
@ -188,6 +204,7 @@ wish to save it, or pipe it to another command or script to convert it to
another format. You'll find these example scripts online at
<https://taskwarrior.org/tools/>:
.nf
export-csv.pl
export-sql.py
export-xml.py
@ -198,6 +215,7 @@ another format. You'll find these example scripts online at
export-ical.pl
export-xml.pl
export-yad.pl
.fi
.TP
.B task <filter> ghistory.annual
@ -243,12 +261,16 @@ Applies the filter then extracts only the task IDs and presents them as
a space-separated list. This is useful as input to a task command, to achieve
this:
.nf
task $(task project:Home ids) modify priority:H
.fi
This example first gets the IDs for the project:Home filter, then sets
the priority to H for each of those tasks. This can also be achieved directly:
.nf
task project:Home modify priority:H
.fi
This command is mainly of use to external scripts.
@ -258,7 +280,9 @@ Applies the filter on all tasks (even deleted and completed tasks)
then extracts only the task UUIDs and presents them as a space-separated list.
This is useful as input to a task command, to achieve this:
.nf
task $(task project:Home status:completed uuids) modify status:pending
.fi
This example first gets the UUIDs for the project:Home and status:completed
filters, then makes each of those tasks pending again.
@ -385,8 +409,15 @@ if import is to be used in automated workflows. See taskrc(5).
For importing other file formats, the standard task release comes with a
few example scripts, such as:
.nf
import-todo.sh.pl
import-yaml.pl
.fi
.TP
.B task import-v2
Imports tasks from the Taskwarrior v2.x format. This is used when upgrading from
version 2.x to version 3.x.
.TP
.B task log <mods>
@ -401,6 +432,15 @@ Modifies the existing task with provided information.
.B task <filter> prepend <mods>
Prepends description text to an existing task. Is affected by the context.
.TP
.B task <filter> purge
Permanently removes the specified tasks from the data files. Only
tasks that are already deleted can be purged. This command has a
local-only effect and changes introduced by it are not synced.
Is affected by the context.
Warning: causes permanent, non-revertible loss of data.
.TP
.B task <filter> start <mods>
Marks the specified tasks as started. Is affected by the context.
@ -423,14 +463,20 @@ parses and evaluates the expression given on the command line.
Examples:
.nf
task calc 1 + 1
2
.fi
.nf
task calc now + 8d
2015-03-26T18:06:57
.fi
.nf
task calc eom
2015-03-31T23:59:59
.fi
.TP
.B task config [<name> [<value> | '']]
@ -438,16 +484,22 @@ Add, modify and remove settings directly in the Taskwarrior configuration.
This command either modifies the 'name' setting with a new value of 'value',
or adds a new entry that is equivalent to 'name=value':
.nf
task config name value
.fi
This command sets a blank value. This has the effect of suppressing any
default value:
.nf
task config name ''
.fi
Finally, this command removes any 'name=...' entry from the .taskrc file:
.nf
task config name
.fi
.TP
.B task context <name>
@ -455,7 +507,9 @@ Sets the currently active context. See the CONTEXT section.
Example:
.nf
task context work
.fi
.TP
.B task context delete <name>
@ -464,7 +518,9 @@ set as active, it will be unset.
Example:
.nf
task context delete work
.fi
.TP
.B task context define <name> <filter>
@ -473,9 +529,11 @@ does not affect the currently set context, just adds a new context definition.
Examples:
.nf
task context define work project:Work
task context define home project:Home or +home
task context define superurgent due:today and +urgent
.fi
.TP
.B task context list
@ -497,11 +555,10 @@ are important. Running this command generates a summary of similar information
that should accompany a bug report.
It includes compiler, library and software information. It does not include
any personal information, other than the location and size of your task data
files.
any personal information, other than the location of your task data.
This command also performs a diagnostic scan of your data files looking for
common problems, such as duplicate UUIDs.
This command also performs a diagnostic scan of your data looking for common
problems, such as duplicate UUIDs.
.TP
.B task execute <external command>
@ -544,11 +601,15 @@ The sync command synchronizes data with the Taskserver, if configured.
Note: If you use multiple sync clients, make sure this setting (which is the default)
is on your primary client:
.nf
recurrence=on
.fi
and on all other clients (this is not the default):
.nf
recurrence=off
.fi
This is a workaround to avoid a recurrence bug that duplicates recurring tasks.
@ -608,7 +669,9 @@ by third-party applications.
Reports a unique set of attribute values. For example, to see all the active
projects:
.nf
task +PENDING _unique project
.fi
.TP
.B task <filter> _uuids
@ -655,6 +718,7 @@ Shows the UUIDs and descriptions of matching tasks.
Accesses and displays the DOM reference(s). Used to extract individual values
from tasks, or the system. Supported DOM references are:
.nf
rc.<name>
tw.syncneeded
tw.program
@ -670,6 +734,7 @@ from tasks, or the system. Supported DOM references are:
system.os
<id>.<attribute>
<uuid>.<attribute>
.fi
Note that the 'rc.<name>' reference may need to be escaped using '--' to prevent
the reference from being interpreted as an override.
@ -680,8 +745,10 @@ missing value, the command exits with 1.
Additionally, some components of the attributes of particular types may be
extracted by DOM references.
.nf
$ task _get 2.due.year
2015
.fi
For a full list of supported attribute-specific DOM references, consult
the online documentation at:
@ -691,14 +758,16 @@ the online documentation at:
.TP
.B ID
Tasks can be specified uniquely by IDs, which are simply the indexes of the
tasks in the data file. The ID of a task may therefore change, but only when
a command is run that displays IDs. When modifying tasks, it is safe to
rely on the last displayed ID. Always run a report to check you have the right
ID for a task. IDs can be given to task as a sequence, for example,
.br
.B
Tasks can be specified uniquely by IDs, which are the indexes of the "working
set" of tasks (mostly pending and recurrent tasks). The ID of a task may
therefore change, but only when a report that displays IDs is run. When
modifying tasks, it is safe to rely on the last displayed ID. Always run a
report to check you have the right ID for a task. IDs can be given to task as a
sequence, for example:
.nf
task 1,4-10,19 delete
.fi
.TP
.B +tag|-tag
@ -709,15 +778,18 @@ Certain tags (called 'special tags'), can be used to affect the way tasks are
treated. For example, if a task has the special tag 'nocolor', then it is
exempt from all color rules. The supported special tags are:
.nf
+nocolor Disable color rules processing for this task
+nonag Completion of this task suppresses all nag messages
+nocal This task will not appear on the calendar
+next Elevates task so it appears on 'next' report
.fi
There are also virtual tags, which represent task metadata in tag form. These
tags do not exist, but can be used to filter tasks. The supported virtual tags
are:
.nf
ACTIVE Matches if the task is started
ANNOTATED Matches if the task has annotations
BLOCKED Matches if the task is blocked
@ -725,7 +797,7 @@ are:
CHILD Matches if the task has a parent (deprecated in 2.6.0)
COMPLETED Matches if the task has completed status
DELETED Matches if the task has deleted status
DUE Matches if the task is due
DUE Matches if the task is due within the next 7 days (See rc.due)
INSTANCE Matches if the task is a recurrent instance
LATEST Matches if the task is the newest added task
MONTH Matches if the task is due this month
@ -749,6 +821,7 @@ are:
WEEK Matches if the task is due this week
YEAR Matches if the task is due this year
YESTERDAY Matches if the task was due sometime yesterday
.fi
.\" If you update the above list, update src/commands/CmdInfo.cpp and src/commands/CmdTags.cpp as well.
@ -760,6 +833,10 @@ add or remove a virtual tag.
.B project:<project-name>
Specifies the project to which a task is related to.
.TP
.B status:pending|deleted|completed|waiting|recurring
Specifies the state of the task.
.TP
.B priority:H|M|L or priority:
Specifies High, Medium, Low and no priority for a task.
@ -806,6 +883,10 @@ by '-', the specified tasks are removed from the dependency list.
.B entry:<entry-date>
For report purposes, specifies the date that a task was created.
.TP
.B modified:<modified-date>
Specifies the most recent modification date.
.SH ATTRIBUTE MODIFIERS
Attribute modifiers improve filters. Supported modifiers are:
@ -846,9 +927,9 @@ calculated attributes:
For example:
.RS
task due.before:eom priority.not:L list
.RE
.nf
task due.before:eom priority.not:L list
.fi
The
.I before
@ -868,15 +949,21 @@ The
modifier is the same as 'before', except it also includes the moment in
question. For example:
.nf
task add test due:eoy
.fi
will be found when using the inclusive filter 'by':
.nf
task due.by:eoy
.fi
but not when the non-inclusive filter 'before' is used:
.nf
task due.before:eoy
.fi
this applies equally to other named dates such as 'eom', 'eod', etc; the
modifier compares using '<=' rather than '<' like 'before' does.
@ -885,8 +972,10 @@ The
.I none
modifier requires that the attribute does not have a value. For example:
.nf
task priority: list
task priority.none: list
.fi
are equivalent, and list tasks that do not have a priority.
@ -908,8 +997,10 @@ The
.I has
modifier is used to search for a substring, such as:
.nf
task description.has:foo list
task foo list
.fi
These are equivalent and will return any task that has 'foo' in the description
or annotations.
@ -924,13 +1015,17 @@ The
.I startswith
modifier matches against the left, or beginning of an attribute, such that:
.nf
task project.startswith:H list
task project:H list
.fi
are equivalent and will match any project starting with 'H'. Matching all
projects not starting with 'H' is done with:
.nf
task project.not:H list
.fi
The
.I endswith
@ -941,7 +1036,9 @@ The
modifier requires that the attribute contain the whole word specified, such
that this:
task description.word:bar list
.nf
task description.word:foo list
.fi
Will match the description 'foo bar baz' but does not match 'dog food'.
@ -955,15 +1052,19 @@ modifier.
You can use the following operators in filter expressions:
.nf
and or xor ! Logical operators
< <= = == != !== >= > Relational operators
( ) Precedence
.fi
For example:
.nf
task due.before:eom priority.not:L list
task '( due < eom or priority != L )' list
task '! ( project:Home or project:Garden )' list
.fi
The
.I =
@ -987,32 +1088,44 @@ Note that the parentheses are required when using a logical operator other than
the 'and' operator. The reason is that some reports contain filters that must
be combined with the command line. Consider this example:
.nf
task project:Home or project:Garden list
.fi
While this looks correct, it is not. The 'list' report contains a filter of:
.nf
task show report.list.filter
Config Variable Value
----------------- --------------
report.list.filter status:pending
.fi
Which means the example is really:
.nf
task status:pending project:Home or project:Garden list
.fi
The implied 'and' operator makes it:
.nf
task status:pending and project:Home or project:Garden list
.fi
This is a precedence error - the 'and' and 'or' need to be grouped using
parentheses, like this:
.nf
task status:pending and ( project:Home or project:Garden ) list
.fi
The original example therefore must be entered as:
.nf
task '( project:Home or project:Garden )' list
.fi
This includes quotes to escape the parentheses, so that the shell doesn't
interpret them and hide them from Taskwarrior.
@ -1020,11 +1133,13 @@ interpret them and hide them from Taskwarrior.
There is redundancy between operators, attribute modifiers and other syntactic
sugar. For example, the following are all equivalent:
.nf
task foo list
task /foo/ list
task description.contains:foo list
task description.has:foo list
task 'description ~ foo' list
.fi
.SH SPECIFYING DATES AND FREQUENCIES
@ -1038,92 +1153,83 @@ configuration variable
.RS
.TP
Exact specification
task ... due:7/14/2008
.nf
task ... due:7/14/2008
.fi
.TP
ISO-8601
task ... due:2013-03-14T22:30:00Z
.nf
task ... due:2013-03-14T22:30:00Z
.fi
.TP
Relative wording
task ... due:now
.br
task ... due:today
.br
task ... due:yesterday
.br
task ... due:tomorrow
.nf
task ... due:now
task ... due:today
task ... due:yesterday
task ... due:tomorrow
.fi
.TP
Day number with ordinal
task ... due:23rd
.br
task ... due:3wks
.br
task ... due:1day
.br
task ... due:9hrs
.nf
task ... due:23rd
task ... due:3wks
task ... due:1day
task ... due:9hrs
.fi
.TP
Start of next (work) week (Monday), calendar week (Sunday or Monday), month, quarter and year
.br
task ... due:sow
.br
task ... due:soww
.br
task ... due:socw
.br
task ... due:som
.br
task ... due:soq
.br
task ... due:soy
.nf
task ... due:sow
task ... due:soww
task ... due:socw
task ... due:som
task ... due:soq
task ... due:soy
.fi
.TP
End of current (work) week (Friday), calendar week (Saturday or Sunday), month, quarter and year
.br
task ... due:eow
.br
task ... due:eoww
.br
task ... due:eocw
.br
task ... due:eom
.br
task ... due:eoq
.br
task ... due:eoy
.nf
task ... due:eow
task ... due:eoww
task ... due:eocw
task ... due:eom
task ... due:eoq
task ... due:eoy
.fi
.TP
At some point or later
.br
task ... wait:later
.br
task ... wait:someday
.nf
task ... wait:later
task ... wait:someday
.fi
This sets the wait date to 12/30/9999.
.TP
Next occurring weekday
task ... due:fri
.nf
task ... due:fri
.fi
.TP
Predictable holidays
task ... due:goodfriday
.br
task ... due:easter
.br
task ... due:eastermonday
.br
task ... due:ascension
.br
task ... due:pentecost
.br
task ... due:midsommar
.br
task ... due:midsommarafton
.br
task ... due:juhannus
.nf
task ... due:goodfriday
task ... due:easter
task ... due:eastermonday
task ... due:ascension
task ... due:pentecost
task ... due:midsommar
task ... due:midsommarafton
task ... due:juhannus
.fi
.RE
.SS FREQUENCIES
@ -1174,7 +1280,8 @@ Context is a user-defined query, which is automatically applied to all commands
that filter the task list and to commands that create new tasks (add, log). For
example, any report command will have its result affected by the current
active context. Here is a list of the commands that are affected:
.IP
.nf
add
burndown
count
@ -1187,38 +1294,50 @@ active context. Here is a list of the commands that are affected:
log
prepend
projects
purge
start
stats
stop
summary
tags
.fi
All other commands are NOT affected by the context.
.nf
$ task list
ID Age Project Description Urg
1 2d Sport Run 5 miles 1.42
2 1d Home Clean the dishes 1.14
.fi
.nf
$ task context home
Context 'home' set. Use 'task context none' to remove.
.fi
.nf
$ task list
ID Age Project Description Urg
2 1d Home Clean the dishes 1.14
Context 'home' set. Use 'task context none' to remove.
.fi
Task list got automatically filtered for project:Home.
.nf
$ task add Vaccuum the carpet
Created task 3.
Context 'home' set. Use 'task context none' to remove.
.fi
.nf
$ task list
ID Age Project Description Urg
2 1d Home Clean the dishes 1.14
3 5s Home Vaccuum the carpet 1.14
Context 'home' set. Use 'task context none' to remove.
.fi
Note that the newly added task "Vaccuum the carpet" has "project:Home" set
automatically.
@ -1229,22 +1348,28 @@ new context's name to the 'context' command.
To unset any context, use the 'none' subcommand.
.nf
$ task context none
Context unset.
.fi
.nf
$ task list
ID Age Project Description Urg
1 2d Sport Run 5 miles 1.42
2 1d Home Clean the dishes 1.14
3 7s Home Vaccuum the carpet 1.14
.fi
Context can be defined using the 'define' subcommand, specifying both the name
of the new context, and it's assigned filter.
.nf
$ task context define home project:Home
Are you sure you want to add 'context.home.read' with a value of 'project:Home'? (yes/no) yes
Are you sure you want to add 'context.home.write' with a value of 'project:Home'? (yes/no) yes
Context 'home' successfully defined.
.fi
Note that you were separately prompted to set the 'read' and 'write' context.
This allows you to specify contexts that only work for reporting commands or
@ -1252,13 +1377,16 @@ only for commands that create tasks.
To remove the definition, use the 'delete' subcommand.
.nf
$ task context delete home
Are you sure you want to remove 'context.home.read'? (yes/no) yes
Are you sure you want to remove 'context.home.write'? (yes/no) yes
Context 'home' deleted.
.fi
To check what is the currently active context, use the 'show' subcommand.
.nf
$ task context show
Context 'home' with
@ -1266,13 +1394,16 @@ To check what is the currently active context, use the 'show' subcommand.
* write filter: '+home'
is currently applied.
.fi
Contexts can store arbitrarily complex filters.
.nf
$ task context define family project:Family or +paul or +nancy
Are you sure you want to add 'context.family.read' with a value of 'project:Family or +paul or +nancy'? (yes/no) yes
Are you sure you want to add 'context.family.write' with a value of 'project:Family or +paul or +nancy'? (yes/no) no
Context 'family' successfully defined.
.fi
Contexts are permanent, and the currently set context name is stored in the
"context" configuration variable. The context definition is stored in the
@ -1285,13 +1416,17 @@ filter as writeable context. The reason for this decision is that the complex
filter in the example does not directly translate to a modification. In fact,
if such a context is used as a writeable context, the following happens:
.nf
$ task add Call Paul
Created task 4.
Context 'family' set. Use 'task context none' to remove.
.fi
.nf
$ task 4 list
ID Age Project Tags Description Urg
4 9min Family nancy paul or or Call Paul 0
.fi
There is no clear mapping between the complex filter used and the modifications
@ -1300,16 +1435,20 @@ operators being present in the description. Taskwarrior does not try to guess
the user intention here, and instead, the user is expected to set the
"context.<name>.write" variable to make their intention explicit, for example:
.nf
$ task config context.family.write project:Family
Are you sure you want to change the value of 'context.family.write' from 'project:Family or +paul or +nancy' to 'project:Family'? (yes/no) yes
Config file /home/tbabej/.config/task/taskrc modified.
.fi
.nf
$ task context
Name Type Definition Active
family read project:Family or +paul or +nancy yes
write project:Family yes
home read +home no
write +home no
.fi
Note how read and write contexts differ for context "family", while for context
"home" they stay the same.
@ -1318,77 +1457,77 @@ In addition, every configuration parameter can be overridden for the current
context, by specifying context.<name>.rc.<parameter>. For example, if the default
command for the family context should be displaying the family_report:
.nf
$ task config context.family.rc.default.command family_report
.fi
.SH COMMAND ABBREVIATION
All Taskwarrior commands may be abbreviated as long as a unique prefix is used,
for example:
.RS
$ task li
.RE
.nf
$ task li
.fi
is an unambiguous abbreviation for
.RS
$ task list
.RE
.nf
$ task list
.fi
but
.RS
$ task l
.RE
.nf
$ task l
.fi
could be list, ls or long.
Note that you can restrict the minimum abbreviation size using the configuration
setting:
.RS
abbreviation.minimum=3
.RE
.nf
abbreviation.minimum=3
.fi
.SH SPECIFYING DESCRIPTIONS
Some task descriptions need to be escaped because of the shell and the special
meaning of some characters to the shell. This can be done either by adding
quotes to the description or escaping the special character:
.RS
$ task add "quoted ' quote"
.br
$ task add escaped \\' quote
.RE
.nf
$ task add "quoted ' quote"
$ task add escaped \\' quote
.fi
The argument \-\- (a double dash) tells Taskwarrior to treat all other args
as description:
.RS
$ task add -- project:Home needs scheduling
.RE
.nf
$ task add -- project:Home needs scheduling
.fi
In other situations, the shell sees spaces and breaks up arguments. For
example, this command:
.RS
$ task 123 modify /from this/to that/
.RE
.nf
$ task 123 modify /from this/to that/
.fi
is broken up into several arguments, which is corrected with quotes:
.RS
$ task 123 modify "/from this/to that/"
.RE
.nf
$ task 123 modify "/from this/to that/"
.fi
It is sometimes necessary to force the shell to pass quotes to Taskwarrior
intact, so you can use:
.RS
$ task add project:\\'Three Word Project\\' description
.RE
.nf
$ task add project:\\'Three Word Project\\' description
.fi
Taskwarrior supports Unicode using only the UTF8 encoding, with no Byte Order
Marks in the data files.
Taskwarrior supports Unicode using only the UTF8 encoding.
.SH CONFIGURATION FILE AND OVERRIDE OPTIONS
Taskwarrior stores its configuration in a file in the user's home directory:
@ -1439,21 +1578,13 @@ will check if $XDG_CONFIG_HOME/task/taskrc exists and attempt to read it
.TP
~/.task
The default directory where task stores its data files. The location
can be configured in the configuration variable 'data.location', or
overridden with the TASKDATA environment variable..
The default directory where task stores its data. The location can be
configured in the configuration variable 'data.location', or overridden with
the TASKDATA environment variable.
.TP
~/.task/pending.data
The file that contains the tasks that are not yet done.
.TP
~/.task/completed.data
The file that contains the completed ("done") tasks.
.TP
~/.task/undo.data
The file that contains information needed by the "undo" command.
~/.task/taskchampion.sqlite3
The database file.
.SH "CREDITS & COPYRIGHTS"
Copyright (C) 2006 \- 2021 T. Babej, P. Beckingham, F. Hernandez.

View file

@ -18,43 +18,43 @@ obtains its configuration data from a file called
.I .taskrc
\&. This file is normally located in the user's home directory:
.RS
$HOME/.taskrc
.RE
.nf
$HOME/.taskrc
.fi
The default location can be overridden using the
.I rc:
attribute when running task:
.RS
$ task rc:<directory-path>/.taskrc ...
.RE
.nf
$ task rc:<directory-path>/.taskrc ...
.fi
or using the TASKRC environment variable:
.RS
$ TASKRC=/tmp/.taskrc task ...
.RE
.nf
$ TASKRC=/tmp/.taskrc task ...
.fi
Additionally, if no ~/.taskrc exists, taskwarrior will check if the XDG_CONFIG_HOME environment variable is defined:
.RS
$ XDG_CONFIG_HOME=~/.config task ...
.RE
.nf
$ XDG_CONFIG_HOME=~/.config task ...
.fi
Individual options can be overridden by using the
.I rc.<name>:
attribute when running task:
.RS
$ task rc.<name>:<value> ...
.RE
.nf
$ task rc.<name>:<value> ...
.fi
or
.RS
$ task rc.<name>=<value> ...
.RE
.nf
$ task rc.<name>=<value> ...
.fi
If
.B Taskwarrior
@ -65,9 +65,9 @@ file in the user's home directory.
The .taskrc file follows a very simple syntax defining name/value pairs:
.RS
<name> = <value>
.RE
.nf
<name> = <value>
.fi
There may be whitespace around <name>, '=' and <value>, and it is ignored.
Whitespace within the <value> is left intact.
@ -77,11 +77,11 @@ Values support UTF8 as well as JSON encoding, such as \\uNNNN.
Note that Taskwarrior is flexible about the values used to represent Boolean
items. You can use "1" to enable, anything else is interpreted as disabled.
The values "on", "yes", "y" and "true" are currently supported but deprecated.
The values "on", "yes", "y" and "true" are also supported.
.RS
include <file>
.RE
.nf
include <file>
.fi
There may be whitespace around 'include' and <file>. The file may be an
absolute or relative path, and the special character '~' is expanded to mean
@ -95,9 +95,9 @@ respect to the following directories (listed in order of precedence):
Note that environment variables are also expanded in paths (and any other
taskrc variables).
.RS
# <comment>
.RE
.nf
# <comment>
.fi
A comment consists of the character '#', and extends from the '#' to the end
of the line. There is no way to comment a multi-line block. There may be
@ -108,9 +108,9 @@ that makes use of every default. The contents of the .taskrc file therefore
represent overrides of the default values. To remove a default value completely
there must be an entry like this:
.RS
<name> =
.RE
.nf
<name> =
.fi
This entry overrides the default value with a blank value.
@ -118,28 +118,28 @@ This entry overrides the default value with a blank value.
You can edit your .taskrc file by hand if you wish, or you can use the 'config'
command. To permanently set a value in your .taskrc file, use this command:
.RS
$ task config nag "You have more urgent tasks."
.RE
.nf
$ task config nag "You have more urgent tasks."
.fi
To delete an entry, use this command:
.RS
$ task config nag
.RE
.nf
$ task config nag
.fi
Taskwarrior will then use the default value. To explicitly set a value to
blank, and therefore avoid using the default value, use this command:
.RS
$ task config nag ""
.RE
.nf
$ task config nag ""
.fi
Taskwarrior will also display all your settings with this command:
.RS
$ task show
.RE
.nf
$ task show
.fi
and in addition, will also perform a check of all the values in the file,
warning you of anything it finds amiss.
@ -149,20 +149,19 @@ The .taskrc can include other files containing configuration settings by using t
.B include
statement:
.RS
include <path/to/the/configuration/file/to/be/included>
.RE
.nf
include <path/to/the/configuration/file/to/be/included>
.fi
By using include files you can divide your main configuration file into several
ones containing just the relevant configuration data like colors, etc.
There are two excellent uses of includes in your .taskrc, shown here:
.RS
include holidays.en-US.rc
.br
include dark-16.theme
.RE
.nf
include holidays.en-US.rc
include dark-16.theme
.fi
This includes two standard files that are distributed with Taskwarrior, which
define a set of US holidays, and set up a 16-color theme to use, to color the
@ -173,7 +172,7 @@ These environment variables override defaults, but not command-line arguments.
.TP
.B TASKDATA=~/.task
This overrides the default path for the Taskwarrior data files.
This overrides the default path for the Taskwarrior data.
.TP
.B TASKRC=~/.taskrc
@ -197,7 +196,7 @@ Valid variable names and their default values are:
.TP
.B data.location=$HOME/.task
This is a path to the directory containing all the Taskwarrior files. By
This is a path to the directory containing all the Taskwarrior data. By
default, it is set up to be ~/.task, for example: /home/paul/.task
Note that you can use the
@ -211,19 +210,20 @@ Note that the TASKDATA environment variable overrides this setting.
This is a path to the hook scripts directory. By default it is ~/.task/hooks.
.TP
.B locking=1
Determines whether to use file locking when accessing the pending.data and
completed.data files. Defaults to "1". Solaris users who store the data
files on an NFS mount may need to set locking to "0". Note that there is
danger in setting this value to "0" - another program (or another instance of
task) may write to the task.pending file at the same time.
.B gc=1
Can be used to temporarily suspend rebuilding, so that task IDs don't change.
Rebuilding requires read/write access to the database, so disabling `gc` may
result in better performance.
Note that this should be used in the form of a command line override (task
rc.gc=0 ...), and not permanently used in the .taskrc file, as this
significantly affects performance in the long term.
.TP
.B gc=1
Can be used to temporarily suspend garbage collection (gc), so that task IDs
don't change. Note that this should be used in the form of a command line
override (task rc.gc=0 ...), and not permanently used in the .taskrc file,
as this significantly affects performance in the long term.
.B purge.on-sync=0
If set, old tasks will be purged automatically after each synchronization.
Tasks are identified as "old" when they have status "Deleted" and have not
been modified for 180 days.
.TP
.B hooks=1
@ -241,6 +241,13 @@ rc.data.location or TASKDATA override) is missing. Default value is '0'.
Determines whether to use ioctl to establish the size of the window you are
using, for text wrapping.
.TP
.B limit:25
Specifies the desired number of tasks a report should show, if a positive
integer is given. The value 'page' may also be used, and will limit the
report output to as many lines of text as will fit on screen. Default value
is '25'.
.TP
.B defaultwidth=80
The width of output used when auto-detection support is not available. Defaults
@ -292,12 +299,14 @@ is most readily parsed and used by shell scripts.
Alternatively, you can specify a comma-separated list of verbosity tokens that
control specific occasions when output is generated. This list may contain:
.nf
blank Inserts extra blank lines in output, for clarity
header Messages that appear before report output (this includes .taskrc/.task overrides and the "[task next]" message)
footnote Messages that appear after report output (mostly status messages and change descriptions)
label Column labels on tabular reports
new-id Provides feedback on any new task with IDs (and UUIDs for new tasks with ID 0, such as new completed tasks).
new-uuid Provides feedback on any new task with UUIDs. Overrides new-id. Useful for automation.
news Reminds to read new release highlights until the user runs "task news".
affected Reports 'N tasks affected' and similar
edit Used the verbose template for the 'edit' command
special Feedback when applying special tags
@ -308,6 +317,7 @@ control specific occasions when output is generated. This list may contain:
override Notification when configuration options are overridden
recur Notification when a new recurring task instance is created
default Notifications about taskwarrior choosing to perform a default action.
.fi
The tokens "affected", "new-id", "new-uuid", "project", "override" and "recur"
imply "footnote".
@ -319,14 +329,20 @@ and the "nothing" setting is equivalent to none of the tokens being specified.
Here are the shortcut equivalents:
.nf
verbose=on
verbose=blank,header,footnote,label,new-id,affected,edit,special,project,sync,filter,override,recur
verbose=blank,header,footnote,label,new-id,news,affected,edit,special,project,sync,filter,override,recur
.fi
.nf
verbose=0
verbose=blank,label,new-id,edit
.fi
.nf
verbose=nothing
verbose=
.fi
Those additional comments are sent to the standard error for header, footnote
and project. The others are sent to standard output.
@ -495,13 +511,6 @@ weekly recurring task is added with a due date of tomorrow, and recurrence.limit
is set to 2, then a report will list 2 pending recurring tasks, one for tomorrow,
and one for a week from tomorrow.
.TP
.B undo.style=side
When the 'undo' command is run, Taskwarrior presents a before and after
comparison of the data. This can be in either the 'side' style, which compares
values side-by-side in a table, or 'diff' style, which uses a format similar to
the 'diff' command.
.TP
.B abbreviation.minimum=2
Minimum length of any abbreviated command/value. This means that "ve", "ver",
@ -577,51 +586,29 @@ are formatted according to dateformat.
The default value is the ISO-8601 standard: Y-M-D. The string can contain the
characters:
.RS
.RS
m minimal-digit month, for example 1 or 12
.br
d minimal-digit day, for example 1 or 30
.br
y two-digit year, for example 09 or 12
.br
D two-digit day, for example 01 or 30
.br
M two-digit month, for example 01 or 12
.br
Y four-digit year, for example 2009 or 2015
.br
a short name of weekday, for example Mon or Wed
.br
A long name of weekday, for example Monday or Wednesday
.br
b short name of month, for example Jan or Aug
.br
B long name of month, for example January or August
.br
v minimal-digit week, for example 3 or 37
.br
V two-digit week, for example 03 or 37
.br
h minimal-digit hour, for example 3 or 21
.br
n minimal-digit minutes, for example 5 or 42
.br
s minimal-digit seconds, for example 7 or 47
.br
H two-digit hour, for example 03 or 21
.br
N two-digit minutes, for example 05 or 42
.br
S two-digit seconds, for example 07 or 47
.br
J three-digit Julian day, for example 023 or 365
.br
j Julian day, for example 23 or 365
.br
w Week day, for example 0 for Monday, 5 for Friday
.RE
.RE
.nf
m minimal-digit month, for example 1 or 12
d minimal-digit day, for example 1 or 30
y two-digit year, for example 09 or 12
D two-digit day, for example 01 or 30
M two-digit month, for example 01 or 12
Y four-digit year, for example 2009 or 2015
a short name of weekday, for example Mon or Wed
A long name of weekday, for example Monday or Wednesday
b short name of month, for example Jan or Aug
B long name of month, for example January or August
v minimal-digit week, for example 3 or 37
V two-digit week, for example 03 or 37
h minimal-digit hour, for example 3 or 21
n minimal-digit minutes, for example 5 or 42
s minimal-digit seconds, for example 7 or 47
H two-digit hour, for example 03 or 21
N two-digit minutes, for example 05 or 42
S two-digit seconds, for example 07 or 47
J three-digit Julian day, for example 023 or 365
j Julian day, for example 23 or 365
w Week day, for example 0 for Monday, 5 for Friday
.fi
.RS
The characters 'v', 'V', 'a' and 'A' can only be used for formatting printed
@ -633,37 +620,24 @@ The string may also contain other characters to act as spacers, or formatting.
Examples for other values of dateformat:
.RE
.RS
.RS
.br
d/m/Y would use for input and output 24/7/2009
.br
yMD would use for input and output 090724
.br
M-D-Y would use for input and output 07-24-2009
.RE
.RE
.nf
d/m/Y would use for input and output 24/7/2009
yMD would use for input and output 090724
M-D-Y would use for input and output 07-24-2009
.fi
.RS
Examples for other values of dateformat.report:
.RE
.RS
.RS
.br
a D b Y (V) would emit "Fri 24 Jul 2009 (30)"
.br
A, B D, Y would emit "Friday, July 24, 2009"
.br
wV a Y-M-D would emit "w30 Fri 2009-07-24"
.br
yMD.HN would emit "110124.2342"
.br
m/d/Y H:N would emit "1/24/2011 10:42"
.br
a D b Y H:N:S would emit "Mon 24 Jan 2011 11:19:42"
.RE
.RE
.nf
a D b Y (V) would emit "Fri 24 Jul 2009 (30)"
A, B D, Y would emit "Friday, July 24, 2009"
wV a Y-M-D would emit "w30 Fri 2009-07-24"
yMD.HN would emit "110124.2342"
m/d/Y H:N would emit "1/24/2011 10:42"
a D b Y H:N:S would emit "Mon 24 Jan 2011 11:19:42"
.fi
.RS
Undefined fields are put to their minimal valid values (1 for month and day and
@ -672,14 +646,10 @@ field that is set. Otherwise, they are set to the corresponding values of
"now". For example:
.RE
.RS
.RS
.br
8/1/2013 with m/d/Y implies August 1, 2013 at midnight (inferred)
.br
8/1 20:40 with m/d H:N implies August 1, 2013 (inferred) at 20:40
.RE
.RE
.nf
8/1/2013 with m/d/Y implies August 1, 2013 at midnight (inferred)
8/1 20:40 with m/d H:N implies August 1, 2013 (inferred) at 20:40
.fi
.TP
.B date.iso=1
@ -773,28 +743,19 @@ Holidays are entered either directly in the .taskrc file or via an include file
that is specified in .taskrc. For single-day holidays the name and the date is
required to be given:
.RS
.RS
.br
holiday.towel.name=Day of the towel
.br
holiday.towel.date=20100525
.RE
.RE
.nf
holiday.towel.name=Day of the towel
holiday.towel.date=20100525
.fi
For holidays that span a range of days (i.e. vacation), you can use a start date
and an end date:
.RS
.RS
.br
holiday.sysadmin.name=System Administrator Appreciation Week
.br
holiday.sysadmin.start=20100730
.br
holiday.sysadmin.end=20100805
.RE
.RE
.nf
holiday.sysadmin.name=System Administrator Appreciation Week
holiday.sysadmin.start=20100730
holiday.sysadmin.end=20100805
.fi
.RS
Dates are to be entered according to the setting in the dateformat.holiday
@ -807,24 +768,17 @@ Easter (easter), Easter Monday (eastermonday), Ascension (ascension), Pentecost
(pentecost). The date for these holidays is the given keyword:
.RE
.RS
.RS
.br
holiday.eastersunday.name=Easter
.br
holiday.eastersunday.date=easter
.RE
.RE
.nf
holiday.eastersunday.name=Easter
holiday.eastersunday.date=easter
.fi
Note that the Taskwarrior distribution contains example holiday files that can
be included like this:
.RS
.RS
.br
include holidays.en-US.rc
.RE
.RE
.nf
include holidays.en-US.rc
.fi
.SS DEPENDENCIES
@ -912,10 +866,9 @@ Task is deleted.
.RS
To disable a coloration rule for which there is a default, set the value to
nothing, for example:
.RS
.B color.tagged=
.RE
.RE
.nf
color.tagged=
.fi
.RS
By default, colors produced by rules blend. This has the advantage of
@ -1092,6 +1045,9 @@ yellow bars.
.RS
Colors used by the undo command, to indicate the values both before and after
a change that is to be reverted.
Currently not supported.
.RE
.TP
@ -1250,30 +1206,23 @@ default.command=next
Provides a default command that is run every time Taskwarrior is invoked with no
arguments. For example, if set to:
.RS
.RS
default.command=project:foo list
.RE
.RE
.nf
default.command=project:foo list
.fi
.RS
then Taskwarrior will run the "project:foo list" command if no command is
specified. This means that by merely typing
.RE
.RS
.RS
$ task
.br
[task project:foo list]
.br
\&
.br
ID Project Pri Description
1 foo H Design foo
2 foo Build foo
.RE
.RE
.nf
$ task
[task project:foo list]
ID Project Pri Description
1 foo H Design foo
2 foo Build foo
.fi
.SS REPORTS
@ -1312,12 +1261,16 @@ specified by using the column ids post-fixed by a "+" for ascending sort order
or a "-" for descending sort order. The sort IDs are separated by commas.
For example:
.nf
report.list.sort=due+,priority-,start.active-,project+
.fi
Additionally, after the "+" or "-", there can be a solidus "/" which indicates
that there are breaks after the column values change. For example:
.nf
report.minimal.sort=project+/,description+
.fi
This sort order now specifies that there is a listing break between each
project. A listing break is simply a blank line, which provides a visual
@ -1431,7 +1384,7 @@ if you define a UDA named 'estimate', Taskwarrior will not know that this value
is weeks, hours, minutes, money, or some other resource count.
.TP
.B uda.<name>.type=string|numeric|date|duration
.B uda.<name>.type=string|numeric|uuid|date|duration
.RS
Defines a UDA called '<name>', of the specified type.
.RE

View file

@ -0,0 +1,99 @@
###############################################################################
#
# Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# https://www.opensource.org/licenses/mit-license.php
#
###############################################################################
# Theme author: Adrian Galilea @adriangalilea
rule.precedence.color=deleted,completed,active,keyword.,tag.,project.,overdue,scheduled,due.today,due,blocked,blocking,recurring,tagged,uda.
# General decoration
color.label=
color.label.sort=
color.alternate=on gray2
color.header=rgb013
color.footnote=rgb013
color.warning=rgb520
color.error=red
color.debug=blue
# Task state
color.completed=
color.deleted=
color.active=rgb553
color.recurring=bright rgb535
color.scheduled=
color.until=
color.blocked=gray10
color.blocking=on rgb002
# Project
color.project.none=
# Priority
color.uda.priority.H=rgb435
color.uda.priority.L=gray15
# Tags
color.tag.next=rgb253
color.tag.none=
color.tagged=
# Due
color.due=
color.due.today=rgb125
color.overdue=bold inverse
# Report: burndown
color.burndown.pending=on rgb103
color.burndown.started=on rgb214
color.burndown.done=on gray4
# Report: history
color.history.add=color0 on rgb105
color.history.done=color0 on rgb205
color.history.delete=color0 on rgb305
# Report: summary
color.summary.bar=white on rgb104
color.summary.background=white on rgb001
# Command: calendar
color.calendar.due=color0 on rgb325
color.calendar.due.today=color0 on rgb404
color.calendar.holiday=color15 on rgb102
color.calendar.overdue=color0 on color5
color.calendar.scheduled=
color.calendar.today=color15 on rgb103
color.calendar.weekend=gray12 on gray3
color.calendar.weeknumber=rgb104
# Command: sync
color.sync.added=gray4
color.sync.changed=rgb214
color.sync.rejected=rgb103
# Command: undo
color.undo.before=rgb103
color.undo.after=rgb305

View file

@ -86,6 +86,7 @@ color.calendar.due=white on red
color.calendar.due.today=bold white on red
color.calendar.holiday=black on bright yellow
color.calendar.overdue=black on bright red
color.calendar.scheduled=
color.calendar.today=bold white on bright blue
color.calendar.weekend=white on bright black
color.calendar.weeknumber=bold blue
@ -98,4 +99,3 @@ color.sync.rejected=red
# Command: undo
color.undo.after=green
color.undo.before=red

View file

@ -82,6 +82,7 @@ color.summary.bar=black on rgb141
color.calendar.due.today=color15 on color1
color.calendar.due=color0 on color1
color.calendar.holiday=color0 on color11
color.calendar.scheduled=
color.calendar.overdue=color0 on color9
color.calendar.today=color15 on rgb013
color.calendar.weekend=on color235
@ -95,4 +96,3 @@ color.sync.rejected=color9
# Command: undo
color.undo.after=color2
color.undo.before=color1

View file

@ -83,6 +83,7 @@ color.calendar.due.today=color0 on color252
color.calendar.due=color0 on color249
color.calendar.holiday=color255 on rgb013
color.calendar.overdue=color0 on color255
color.calendar.scheduled=
color.calendar.today=color0 on rgb115
color.calendar.weekend=on color235
color.calendar.weeknumber=rgb015
@ -95,4 +96,3 @@ color.sync.rejected=rgb004
# Command: undo
color.undo.after=rgb035
color.undo.before=rgb013

View file

@ -83,6 +83,7 @@ color.calendar.due=on gray8
color.calendar.due.today=black on gray15
color.calendar.holiday=black on gray20
color.calendar.overdue=gray2 on gray10
color.calendar.scheduled=
color.calendar.today=bold white
color.calendar.weekend=on gray2
color.calendar.weeknumber=gray6
@ -95,4 +96,3 @@ color.sync.rejected=gray5 on gray23
# Command: undo
color.undo.before=white on black
color.undo.after=black on white

View file

@ -83,6 +83,7 @@ color.calendar.due=color0 on gray10
color.calendar.due.today=color0 on gray15
color.calendar.holiday=color15 on rgb005
color.calendar.overdue=color0 on gray20
color.calendar.scheduled=
color.calendar.today=underline black on color15
color.calendar.weekend=on gray4
color.calendar.weeknumber=gray10
@ -95,4 +96,3 @@ color.sync.rejected=gray23
# Command: undo
color.undo.before=rgb013
color.undo.after=rgb035

View file

@ -83,6 +83,7 @@ color.calendar.due.today=color0 on color225
color.calendar.due=color0 on color249
color.calendar.holiday=rgb151 on rgb020
color.calendar.overdue=color0 on color255
color.calendar.scheduled=
color.calendar.today=color0 on rgb151
color.calendar.weekend=on color235
color.calendar.weeknumber=rgb010

View file

@ -83,6 +83,7 @@ color.calendar.due.today=color0 on color252
color.calendar.due=color0 on color249
color.calendar.holiday=rgb522 on rgb300
color.calendar.overdue=color0 on color255
color.calendar.scheduled=
color.calendar.today=color0 on rgb511
color.calendar.weekend=on color235
color.calendar.weeknumber=rgb100
@ -95,4 +96,3 @@ color.sync.rejected=rgb200
# Command: undo
color.undo.after=rgb511
color.undo.before=rgb200

View file

@ -83,6 +83,7 @@ color.calendar.due=color0 on rgb325
color.calendar.due.today=color0 on rgb404
color.calendar.holiday=color15 on rgb102
color.calendar.overdue=color0 on color5
color.calendar.scheduled=
color.calendar.today=color15 on rgb103
color.calendar.weekend=gray12 on gray3
color.calendar.weeknumber=rgb104
@ -95,4 +96,3 @@ color.sync.rejected=rgb103
# Command: undo
color.undo.before=rgb103
color.undo.after=rgb305

View file

@ -83,6 +83,7 @@ color.calendar.due=color0 on rgb440
color.calendar.due.today=color0 on rgb430
color.calendar.holiday=rgb151 on rgb020
color.calendar.overdue=color0 on rgb420
color.calendar.scheduled=
color.calendar.today=color15 on rgb110
color.calendar.weekend=on color235
color.calendar.weeknumber=rgb110
@ -95,4 +96,3 @@ color.sync.rejected=rgb110
# Command: undo
color.undo.before=rgb021
color.undo.after=rgb042

View file

@ -83,6 +83,7 @@ color.calendar.due=on bright green
color.calendar.due.today=blue on bright yellow
color.calendar.holiday=on yellow
color.calendar.overdue=on bright red
color.calendar.scheduled=
color.calendar.today=blue
color.calendar.weekend=on white
color.calendar.weeknumber=blue
@ -95,4 +96,3 @@ color.sync.rejected=red
# Command: undo
color.undo.before=yellow
color.undo.after=green

View file

@ -65,7 +65,7 @@ color.due.today=on rgb353
color.overdue=on rgb544
# Report: burndown
color.burndown.pending=on rgb411
color.burndown.pending=on rgb411
color.burndown.started=on rgb550
color.burndown.done=on rgb151
@ -83,6 +83,7 @@ color.calendar.due=on rgb343
color.calendar.due.today=on rgb353
color.calendar.holiday=color0 on rgb530
color.calendar.overdue=on rgb533
color.calendar.scheduled=
color.calendar.today=rgb005
color.calendar.weekend=on gray21
color.calendar.weeknumber=gray16
@ -95,4 +96,3 @@ color.sync.rejected=red
# Command: undo
color.undo.before=yellow
color.undo.after=green

View file

@ -86,6 +86,7 @@ color.calendar.due=
color.calendar.due.today=
color.calendar.holiday=
color.calendar.overdue=
color.calendar.scheduled=
color.calendar.today=
color.calendar.weekend=
color.calendar.weeknumber=
@ -98,4 +99,3 @@ color.sync.rejected=
# Command: undo
color.undo.after=
color.undo.before=

View file

@ -6,4 +6,3 @@ do
echo $locale
../../scripts/add-ons/update-holidays.pl --locale $locale --file holidays.${locale}.rc
done

View file

@ -100,6 +100,7 @@ color.calendar.due=color0 on color9
color.calendar.due.today=color0 on color1
color.calendar.holiday=color0 on color3
color.calendar.overdue=color0 on color5
color.calendar.scheduled=
color.calendar.today=color0 on color4
color.calendar.weekend=on color0
color.calendar.weeknumber=color4
@ -112,4 +113,3 @@ color.sync.rejected=color13
# Command: undo
color.undo.after=color2
color.undo.before=color1

View file

@ -100,6 +100,7 @@ color.calendar.due=color7 on color9
color.calendar.due.today=color7 on color1
color.calendar.holiday=color7 on color3
color.calendar.overdue=color7 on color5
color.calendar.scheduled=
color.calendar.today=color7 on color4
color.calendar.weekend=on color7
color.calendar.weeknumber=color14
@ -112,4 +113,3 @@ color.sync.rejected=color13
# Command: undo
color.undo.after=color2
color.undo.before=color1

View file

@ -1,17 +1,17 @@
version: '3'
services:
test-fedora38:
test-fedora40:
build:
context: .
dockerfile: test/docker/fedora38
dockerfile: test/docker/fedora40
network_mode: "host"
security_opt:
- label=type:container_runtime_t
tty: true
test-fedora39:
test-fedora41:
build:
context: .
dockerfile: test/docker/fedora39
dockerfile: test/docker/fedora41
network_mode: "host"
security_opt:
- label=type:container_runtime_t

View file

@ -25,4 +25,3 @@ Using a solarized light terminal, run the following:
Note that for the solarized themes, the terminal color palette needs to be set
to specific colors.

View file

@ -30,4 +30,3 @@ task rc:x add Deleted_1
task rc:x 14 mod depends:13
task rc:x 15 delete

View file

@ -1,3 +0,0 @@
*.data
*.rc
export.json

View file

@ -1,6 +1,9 @@
cmake_minimum_required (VERSION 3.22)
add_custom_target (performance ./run_perf
DEPENDS task_executable
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/performance)
configure_file(compare_runs.py compare_runs.py COPYONLY)
configure_file(load load)
configure_file(run_perf run_perf)
add_custom_target (performance ${CMAKE_BINARY_DIR}/performance/run_perf
DEPENDS task_executable
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/performance)

View file

@ -29,10 +29,13 @@ def parse_perf(input):
tests[command] = []
# Parse concatenated run_perf output
for i in re.findall("^ - task %s\.\.\.\n"
"Perf task ([^ ]+) ([^ ]+) ([^ ]+) (.+)$"
% command, input, re.MULTILINE):
info = i[0:3] + ({k:v for k, v in (i.split(":") for i in i[-1].split())},)
for i in re.findall(
"^ - task %s\\.\\.\\.\n"
"Perf task ([^ ]+) ([^ ]+) ([^ ]+) (.+)$" % command,
input,
re.MULTILINE,
):
info = i[0:3] + ({k: v for k, v in (i.split(":") for i in i[-1].split())},)
pt = TaskPerf(*info)
tests[command].append(pt)
return tests
@ -61,8 +64,14 @@ with open(sys.argv[2], "r") as fh:
tests_cur = parse_perf(fh.read())
best_cur = get_best(tests_cur)
print("Previous: %s (%s)" % (tests_prev[COMMANDS[0]][0].version, tests_prev[COMMANDS[0]][0].commit))
print("Current: %s (%s)" % (tests_cur[COMMANDS[0]][0].version, tests_cur[COMMANDS[0]][0].commit))
print(
"Previous: %s (%s)"
% (tests_prev[COMMANDS[0]][0].version, tests_prev[COMMANDS[0]][0].commit)
)
print(
"Current: %s (%s)"
% (tests_cur[COMMANDS[0]][0].version, tests_cur[COMMANDS[0]][0].commit)
)
for test in COMMANDS:
print("# %s:" % test)
@ -76,7 +85,9 @@ for test in COMMANDS:
else:
percentage = "0%"
pad = max(map(len, (k, best_prev[test][k], best_cur[test][k], diff, percentage)))
pad = max(
map(len, (k, best_prev[test][k], best_cur[test][k], diff, percentage))
)
out[0] += " %s" % k.rjust(pad)
out[1] += " %s" % best_prev[test][k].rjust(pad)
out[2] += " %s" % best_cur[test][k].rjust(pad)

View file

@ -14,7 +14,7 @@ if (open my $fh, '>', 'perf.rc')
close $fh;
}
my $filename = 'sample-text.txt';
my $filename = '${CMAKE_SOURCE_DIR}/performance/sample-text.txt';
open(my $fh, '<:encoding(UTF-8)', $filename)
or die "Could not open file '$filename' $!";
@ -31,18 +31,18 @@ while (my $line = <$fh>)
if ($. % 20 == 19)
{
my $anno_id = $id - 1;
qx{../build/src/task rc:perf.rc rc.gc=off $anno_id annotate $line};
qx{${CMAKE_BINARY_DIR}/src/task rc:perf.rc rc.gc=off $anno_id annotate $line};
print "[$.] task rc:perf.rc rc.gc=off $anno_id annotate $line\n" if $?;
}
elsif ($. % 4 == 1)
{
qx{../build/src/task rc:perf.rc rc.gc=off add $line};
qx{${CMAKE_BINARY_DIR}/src/task rc:perf.rc rc.gc=off add $line};
print "[$.] task rc:perf.rc rc.gc=off add $line\n" if $?;
++$id;
}
else
{
qx{../build/src/task rc:perf.rc rc.gc=off log $line};
qx{${CMAKE_BINARY_DIR}/src/task rc:perf.rc rc.gc=off log $line};
print "[$.] task rc:perf.rc rc.gc=off log $line\n" if $?;
}
}

View file

@ -1,22 +1,22 @@
#! /bin/bash
echo 'Performance: setup'
rm -f ./pending.data ./completed.data ./undo.data ./backlog.data perf.rc
if [[ -e data/pending.data && -e data/completed.data ]]
rm -f ./taskchampion.sqlite3
if [[ -e ./data/taskchampion.sqlite3 ]]
then
echo ' - Using existing data'
echo ' - Using existing data.'
cp data/* .
else
echo ' - This step will take several minutes'
echo ' - Loading data. This step will take several minutes.'
./load
mkdir -p data
cp *.data perf.rc data
cp taskchampion.sqlite3 perf.rc data
fi
# Allow override.
if [[ -z $TASK ]]
then
TASK=../build/src/task
TASK=${CMAKE_BINARY_DIR}/src/task
fi
# Run benchmarks.
@ -45,9 +45,8 @@ $TASK rc.debug:1 rc:perf.rc export >/dev/null 2>&1
$TASK rc.debug:1 rc:perf.rc export 2>&1 >export.json | grep "Perf task"
echo ' - task import...'
rm -f ./pending.data ./completed.data ./undo.data ./backlog.data
$TASK rc.debug:1 rc:perf.rc import export.json 2>&1 | grep "Perf task"
rm -f ./taskchampion.sqlite3
$TASK rc.debug:1 rc:perf.rc import ${CMAKE_SOURCE_DIR}/performance/export.json 2>&1 | grep "Perf task"
echo 'End'
exit 0

View file

@ -3963,7 +3963,7 @@ GLOUCESTER Give me the letter, sir.
EDMUND I shall offend, either to detain or give it. The contents, as in part I understand them, are to blame.
GLOUCESTER Lets see, lets see.
EDMUND I hope, for my brothers justification, he wrote this but as an essay or taste of my virtue.
GLOUCESTER This policy and reverence of age makes the world bitter to the best of our times, keeps our fortunes from us till our oldness cannot relish them. I begin to find an idle and fond bondage in the oppression of aged tyranny, who sways, not as it hath power, but as it is suffered. Come to me, that of this I may speak more. If our father would sleep till I waked him, you should enjoy half his revenue for ever, and live the beloved of your brother,
GLOUCESTER This policy and reverence of age makes the world bitter to the best of our times, keeps our fortunes from us till our oldness cannot relish them. I begin to find an idle and fond bondage in the oppression of aged tyranny, who sways, not as it hath power, but as it is suffered. Come to me, that of this I may speak more. If our father would sleep till I waked him, you should enjoy half his revenue for ever, and live the beloved of your brother,
EDMUND It was not brought me, my lord, theres the cunning of it, I found it thrown in at the casement of my closet.
GLOUCESTER You know the character to be your brothers?
EDMUND If the matter were good, my lord, I durst swear it were his, but, in respect of that, I would fain think it were not.

View file

@ -8,4 +8,3 @@ install (DIRECTORY add-ons
FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE)

View file

@ -221,4 +221,3 @@ if (open my $fh, '>:utf8', $file)
exit 0;
################################################################################

View file

@ -24,4 +24,3 @@ Expected Permissions
Interface
Each hook script has a unique interface. This is documented in the example
scripts here.

View file

@ -22,9 +22,8 @@ SHADOW_FILE=$(task _get rc.shadow.file)
# rc.detection=off Disables terminal size detection
# rc.gc=off Disables GC, thus not changing IDs unexpectedly
# rc.color=off Disable color in the shadow file
# rc.locking=off Disable file locking, to prevent race condition
# rc.hooks=off Disable hooks, to prevent race condition
task $SHADOW_COMMAND rc.detection=off rc.gc=off rc.color=off rc.locking=off rc.hooks=off > $SHADOW_FILE 2>/dev/null
task $SHADOW_COMMAND rc.detection=off rc.gc=off rc.color=off rc.hooks=off > $SHADOW_FILE 2>/dev/null
if [[ $? != 0 ]]
then
echo Could not create $SHADOW_FILE
@ -33,4 +32,3 @@ fi
echo Shadow file $SHADOW_FILE updated.
exit 0

View file

@ -14,4 +14,3 @@ echo 'on-launch'
# - 0: JSON ignored, non-JSON is feedback.
# - non-0: JSON ignored, non-JSON is error.
exit 0

View file

@ -1,34 +0,0 @@
# Dockerfile for containers to perform PR review in
# Use with make as follows: make RELEASE=v2.5.1 reproduce
FROM centos:8
RUN dnf update -y
RUN yum install epel-release -y
RUN dnf install python38 vim git gcc gcc-c++ cmake make libuuid-devel libfaketime sudo man gdb -y
RUN useradd warrior
RUN echo warrior ALL=NOPASSWD:ALL > /etc/sudoers.d/warrior
USER warrior
WORKDIR /home/warrior/
# Setup taskwarrior
# The purpose is to speed up subsequent re-installs due to Docker layer caching
RUN git clone https://github.com/GothenburgBitFactory/taskwarrior.git
WORKDIR /home/warrior/taskwarrior/
RUN git submodule init
# Install the given release
ARG RELEASE
RUN git checkout $RELEASE
RUN git submodule update --init
RUN cmake -DCMAKE_BUILD_TYPE=debug .
RUN make -j8
RUN sudo make install
# Set the PS1 variable
ENV PS1="[\u@\H \W]\$ "
WORKDIR /home/warrior
RUN task rc.confirmation=0 _ids || : # Generate default taskrc

View file

@ -1,54 +0,0 @@
# Dockerfile for containers to perform PR review in
# Use with make as follows: make PR=1234 review
FROM centos:8
# Workaround to the location of the repos
RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
RUN dnf update -y
RUN yum install epel-release -y
RUN dnf install python38 git gcc gcc-c++ cmake make libuuid-devel libfaketime sudo man -y
RUN useradd warrior
RUN echo warrior ALL=NOPASSWD:ALL > /etc/sudoers.d/warrior
USER warrior
WORKDIR /home/warrior/
# Setup Rust
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh && \
sh rustup.sh -y --profile minimal --default-toolchain stable --component rust-docs
# Setup taskwarrior
# The purpose is to speed up subsequent re-installs due to Docker layer caching
RUN git clone https://github.com/GothenburgBitFactory/taskwarrior.git
WORKDIR /home/warrior/taskwarrior/
RUN git submodule init
RUN git submodule update
RUN cmake -DCMAKE_BUILD_TYPE=debug .
RUN make -j8
RUN sudo make install
# Use specified PR's branch, if provided
ARG PR
RUN if [[ ! -z $PR ]]; then \
git fetch origin refs/pull/${PR}/head:pr-${PR}; \
git checkout pr-${PR}; fi
# Use specified libshared PR's branch, if provided
ARG LIBPR
WORKDIR /home/warrior/taskwarrior/src/libshared/
RUN if [[ ! -z $LIBPR ]]; then \
git fetch origin refs/pull/${LIBPR}/head:libpr-${LIBPR}; \
git checkout libpr-${LIBPR}; fi
# Install taskwarrior
WORKDIR /home/warrior/taskwarrior/
RUN cmake -DCMAKE_BUILD_TYPE=debug .
RUN make -j8
RUN sudo make install
WORKDIR /home/warrior
RUN task rc.confirmation=0 _ids || : # Generate default taskrc

View file

@ -1,4 +1,4 @@
" Vim support file to detect Taskwarrior data and configuration files and
" Vim support file to detect Taskwarrior data and configuration files and
" single task edits
"
" Maintainer: John Florian <jflorian@doubledog.org>

View file

@ -132,6 +132,7 @@ syn match taskrcGoodKey '^\s*\Vexpressions='he=e-1
syn match taskrcGoodKey '^\s*\Vextensions='he=e-1
syn match taskrcGoodKey '^\s*\Vfontunderline='he=e-1
syn match taskrcGoodKey '^\s*\Vgc='he=e-1
syn match taskrcGoodKey '^\s*\Vpurge.on-sync='he=e-1
syn match taskrcGoodKey '^\s*\Vhooks='he=e-1
syn match taskrcGoodKey '^\s*\Vhooks.location='he=e-1
syn match taskrcGoodKey '^\s*\Vhyphenate='he=e-1
@ -142,6 +143,7 @@ syn match taskrcGoodKey '^\s*\Vjournal.time='he=e-1
syn match taskrcGoodKey '^\s*\Vjournal.time.start.annotation='he=e-1
syn match taskrcGoodKey '^\s*\Vjournal.time.stop.annotation='he=e-1
syn match taskrcGoodKey '^\s*\Vjson.array='he=e-1
syn match taskrcGoodKey '^\s*\Vlimit='he=e-1
syn match taskrcGoodKey '^\s*\Vlist.all.projects='he=e-1
syn match taskrcGoodKey '^\s*\Vlist.all.tags='he=e-1
syn match taskrcGoodKey '^\s*\Vlocale='he=e-1
@ -163,10 +165,9 @@ syn match taskrcGoodKey '^\s*\Vrule.precedence.color='he=e-1
syn match taskrcGoodKey '^\s*\Vsearch.case.sensitive='he=e-1
syn match taskrcGoodKey '^\s*\Vsummary.all.projects='he=e-1
syn match taskrcGoodKey '^\s*\Vsugar='he=e-1
syn match taskrcGoodKey '^\s*\Vsync.\(server.\(origin\|client_id\|encryption_secret\)\|local.server_dir\)='he=e-1
syn match taskrcGoodKey '^\s*\Vsync.\(server.\(url\|origin\|client_id\|encryption_secret\)\|local.server_dir\)='he=e-1
syn match taskrcGoodKey '^\s*\Vtag.indicator='he=e-1
syn match taskrcGoodKey '^\s*\Vuda.\S\{-}.\(default\|type\|label\|values\|indicator\)='he=e-1
syn match taskrcGoodKey '^\s*\Vundo.style='he=e-1
syn match taskrcGoodKey '^\s*\Vurgency.active.coefficient='he=e-1
syn match taskrcGoodKey '^\s*\Vurgency.age.coefficient='he=e-1
syn match taskrcGoodKey '^\s*\Vurgency.age.max='he=e-1

View file

@ -31,7 +31,7 @@ _task_filter() {
local word=$'[^\0]#\0'
# projects
local _task_projects=($(task _projects))
local _task_projects=($(task rc.hooks=0 _projects))
local task_projects=(
/"$word"/
":values:task projects:compadd -a _task_projects"
@ -157,7 +157,7 @@ _task_filter() {
local uda_name uda_label uda_values
local -a udas_spec
task _udas | while read uda_name; do
uda_label="$(task _get rc.uda."$uda_name".label)"
uda_label="$(task rc.hooks=0 _get rc.uda."$uda_name".label)"
# TODO: we could have got the values of every uda and try to complete that
# but that can become extremly slow with a lot of udas
#uda_values=(${(@s:,:)"$(task _get rc.uda."$uda_name".values)"})
@ -167,8 +167,8 @@ _task_filter() {
_regex_words -t ':' default 'task attributes' "${_task_all_attributes[@]}"
local task_attributes=("$reply[@]")
local _task_tags=($(task _tags))
local _task_config=($(task _config))
local _task_tags=($(task rc.hooks=0 _tags))
local _task_config=($(task rc.hooks=0 _config))
local _task_modifiers=(
'before'
'after'
@ -213,12 +213,12 @@ _task_filter() {
# id-only completion
(( $+functions[_task_ids] )) ||
_task_ids() {
local _ids=( ${(f)"$(task _zshids)"} )
local _ids=( ${(f)"$(task rc.hooks=0 _zshids)"} )
_describe 'task ids' _ids
}
(( $+functions[_task_aliases] )) ||
_task_aliases() {
local _aliases=( ${(f)"$(task _aliases)"} )
local _aliases=( ${(f)"$(task rc.hooks=0 _aliases)"} )
_describe 'task aliases' _aliases
}
@ -230,7 +230,7 @@ _task_subcommands() {
local cmd category desc
local lastcategory=''
# The list is sorted by category, in the right order.
local _task_zshcmds=( ${(f)"$(task _zshcommands)"} sentinel:sentinel:sentinel )
local _task_zshcmds=( ${(f)"$(task rc.hooks=0 _zshcommands)"} sentinel:sentinel:sentinel )
for _zshcmd in "$_task_zshcmds[@]"; do
# Parse out the three fields
cmd=${_zshcmd%%:*}
@ -254,7 +254,7 @@ _task_subcommands() {
## contexts
(( $+functions[_task_context] )) ||
_task_context() {
local _contexts=(${(f)"$(task _context)"})
local _contexts=(${(f)"$(task rc.hooks=0 _context)"})
_describe 'task contexts' _contexts
}
@ -264,7 +264,7 @@ _task_default() {
local cmd ret=1
integer i=1
local _task_cmds=($(task _commands; task _aliases))
local _task_cmds=($(task rc.hooks=0 _commands; task _aliases))
while (( i < $#words ))
do
cmd="${_task_cmds[(r)$words[$i]]}"

5
src/.gitignore vendored
View file

@ -1,6 +1 @@
*.o
Makefile.in
debug
calc
lex
liblibshared.a

File diff suppressed because it is too large Load diff

View file

@ -26,100 +26,98 @@
#ifndef INCLUDED_CLI2
#define INCLUDED_CLI2
#include <string>
#include <vector>
#include <map>
#include <unordered_map>
#include <Lexer.h>
#include <FS.h>
#include <Lexer.h>
#include <map>
#include <string>
#include <unordered_map>
#include <vector>
// Represents a single argument.
class A2
{
public:
A2 (const std::string&, Lexer::Type);
A2 (const A2&);
A2& operator= (const A2&);
bool hasTag (const std::string&) const;
void tag (const std::string&);
void unTag (const std::string&);
void attribute (const std::string&, const std::string&);
const std::string attribute (const std::string&) const;
const std::string getToken () const;
const std::string dump () const;
void decompose ();
class A2 {
public:
A2(const std::string&, Lexer::Type);
A2(const A2&);
A2& operator=(const A2&);
bool hasTag(const std::string&) const;
void tag(const std::string&);
void unTag(const std::string&);
void attribute(const std::string&, const std::string&);
const std::string attribute(const std::string&) const;
const std::string getToken() const;
const std::string dump() const;
void decompose();
public:
Lexer::Type _lextype {Lexer::Type::word};
std::vector <std::string> _tags {};
std::map <std::string, std::string> _attributes {};
public:
Lexer::Type _lextype{Lexer::Type::word};
std::vector<std::string> _tags{};
std::map<std::string, std::string> _attributes{};
};
// Represents the command line.
class CLI2
{
public:
class CLI2 {
public:
static int minimumMatchLength;
static bool getOverride (int, const char**, File&);
static bool getDataLocation (int, const char**, Path&);
static void applyOverrides (int, const char**);
static bool getOverride(int, const char**, File&);
static bool getDataLocation(int, const char**, Path&);
static void applyOverrides(int, const char**);
public:
CLI2 () = default;
void alias (const std::string&, const std::string&);
void entity (const std::string&, const std::string&);
public:
CLI2() = default;
void alias(const std::string&, const std::string&);
void entity(const std::string&, const std::string&);
void add (const std::string&);
void add (const std::vector <std::string>&, int offset = 0);
void analyze ();
void addFilter (const std::string& arg);
void addModifications (const std::string& arg);
void addContext (bool readable, bool writeable);
void prepareFilter ();
const std::vector <std::string> getWords ();
const std::vector <A2> getMiscellaneous ();
bool canonicalize (std::string&, const std::string&, const std::string&);
std::string getBinary () const;
std::string getCommand (bool canonical = true) const;
const std::string dump (const std::string& title = "CLI2 Parser") const;
void add(const std::string&);
void add(const std::vector<std::string>&, int offset = 0);
void analyze();
void addFilter(const std::string& arg);
void addModifications(const std::string& arg);
void addContext(bool readable, bool writeable);
void prepareFilter();
const std::vector<std::string> getWords();
const std::vector<A2> getMiscellaneous();
bool canonicalize(std::string&, const std::string&, const std::string&);
std::string getBinary() const;
std::string getCommand(bool canonical = true) const;
const std::string dump(const std::string& title = "CLI2 Parser") const;
private:
void handleArg0 ();
void lexArguments ();
void demotion ();
void aliasExpansion ();
void canonicalizeNames ();
void categorizeArgs ();
void parenthesizeOriginalFilter ();
bool findCommand ();
bool exactMatch (const std::string&, const std::string&) const;
void desugarFilterTags ();
void findStrayModifications ();
void desugarFilterAttributes ();
void desugarFilterPatterns ();
void findIDs ();
void findUUIDs ();
void insertIDExpr ();
void lexFilterArgs ();
bool isEmptyParenExpression (std::vector<A2>::iterator it, bool forward = true) const;
void desugarFilterPlainArgs ();
void insertJunctions ();
void defaultCommand ();
std::vector <A2> lexExpression (const std::string&);
private:
void handleArg0();
void lexArguments();
void demotion();
void aliasExpansion();
void canonicalizeNames();
void categorizeArgs();
void parenthesizeOriginalFilter();
bool findCommand();
bool exactMatch(const std::string&, const std::string&) const;
void desugarFilterTags();
void findStrayModifications();
void desugarFilterAttributes();
void desugarFilterPatterns();
void findIDs();
void findUUIDs();
void insertIDExpr();
void lexFilterArgs();
bool isEmptyParenExpression(std::vector<A2>::iterator it, bool forward = true) const;
void desugarFilterPlainArgs();
void insertJunctions();
void defaultCommand();
std::vector<A2> lexExpression(const std::string&);
public:
std::multimap <std::string, std::string> _entities {};
std::map <std::string, std::string> _aliases {};
std::unordered_map <int, std::string> _canonical_cache {};
std::vector <A2> _original_args {};
std::vector <A2> _args {};
public:
std::multimap<std::string, std::string> _entities{};
std::map<std::string, std::string> _aliases{};
std::unordered_map<int, std::string> _canonical_cache{};
std::vector<A2> _original_args{};
std::vector<A2> _args{};
std::vector <std::pair <std::string, std::string>> _id_ranges {};
std::vector <std::string> _uuid_list {};
std::string _command {""};
bool _context_added {false};
std::vector<std::pair<std::string, std::string>> _id_ranges{};
std::vector<std::string> _uuid_list{};
std::string _command{""};
bool _context_added{false};
};
#endif

View file

@ -1,25 +1,25 @@
cmake_minimum_required (VERSION 3.22)
include_directories (${CMAKE_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/src/tc
${CMAKE_SOURCE_DIR}/src/commands
${CMAKE_SOURCE_DIR}/src/columns
${CMAKE_SOURCE_DIR}/src/libshared/src
${CMAKE_SOURCE_DIR}/taskchampion/lib
${TASK_INCLUDE_DIRS})
add_library (task STATIC CLI2.cpp CLI2.h
Context.cpp Context.h
DOM.cpp DOM.h
Eval.cpp Eval.h
Filter.cpp Filter.h
Hooks.cpp Hooks.h
Lexer.cpp Lexer.h
TDB2.cpp TDB2.h
Task.cpp Task.h
Variant.cpp Variant.h
Version.cpp Version.h
ViewTask.cpp ViewTask.h
add_library (task STATIC CLI2.cpp
Context.cpp
DOM.cpp
Eval.cpp
Filter.cpp
Hooks.cpp
Lexer.cpp
Operation.cpp
TF2.cpp
TDB2.cpp
Task.cpp
Variant.cpp
Version.cpp
ViewTask.cpp
dependency.cpp
feedback.cpp
legacy.cpp
@ -27,35 +27,34 @@ add_library (task STATIC CLI2.cpp CLI2.h
recur.cpp
rules.cpp
sort.cpp
util.cpp util.h)
util.cpp)
target_link_libraries(task taskchampion-cpp)
add_library (libshared STATIC libshared/src/Color.cpp libshared/src/Color.h
libshared/src/Configuration.cpp libshared/src/Configuration.h
libshared/src/Datetime.cpp libshared/src/Datetime.h
libshared/src/Duration.cpp libshared/src/Duration.h
libshared/src/FS.cpp libshared/src/FS.h
libshared/src/JSON.cpp libshared/src/JSON.h
libshared/src/Msg.cpp libshared/src/Msg.h
libshared/src/Pig.cpp libshared/src/Pig.h
libshared/src/RX.cpp libshared/src/RX.h
libshared/src/Table.cpp libshared/src/Table.h
libshared/src/Timer.cpp libshared/src/Timer.h
libshared/src/format.cpp libshared/src/format.h
add_library (libshared STATIC libshared/src/Color.cpp
libshared/src/Configuration.cpp
libshared/src/Datetime.cpp
libshared/src/Duration.cpp
libshared/src/FS.cpp
libshared/src/JSON.cpp
libshared/src/Msg.cpp
libshared/src/Pig.cpp
libshared/src/RX.cpp
libshared/src/Table.cpp
libshared/src/Timer.cpp
libshared/src/format.cpp
libshared/src/ip.cpp
libshared/src/shared.cpp libshared/src/shared.h
libshared/src/unicode.cpp libshared/src/unicode.h
libshared/src/utf8.cpp libshared/src/utf8.h
libshared/src/wcwidth.h)
libshared/src/shared.cpp
libshared/src/unicode.cpp
libshared/src/utf8.cpp)
add_executable (task_executable main.cpp)
add_executable (calc_executable calc.cpp)
add_executable (lex_executable lex.cpp)
# Yes, 'task' (and hence libshared) is included twice, otherwise linking fails on assorted OSes.
# Similarly for `tc`.
target_link_libraries (task_executable task tc commands tc columns libshared task libshared ${TASK_LIBRARIES})
target_link_libraries (calc_executable task tc commands tc columns libshared task libshared ${TASK_LIBRARIES})
target_link_libraries (lex_executable task tc commands tc columns libshared task libshared ${TASK_LIBRARIES})
target_link_libraries (task_executable task commands columns libshared task libshared ${TASK_LIBRARIES})
target_link_libraries (calc_executable task commands columns libshared task libshared ${TASK_LIBRARIES})
target_link_libraries (lex_executable task commands columns libshared task libshared ${TASK_LIBRARIES})
if (DARWIN)
# SystemConfiguration is required by Rust libraries like reqwest, to get proxy configuration.
target_link_libraries (task_executable "-framework CoreFoundation -framework Security -framework SystemConfiguration")

File diff suppressed because it is too large Load diff

View file

@ -27,112 +27,111 @@
#ifndef INCLUDED_CONTEXT
#define INCLUDED_CONTEXT
#include <Command.h>
#include <Column.h>
#include <Configuration.h>
#include <Task.h>
#include <TDB2.h>
#include <Hooks.h>
#include <FS.h>
#include <CLI2.h>
#include <Column.h>
#include <Command.h>
#include <Configuration.h>
#include <FS.h>
#include <Hooks.h>
#include <TDB2.h>
#include <Task.h>
#include <Timer.h>
#include <set>
class CurrentTask;
class Context
{
public:
Context () = default; // Default constructor
~Context (); // Destructor
class Context {
public:
Context() = default; // Default constructor
~Context(); // Destructor
Context (const Context&);
Context& operator= (const Context&);
Context(const Context &);
Context &operator=(const Context &);
static Context& getContext ();
static void setContext (Context*);
static Context &getContext();
static void setContext(Context *);
int initialize (int, const char**); // all startup
int run ();
int dispatch (std::string&); // command handler dispatch
int initialize(int, const char **); // all startup
int run();
int dispatch(std::string &); // command handler dispatch
int getWidth (); // determine terminal width
int getHeight (); // determine terminal height
int getWidth(); // determine terminal width
int getHeight(); // determine terminal height
std::string getTaskContext (const std::string&, std::string, bool fallback=true);
std::string getTaskContext(const std::string &, std::string, bool fallback = true);
const std::vector <std::string> getColumns () const;
void getLimits (int&, int&);
const std::vector<std::string> getColumns() const;
void getLimits(int &, int &);
bool color (); // TTY or <other>?
bool verbose (const std::string&); // Verbosity control
bool color(); // TTY or <other>?
bool verbose(const std::string &); // Verbosity control
void header (const std::string&); // Header message sink
void footnote (const std::string&); // Footnote message sink
void debug (const std::string&); // Debug message sink
void error (const std::string&); // Error message sink - non-maskable
void header(const std::string &); // Header message sink
void footnote(const std::string &); // Footnote message sink
void debug(const std::string &); // Debug message sink
void error(const std::string &); // Error message sink - non-maskable
void decomposeSortField (const std::string&, std::string&, bool&, bool&);
void debugTiming (const std::string&, const Timer&);
void decomposeSortField(const std::string &, std::string &, bool &, bool &);
void debugTiming(const std::string &, const Timer &);
CurrentTask withCurrentTask (const Task *);
CurrentTask withCurrentTask(const Task *);
friend class CurrentTask;
private:
void staticInitialization ();
void createDefaultConfig ();
void updateXtermTitle ();
void updateVerbosity ();
void loadAliases ();
void propagateDebug ();
private:
void staticInitialization();
void createDefaultConfig();
void updateXtermTitle();
void updateVerbosity();
void loadAliases();
void propagateDebug();
static Context* context;
static Context *context;
public:
CLI2 cli2 {};
std::string home_dir {};
File rc_file {"~/.taskrc"};
Path data_dir {"~/.task"};
Configuration config {};
TDB2 tdb2 {};
Hooks hooks {};
bool determine_color_use {true};
bool use_color {true};
bool run_gc {true};
bool verbosity_legacy {false};
std::set <std::string> verbosity {};
std::vector <std::string> headers {};
std::vector <std::string> footnotes {};
std::vector <std::string> errors {};
std::vector <std::string> debugMessages {};
std::map <std::string, Command*> commands {};
std::map <std::string, Column*> columns {};
int terminal_width {0};
int terminal_height {0};
public:
CLI2 cli2{};
std::string home_dir{};
File rc_file{"~/.taskrc"};
Path data_dir{"~/.task"};
Configuration config{};
TDB2 tdb2{};
Hooks hooks{};
bool determine_color_use{true};
bool use_color{true};
bool verbosity_legacy{false};
std::set<std::string> verbosity{};
std::vector<std::string> headers{};
std::vector<std::string> footnotes{};
std::vector<std::string> errors{};
std::vector<std::string> debugMessages{};
std::map<std::string, Command *> commands{};
std::map<std::string, Column *> columns{};
int terminal_width{0};
int terminal_height{0};
Timer timer_total {};
long time_total_us {0};
long time_init_us {0};
long time_load_us {0};
long time_gc_us {0};
long time_filter_us {0};
long time_commit_us {0};
long time_sort_us {0};
long time_render_us {0};
long time_hooks_us {0};
Timer timer_total{};
long time_total_us{0};
long time_init_us{0};
long time_load_us{0};
long time_gc_us{0};
long time_filter_us{0};
long time_commit_us{0};
long time_sort_us{0};
long time_render_us{0};
long time_hooks_us{0};
// the current task for DOM references, or NULL if there is no task
const Task * currentTask {NULL};
const Task *currentTask{NULL};
};
////////////////////////////////////////////////////////////////////////////////
// CurrentTask resets Context::currentTask to previous context task on destruction; this ensures
// that this context value is restored when exiting the scope where the context was applied.
class CurrentTask {
public:
public:
~CurrentTask();
private:
private:
CurrentTask(Context &context, const Task *previous);
Context &context;

View file

@ -25,19 +25,22 @@
////////////////////////////////////////////////////////////////////////////////
#include <cmake.h>
#include <DOM.h>
#include <sstream>
#include <map>
#include <stdlib.h>
#include <Variant.h>
#include <Lexer.h>
// cmake.h include header must come first
#include <Context.h>
#include <DOM.h>
#include <Datetime.h>
#include <Duration.h>
#include <shared.h>
#include <Lexer.h>
#include <Variant.h>
#include <format.h>
#include <shared.h>
#include <stdlib.h>
#include <util.h>
#include <map>
#include <sstream>
////////////////////////////////////////////////////////////////////////////////
// DOM Supported References:
//
@ -60,23 +63,18 @@
// system.version
// system.os
//
bool getDOM (const std::string& name, Variant& value)
{
bool getDOM(const std::string& name, Variant& value) {
// Special case, blank refs cause problems.
if (name == "")
return false;
if (name == "") return false;
auto len = name.length ();
auto len = name.length();
// rc. --> context.config
if (len > 3 &&
! name.compare (0, 3, "rc.", 3))
{
auto key = name.substr (3);
auto c = Context::getContext ().config.find (key);
if (c != Context::getContext ().config.end ())
{
value = Variant (c->second);
if (len > 3 && !name.compare(0, 3, "rc.", 3)) {
auto key = name.substr(3);
auto c = Context::getContext().config.find(key);
if (c != Context::getContext().config.end()) {
value = Variant(c->second);
return true;
}
@ -84,55 +82,41 @@ bool getDOM (const std::string& name, Variant& value)
}
// tw.*
if (len > 3 &&
! name.compare (0, 3, "tw.", 3))
{
if (name == "tw.syncneeded")
{
value = Variant (0);
if (Context::getContext ().tdb2.num_local_changes () > 0) {
value = Variant (1);
if (len > 3 && !name.compare(0, 3, "tw.", 3)) {
if (name == "tw.syncneeded") {
value = Variant(0);
if (Context::getContext().tdb2.num_local_changes() > 0) {
value = Variant(1);
}
return true;
}
else if (name == "tw.program")
{
value = Variant (Context::getContext ().cli2.getBinary ());
} else if (name == "tw.program") {
value = Variant(Context::getContext().cli2.getBinary());
return true;
}
else if (name == "tw.args")
{
} else if (name == "tw.args") {
std::string commandLine;
for (auto& arg : Context::getContext ().cli2._original_args)
{
if (commandLine != "")
commandLine += ' ';
for (auto& arg : Context::getContext().cli2._original_args) {
if (commandLine != "") commandLine += ' ';
commandLine += arg.attribute("raw");
}
value = Variant (commandLine);
value = Variant(commandLine);
return true;
}
else if (name == "tw.width")
{
value = Variant (static_cast<int> (Context::getContext ().terminal_width
? Context::getContext ().terminal_width
: Context::getContext ().getWidth ()));
} else if (name == "tw.width") {
value = Variant(static_cast<int>(Context::getContext().terminal_width
? Context::getContext().terminal_width
: Context::getContext().getWidth()));
return true;
}
else if (name == "tw.height")
{
value = Variant (static_cast<int> (Context::getContext ().terminal_height
? Context::getContext ().terminal_height
: Context::getContext ().getHeight ()));
} else if (name == "tw.height") {
value = Variant(static_cast<int>(Context::getContext().terminal_height
? Context::getContext().terminal_height
: Context::getContext().getHeight()));
return true;
}
else if (name == "tw.version")
{
value = Variant (VERSION);
else if (name == "tw.version") {
value = Variant(VERSION);
return true;
}
@ -140,40 +124,29 @@ bool getDOM (const std::string& name, Variant& value)
}
// context.*
if (len > 8 &&
! name.compare (0, 8, "context.", 8))
{
if (name == "context.program")
{
value = Variant (Context::getContext ().cli2.getBinary ());
if (len > 8 && !name.compare(0, 8, "context.", 8)) {
if (name == "context.program") {
value = Variant(Context::getContext().cli2.getBinary());
return true;
}
else if (name == "context.args")
{
} else if (name == "context.args") {
std::string commandLine;
for (auto& arg : Context::getContext ().cli2._original_args)
{
if (commandLine != "")
commandLine += ' ';
for (auto& arg : Context::getContext().cli2._original_args) {
if (commandLine != "") commandLine += ' ';
commandLine += arg.attribute("raw");
}
value = Variant (commandLine);
value = Variant(commandLine);
return true;
}
else if (name == "context.width")
{
value = Variant (static_cast<int> (Context::getContext ().terminal_width
? Context::getContext ().terminal_width
: Context::getContext ().getWidth ()));
} else if (name == "context.width") {
value = Variant(static_cast<int>(Context::getContext().terminal_width
? Context::getContext().terminal_width
: Context::getContext().getWidth()));
return true;
}
else if (name == "context.height")
{
value = Variant (static_cast<int> (Context::getContext ().terminal_height
? Context::getContext ().terminal_height
: Context::getContext ().getHeight ()));
} else if (name == "context.height") {
value = Variant(static_cast<int>(Context::getContext().terminal_height
? Context::getContext().terminal_height
: Context::getContext().getHeight()));
return true;
}
@ -181,20 +154,16 @@ bool getDOM (const std::string& name, Variant& value)
}
// system. --> Implement locally.
if (len > 7 &&
! name.compare (0, 7, "system.", 7))
{
if (len > 7 && !name.compare(0, 7, "system.", 7)) {
// Taskwarrior version number.
if (name == "system.version")
{
value = Variant (VERSION);
if (name == "system.version") {
value = Variant(VERSION);
return true;
}
// OS type.
else if (name == "system.os")
{
value = Variant (osName ());
else if (name == "system.os") {
value = Variant(osName());
return true;
}
@ -237,210 +206,199 @@ bool getDOM (const std::string& name, Variant& value)
//
// If task is NULL, then the contextual task will be determined from the DOM
// string, if any exists.
bool getDOM (const std::string& name, const Task* task, Variant& value)
{
bool getDOM(const std::string& name, const Task* task, Variant& value) {
// Special case, blank refs cause problems.
if (name == "")
return false;
if (name == "") return false;
// Quickly deal with the most common cases.
if (task && name == "id")
{
value = Variant (static_cast<int> (task->id));
if (task && name == "id") {
value = Variant(static_cast<int>(task->id));
return true;
}
if (task && name == "urgency")
{
value = Variant (task->urgency_c ());
if (task && name == "urgency") {
value = Variant(task->urgency_c());
return true;
}
// split name on '.'
auto elements = split (name, '.');
auto elements = split(name, '.');
Task loaded_task;
// decide whether the reference is going to be the passed
// "task" or whether it's going to be a newly loaded task (if id/uuid was
// given).
const Task* ref = task;
Lexer lexer (elements[0]);
Lexer lexer(elements[0]);
std::string token;
Lexer::Type type;
// If this can be ID/UUID reference (the name contains '.'),
// lex it to figure out. Otherwise don't lex, as lexing can be slow.
if ((elements.size() > 1) and lexer.token (token, type))
{
if ((elements.size() > 1) and lexer.token(token, type)) {
bool reloaded = false;
if (type == Lexer::Type::uuid &&
token.length () == elements[0].length ())
{
if (!task || token != task->get ("uuid"))
{
if (Context::getContext ().tdb2.get (token, loaded_task))
reloaded = true;
if (type == Lexer::Type::uuid && token.length() == elements[0].length()) {
if (!task || token != task->get("uuid")) {
if (Context::getContext().tdb2.get(token, loaded_task)) reloaded = true;
}
// Eat elements[0]/UUID.
elements.erase (elements.begin ());
}
else if (type == Lexer::Type::number &&
token.find ('.') == std::string::npos)
{
auto id = strtol (token.c_str (), nullptr, 10);
if (id && (!task || id != task->id))
{
if (Context::getContext ().tdb2.get (id, loaded_task))
reloaded = true;
elements.erase(elements.begin());
} else if (type == Lexer::Type::number && token.find('.') == std::string::npos) {
auto id = strtol(token.c_str(), nullptr, 10);
if (id && (!task || id != task->id)) {
if (Context::getContext().tdb2.get(id, loaded_task)) reloaded = true;
}
// Eat elements[0]/ID.
elements.erase (elements.begin ());
elements.erase(elements.begin());
}
if (reloaded)
ref = &loaded_task;
if (reloaded) ref = &loaded_task;
}
// The remainder of this method requires a contextual task, so if we do not
// have one, delegate to the two-argument getDOM
if (!ref)
return getDOM (name, value);
if (!ref) return getDOM(name, value);
auto size = elements.size ();
auto size = elements.size();
std::string canonical;
if ((size == 1 || size == 2) && Context::getContext ().cli2.canonicalize (canonical, "attribute", elements[0]))
{
if ((size == 1 || size == 2) &&
Context::getContext().cli2.canonicalize(canonical, "attribute", elements[0])) {
// Now that 'ref' is the contextual task, and any ID/UUID is chopped off the
// elements vector, DOM resolution is now simple.
if (size == 1 && canonical == "id")
{
value = Variant (static_cast<int> (ref->id));
if (size == 1 && canonical == "id") {
value = Variant(static_cast<int>(ref->id));
return true;
}
if (size == 1 && canonical == "urgency")
{
value = Variant (ref->urgency_c ());
if (size == 1 && canonical == "urgency") {
value = Variant(ref->urgency_c());
return true;
}
// Special handling of status required for virtual waiting status
// implementation. Remove in 3.0.0.
if (size == 1 && canonical == "status")
{
value = Variant (ref->statusToText (ref->getStatus ()));
if (size == 1 && canonical == "status") {
value = Variant(ref->statusToText(ref->getStatus()));
return true;
}
Column* column = Context::getContext ().columns[canonical];
// The "tags" property is deprecated, but it is documented as part of the DOM, so simulate it.
if (size == 1 && canonical == "tags") {
auto tags = ref->getTags();
value = Variant(join(",", tags));
return true;
}
if (size == 1 && column)
{
if (column->is_uda () && ! ref->has (canonical))
{
value = Variant ("");
Column* column = Context::getContext().columns[canonical];
if (size == 1 && column) {
if (column->is_uda() && !ref->has(canonical)) {
value = Variant("");
return true;
}
if (column->type () == "date")
{
auto numeric = ref->get_date (canonical);
if (column->type() == "date") {
auto numeric = ref->get_date(canonical);
if (numeric == 0)
value = Variant ("");
value = Variant("");
else
value = Variant (numeric, Variant::type_date);
}
else if (column->type () == "duration" || canonical == "recur")
{
auto period = ref->get (canonical);
value = Variant(numeric, Variant::type_date);
} else if (column->type() == "duration" || canonical == "recur") {
auto period = ref->get(canonical);
Duration iso;
std::string::size_type cursor = 0;
if (iso.parse (period, cursor))
value = Variant (iso.toTime_t (), Variant::type_duration);
if (iso.parse(period, cursor))
value = Variant(iso.toTime_t(), Variant::type_duration);
else
value = Variant (Duration (ref->get (canonical)).toTime_t (), Variant::type_duration);
}
else if (column->type () == "numeric")
value = Variant (ref->get_float (canonical));
value = Variant(Duration(ref->get(canonical)).toTime_t(), Variant::type_duration);
} else if (column->type() == "numeric")
value = Variant(ref->get_float(canonical));
else
value = Variant (ref->get (canonical));
value = Variant(ref->get(canonical));
return true;
}
if (size == 2 && canonical == "tags")
{
value = Variant (ref->hasTag (elements[1]) ? elements[1] : "");
if (size == 2 && canonical == "tags") {
value = Variant(ref->hasTag(elements[1]) ? elements[1] : "");
return true;
}
if (size == 2 && column && column->type () == "date")
{
Datetime date (ref->get_date (canonical));
if (elements[1] == "year") { value = Variant (static_cast<int> (date.year ())); return true; }
else if (elements[1] == "month") { value = Variant (static_cast<int> (date.month ())); return true; }
else if (elements[1] == "day") { value = Variant (static_cast<int> (date.day ())); return true; }
else if (elements[1] == "week") { value = Variant (static_cast<int> (date.week ())); return true; }
else if (elements[1] == "weekday") { value = Variant (static_cast<int> (date.dayOfWeek ())); return true; }
else if (elements[1] == "julian") { value = Variant (static_cast<int> (date.dayOfYear ())); return true; }
else if (elements[1] == "hour") { value = Variant (static_cast<int> (date.hour ())); return true; }
else if (elements[1] == "minute") { value = Variant (static_cast<int> (date.minute ())); return true; }
else if (elements[1] == "second") { value = Variant (static_cast<int> (date.second ())); return true; }
if (size == 2 && column && column->type() == "date") {
Datetime date(ref->get_date(canonical));
if (elements[1] == "year") {
value = Variant(static_cast<int>(date.year()));
return true;
} else if (elements[1] == "month") {
value = Variant(static_cast<int>(date.month()));
return true;
} else if (elements[1] == "day") {
value = Variant(static_cast<int>(date.day()));
return true;
} else if (elements[1] == "week") {
value = Variant(static_cast<int>(date.week()));
return true;
} else if (elements[1] == "weekday") {
value = Variant(static_cast<int>(date.dayOfWeek()));
return true;
} else if (elements[1] == "julian") {
value = Variant(static_cast<int>(date.dayOfYear()));
return true;
} else if (elements[1] == "hour") {
value = Variant(static_cast<int>(date.hour()));
return true;
} else if (elements[1] == "minute") {
value = Variant(static_cast<int>(date.minute()));
return true;
} else if (elements[1] == "second") {
value = Variant(static_cast<int>(date.second()));
return true;
}
}
}
if (size == 2 && elements[0] == "annotations" && elements[1] == "count")
{
value = Variant (static_cast<int> (ref->getAnnotationCount ()));
if (size == 2 && elements[0] == "annotations" && elements[1] == "count") {
value = Variant(static_cast<int>(ref->getAnnotationCount()));
return true;
}
if (size == 3 && elements[0] == "annotations")
{
auto annos = ref->getAnnotations ();
if (size == 3 && elements[0] == "annotations") {
auto annos = ref->getAnnotations();
int a = strtol (elements[1].c_str (), nullptr, 10);
int a = strtol(elements[1].c_str(), nullptr, 10);
int count = 0;
// Count off the 'a'th annotation.
for (const auto& i : annos)
{
if (++count == a)
{
if (elements[2] == "entry")
{
for (const auto& i : annos) {
if (++count == a) {
if (elements[2] == "entry") {
// annotation_1234567890
// 0 ^11
value = Variant ((time_t) strtoll (i.first.substr (11).c_str (), NULL, 10), Variant::type_date);
value =
Variant((time_t)strtoll(i.first.substr(11).c_str(), NULL, 10), Variant::type_date);
return true;
}
else if (elements[2] == "description")
{
value = Variant (i.second);
} else if (elements[2] == "description") {
value = Variant(i.second);
return true;
}
}
}
}
if (size == 4 && elements[0] == "annotations" && elements[2] == "entry")
{
auto annos = ref->getAnnotations ();
if (size == 4 && elements[0] == "annotations" && elements[2] == "entry") {
auto annos = ref->getAnnotations();
int a = strtol (elements[1].c_str (), nullptr, 10);
int a = strtol(elements[1].c_str(), nullptr, 10);
int count = 0;
// Count off the 'a'th annotation.
for (const auto& i : annos)
{
if (++count == a)
{
for (const auto& i : annos) {
if (++count == a) {
// <annotations>.<N>.entry.year
// <annotations>.<N>.entry.month
// <annotations>.<N>.entry.day
@ -450,22 +408,41 @@ bool getDOM (const std::string& name, const Task* task, Variant& value)
// <annotations>.<N>.entry.hour
// <annotations>.<N>.entry.minute
// <annotations>.<N>.entry.second
Datetime date (i.first.substr (11));
if (elements[3] == "year") { value = Variant (static_cast<int> (date.year ())); return true; }
else if (elements[3] == "month") { value = Variant (static_cast<int> (date.month ())); return true; }
else if (elements[3] == "day") { value = Variant (static_cast<int> (date.day ())); return true; }
else if (elements[3] == "week") { value = Variant (static_cast<int> (date.week ())); return true; }
else if (elements[3] == "weekday") { value = Variant (static_cast<int> (date.dayOfWeek ())); return true; }
else if (elements[3] == "julian") { value = Variant (static_cast<int> (date.dayOfYear ())); return true; }
else if (elements[3] == "hour") { value = Variant (static_cast<int> (date.hour ())); return true; }
else if (elements[3] == "minute") { value = Variant (static_cast<int> (date.minute ())); return true; }
else if (elements[3] == "second") { value = Variant (static_cast<int> (date.second ())); return true; }
Datetime date(i.first.substr(11));
if (elements[3] == "year") {
value = Variant(static_cast<int>(date.year()));
return true;
} else if (elements[3] == "month") {
value = Variant(static_cast<int>(date.month()));
return true;
} else if (elements[3] == "day") {
value = Variant(static_cast<int>(date.day()));
return true;
} else if (elements[3] == "week") {
value = Variant(static_cast<int>(date.week()));
return true;
} else if (elements[3] == "weekday") {
value = Variant(static_cast<int>(date.dayOfWeek()));
return true;
} else if (elements[3] == "julian") {
value = Variant(static_cast<int>(date.dayOfYear()));
return true;
} else if (elements[3] == "hour") {
value = Variant(static_cast<int>(date.hour()));
return true;
} else if (elements[3] == "minute") {
value = Variant(static_cast<int>(date.minute()));
return true;
} else if (elements[3] == "second") {
value = Variant(static_cast<int>(date.second()));
return true;
}
}
}
}
// Delegate to the context-free version of DOM::get.
return getDOM (name, value);
return getDOM(name, value);
}
////////////////////////////////////////////////////////////////////////////////
@ -511,41 +488,28 @@ bool getDOM (const std::string& name, const Task* task, Variant& value)
// This makes the DOM class a reusible object.
////////////////////////////////////////////////////////////////////////////////
DOM::~DOM ()
{
delete _node;
DOM::~DOM() { delete _node; }
////////////////////////////////////////////////////////////////////////////////
void DOM::addSource(const std::string& reference, bool (*provider)(const std::string&, Variant&)) {
if (_node == nullptr) _node = new DOM::Node();
_node->addSource(reference, provider);
}
////////////////////////////////////////////////////////////////////////////////
void DOM::addSource (
const std::string& reference,
bool (*provider)(const std::string&, Variant&))
{
if (_node == nullptr)
_node = new DOM::Node ();
_node->addSource (reference, provider);
bool DOM::valid(const std::string& reference) const {
return _node && _node->find(reference) != nullptr;
}
////////////////////////////////////////////////////////////////////////////////
bool DOM::valid (const std::string& reference) const
{
return _node && _node->find (reference) != nullptr;
}
Variant DOM::get(const std::string& reference) const {
Variant v("");
////////////////////////////////////////////////////////////////////////////////
Variant DOM::get (const std::string& reference) const
{
Variant v ("");
if (_node)
{
auto node = _node->find (reference);
if (node != nullptr &&
node->_provider != nullptr)
{
if (node->_provider (reference, v))
return v;
if (_node) {
auto node = _node->find(reference);
if (node != nullptr && node->_provider != nullptr) {
if (node->_provider(reference, v)) return v;
}
}
@ -553,60 +517,47 @@ Variant DOM::get (const std::string& reference) const
}
////////////////////////////////////////////////////////////////////////////////
int DOM::count () const
{
if (_node)
return _node->count ();
int DOM::count() const {
if (_node) return _node->count();
return 0;
}
////////////////////////////////////////////////////////////////////////////////
std::vector <std::string> DOM::decomposeReference (const std::string& reference)
{
return split (reference, '.');
std::vector<std::string> DOM::decomposeReference(const std::string& reference) {
return split(reference, '.');
}
////////////////////////////////////////////////////////////////////////////////
std::string DOM::dump () const
{
if (_node)
return _node->dump ();
std::string DOM::dump() const {
if (_node) return _node->dump();
return "";
}
////////////////////////////////////////////////////////////////////////////////
DOM::Node::~Node ()
{
for (auto& branch : _branches)
delete branch;
DOM::Node::~Node() {
for (auto& branch : _branches) delete branch;
}
////////////////////////////////////////////////////////////////////////////////
void DOM::Node::addSource (
const std::string& reference,
bool (*provider)(const std::string&, Variant&))
{
void DOM::Node::addSource(const std::string& reference,
bool (*provider)(const std::string&, Variant&)) {
auto cursor = this;
for (const auto& element : DOM::decomposeReference (reference))
{
auto found {false};
for (auto& branch : cursor->_branches)
{
if (branch->_name == element)
{
for (const auto& element : DOM::decomposeReference(reference)) {
auto found{false};
for (auto& branch : cursor->_branches) {
if (branch->_name == element) {
cursor = branch;
found = true;
break;
}
}
if (! found)
{
auto branch = new DOM::Node ();
if (!found) {
auto branch = new DOM::Node();
branch->_name = element;
cursor->_branches.push_back (branch);
cursor->_branches.push_back(branch);
cursor = branch;
}
}
@ -616,85 +567,66 @@ void DOM::Node::addSource (
////////////////////////////////////////////////////////////////////////////////
// A valid reference is one that has a provider function.
bool DOM::Node::valid (const std::string& reference) const
{
return find (reference) != nullptr;
}
bool DOM::Node::valid(const std::string& reference) const { return find(reference) != nullptr; }
////////////////////////////////////////////////////////////////////////////////
const DOM::Node* DOM::Node::find (const std::string& reference) const
{
const DOM::Node* DOM::Node::find(const std::string& reference) const {
auto cursor = this;
for (const auto& element : DOM::decomposeReference (reference))
{
auto found {false};
for (auto& branch : cursor->_branches)
{
if (branch->_name == element)
{
for (const auto& element : DOM::decomposeReference(reference)) {
auto found{false};
for (auto& branch : cursor->_branches) {
if (branch->_name == element) {
cursor = branch;
found = true;
break;
}
}
if (! found)
break;
if (!found) break;
}
if (reference.length () && cursor != this)
return cursor;
if (reference.length() && cursor != this) return cursor;
return nullptr;
}
////////////////////////////////////////////////////////////////////////////////
int DOM::Node::count () const
{
int DOM::Node::count() const {
// Recurse and count the branches.
int total {0};
for (auto& branch : _branches)
{
if (branch->_provider)
++total;
total += branch->count ();
int total{0};
for (auto& branch : _branches) {
if (branch->_provider) ++total;
total += branch->count();
}
return total;
}
////////////////////////////////////////////////////////////////////////////////
std::string DOM::Node::dumpNode (
const DOM::Node* node,
int depth) const
{
std::string DOM::Node::dumpNode(const DOM::Node* node, int depth) const {
std::stringstream out;
// Indent.
out << std::string (depth * 2, ' ');
out << std::string(depth * 2, ' ');
out << "\033[31m" << node->_name << "\033[0m";
if (node->_provider)
out << " 0x" << std::hex << (long long) (void*) node->_provider;
if (node->_provider) out << " 0x" << std::hex << (long long)(void*)node->_provider;
out << '\n';
// Recurse for branches.
for (auto& b : node->_branches)
out << dumpNode (b, depth + 1);
for (auto& b : node->_branches) out << dumpNode(b, depth + 1);
return out.str ();
return out.str();
}
////////////////////////////////////////////////////////////////////////////////
std::string DOM::Node::dump () const
{
std::string DOM::Node::dump() const {
std::stringstream out;
out << "DOM::Node (" << count () << " nodes)\n"
<< dumpNode (this, 1);
out << "DOM::Node (" << count() << " nodes)\n" << dumpNode(this, 1);
return out.str ();
return out.str();
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -27,49 +27,48 @@
#ifndef INCLUDED_DOM
#define INCLUDED_DOM
#include <string>
#include <Variant.h>
#include <Task.h>
#include <Variant.h>
#include <string>
// 2017-04-22 Deprecated, use DOM::get.
bool getDOM (const std::string&, Variant&);
bool getDOM (const std::string&, const Task*, Variant&);
bool getDOM(const std::string&, Variant&);
bool getDOM(const std::string&, const Task*, Variant&);
class DOM
{
public:
~DOM ();
void addSource (const std::string&, bool (*)(const std::string&, Variant&));
bool valid (const std::string&) const;
/*
// TODO Task object should register a generic provider.
Variant get (const Task&, const std::string&) const;
*/
Variant get (const std::string&) const;
int count () const;
static std::vector <std::string> decomposeReference (const std::string&);
std::string dump () const;
class DOM {
public:
~DOM();
void addSource(const std::string&, bool (*)(const std::string&, Variant&));
bool valid(const std::string&) const;
/*
// TODO Task object should register a generic provider.
Variant get (const Task&, const std::string&) const;
*/
Variant get(const std::string&) const;
int count() const;
static std::vector<std::string> decomposeReference(const std::string&);
std::string dump() const;
private:
class Node
{
public:
~Node ();
void addSource (const std::string&, bool (*)(const std::string&, Variant&));
bool valid (const std::string&) const;
const DOM::Node* find (const std::string&) const;
int count () const;
std::string dumpNode (const DOM::Node*, int) const;
std::string dump () const;
private:
class Node {
public:
~Node();
void addSource(const std::string&, bool (*)(const std::string&, Variant&));
bool valid(const std::string&) const;
const DOM::Node* find(const std::string&) const;
int count() const;
std::string dumpNode(const DOM::Node*, int) const;
std::string dump() const;
public:
std::string _name {"Unknown"};
bool (*_provider)(const std::string&, Variant&) {nullptr};
std::vector <DOM::Node*> _branches {};
public:
std::string _name{"Unknown"};
bool (*_provider)(const std::string&, Variant&){nullptr};
std::vector<DOM::Node*> _branches{};
};
private:
DOM::Node* _node {nullptr};
private:
DOM::Node* _node{nullptr};
};
#endif

File diff suppressed because it is too large Load diff

View file

@ -27,50 +27,51 @@
#ifndef INCLUDED_EVAL
#define INCLUDED_EVAL
#include <vector>
#include <string>
#include <Lexer.h>
#include <Variant.h>
bool domSource (const std::string&, Variant&);
#include <string>
#include <vector>
class Eval
{
public:
Eval ();
bool domSource(const std::string &, Variant &);
void addSource (bool (*fn)(const std::string&, Variant&));
void evaluateInfixExpression (const std::string&, Variant&) const;
void evaluatePostfixExpression (const std::string&, Variant&) const;
void compileExpression (const std::vector <std::pair <std::string, Lexer::Type>>&);
void evaluateCompiledExpression (Variant&);
void debug (bool);
class Eval {
public:
Eval();
static std::vector <std::string> getOperators ();
static std::vector <std::string> getBinaryOperators ();
void addSource(bool (*fn)(const std::string &, Variant &));
void evaluateInfixExpression(const std::string &, Variant &) const;
void evaluatePostfixExpression(const std::string &, Variant &) const;
void compileExpression(const std::vector<std::pair<std::string, Lexer::Type>> &);
void evaluateCompiledExpression(Variant &);
void debug(bool);
private:
void evaluatePostfixStack (const std::vector <std::pair <std::string, Lexer::Type>>&, Variant&) const;
void infixToPostfix (std::vector <std::pair <std::string, Lexer::Type>>&) const;
void infixParse (std::vector <std::pair <std::string, Lexer::Type>>&) const;
bool parseLogical (std::vector <std::pair <std::string, Lexer::Type>>&, unsigned int &) const;
bool parseRegex (std::vector <std::pair <std::string, Lexer::Type>>&, unsigned int &) const;
bool parseEquality (std::vector <std::pair <std::string, Lexer::Type>>&, unsigned int &) const;
bool parseComparative (std::vector <std::pair <std::string, Lexer::Type>>&, unsigned int &) const;
bool parseArithmetic (std::vector <std::pair <std::string, Lexer::Type>>&, unsigned int &) const;
bool parseGeometric (std::vector <std::pair <std::string, Lexer::Type>>&, unsigned int &) const;
bool parseTag (std::vector <std::pair <std::string, Lexer::Type>>&, unsigned int &) const;
bool parseUnary (std::vector <std::pair <std::string, Lexer::Type>>&, unsigned int &) const;
bool parseExponent (std::vector <std::pair <std::string, Lexer::Type>>&, unsigned int &) const;
bool parsePrimitive (std::vector <std::pair <std::string, Lexer::Type>>&, unsigned int &) const;
bool identifyOperator (const std::string&, char&, unsigned int&, char&) const;
static std::vector<std::string> getOperators();
static std::vector<std::string> getBinaryOperators();
std::string dump (std::vector <std::pair <std::string, Lexer::Type>>&) const;
private:
void evaluatePostfixStack(const std::vector<std::pair<std::string, Lexer::Type>> &,
Variant &) const;
void infixToPostfix(std::vector<std::pair<std::string, Lexer::Type>> &) const;
void infixParse(std::vector<std::pair<std::string, Lexer::Type>> &) const;
bool parseLogical(std::vector<std::pair<std::string, Lexer::Type>> &, unsigned int &) const;
bool parseRegex(std::vector<std::pair<std::string, Lexer::Type>> &, unsigned int &) const;
bool parseEquality(std::vector<std::pair<std::string, Lexer::Type>> &, unsigned int &) const;
bool parseComparative(std::vector<std::pair<std::string, Lexer::Type>> &, unsigned int &) const;
bool parseArithmetic(std::vector<std::pair<std::string, Lexer::Type>> &, unsigned int &) const;
bool parseGeometric(std::vector<std::pair<std::string, Lexer::Type>> &, unsigned int &) const;
bool parseTag(std::vector<std::pair<std::string, Lexer::Type>> &, unsigned int &) const;
bool parseUnary(std::vector<std::pair<std::string, Lexer::Type>> &, unsigned int &) const;
bool parseExponent(std::vector<std::pair<std::string, Lexer::Type>> &, unsigned int &) const;
bool parsePrimitive(std::vector<std::pair<std::string, Lexer::Type>> &, unsigned int &) const;
bool identifyOperator(const std::string &, char &, unsigned int &, char &) const;
private:
std::vector <bool (*)(const std::string&, Variant&)> _sources {};
bool _debug {false};
std::vector <std::pair <std::string, Lexer::Type>> _compiled {};
std::string dump(std::vector<std::pair<std::string, Lexer::Type>> &) const;
private:
std::vector<bool (*)(const std::string &, Variant &)> _sources{};
bool _debug{false};
std::vector<std::pair<std::string, Lexer::Type>> _compiled{};
};
#endif

View file

@ -25,145 +25,130 @@
////////////////////////////////////////////////////////////////////////////////
#include <cmake.h>
#include <Filter.h>
#include <algorithm>
// cmake.h include header must come first
#include <Context.h>
#include <Timer.h>
#include <DOM.h>
#include <Eval.h>
#include <Filter.h>
#include <Timer.h>
#include <Variant.h>
#include <format.h>
#include <shared.h>
////////////////////////////////////////////////////////////////////////////////
// Take an input set of tasks and filter into a subset.
void Filter::subset (const std::vector <Task>& input, std::vector <Task>& output)
{
void Filter::subset(const std::vector<Task>& input, std::vector<Task>& output) {
Timer timer;
_startCount = (int) input.size ();
_startCount = (int)input.size();
Context::getContext ().cli2.prepareFilter ();
Context::getContext().cli2.prepareFilter();
std::vector <std::pair <std::string, Lexer::Type>> precompiled;
for (auto& a : Context::getContext ().cli2._args)
if (a.hasTag ("FILTER"))
precompiled.emplace_back (a.getToken (), a._lextype);
std::vector<std::pair<std::string, Lexer::Type>> precompiled;
for (auto& a : Context::getContext().cli2._args)
if (a.hasTag("FILTER")) precompiled.emplace_back(a.getToken(), a._lextype);
if (precompiled.size ())
{
if (precompiled.size()) {
Eval eval;
eval.addSource (domSource);
eval.addSource(domSource);
// Debug output from Eval during compilation is useful. During evaluation
// it is mostly noise.
eval.debug (Context::getContext ().config.getInteger ("debug.parser") >= 3 ? true : false);
eval.compileExpression (precompiled);
eval.debug(Context::getContext().config.getInteger("debug.parser") >= 3 ? true : false);
eval.compileExpression(precompiled);
for (auto& task : input)
{
for (auto& task : input) {
// Set up context for any DOM references.
auto currentTask = Context::getContext ().withCurrentTask(&task);
auto currentTask = Context::getContext().withCurrentTask(&task);
Variant var;
eval.evaluateCompiledExpression (var);
if (var.get_bool ())
output.push_back (task);
eval.evaluateCompiledExpression(var);
if (var.get_bool()) output.push_back(task);
}
eval.debug (false);
}
else
eval.debug(false);
} else
output = input;
_endCount = (int) output.size ();
Context::getContext ().debug (format ("Filtered {1} tasks --> {2} tasks [list subset]", _startCount, _endCount));
Context::getContext ().time_filter_us += timer.total_us ();
_endCount = (int)output.size();
Context::getContext().debug(
format("Filtered {1} tasks --> {2} tasks [list subset]", _startCount, _endCount));
Context::getContext().time_filter_us += timer.total_us();
}
////////////////////////////////////////////////////////////////////////////////
// Take the set of all tasks and filter into a subset.
void Filter::subset (std::vector <Task>& output)
{
void Filter::subset(std::vector<Task>& output) {
Timer timer;
Context::getContext ().cli2.prepareFilter ();
Context::getContext().cli2.prepareFilter();
std::vector <std::pair <std::string, Lexer::Type>> precompiled;
for (auto& a : Context::getContext ().cli2._args)
if (a.hasTag ("FILTER"))
precompiled.emplace_back (a.getToken (), a._lextype);
std::vector<std::pair<std::string, Lexer::Type>> precompiled;
for (auto& a : Context::getContext().cli2._args)
if (a.hasTag("FILTER")) precompiled.emplace_back(a.getToken(), a._lextype);
// Shortcut indicates that only pending.data needs to be loaded.
bool shortcut = false;
if (precompiled.size ())
{
if (precompiled.size()) {
Timer timer_pending;
auto pending = Context::getContext ().tdb2.pending_tasks ();
Context::getContext ().time_filter_us -= timer_pending.total_us ();
_startCount = (int) pending.size ();
auto pending = Context::getContext().tdb2.pending_tasks();
Context::getContext().time_filter_us -= timer_pending.total_us();
_startCount = (int)pending.size();
Eval eval;
eval.addSource (domSource);
eval.addSource(domSource);
// Debug output from Eval during compilation is useful. During evaluation
// it is mostly noise.
eval.debug (Context::getContext ().config.getInteger ("debug.parser") >= 3 ? true : false);
eval.compileExpression (precompiled);
eval.debug(Context::getContext().config.getInteger("debug.parser") >= 3 ? true : false);
eval.compileExpression(precompiled);
output.clear ();
for (auto& task : pending)
{
output.clear();
for (auto& task : pending) {
// Set up context for any DOM references.
auto currentTask = Context::getContext ().withCurrentTask(&task);
auto currentTask = Context::getContext().withCurrentTask(&task);
Variant var;
eval.evaluateCompiledExpression (var);
if (var.get_bool ())
output.push_back (task);
eval.evaluateCompiledExpression(var);
if (var.get_bool()) output.push_back(task);
}
shortcut = pendingOnly ();
if (! shortcut)
{
shortcut = pendingOnly();
if (!shortcut) {
Timer timer_completed;
auto completed = Context::getContext ().tdb2.completed_tasks ();
Context::getContext ().time_filter_us -= timer_completed.total_us ();
_startCount += (int) completed.size ();
auto completed = Context::getContext().tdb2.completed_tasks();
Context::getContext().time_filter_us -= timer_completed.total_us();
_startCount += (int)completed.size();
for (auto& task : completed)
{
for (auto& task : completed) {
// Set up context for any DOM references.
auto currentTask = Context::getContext ().withCurrentTask(&task);
auto currentTask = Context::getContext().withCurrentTask(&task);
Variant var;
eval.evaluateCompiledExpression (var);
if (var.get_bool ())
output.push_back (task);
eval.evaluateCompiledExpression(var);
if (var.get_bool()) output.push_back(task);
}
}
eval.debug (false);
}
else
{
safety ();
eval.debug(false);
} else {
safety();
Timer pending_completed;
output = Context::getContext ().tdb2.all_tasks ();
Context::getContext ().time_filter_us -= pending_completed.total_us ();
output = Context::getContext().tdb2.all_tasks();
Context::getContext().time_filter_us -= pending_completed.total_us();
}
_endCount = (int) output.size ();
Context::getContext ().debug (format ("Filtered {1} tasks --> {2} tasks [{3}]", _startCount, _endCount, (shortcut ? "pending only" : "all tasks")));
Context::getContext ().time_filter_us += timer.total_us ();
_endCount = (int)output.size();
Context::getContext().debug(format("Filtered {1} tasks --> {2} tasks [{3}]", _startCount,
_endCount, (shortcut ? "pending only" : "all tasks")));
Context::getContext().time_filter_us += timer.total_us();
}
////////////////////////////////////////////////////////////////////////////////
bool Filter::hasFilter () const
{
for (const auto& a : Context::getContext ().cli2._args)
if (a.hasTag ("FILTER"))
return true;
bool Filter::hasFilter() const {
for (const auto& a : Context::getContext().cli2._args)
if (a.hasTag("FILTER")) return true;
return false;
}
@ -172,11 +157,9 @@ bool Filter::hasFilter () const
// If the filter contains no 'or', 'xor' or 'not' operators, and only includes
// status values 'pending', 'waiting' or 'recurring', then the filter is
// guaranteed to only need data from pending.data.
bool Filter::pendingOnly () const
{
bool Filter::pendingOnly() const {
// When GC is off, there are no shortcuts.
if (! Context::getContext ().config.getBoolean ("gc"))
return false;
if (!Context::getContext().config.getBoolean("gc")) return false;
// To skip loading completed.data, there should be:
// - 'status' in filter
@ -184,61 +167,51 @@ bool Filter::pendingOnly () const
// - no 'deleted'
// - no 'xor'
// - no 'or'
int countStatus = 0;
int countPending = 0;
int countWaiting = 0;
int countStatus = 0;
int countPending = 0;
int countWaiting = 0;
int countRecurring = 0;
int countId = (int) Context::getContext ().cli2._id_ranges.size ();
int countUUID = (int) Context::getContext ().cli2._uuid_list.size ();
int countOr = 0;
int countXor = 0;
int countNot = 0;
int countId = (int)Context::getContext().cli2._id_ranges.size();
int countUUID = (int)Context::getContext().cli2._uuid_list.size();
int countOr = 0;
int countXor = 0;
int countNot = 0;
bool pendingTag = false;
bool activeTag = false;
bool activeTag = false;
for (const auto& a : Context::getContext ().cli2._args)
{
if (a.hasTag ("FILTER"))
{
std::string raw = a.attribute ("raw");
std::string canonical = a.attribute ("canonical");
for (const auto& a : Context::getContext().cli2._args) {
if (a.hasTag("FILTER")) {
std::string raw = a.attribute("raw");
std::string canonical = a.attribute("canonical");
if (a._lextype == Lexer::Type::op && raw == "or") ++countOr;
if (a._lextype == Lexer::Type::op && raw == "xor") ++countXor;
if (a._lextype == Lexer::Type::op && raw == "not") ++countNot;
if (a._lextype == Lexer::Type::op && raw == "or") ++countOr;
if (a._lextype == Lexer::Type::op && raw == "xor") ++countXor;
if (a._lextype == Lexer::Type::op && raw == "not") ++countNot;
if (a._lextype == Lexer::Type::dom && canonical == "status") ++countStatus;
if ( raw == "pending") ++countPending;
if ( raw == "waiting") ++countWaiting;
if ( raw == "recurring") ++countRecurring;
if (raw == "pending") ++countPending;
if (raw == "waiting") ++countWaiting;
if (raw == "recurring") ++countRecurring;
}
}
for (const auto& word : Context::getContext ().cli2._original_args)
{
if (word.attribute ("raw") == "+PENDING") pendingTag = true;
if (word.attribute ("raw") == "+ACTIVE") activeTag = true;
for (const auto& word : Context::getContext().cli2._original_args) {
if (word.attribute("raw") == "+PENDING") pendingTag = true;
if (word.attribute("raw") == "+ACTIVE") activeTag = true;
}
if (countUUID) return false;
if (countUUID)
return false;
if (countOr || countXor || countNot) return false;
if (countOr || countXor || countNot)
return false;
if (pendingTag || activeTag) return true;
if (pendingTag || activeTag)
return true;
if (countStatus)
{
if (!countPending && !countWaiting && !countRecurring)
return false;
if (countStatus) {
if (!countPending && !countWaiting && !countRecurring) return false;
return true;
}
if (countId)
return true;
if (countId) return true;
return false;
}
@ -246,43 +219,35 @@ bool Filter::pendingOnly () const
////////////////////////////////////////////////////////////////////////////////
// Disaster avoidance mechanism. If a !READONLY has no filter, then it can cause
// all tasks to be modified. This is usually not intended.
void Filter::safety () const
{
if (_safety)
{
void Filter::safety() const {
if (_safety) {
bool readonly = true;
bool filter = false;
for (const auto& a : Context::getContext ().cli2._args)
{
if (a.hasTag ("CMD") &&
! a.hasTag ("READONLY"))
readonly = false;
for (const auto& a : Context::getContext().cli2._args) {
if (a.hasTag("CMD") && !a.hasTag("READONLY")) readonly = false;
if (a.hasTag ("FILTER"))
filter = true;
if (a.hasTag("FILTER")) filter = true;
}
if (! readonly &&
! filter)
{
if (! Context::getContext ().config.getBoolean ("allow.empty.filter"))
throw std::string ("You did not specify a filter, and with the 'allow.empty.filter' value, no action is taken.");
if (!readonly && !filter) {
if (!Context::getContext().config.getBoolean("allow.empty.filter"))
throw std::string(
"You did not specify a filter, and with the 'allow.empty.filter' value, no action is "
"taken.");
// If user is willing to be asked, this can be avoided.
if (Context::getContext ().config.getBoolean ("confirmation") &&
confirm ("This command has no filter, and will modify all (including completed and deleted) tasks. Are you sure?"))
if (Context::getContext().config.getBoolean("confirmation") &&
confirm("This command has no filter, and will modify all (including completed and "
"deleted) tasks. Are you sure?"))
return;
// Sound the alarm.
throw std::string ("Command prevented from running.");
throw std::string("Command prevented from running.");
}
}
}
////////////////////////////////////////////////////////////////////////////////
void Filter::disableSafety ()
{
_safety = false;
}
void Filter::disableSafety() { _safety = false; }
////////////////////////////////////////////////////////////////////////////////

View file

@ -27,28 +27,26 @@
#ifndef INCLUDED_FILTER
#define INCLUDED_FILTER
#include <string>
#include <vector>
#include <Task.h>
#include <Variant.h>
class Filter
{
public:
Filter () = default;
#include <vector>
void subset (const std::vector <Task>&, std::vector <Task>&);
void subset (std::vector <Task>&);
bool hasFilter () const;
bool pendingOnly () const;
void safety () const;
void disableSafety ();
class Filter {
public:
Filter() = default;
private:
int _startCount {0};
int _endCount {0};
bool _safety {true};
void subset(const std::vector<Task>&, std::vector<Task>&);
void subset(std::vector<Task>&);
bool hasFilter() const;
bool pendingOnly() const;
void safety() const;
void disableSafety();
private:
int _startCount{0};
int _endCount{0};
bool _safety{true};
};
#endif

View file

@ -25,91 +25,85 @@
////////////////////////////////////////////////////////////////////////////////
#include <cmake.h>
// cmake.h include header must come first
#include <Hooks.h>
#include <algorithm>
// If <iostream> is included, put it after <stdio.h>, because it includes
// <stdio.h>, and therefore would ignore the _WITH_GETLINE.
#ifdef FREEBSD
#define _WITH_GETLINE
#endif
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <Context.h>
#include <Variant.h>
#include <DOM.h>
#include <Lexer.h>
#include <JSON.h>
#include <Timer.h>
#include <FS.h>
#include <JSON.h>
#include <Lexer.h>
#include <Timer.h>
#include <Variant.h>
#include <format.h>
#include <shared.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <util.h>
#define STRING_HOOK_ERROR_OBJECT "Hook Error: JSON Object '{...}' expected from hook script: {1}"
#define STRING_HOOK_ERROR_NODESC "Hook Error: JSON Object missing 'description' attribute from hook script: {1}"
#define STRING_HOOK_ERROR_NOUUID "Hook Error: JSON Object missing 'uuid' attribute from hook script: {1}"
#define STRING_HOOK_ERROR_SYNTAX "Hook Error: JSON syntax error in: {1}"
#define STRING_HOOK_ERROR_JSON "Hook Error: JSON "
#define STRING_HOOK_ERROR_NOPARSE "Hook Error: JSON failed to parse: "
#define STRING_HOOK_ERROR_BAD_NUM "Hook Error: Expected {1} JSON task(s), found {2}, in hook script: {3}"
#define STRING_HOOK_ERROR_SAME1 "Hook Error: JSON must be for the same task: {1}, in hook script: {2}"
#define STRING_HOOK_ERROR_SAME2 "Hook Error: JSON must be for the same task: {1} != {2}, in hook script: {3}"
#define STRING_HOOK_ERROR_OBJECT "Hook Error: JSON Object '{...}' expected from hook script: {1}"
#define STRING_HOOK_ERROR_NODESC \
"Hook Error: JSON Object missing 'description' attribute from hook script: {1}"
#define STRING_HOOK_ERROR_NOUUID \
"Hook Error: JSON Object missing 'uuid' attribute from hook script: {1}"
#define STRING_HOOK_ERROR_SYNTAX "Hook Error: JSON syntax error in: {1}"
#define STRING_HOOK_ERROR_JSON "Hook Error: JSON "
#define STRING_HOOK_ERROR_NOPARSE "Hook Error: JSON failed to parse: "
#define STRING_HOOK_ERROR_BAD_NUM \
"Hook Error: Expected {1} JSON task(s), found {2}, in hook script: {3}"
#define STRING_HOOK_ERROR_SAME1 \
"Hook Error: JSON must be for the same task: {1}, in hook script: {2}"
#define STRING_HOOK_ERROR_SAME2 \
"Hook Error: JSON must be for the same task: {1} != {2}, in hook script: {3}"
#define STRING_HOOK_ERROR_NOFEEDBACK "Hook Error: Expected feedback from failing hook script: {1}"
////////////////////////////////////////////////////////////////////////////////
void Hooks::initialize ()
{
_debug = Context::getContext ().config.getInteger ("debug.hooks");
void Hooks::initialize() {
_debug = Context::getContext().config.getInteger("debug.hooks");
// Scan <rc.hooks.location>
// <rc.data.location>/hooks
Directory d;
if (Context::getContext ().config.has ("hooks.location"))
{
d = Directory (Context::getContext ().config.get ("hooks.location"));
}
else
{
d = Directory (Context::getContext ().config.get ("data.location"));
if (Context::getContext().config.has("hooks.location")) {
d = Directory(Context::getContext().config.get("hooks.location"));
} else {
d = Directory(Context::getContext().config.get("data.location"));
d += "hooks";
}
if (d.is_directory () &&
d.readable ())
{
_scripts = d.list ();
std::sort (_scripts.begin (), _scripts.end ());
if (d.is_directory() && d.readable()) {
_scripts = d.list();
std::sort(_scripts.begin(), _scripts.end());
if (_debug >= 1)
{
for (auto& i : _scripts)
{
Path p (i);
if (! p.is_directory ())
{
std::string name = p.name ();
if (name.substr (0, 6) == "on-add" ||
name.substr (0, 9) == "on-modify" ||
name.substr (0, 9) == "on-launch" ||
name.substr (0, 7) == "on-exit")
Context::getContext ().debug ("Found hook script " + i);
if (_debug >= 1) {
for (auto& i : _scripts) {
Path p(i);
if (!p.is_directory()) {
std::string name = p.name();
if (name.substr(0, 6) == "on-add" || name.substr(0, 9) == "on-modify" ||
name.substr(0, 9) == "on-launch" || name.substr(0, 7) == "on-exit")
Context::getContext().debug("Found hook script " + i);
else
Context::getContext ().debug ("Found misnamed hook script " + i);
Context::getContext().debug("Found misnamed hook script " + i);
}
}
}
}
else if (_debug >= 1)
Context::getContext ().debug ("Hook directory not readable: " + d._data);
} else if (_debug >= 1)
Context::getContext().debug("Hook directory not readable: " + d._data);
_enabled = Context::getContext ().config.getBoolean ("hooks");
_enabled = Context::getContext().config.getBoolean("hooks");
}
////////////////////////////////////////////////////////////////////////////////
bool Hooks::enable (bool value)
{
bool Hooks::enable(bool value) {
bool old_value = _enabled;
_enabled = value;
return old_value;
@ -127,45 +121,36 @@ bool Hooks::enable (bool value)
// - all emitted non-JSON lines are considered feedback or error messages
// depending on the status code.
//
void Hooks::onLaunch () const
{
if (! _enabled)
return;
void Hooks::onLaunch() const {
if (!_enabled) return;
Timer timer;
std::vector <std::string> matchingScripts = scripts ("on-launch");
if (matchingScripts.size ())
{
for (auto& script : matchingScripts)
{
std::vector <std::string> input;
std::vector <std::string> output;
int status = callHookScript (script, input, output);
std::vector<std::string> matchingScripts = scripts("on-launch");
if (matchingScripts.size()) {
for (auto& script : matchingScripts) {
std::vector<std::string> input;
std::vector<std::string> output;
int status = callHookScript(script, input, output);
std::vector <std::string> outputJSON;
std::vector <std::string> outputFeedback;
separateOutput (output, outputJSON, outputFeedback);
std::vector<std::string> outputJSON;
std::vector<std::string> outputFeedback;
separateOutput(output, outputJSON, outputFeedback);
assertNTasks (outputJSON, 0, script);
assertNTasks(outputJSON, 0, script);
if (status == 0)
{
for (auto& message : outputFeedback)
Context::getContext ().footnote (message);
}
else
{
assertFeedback (outputFeedback, script);
for (auto& message : outputFeedback)
Context::getContext ().error (message);
if (status == 0) {
for (auto& message : outputFeedback) Context::getContext().footnote(message);
} else {
assertFeedback(outputFeedback, script);
for (auto& message : outputFeedback) Context::getContext().error(message);
throw 0; // This is how hooks silently terminate processing.
}
}
}
Context::getContext ().time_hooks_us += timer.total_us ();
Context::getContext().time_hooks_us += timer.total_us();
}
////////////////////////////////////////////////////////////////////////////////
@ -180,55 +165,45 @@ void Hooks::onLaunch () const
// - all emitted non-JSON lines are considered feedback or error messages
// depending on the status code.
//
void Hooks::onExit () const
{
if (! _enabled)
return;
void Hooks::onExit() const {
if (!_enabled) return;
Timer timer;
std::vector <std::string> matchingScripts = scripts ("on-exit");
if (matchingScripts.size ())
{
std::vector<std::string> matchingScripts = scripts("on-exit");
if (matchingScripts.size()) {
// Get the set of changed tasks.
std::vector <Task> tasks;
Context::getContext ().tdb2.get_changes (tasks);
std::vector<Task> tasks;
Context::getContext().tdb2.get_changes(tasks);
// Convert to a vector of strings.
std::vector <std::string> input;
std::vector<std::string> input;
input.reserve(tasks.size());
for (auto& t : tasks)
input.push_back (t.composeJSON ());
for (auto& t : tasks) input.push_back(t.composeJSON());
// Call the hook scripts, with the invariant input.
for (auto& script : matchingScripts)
{
std::vector <std::string> output;
int status = callHookScript (script, input, output);
for (auto& script : matchingScripts) {
std::vector<std::string> output;
int status = callHookScript(script, input, output);
std::vector <std::string> outputJSON;
std::vector <std::string> outputFeedback;
separateOutput (output, outputJSON, outputFeedback);
std::vector<std::string> outputJSON;
std::vector<std::string> outputFeedback;
separateOutput(output, outputJSON, outputFeedback);
assertNTasks (outputJSON, 0, script);
assertNTasks(outputJSON, 0, script);
if (status == 0)
{
for (auto& message : outputFeedback)
Context::getContext ().footnote (message);
}
else
{
assertFeedback (outputFeedback, script);
for (auto& message : outputFeedback)
Context::getContext ().error (message);
if (status == 0) {
for (auto& message : outputFeedback) Context::getContext().footnote(message);
} else {
assertFeedback(outputFeedback, script);
for (auto& message : outputFeedback) Context::getContext().error(message);
throw 0; // This is how hooks silently terminate processing.
}
}
}
Context::getContext ().time_hooks_us += timer.total_us ();
Context::getContext().time_hooks_us += timer.total_us();
}
////////////////////////////////////////////////////////////////////////////////
@ -243,57 +218,48 @@ void Hooks::onExit () const
// - all emitted non-JSON lines are considered feedback or error messages
// depending on the status code.
//
void Hooks::onAdd (Task& task) const
{
if (! _enabled)
return;
void Hooks::onAdd(Task& task) const {
if (!_enabled) return;
Timer timer;
std::vector <std::string> matchingScripts = scripts ("on-add");
if (matchingScripts.size ())
{
std::vector<std::string> matchingScripts = scripts("on-add");
if (matchingScripts.size()) {
// Convert task to a vector of strings.
std::vector <std::string> input;
input.push_back (task.composeJSON ());
std::vector<std::string> input;
input.push_back(task.composeJSON());
// Call the hook scripts.
for (auto& script : matchingScripts)
{
std::vector <std::string> output;
int status = callHookScript (script, input, output);
for (auto& script : matchingScripts) {
std::vector<std::string> output;
int status = callHookScript(script, input, output);
std::vector <std::string> outputJSON;
std::vector <std::string> outputFeedback;
separateOutput (output, outputJSON, outputFeedback);
std::vector<std::string> outputJSON;
std::vector<std::string> outputFeedback;
separateOutput(output, outputJSON, outputFeedback);
if (status == 0)
{
assertNTasks (outputJSON, 1, script);
assertValidJSON (outputJSON, script);
assertSameTask (outputJSON, task, script);
if (status == 0) {
assertNTasks(outputJSON, 1, script);
assertValidJSON(outputJSON, script);
assertSameTask(outputJSON, task, script);
// Propagate forward to the next script.
input[0] = outputJSON[0];
for (auto& message : outputFeedback)
Context::getContext ().footnote (message);
}
else
{
assertFeedback (outputFeedback, script);
for (auto& message : outputFeedback)
Context::getContext ().error (message);
for (auto& message : outputFeedback) Context::getContext().footnote(message);
} else {
assertFeedback(outputFeedback, script);
for (auto& message : outputFeedback) Context::getContext().error(message);
throw 0; // This is how hooks silently terminate processing.
}
}
// Transfer the modified task back to the original task.
task = Task (input[0]);
task = Task(input[0]);
}
Context::getContext ().time_hooks_us += timer.total_us ();
Context::getContext().time_hooks_us += timer.total_us();
}
////////////////////////////////////////////////////////////////////////////////
@ -309,76 +275,60 @@ void Hooks::onAdd (Task& task) const
// - all emitted non-JSON lines are considered feedback or error messages
// depending on the status code.
//
void Hooks::onModify (const Task& before, Task& after) const
{
if (! _enabled)
return;
void Hooks::onModify(Task& before, Task& after) const {
if (!_enabled) return;
Timer timer;
std::vector <std::string> matchingScripts = scripts ("on-modify");
if (matchingScripts.size ())
{
std::vector<std::string> matchingScripts = scripts("on-modify");
if (matchingScripts.size()) {
// Convert vector of tasks to a vector of strings.
std::vector <std::string> input;
input.push_back (before.composeJSON ()); // [line 0] original, never changes
input.push_back (after.composeJSON ()); // [line 1] modified
std::vector<std::string> input;
input.push_back(before.composeJSON()); // [line 0] original, never changes
input.push_back(after.composeJSON()); // [line 1] modified
// Call the hook scripts.
for (auto& script : matchingScripts)
{
std::vector <std::string> output;
int status = callHookScript (script, input, output);
for (auto& script : matchingScripts) {
std::vector<std::string> output;
int status = callHookScript(script, input, output);
std::vector <std::string> outputJSON;
std::vector <std::string> outputFeedback;
separateOutput (output, outputJSON, outputFeedback);
std::vector<std::string> outputJSON;
std::vector<std::string> outputFeedback;
separateOutput(output, outputJSON, outputFeedback);
if (status == 0)
{
assertNTasks (outputJSON, 1, script);
assertValidJSON (outputJSON, script);
assertSameTask (outputJSON, before, script);
if (status == 0) {
assertNTasks(outputJSON, 1, script);
assertValidJSON(outputJSON, script);
assertSameTask(outputJSON, before, script);
// Propagate accepted changes forward to the next script.
input[1] = outputJSON[0];
for (auto& message : outputFeedback)
Context::getContext ().footnote (message);
}
else
{
assertFeedback (outputFeedback, script);
for (auto& message : outputFeedback)
Context::getContext ().error (message);
for (auto& message : outputFeedback) Context::getContext().footnote(message);
} else {
assertFeedback(outputFeedback, script);
for (auto& message : outputFeedback) Context::getContext().error(message);
throw 0; // This is how hooks silently terminate processing.
}
}
after = Task (input[1]);
after = Task(input[1]);
}
Context::getContext ().time_hooks_us += timer.total_us ();
Context::getContext().time_hooks_us += timer.total_us();
}
////////////////////////////////////////////////////////////////////////////////
std::vector <std::string> Hooks::list () const
{
return _scripts;
}
std::vector<std::string> Hooks::list() const { return _scripts; }
////////////////////////////////////////////////////////////////////////////////
std::vector <std::string> Hooks::scripts (const std::string& event) const
{
std::vector <std::string> matching;
for (const auto& i : _scripts)
{
if (i.find ("/" + event) != std::string::npos)
{
File script (i);
if (script.executable ())
matching.push_back (i);
std::vector<std::string> Hooks::scripts(const std::string& event) const {
std::vector<std::string> matching;
for (const auto& i : _scripts) {
if (i.find("/" + event) != std::string::npos) {
File script(i);
if (script.executable()) matching.push_back(i);
}
}
@ -386,124 +336,95 @@ std::vector <std::string> Hooks::scripts (const std::string& event) const
}
////////////////////////////////////////////////////////////////////////////////
void Hooks::separateOutput (
const std::vector <std::string>& output,
std::vector <std::string>& json,
std::vector <std::string>& feedback) const
{
for (auto& i : output)
{
if (isJSON (i))
json.push_back (i);
void Hooks::separateOutput(const std::vector<std::string>& output, std::vector<std::string>& json,
std::vector<std::string>& feedback) const {
for (auto& i : output) {
if (isJSON(i))
json.push_back(i);
else
feedback.push_back (i);
feedback.push_back(i);
}
}
////////////////////////////////////////////////////////////////////////////////
bool Hooks::isJSON (const std::string& input) const
{
return input.length () > 2 &&
input[0] == '{' &&
input[input.length () - 1] == '}';
bool Hooks::isJSON(const std::string& input) const {
return input.length() > 2 && input[0] == '{' && input[input.length() - 1] == '}';
}
////////////////////////////////////////////////////////////////////////////////
void Hooks::assertValidJSON (
const std::vector <std::string>& input,
const std::string& script) const
{
for (auto& i : input)
{
if (i.length () < 3 ||
i[0] != '{' ||
i[i.length () - 1] != '}')
{
Context::getContext ().error (format (STRING_HOOK_ERROR_OBJECT, Path (script).name ()));
void Hooks::assertValidJSON(const std::vector<std::string>& input,
const std::string& script) const {
for (auto& i : input) {
if (i.length() < 3 || i[0] != '{' || i[i.length() - 1] != '}') {
Context::getContext().error(format(STRING_HOOK_ERROR_OBJECT, Path(script).name()));
throw 0;
}
try
{
json::value* root = json::parse (i);
if (root->type () != json::j_object)
{
Context::getContext ().error (format (STRING_HOOK_ERROR_OBJECT, Path (script).name ()));
try {
json::value* root = json::parse(i);
if (root->type() != json::j_object) {
Context::getContext().error(format(STRING_HOOK_ERROR_OBJECT, Path(script).name()));
throw 0;
}
if (((json::object*)root)->_data.find ("description") == ((json::object*)root)->_data.end ())
{
Context::getContext ().error (format (STRING_HOOK_ERROR_NODESC, Path (script).name ()));
if (((json::object*)root)->_data.find("description") == ((json::object*)root)->_data.end()) {
Context::getContext().error(format(STRING_HOOK_ERROR_NODESC, Path(script).name()));
throw 0;
}
if (((json::object*)root)->_data.find ("uuid") == ((json::object*)root)->_data.end ())
{
Context::getContext ().error (format (STRING_HOOK_ERROR_NOUUID, Path (script).name ()));
if (((json::object*)root)->_data.find("uuid") == ((json::object*)root)->_data.end()) {
Context::getContext().error(format(STRING_HOOK_ERROR_NOUUID, Path(script).name()));
throw 0;
}
delete root;
}
catch (const std::string& e)
{
Context::getContext ().error (format (STRING_HOOK_ERROR_SYNTAX, i));
if (_debug)
Context::getContext ().error (STRING_HOOK_ERROR_JSON + e);
catch (const std::string& e) {
Context::getContext().error(format(STRING_HOOK_ERROR_SYNTAX, i));
if (_debug) Context::getContext().error(STRING_HOOK_ERROR_JSON + e);
throw 0;
}
catch (...)
{
Context::getContext ().error (STRING_HOOK_ERROR_NOPARSE + i);
catch (...) {
Context::getContext().error(STRING_HOOK_ERROR_NOPARSE + i);
throw 0;
}
}
}
////////////////////////////////////////////////////////////////////////////////
void Hooks::assertNTasks (
const std::vector <std::string>& input,
unsigned int n,
const std::string& script) const
{
if (input.size () != n)
{
Context::getContext ().error (format (STRING_HOOK_ERROR_BAD_NUM, n, (int) input.size (), Path (script).name ()));
void Hooks::assertNTasks(const std::vector<std::string>& input, unsigned int n,
const std::string& script) const {
if (input.size() != n) {
Context::getContext().error(
format(STRING_HOOK_ERROR_BAD_NUM, n, (int)input.size(), Path(script).name()));
throw 0;
}
}
////////////////////////////////////////////////////////////////////////////////
void Hooks::assertSameTask (
const std::vector <std::string>& input,
const Task& task,
const std::string& script) const
{
std::string uuid = task.get ("uuid");
void Hooks::assertSameTask(const std::vector<std::string>& input, const Task& task,
const std::string& script) const {
std::string uuid = task.get("uuid");
for (auto& i : input)
{
auto root_obj = (json::object*)json::parse (i);
for (auto& i : input) {
auto root_obj = (json::object*)json::parse(i);
// If there is no UUID at all.
auto u = root_obj->_data.find ("uuid");
if (u == root_obj->_data.end () ||
u->second->type () != json::j_string)
{
Context::getContext ().error (format (STRING_HOOK_ERROR_SAME1, uuid, Path (script).name ()));
auto u = root_obj->_data.find("uuid");
if (u == root_obj->_data.end() || u->second->type() != json::j_string) {
Context::getContext().error(format(STRING_HOOK_ERROR_SAME1, uuid, Path(script).name()));
throw 0;
}
auto up = (json::string*) u->second;
auto text = up->dump ();
Lexer::dequote (text);
std::string json_uuid = json::decode (text);
if (json_uuid != uuid)
{
Context::getContext ().error (format (STRING_HOOK_ERROR_SAME2, uuid, json_uuid, Path (script).name ()));
auto up = (json::string*)u->second;
auto text = up->dump();
Lexer::dequote(text);
std::string json_uuid = json::decode(text);
if (json_uuid != uuid) {
Context::getContext().error(
format(STRING_HOOK_ERROR_SAME2, uuid, json_uuid, Path(script).name()));
throw 0;
}
@ -512,101 +433,82 @@ void Hooks::assertSameTask (
}
////////////////////////////////////////////////////////////////////////////////
void Hooks::assertFeedback (
const std::vector <std::string>& input,
const std::string& script) const
{
void Hooks::assertFeedback(const std::vector<std::string>& input, const std::string& script) const {
bool foundSomething = false;
for (auto& i : input)
if (nontrivial (i))
foundSomething = true;
if (nontrivial(i)) foundSomething = true;
if (! foundSomething)
{
Context::getContext ().error (format (STRING_HOOK_ERROR_NOFEEDBACK, Path (script).name ()));
if (!foundSomething) {
Context::getContext().error(format(STRING_HOOK_ERROR_NOFEEDBACK, Path(script).name()));
throw 0;
}
}
////////////////////////////////////////////////////////////////////////////////
std::vector <std::string>& Hooks::buildHookScriptArgs (std::vector <std::string>& args) const
{
std::vector<std::string>& Hooks::buildHookScriptArgs(std::vector<std::string>& args) const {
Variant v;
// Hooks API version.
args.push_back ("api:2");
args.push_back("api:2");
// Command line Taskwarrior was called with.
getDOM ("context.args", v);
args.push_back ("args:" + std::string (v));
getDOM("context.args", v);
args.push_back("args:" + std::string(v));
// Command to be executed.
args.push_back ("command:" + Context::getContext ().cli2.getCommand ());
args.push_back("command:" + Context::getContext().cli2.getCommand());
// rc file used after applying all overrides.
args.push_back ("rc:" + Context::getContext ().rc_file._data);
args.push_back("rc:" + Context::getContext().rc_file._data);
// Directory containing *.data files.
args.push_back ("data:" + Context::getContext ().data_dir._data);
args.push_back("data:" + Context::getContext().data_dir._data);
// Taskwarrior version, same as returned by "task --version"
args.push_back ("version:" + std::string(VERSION));
args.push_back("version:" + std::string(VERSION));
return args;
}
////////////////////////////////////////////////////////////////////////////////
int Hooks::callHookScript (
const std::string& script,
const std::vector <std::string>& input,
std::vector <std::string>& output) const
{
if (_debug >= 1)
Context::getContext ().debug ("Hook: Calling " + script);
int Hooks::callHookScript(const std::string& script, const std::vector<std::string>& input,
std::vector<std::string>& output) const {
if (_debug >= 1) Context::getContext().debug("Hook: Calling " + script);
if (_debug >= 2)
{
Context::getContext ().debug ("Hook: input");
for (const auto& i : input)
Context::getContext ().debug (" " + i);
if (_debug >= 2) {
Context::getContext().debug("Hook: input");
for (const auto& i : input) Context::getContext().debug(" " + i);
}
std::string inputStr;
for (const auto& i : input)
inputStr += i + "\n";
for (const auto& i : input) inputStr += i + "\n";
std::vector <std::string> args;
buildHookScriptArgs (args);
if (_debug >= 2)
{
Context::getContext ().debug ("Hooks: args");
for (const auto& arg: args)
Context::getContext ().debug (" " + arg);
std::vector<std::string> args;
buildHookScriptArgs(args);
if (_debug >= 2) {
Context::getContext().debug("Hooks: args");
for (const auto& arg : args) Context::getContext().debug(" " + arg);
}
// Measure time for each hook if running in debug
int status;
std::string outputStr;
if (_debug >= 2)
{
if (_debug >= 2) {
Timer timer;
status = execute (script, args, inputStr, outputStr);
Context::getContext ().debugTiming (format ("Hooks::execute ({1})", script), timer);
}
else
status = execute (script, args, inputStr, outputStr);
status = execute(script, args, inputStr, outputStr);
Context::getContext().debugTiming(format("Hooks::execute ({1})", script), timer);
} else
status = execute(script, args, inputStr, outputStr);
output = split (outputStr, '\n');
output = split(outputStr, '\n');
if (_debug >= 2)
{
Context::getContext ().debug ("Hook: output");
if (_debug >= 2) {
Context::getContext().debug("Hook: output");
for (const auto& i : output)
if (i != "")
Context::getContext ().debug (" " + i);
if (i != "") Context::getContext().debug(" " + i);
Context::getContext ().debug (format ("Hook: Completed with status {1}", status));
Context::getContext ().debug (" "); // Blank line
Context::getContext().debug(format("Hook: Completed with status {1}", status));
Context::getContext().debug(" "); // Blank line
}
return status;

View file

@ -27,37 +27,39 @@
#ifndef INCLUDED_HOOKS
#define INCLUDED_HOOKS
#include <vector>
#include <string>
#include <Task.h>
class Hooks
{
public:
Hooks () = default;
void initialize ();
bool enable (bool);
void onLaunch () const;
void onExit () const;
void onAdd (Task&) const;
void onModify (const Task&, Task&) const;
std::vector <std::string> list () const;
#include <string>
#include <vector>
private:
std::vector <std::string> scripts (const std::string&) const;
void separateOutput (const std::vector <std::string>&, std::vector <std::string>&, std::vector <std::string>&) const;
bool isJSON (const std::string&) const;
void assertValidJSON (const std::vector <std::string>&, const std::string&) const;
void assertNTasks (const std::vector <std::string>&, unsigned int, const std::string&) const;
void assertSameTask (const std::vector <std::string>&, const Task&, const std::string&) const;
void assertFeedback (const std::vector <std::string>&, const std::string&) const;
std::vector <std::string>& buildHookScriptArgs (std::vector <std::string>&) const;
int callHookScript (const std::string&, const std::vector <std::string>&, std::vector <std::string>&) const;
class Hooks {
public:
Hooks() = default;
void initialize();
bool enable(bool);
void onLaunch() const;
void onExit() const;
void onAdd(Task&) const;
void onModify(Task&, Task&) const;
std::vector<std::string> list() const;
private:
bool _enabled {true};
int _debug {0};
std::vector <std::string> _scripts {};
private:
std::vector<std::string> scripts(const std::string&) const;
void separateOutput(const std::vector<std::string>&, std::vector<std::string>&,
std::vector<std::string>&) const;
bool isJSON(const std::string&) const;
void assertValidJSON(const std::vector<std::string>&, const std::string&) const;
void assertNTasks(const std::vector<std::string>&, unsigned int, const std::string&) const;
void assertSameTask(const std::vector<std::string>&, const Task&, const std::string&) const;
void assertFeedback(const std::vector<std::string>&, const std::string&) const;
std::vector<std::string>& buildHookScriptArgs(std::vector<std::string>&) const;
int callHookScript(const std::string&, const std::vector<std::string>&,
std::vector<std::string>&) const;
private:
bool _enabled{true};
int _debug{0};
std::vector<std::string> _scripts{};
};
#endif

File diff suppressed because it is too large Load diff

View file

@ -27,95 +27,108 @@
#ifndef INCLUDED_LEXER
#define INCLUDED_LEXER
#include <string>
#include <map>
#include <vector>
#include <cstddef>
#include <map>
#include <string>
#include <vector>
// Lexer: A UTF8 lexical analyzer for every construct used on the Taskwarrior
// command line, with additional recognized types for disambiguation.
class Lexer
{
public:
class Lexer {
public:
// These are overridable.
static std::string dateFormat;
static std::string::size_type minimumMatchLength;
static std::map <std::string, std::string> attributes;
static std::map<std::string, std::string> attributes;
enum class Type { uuid, number, hex,
string,
url, pair, set, separator,
tag,
path,
substitution, pattern,
op,
dom, identifier, word,
date, duration };
enum class Type {
uuid,
number,
hex,
string,
url,
pair,
set,
separator,
tag,
path,
substitution,
pattern,
op,
dom,
identifier,
word,
date,
duration
};
Lexer (const std::string&);
~Lexer () = default;
bool token (std::string&, Lexer::Type&);
static std::vector <std::string> split (const std::string&);
static std::string typeToString (Lexer::Type);
Lexer(const std::string&);
~Lexer() = default;
bool token(std::string&, Lexer::Type&);
static std::vector<std::string> split(const std::string&);
static std::string typeToString(Lexer::Type);
// Static helpers.
static const std::string typeName (const Lexer::Type&);
static bool isIdentifierStart (int);
static bool isIdentifierNext (int);
static bool isSingleCharOperator (int);
static bool isDoubleCharOperator (int, int, int);
static bool isTripleCharOperator (int, int, int, int);
static bool isBoundary (int, int);
static bool isHardBoundary (int, int);
static bool isPunctuation (int);
static bool isAllDigits (const std::string&);
static bool isDOM (const std::string&);
static void dequote (std::string&, const std::string& quotes = "'\"");
static bool wasQuoted (const std::string&);
static bool readWord (const std::string&, const std::string&, std::string::size_type&, std::string&);
static bool readWord (const std::string&, std::string::size_type&, std::string&);
static bool decomposePair (const std::string&, std::string&, std::string&, std::string&, std::string&);
static bool decomposeSubstitution (const std::string&, std::string&, std::string&, std::string&);
static bool decomposePattern (const std::string&, std::string&, std::string&);
static int hexToInt (int);
static int hexToInt (int, int);
static int hexToInt (int, int, int, int);
static std::string::size_type commonLength (const std::string&, const std::string&);
static std::string::size_type commonLength (const std::string&, std::string::size_type, const std::string&, std::string::size_type);
static std::string commify (const std::string&);
static std::string lowerCase (const std::string&);
static std::string ucFirst (const std::string&);
static std::string trimLeft (const std::string& in, const std::string& t = " ");
static std::string trimRight (const std::string& in, const std::string& t = " ");
static std::string trim (const std::string& in, const std::string& t = " ");
static const std::string typeName(const Lexer::Type&);
static bool isIdentifierStart(int);
static bool isIdentifierNext(int);
static bool isSingleCharOperator(int);
static bool isDoubleCharOperator(int, int, int);
static bool isTripleCharOperator(int, int, int, int);
static bool isBoundary(int, int);
static bool isHardBoundary(int, int);
static bool isPunctuation(int);
static bool isAllDigits(const std::string&);
static bool isDOM(const std::string&);
static void dequote(std::string&, const std::string& quotes = "'\"");
static bool wasQuoted(const std::string&);
static bool readWord(const std::string&, const std::string&, std::string::size_type&,
std::string&);
static bool readWord(const std::string&, std::string::size_type&, std::string&);
static bool decomposePair(const std::string&, std::string&, std::string&, std::string&,
std::string&);
static bool decomposeSubstitution(const std::string&, std::string&, std::string&, std::string&);
static bool decomposePattern(const std::string&, std::string&, std::string&);
static int hexToInt(int);
static int hexToInt(int, int);
static int hexToInt(int, int, int, int);
static std::string::size_type commonLength(const std::string&, const std::string&);
static std::string::size_type commonLength(const std::string&, std::string::size_type,
const std::string&, std::string::size_type);
static std::string commify(const std::string&);
static std::string lowerCase(const std::string&);
static std::string ucFirst(const std::string&);
static std::string trimLeft(const std::string& in, const std::string& t = " ");
static std::string trimRight(const std::string& in, const std::string& t = " ");
static std::string trim(const std::string& in, const std::string& t = " ");
// Stream Classifiers.
bool isEOS () const;
bool isString (std::string&, Lexer::Type&, const std::string&);
bool isDate (std::string&, Lexer::Type&);
bool isDuration (std::string&, Lexer::Type&);
bool isUUID (std::string&, Lexer::Type&, bool);
bool isNumber (std::string&, Lexer::Type&);
bool isInteger (std::string&, Lexer::Type&);
bool isHexNumber (std::string&, Lexer::Type&);
bool isSeparator (std::string&, Lexer::Type&);
bool isURL (std::string&, Lexer::Type&);
bool isPair (std::string&, Lexer::Type&);
bool isSet (std::string&, Lexer::Type&);
bool isTag (std::string&, Lexer::Type&);
bool isPath (std::string&, Lexer::Type&);
bool isSubstitution (std::string&, Lexer::Type&);
bool isPattern (std::string&, Lexer::Type&);
bool isOperator (std::string&, Lexer::Type&);
bool isDOM (std::string&, Lexer::Type&);
bool isIdentifier (std::string&, Lexer::Type&);
bool isWord (std::string&, Lexer::Type&);
bool isLiteral (const std::string&, bool, bool);
bool isOneOf (const std::vector <std::string>&, bool, bool);
bool isOneOf (const std::map <std::string, std::string>&, bool, bool);
bool isEOS() const;
bool isString(std::string&, Lexer::Type&, const std::string&);
bool isDate(std::string&, Lexer::Type&);
bool isDuration(std::string&, Lexer::Type&);
bool isUUID(std::string&, Lexer::Type&, bool);
bool isNumber(std::string&, Lexer::Type&);
bool isInteger(std::string&, Lexer::Type&);
bool isHexNumber(std::string&, Lexer::Type&);
bool isSeparator(std::string&, Lexer::Type&);
bool isURL(std::string&, Lexer::Type&);
bool isPair(std::string&, Lexer::Type&);
bool isSet(std::string&, Lexer::Type&);
bool isTag(std::string&, Lexer::Type&);
bool isPath(std::string&, Lexer::Type&);
bool isSubstitution(std::string&, Lexer::Type&);
bool isPattern(std::string&, Lexer::Type&);
bool isOperator(std::string&, Lexer::Type&);
bool isDOM(std::string&, Lexer::Type&);
bool isIdentifier(std::string&, Lexer::Type&);
bool isWord(std::string&, Lexer::Type&);
bool isLiteral(const std::string&, bool, bool);
bool isOneOf(const std::vector<std::string>&, bool, bool);
bool isOneOf(const std::map<std::string, std::string>&, bool, bool);
private:
private:
std::string _text;
std::size_t _cursor;
std::size_t _eos;

View file

@ -1,6 +1,6 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2022, Dustin J. Mitchell
// Copyright 2006 - 2024, Tomas Babej, Paul Beckingham, Federico Hernandez.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@ -25,57 +25,49 @@
////////////////////////////////////////////////////////////////////////////////
#include <cmake.h>
#include <format.h>
#include <assert.h>
#include "tc/Replica.h"
#include "tc/Task.h"
// cmake.h include header must come first
using namespace tc::ffi;
#include <Operation.h>
#include <taskchampion-cpp/lib.h>
#include <vector>
namespace tc {
////////////////////////////////////////////////////////////////////////////////
TCString string2tc (const std::string& str)
{
return tc_string_clone_with_len (str.data (), str.size ());
Operation::Operation(const tc::Operation& op) : op(&op) {}
////////////////////////////////////////////////////////////////////////////////
std::vector<Operation> Operation::operations(const rust::Vec<tc::Operation>& operations) {
return {operations.begin(), operations.end()};
}
////////////////////////////////////////////////////////////////////////////////
std::string tc2string_clone (const TCString& str)
{
size_t len;
auto ptr = tc_string_content_with_len (&str, &len);
auto rv = std::string (ptr, len);
return rv;
Operation& Operation::operator=(const Operation& other) {
op = other.op;
return *this;
}
////////////////////////////////////////////////////////////////////////////////
std::string tc2string (TCString& str)
{
auto rv = tc2string_clone(str);
tc_string_free (&str);
return rv;
}
////////////////////////////////////////////////////////////////////////////////
TCUuid uuid2tc(const std::string& str)
{
TCString tcstr = tc_string_borrow(str.c_str());
TCUuid rv;
if (TC_RESULT_OK != tc_uuid_from_str(tcstr, &rv)) {
throw std::string ("invalid UUID");
bool Operation::operator<(const Operation& other) const {
if (is_create()) {
return !other.is_create();
} else if (is_update()) {
if (other.is_create()) {
return false;
} else if (other.is_update()) {
return get_timestamp() < other.get_timestamp();
} else {
return true;
}
} else if (is_delete()) {
if (other.is_create() || other.is_update() || other.is_delete()) {
return false;
} else {
return true;
}
} else if (is_undo_point()) {
return !other.is_undo_point();
}
return rv;
return false; // not reachable
}
////////////////////////////////////////////////////////////////////////////////
std::string tc2uuid (TCUuid& uuid)
{
char s[TC_UUID_STRING_BYTES];
tc_uuid_to_buf (uuid, s);
std::string str;
str.assign (s, TC_UUID_STRING_BYTES);
return str;
}
////////////////////////////////////////////////////////////////////////////////
}

89
src/Operation.h Normal file
View file

@ -0,0 +1,89 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2006 - 2025, Tomas Babej, Paul Beckingham, Federico Hernandez,
// Tobias Predel.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// https://www.opensource.org/licenses/mit-license.php
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_OPERATION
#define INCLUDED_OPERATION
#include <taskchampion-cpp/lib.h>
#include <optional>
#include <vector>
// Representation of a TaskChampion operation.
//
// This class wraps `tc::Operation&` and thus cannot outlive that underlying
// type.
class Operation {
public:
explicit Operation(const tc::Operation &);
Operation(const Operation &other) = default;
Operation &operator=(const Operation &other);
// Create a vector of Operations given the result of `Replica::get_undo_operations` or
// `Replica::get_task_operations`. The resulting vector must not outlive the input `rust::Vec`.
static std::vector<Operation> operations(const rust::Vec<tc::Operation> &);
// Methods from the underlying `tc::Operation`.
bool is_create() const { return op->is_create(); }
bool is_update() const { return op->is_update(); }
bool is_delete() const { return op->is_delete(); }
bool is_undo_point() const { return op->is_undo_point(); }
std::string get_uuid() const { return std::string(op->get_uuid().to_string()); }
::rust::Vec<::tc::PropValuePair> get_old_task() const { return op->get_old_task(); };
std::string get_property() const {
std::string value;
op->get_property(value);
return value;
}
std::optional<std::string> get_value() const {
std::optional<std::string> value{std::string()};
if (!op->get_value(value.value())) {
value = std::nullopt;
}
return value;
}
std::optional<std::string> get_old_value() const {
std::optional<std::string> old_value{std::string()};
if (!op->get_old_value(old_value.value())) {
old_value = std::nullopt;
}
return old_value;
}
time_t get_timestamp() const { return static_cast<time_t>(op->get_timestamp()); }
// Define a partial order on Operations:
// - Create < Update < Delete < UndoPoint
// - Given two updates, sort by timestamp
bool operator<(const Operation &other) const;
private:
const tc::Operation *op;
};
#endif
////////////////////////////////////////////////////////////////////////////////

View file

@ -25,109 +25,71 @@
////////////////////////////////////////////////////////////////////////////////
#include <cmake.h>
#include <TDB2.h>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <vector>
#include <list>
#include <unordered_set>
#include <stdlib.h>
#include <signal.h>
#include <Context.h>
// cmake.h include header must come first
#include <Color.h>
#include <Context.h>
#include <Datetime.h>
#include <TDB2.h>
#include <Table.h>
#include <shared.h>
#include <format.h>
#include <main.h>
#include <shared.h>
#include <stdlib.h>
#include <util.h>
#include "tc/Server.h"
#include "tc/util.h"
#include <algorithm>
#include <unordered_set>
#include <vector>
bool TDB2::debug_mode = false;
static void dependency_scan (std::vector<Task> &);
static void dependency_scan(std::vector<Task>&);
////////////////////////////////////////////////////////////////////////////////
TDB2::TDB2 ()
: replica {tc::Replica()} // in-memory Replica
, _working_set {}
{
void TDB2::open_replica(const std::string& location, bool create_if_missing, bool read_write) {
_replica = tc::new_replica_on_disk(location, create_if_missing, read_write);
}
////////////////////////////////////////////////////////////////////////////////
void TDB2::open_replica (const std::string& location, bool create_if_missing)
{
File pending_data = File (location + "/pending.data");
if (pending_data.exists()) {
Color warning = Color (Context::getContext ().config.get ("color.warning"));
std::cerr << warning.colorize (
format ("Found existing '.data' files in {1}", location)) << "\n";
std::cerr << " Taskwarrior's storage format changed in 3.0, requiring a manual migration.\n";
std::cerr << " See https://github.com/GothenburgBitFactory/taskwarrior/releases.\n";
}
replica = tc::Replica(location, create_if_missing);
}
void TDB2::open_replica_in_memory() { _replica = tc::new_replica_in_memory(); }
////////////////////////////////////////////////////////////////////////////////
// Add the new task to the replica.
void TDB2::add (Task& task)
{
void TDB2::add(Task& task) {
// Ensure the task is consistent, and provide defaults if necessary.
// bool argument to validate() is "applyDefault", to apply default values for
// properties not otherwise given.
task.validate (true);
task.validate(true);
std::string uuid = task.get ("uuid");
rust::Vec<tc::Operation> ops;
maybe_add_undo_point(ops);
auto uuid = task.get("uuid");
changes[uuid] = task;
auto innertask = replica.import_task_with_uuid (uuid);
{
auto guard = replica.mutate_task(innertask);
// add the task attributes
for (auto& attr : task.all ()) {
// TaskChampion does not store uuid or id in the taskmap
if (attr == "uuid" || attr == "id") {
continue;
}
// Use `set_status` for the task status, to get expected behavior
// with respect to the working set.
else if (attr == "status") {
innertask.set_status (Task::status2tc (Task::textToStatus (task.get (attr))));
}
// use `set_modified` to set the modified timestamp, avoiding automatic
// updates to this field by TaskChampion.
else if (attr == "modified") {
auto mod = (time_t) std::stoi (task.get (attr));
innertask.set_modified (mod);
}
// otherwise, just set the k/v map value
else {
innertask.set_value (attr, std::make_optional (task.get (attr)));
}
}
}
auto ws = replica.working_set ();
// get the ID that was assigned to this task
auto id = ws.by_uuid (uuid);
// update the cached working set with the new information
_working_set = std::make_optional (std::move (ws));
tc::Uuid tcuuid = tc::uuid_from_string(uuid);
// run hooks for this new task
Context::getContext ().hooks.onAdd (task);
Context::getContext().hooks.onAdd(task);
if (id.has_value ()) {
task.id = id.value();
auto taskdata = tc::create_task(tcuuid, ops);
// add the task attributes
for (auto& attr : task.all()) {
// TaskChampion does not store uuid or id in the task data
if (attr == "uuid" || attr == "id") {
continue;
}
taskdata->update(attr, task.get(attr), ops);
}
replica()->commit_operations(std::move(ops));
invalidate_cached_info();
// get the ID that was assigned to this task
auto id = working_set()->by_uuid(tcuuid);
if (id > 0) {
task.id = id;
}
}
////////////////////////////////////////////////////////////////////////////////
@ -145,31 +107,34 @@ void TDB2::add (Task& task)
// this method. In this case, this method throws an error that will make sense
// to the user. This is especially unlikely since tasks are only deleted when
// they have been unmodified for a long time.
void TDB2::modify (Task& task)
{
void TDB2::modify(Task& task) {
// All locally modified tasks are timestamped, implicitly overwriting any
// changes the user or hooks tried to apply to the "modified" attribute.
task.setAsNow ("modified");
task.validate (false);
auto uuid = task.get ("uuid");
task.setAsNow("modified");
task.validate(false);
auto uuid = task.get("uuid");
rust::Vec<tc::Operation> ops;
maybe_add_undo_point(ops);
changes[uuid] = task;
// invoke the hook and allow it to modify the task before updating
// invoke the hook and allow it to modify the task before updating
Task original;
get (uuid, original);
Context::getContext ().hooks.onModify (original, task);
get(uuid, original);
Context::getContext().hooks.onModify(original, task);
auto maybe_tctask = replica.get_task (uuid);
if (!maybe_tctask.has_value ()) {
throw std::string ("task no longer exists");
tc::Uuid tcuuid = tc::uuid_from_string(uuid);
auto maybe_tctask = replica()->get_task_data(tcuuid);
if (maybe_tctask.is_none()) {
throw std::string("task no longer exists");
}
auto tctask = std::move (maybe_tctask.value ());
auto guard = replica.mutate_task(tctask);
auto tctask_map = tctask.get_taskmap ();
auto tctask = maybe_tctask.take();
// Perform the necessary `update` operations to set all keys in `tctask`
// equal to those in `task`.
std::unordered_set<std::string> seen;
for (auto k : task.all ()) {
for (auto k : task.all()) {
// ignore task keys that aren't stored
if (k == "uuid") {
continue;
@ -177,43 +142,75 @@ void TDB2::modify (Task& task)
seen.insert(k);
bool update = false;
auto v_new = task.get(k);
try {
auto v_tctask = tctask_map.at(k);
std::string v_tctask;
if (tctask->get(k, v_tctask)) {
update = v_tctask != v_new;
} catch (const std::out_of_range& oor) {
// tctask_map does not contain k, so update it
} else {
// tctask does not contain k, so update it
update = true;
}
if (update) {
// An empty string indicates the value should be removed.
if (v_new == "") {
tctask.set_value(k, {});
tctask->update_remove(k, ops);
} else {
tctask.set_value(k, make_optional (v_new));
tctask->update(k, v_new, ops);
}
}
}
// we've now added and updated properties; but must find any deleted properties
for (auto kv : tctask_map) {
if (seen.find (kv.first) == seen.end ()) {
tctask.set_value (kv.first, {});
for (auto k : tctask->properties()) {
auto kstr = static_cast<std::string>(k);
if (seen.find(kstr) == seen.end()) {
tctask->update_remove(kstr, ops);
}
}
replica()->commit_operations(std::move(ops));
invalidate_cached_info();
}
////////////////////////////////////////////////////////////////////////////////
const tc::WorkingSet &TDB2::working_set ()
{
if (!_working_set.has_value ()) {
_working_set = std::make_optional (replica.working_set ());
void TDB2::purge(Task& task) {
auto uuid = tc::uuid_from_string(task.get("uuid"));
rust::Vec<tc::Operation> ops;
auto maybe_tctask = replica()->get_task_data(uuid);
if (maybe_tctask.is_some()) {
auto tctask = maybe_tctask.take();
tctask->delete_task(ops);
replica()->commit_operations(std::move(ops));
}
return _working_set.value ();
invalidate_cached_info();
}
////////////////////////////////////////////////////////////////////////////////
void TDB2::get_changes (std::vector <Task>& changes)
{
rust::Box<tc::Replica>& TDB2::replica() {
// One of the open_replica_ methods must be called before this one.
assert(_replica);
return _replica.value();
}
////////////////////////////////////////////////////////////////////////////////
const rust::Box<tc::WorkingSet>& TDB2::working_set() {
if (!_working_set.has_value()) {
_working_set = replica()->working_set();
}
return _working_set.value();
}
////////////////////////////////////////////////////////////////////////////////
void TDB2::maybe_add_undo_point(rust::Vec<tc::Operation>& ops) {
// Only add an UndoPoint if there are not yet any changes.
if (changes.size() == 0) {
tc::add_undo_point(ops);
}
}
////////////////////////////////////////////////////////////////////////////////
void TDB2::get_changes(std::vector<Task>& changes) {
std::map<std::string, Task>& changes_map = this->changes;
changes.clear();
std::transform(changes_map.begin(), changes_map.end(), std::back_inserter(changes),
@ -221,178 +218,105 @@ void TDB2::get_changes (std::vector <Task>& changes)
}
////////////////////////////////////////////////////////////////////////////////
void TDB2::revert ()
{
auto undo_ops = replica.get_undo_ops();
if (undo_ops.len == 0) {
std::cout << "No operations to undo.";
return;
}
if (confirm_revert(undo_ops)) {
// Has the side-effect of freeing undo_ops.
replica.commit_undo_ops(undo_ops, NULL);
} else {
replica.free_replica_ops(undo_ops);
}
replica.rebuild_working_set (false);
}
////////////////////////////////////////////////////////////////////////////////
bool TDB2::confirm_revert (struct tc::ffi::TCReplicaOpList undo_ops)
{
// TODO Use show_diff rather than this basic listing of operations, though
// this might be a worthy undo.style itself.
std::cout << "The following " << undo_ops.len << " operations would be reverted:\n";
for (size_t i = 0; i < undo_ops.len; i++) {
std::cout << "- ";
tc::ffi::TCReplicaOp op = undo_ops.items[i];
switch(op.operation_type) {
case tc::ffi::TCReplicaOpType::Create:
std::cout << "Create " << replica.get_op_uuid(op);
break;
case tc::ffi::TCReplicaOpType::Delete:
std::cout << "Delete " << replica.get_op_old_task_description(op);
break;
case tc::ffi::TCReplicaOpType::Update:
std::cout << "Update " << replica.get_op_uuid(op) << "\n";
std::cout << " " << replica.get_op_property(op) << ": " << option_string(replica.get_op_old_value(op)) << " -> " << option_string(replica.get_op_value(op));
break;
case tc::ffi::TCReplicaOpType::UndoPoint:
throw std::string ("Can't undo UndoPoint.");
break;
default:
throw std::string ("Can't undo non-operation.");
break;
}
std::cout << "\n";
}
return ! Context::getContext ().config.getBoolean ("confirmation") ||
confirm ("The undo command is not reversible. Are you sure you want to revert to the previous state?");
}
////////////////////////////////////////////////////////////////////////////////
std::string TDB2::option_string(std::string input) {
return input == "" ? "<empty>" : input;
}
////////////////////////////////////////////////////////////////////////////////
void TDB2::show_diff (
const std::string& current,
const std::string& prior,
const std::string& when)
{
Datetime lastChange (strtoll (when.c_str (), nullptr, 10));
// Set the colors.
Color color_red (Context::getContext ().color () ? Context::getContext ().config.get ("color.undo.before") : "");
Color color_green (Context::getContext ().color () ? Context::getContext ().config.get ("color.undo.after") : "");
auto before = prior == "" ? Task() : Task(prior);
auto after = Task(current);
if (Context::getContext ().config.get ("undo.style") == "side")
{
Table view = before.diffForUndoSide(after);
std::cout << '\n'
<< format ("The last modification was made {1}", lastChange.toString ())
<< '\n'
<< '\n'
<< view.render ()
<< '\n';
}
else if (Context::getContext ().config.get ("undo.style") == "diff")
{
Table view = before.diffForUndoPatch(after, lastChange);
std::cout << '\n'
<< view.render ()
<< '\n';
}
}
void TDB2::gc ()
{
void TDB2::gc() {
Timer timer;
// Allowed as an override, but not recommended.
if (Context::getContext ().config.getBoolean ("gc"))
{
replica.rebuild_working_set (true);
if (Context::getContext().config.getBoolean("gc")) {
replica()->rebuild_working_set(true);
}
Context::getContext ().time_gc_us += timer.total_us ();
Context::getContext().time_gc_us += timer.total_us();
}
////////////////////////////////////////////////////////////////////////////////
void TDB2::expire_tasks() { replica()->expire_tasks(); }
////////////////////////////////////////////////////////////////////////////////
// Latest ID is that of the last pending task.
int TDB2::latest_id ()
{
const tc::WorkingSet &ws = working_set ();
return (int)ws.largest_index ();
int TDB2::latest_id() {
auto& ws = working_set();
return (int)ws->largest_index();
}
////////////////////////////////////////////////////////////////////////////////
const std::vector <Task> TDB2::all_tasks ()
{
auto all_tctasks = replica.all_tasks();
std::vector <Task> all;
for (auto& tctask : all_tctasks)
all.push_back (Task (std::move (tctask)));
const std::vector<Task> TDB2::all_tasks() {
Timer timer;
auto all_tctasks = replica()->all_task_data();
std::vector<Task> all;
for (auto& maybe_tctask : all_tctasks) {
auto tctask = maybe_tctask.take();
all.push_back(Task(std::move(tctask)));
}
dependency_scan(all);
Context::getContext().time_load_us += timer.total_us();
return all;
}
////////////////////////////////////////////////////////////////////////////////
const std::vector <Task> TDB2::pending_tasks ()
{
const tc::WorkingSet &ws = working_set ();
auto largest_index = ws.largest_index ();
const std::vector<Task> TDB2::pending_tasks() {
if (!_pending_tasks) {
Timer timer;
std::vector <Task> result;
for (size_t i = 0; i <= largest_index; i++) {
auto maybe_uuid = ws.by_index (i);
if (maybe_uuid.has_value ()) {
auto maybe_task = replica.get_task (maybe_uuid.value ());
if (maybe_task.has_value ()) {
result.push_back (Task (std::move (maybe_task.value ())));
}
auto pending_tctasks = replica()->pending_task_data();
std::vector<Task> result;
for (auto& maybe_tctask : pending_tctasks) {
auto tctask = maybe_tctask.take();
result.push_back(Task(std::move(tctask)));
}
dependency_scan(result);
Context::getContext().time_load_us += timer.total_us();
_pending_tasks = result;
}
dependency_scan(result);
return result;
return *_pending_tasks;
}
////////////////////////////////////////////////////////////////////////////////
const std::vector <Task> TDB2::completed_tasks ()
{
auto all_tctasks = replica.all_tasks();
const tc::WorkingSet &ws = working_set ();
const std::vector<Task> TDB2::completed_tasks() {
if (!_completed_tasks) {
auto all_tctasks = replica()->all_task_data();
auto& ws = working_set();
std::vector <Task> result;
for (auto& tctask : all_tctasks) {
// if this task is _not_ in the working set, return it.
if (!ws.by_uuid (tctask.get_uuid ())) {
result.push_back (Task (std::move (tctask)));
std::vector<Task> result;
for (auto& maybe_tctask : all_tctasks) {
auto tctask = maybe_tctask.take();
// if this task is _not_ in the working set, return it.
if (ws->by_uuid(tctask->get_uuid()) == 0) {
result.push_back(Task(std::move(tctask)));
}
}
_completed_tasks = result;
}
return *_completed_tasks;
}
return result;
////////////////////////////////////////////////////////////////////////////////
void TDB2::invalidate_cached_info() {
_pending_tasks = std::nullopt;
_completed_tasks = std::nullopt;
_working_set = std::nullopt;
}
////////////////////////////////////////////////////////////////////////////////
// Locate task by ID, wherever it is.
bool TDB2::get (int id, Task& task)
{
const tc::WorkingSet &ws = working_set ();
const auto maybe_uuid = ws.by_index (id);
if (maybe_uuid) {
auto maybe_task = replica.get_task(*maybe_uuid);
if (maybe_task) {
task = Task{std::move(*maybe_task)};
return true;
bool TDB2::get(int id, Task& task) {
auto& ws = working_set();
const auto tcuuid = ws->by_index(id);
if (!tcuuid.is_nil()) {
std::string uuid = static_cast<std::string>(tcuuid.to_string());
// Load all pending tasks in order to get dependency data, and in particular
// `task.is_blocking` and `task.is_blocked`, set correctly.
std::vector<Task> pending = pending_tasks();
for (auto& pending_task : pending) {
if (pending_task.get("uuid") == uuid) {
task = pending_task;
return true;
}
}
}
@ -401,25 +325,25 @@ bool TDB2::get (int id, Task& task)
////////////////////////////////////////////////////////////////////////////////
// Locate task by UUID, including by partial ID, wherever it is.
bool TDB2::get (const std::string& uuid, Task& task)
{
bool TDB2::get(const std::string& uuid, Task& task) {
// Load all pending tasks in order to get dependency data, and in particular
// `task.is_blocking` and `task.is_blocked`, set correctly.
std::vector<Task> pending = pending_tasks();
// try by raw uuid, if the length is right
if (uuid.size () == 36) {
try {
auto maybe_task = replica.get_task (uuid);
if (maybe_task) {
task = Task{std::move (*maybe_task)};
return true;
}
} catch (const std::string &err) {
return false;
for (auto& pending_task : pending) {
if (closeEnough(pending_task.get("uuid"), uuid, uuid.length())) {
task = pending_task;
return true;
}
}
// Nothing to do but iterate over all tasks and check whether it's closeEnough
for (auto& tctask : replica.all_tasks ()) {
if (closeEnough (tctask.get_uuid (), uuid, uuid.length ())) {
task = Task{std::move (tctask)};
// Nothing to do but iterate over all tasks and check whether it's closeEnough.
for (auto& maybe_tctask : replica()->all_task_data()) {
auto tctask = maybe_tctask.take();
auto tctask_uuid = static_cast<std::string>(tctask->get_uuid().to_string());
if (closeEnough(tctask_uuid, uuid, uuid.length())) {
task = Task{std::move(tctask)};
return true;
}
}
@ -429,34 +353,24 @@ bool TDB2::get (const std::string& uuid, Task& task)
////////////////////////////////////////////////////////////////////////////////
// Locate task by UUID, wherever it is.
bool TDB2::has (const std::string& uuid)
{
Task task;
return get(uuid, task);
bool TDB2::has(const std::string& uuid) {
return replica()->get_task_data(tc::uuid_from_string(uuid)).is_some();
}
////////////////////////////////////////////////////////////////////////////////
const std::vector <Task> TDB2::siblings (Task& task)
{
std::vector <Task> results;
if (task.has ("parent"))
{
std::string parent = task.get ("parent");
const std::vector<Task> TDB2::siblings(Task& task) {
std::vector<Task> results;
if (task.has("parent")) {
std::string parent = task.get("parent");
for (auto& i : this->pending_tasks())
{
for (auto& i : this->pending_tasks()) {
// Do not include self in results.
if (i.id != task.id)
{
if (i.id != task.id) {
// Do not include completed or deleted tasks.
if (i.getStatus () != Task::completed &&
i.getStatus () != Task::deleted)
{
if (i.getStatus() != Task::completed && i.getStatus() != Task::deleted) {
// If task has the same parent, it is a sibling.
if (i.has ("parent") &&
i.get ("parent") == parent)
{
results.push_back (i);
if (i.has("parent") && i.get("parent") == parent) {
results.push_back(i);
}
}
}
@ -467,107 +381,79 @@ const std::vector <Task> TDB2::siblings (Task& task)
}
////////////////////////////////////////////////////////////////////////////////
const std::vector <Task> TDB2::children (Task& parent)
{
const std::vector<Task> TDB2::children(Task& parent) {
// scan _pending_ tasks for those with `parent` equal to this task
std::vector <Task> results;
std::string this_uuid = parent.get ("uuid");
std::vector<Task> results;
std::string this_uuid = parent.get("uuid");
const tc::WorkingSet &ws = working_set ();
size_t end_idx = ws.largest_index ();
auto& ws = working_set();
size_t end_idx = ws->largest_index();
for (size_t i = 0; i <= end_idx; i++) {
auto uuid_opt = ws.by_index (i);
if (!uuid_opt) {
auto uuid = ws->by_index(i);
if (uuid.is_nil()) {
continue;
}
auto uuid = uuid_opt.value ();
// skip self-references
if (uuid == this_uuid) {
if (uuid.to_string() == this_uuid) {
continue;
}
auto task_opt = replica.get_task (uuid_opt.value ());
if (!task_opt) {
auto task_opt = replica()->get_task_data(uuid);
if (task_opt.is_none()) {
continue;
}
auto task = std::move (task_opt.value ());
auto task = task_opt.take();
auto parent_uuid_opt = task.get_value ("parent");
if (!parent_uuid_opt) {
std::string parent_uuid;
if (!task->get("parent", parent_uuid)) {
continue;
}
auto parent_uuid = parent_uuid_opt.value ();
if (parent_uuid == this_uuid) {
results.push_back (Task (std::move (task)));
results.push_back(Task(std::move(task)));
}
}
return results;
}
////////////////////////////////////////////////////////////////////////////////
std::string TDB2::uuid (int id)
{
const tc::WorkingSet &ws = working_set ();
return ws.by_index ((size_t)id).value_or ("");
std::string TDB2::uuid(int id) {
auto& ws = working_set();
auto uuid = ws->by_index(id);
if (uuid.is_nil()) {
return "";
}
return static_cast<std::string>(uuid.to_string());
}
////////////////////////////////////////////////////////////////////////////////
int TDB2::id (const std::string& uuid)
{
const tc::WorkingSet &ws = working_set ();
return (int)ws.by_uuid (uuid).value_or (0);
int TDB2::id(const std::string& uuid) {
auto& ws = working_set();
return ws->by_uuid(tc::uuid_from_string(uuid));
}
////////////////////////////////////////////////////////////////////////////////
int TDB2::num_local_changes ()
{
return (int)replica.num_local_operations ();
}
int TDB2::num_local_changes() { return (int)replica()->num_local_operations(); }
////////////////////////////////////////////////////////////////////////////////
int TDB2::num_reverts_possible ()
{
return (int)replica.num_undo_points ();
}
////////////////////////////////////////////////////////////////////////////////
void TDB2::sync (tc::Server server, bool avoid_snapshots)
{
replica.sync(std::move(server), avoid_snapshots);
}
////////////////////////////////////////////////////////////////////////////////
void TDB2::dump ()
{
// TODO
}
int TDB2::num_reverts_possible() { return (int)replica()->num_undo_points(); }
////////////////////////////////////////////////////////////////////////////////
// For any task that has depenencies, follow the chain of dependencies until the
// end. Along the way, update the Task::is_blocked and Task::is_blocking data
// cache.
static void dependency_scan (std::vector<Task> &tasks)
{
for (auto& left : tasks)
{
for (auto& dep : left.getDependencyUUIDs ())
{
for (auto& right : tasks)
{
if (right.get ("uuid") == dep)
{
static void dependency_scan(std::vector<Task>& tasks) {
for (auto& left : tasks) {
for (auto& dep : left.getDependencyUUIDs()) {
for (auto& right : tasks) {
if (right.get("uuid") == dep) {
// GC hasn't run yet, check both tasks for their current status
Task::status lstatus = left.getStatus ();
Task::status rstatus = right.getStatus ();
if (lstatus != Task::completed &&
lstatus != Task::deleted &&
rstatus != Task::completed &&
rstatus != Task::deleted)
{
Task::status lstatus = left.getStatus();
Task::status rstatus = right.getStatus();
if (lstatus != Task::completed && lstatus != Task::deleted &&
rstatus != Task::completed && rstatus != Task::deleted) {
left.is_blocked = true;
right.is_blocking = true;
}

View file

@ -27,69 +27,66 @@
#ifndef INCLUDED_TDB2
#define INCLUDED_TDB2
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <vector>
#include <string>
#include <stdio.h>
#include <FS.h>
#include <Task.h>
#include <tc/WorkingSet.h>
#include <tc/Replica.h>
#include <taskchampion-cpp/lib.h>
namespace tc {
class Server;
}
#include <map>
#include <optional>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
// TDB2 Class represents all the files in the task database.
class TDB2
{
public:
class TDB2 {
public:
static bool debug_mode;
TDB2 ();
TDB2() = default;
void open_replica (const std::string&, bool create_if_missing);
void add (Task&);
void modify (Task&);
void get_changes (std::vector <Task>&);
void revert ();
void gc ();
int latest_id ();
void open_replica(const std::string &, bool create_if_missing, bool read_write);
void open_replica_in_memory();
void add(Task &);
void modify(Task &);
void purge(Task &);
void get_changes(std::vector<Task> &);
void gc();
void expire_tasks();
int latest_id();
// Generalized task accessors.
const std::vector <Task> all_tasks ();
const std::vector <Task> pending_tasks ();
const std::vector <Task> completed_tasks ();
bool get (int, Task&);
bool get (const std::string&, Task&);
bool has (const std::string&);
const std::vector <Task> siblings (Task&);
const std::vector <Task> children (Task&);
const std::vector<Task> all_tasks();
const std::vector<Task> pending_tasks();
const std::vector<Task> completed_tasks();
bool get(int, Task &);
bool get(const std::string &, Task &);
bool has(const std::string &);
const std::vector<Task> siblings(Task &);
const std::vector<Task> children(Task &);
// ID <--> UUID mapping.
std::string uuid (int);
int id (const std::string&);
std::string uuid(int);
int id(const std::string &);
int num_local_changes ();
int num_reverts_possible ();
int num_local_changes();
int num_reverts_possible();
void dump ();
rust::Box<tc::Replica> &replica();
void sync (tc::Server server, bool avoid_snapshots);
bool confirm_revert(struct tc::ffi::TCReplicaOpList);
private:
std::optional<rust::Box<tc::Replica>> _replica;
private:
tc::Replica replica;
std::optional<tc::WorkingSet> _working_set;
// Cached information from the replica
std::optional<rust::Box<tc::WorkingSet>> _working_set;
std::optional<std::vector<Task>> _pending_tasks;
std::optional<std::vector<Task>> _completed_tasks;
void invalidate_cached_info();
// UUID -> Task containing all tasks modified in this invocation.
std::map<std::string, Task> changes;
const tc::WorkingSet &working_set ();
static std::string option_string (std::string input);
static void show_diff (const std::string&, const std::string&, const std::string&);
const rust::Box<tc::WorkingSet> &working_set();
void maybe_add_undo_point(rust::Vec<tc::Operation> &);
};
#endif

178
src/TF2.cpp Normal file
View file

@ -0,0 +1,178 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// https://www.opensource.org/licenses/mit-license.php
//
////////////////////////////////////////////////////////////////////////////////
#include <Color.h>
#include <Context.h>
#include <Datetime.h>
#include <TF2.h>
#include <Table.h>
#include <cmake.h>
#include <format.h>
#ifdef PRODUCT_TASKWARRIOR
#include <legacy.h>
#endif
#include <shared.h>
#include <util.h>
#define STRING_TDB2_REVERTED "Modified task reverted."
////////////////////////////////////////////////////////////////////////////////
TF2::TF2() : _loaded_tasks(false), _loaded_lines(false) {}
////////////////////////////////////////////////////////////////////////////////
TF2::~TF2() {}
////////////////////////////////////////////////////////////////////////////////
void TF2::target(const std::string& f) { _file = File(f); }
////////////////////////////////////////////////////////////////////////////////
const std::vector<std::map<std::string, std::string>>& TF2::get_tasks() {
if (!_loaded_tasks) load_tasks();
return _tasks;
}
////////////////////////////////////////////////////////////////////////////////
// Attempt an FF4 parse.
//
// Note that FF1, FF2, FF3, and JSON are no longer supported.
//
// start --> [ --> Att --> ] --> end
// ^ |
// +-------+
//
std::map<std::string, std::string> TF2::load_task(const std::string& input) {
std::map<std::string, std::string> data;
// File format version 4, from 2009-5-16 - now, v1.7.1+
// This is the parse format tried first, because it is most used.
data.clear();
if (input[0] == '[') {
// Not using Pig to parse here (which would be idiomatic), because we
// don't need to differentiate betwen utf-8 and normal characters.
// Pig's scanning the string can be expensive.
auto ending_bracket = input.find_last_of(']');
if (ending_bracket != std::string::npos) {
std::string line = input.substr(1, ending_bracket);
if (line.length() == 0) throw std::string("Empty record in input.");
Pig attLine(line);
std::string name;
std::string value;
while (!attLine.eos()) {
if (attLine.getUntilAscii(':', name) && attLine.skip(':') &&
attLine.getQuoted('"', value)) {
#ifdef PRODUCT_TASKWARRIOR
legacyAttributeMap(name);
#endif
data[name] = decode(json::decode(value));
}
attLine.skip(' ');
}
std::string remainder;
attLine.getRemainder(remainder);
if (remainder.length()) throw std::string("Unrecognized characters at end of line.");
}
} else {
throw std::string("Record not recognized as format 4.");
}
// for compatibility, include all tags in `tags` as `tag_..` attributes
if (data.find("tags") != data.end()) {
for (auto& tag : split(data["tags"], ',')) {
data[Task::tag2Attr(tag)] = "x";
}
}
// same for `depends` / `dep_..`
if (data.find("depends") != data.end()) {
for (auto& dep : split(data["depends"], ',')) {
data[Task::dep2Attr(dep)] = "x";
}
}
return data;
}
////////////////////////////////////////////////////////////////////////////////
// Decode values after parse.
// [ <- &open;
// ] <- &close;
const std::string TF2::decode(const std::string& value) const {
if (value.find('&') == std::string::npos) return value;
auto modified = str_replace(value, "&open;", "[");
return str_replace(modified, "&close;", "]");
}
////////////////////////////////////////////////////////////////////////////////
void TF2::load_tasks() {
Timer timer;
if (!_loaded_lines) {
load_lines();
}
// Reduce unnecessary allocations/copies.
// Calling it on _tasks is the right thing to do even when from_gc is set.
_tasks.reserve(_lines.size());
int line_number = 0; // Used for error message in catch block.
try {
for (auto& line : _lines) {
++line_number;
auto task = load_task(line);
_tasks.push_back(task);
}
_loaded_tasks = true;
}
catch (const std::string& e) {
throw e + format(" in {1} at line {2}", _file._data, line_number);
}
Context::getContext().time_load_us += timer.total_us();
}
////////////////////////////////////////////////////////////////////////////////
void TF2::load_lines() {
if (_file.open()) {
if (Context::getContext().config.getBoolean("locking")) _file.lock();
_file.read(_lines);
_file.close();
_loaded_lines = true;
}
}
////////////////////////////////////////////////////////////////////////////////
// vim: ts=2 et sw=2

63
src/TF2.h Normal file
View file

@ -0,0 +1,63 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2006 - 2024, Tomas Babej, Paul Beckingham, Federico Hernandez.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// https://www.opensource.org/licenses/mit-license.php
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_TF2
#define INCLUDED_TF2
#include <FS.h>
#include <Task.h>
#include <map>
#include <string>
#include <vector>
// TF2 Class represents a single 2.x-style file in the task database.
//
// This is only used for importing tasks from 2.x. It only reads format 4, based
// on a stripped-down version of the TF2 class from v2.6.2.
class TF2 {
public:
TF2();
~TF2();
void target(const std::string&);
const std::vector<std::map<std::string, std::string>>& get_tasks();
std::map<std::string, std::string> load_task(const std::string&);
void load_tasks();
void load_lines();
const std::string decode(const std::string& value) const;
bool _loaded_tasks;
bool _loaded_lines;
std::vector<std::map<std::string, std::string>> _tasks;
std::vector<std::string> _lines;
File _file;
};
#endif
////////////////////////////////////////////////////////////////////////////////

Some files were not shown because too many files have changed in this diff Show more