Compare commits

..

203 Commits

Author SHA1 Message Date
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
Dustin J. Mitchell
0627447a6a Update for 3.0.1 (#3382)
update for 3.0.1
2024-04-20 23:18:57 +00:00
Dustin J. Mitchell
f054a4061e Remove taskchampion-sync-server (#3380)
This crate has been moved to
https://github.com/GothenburgBitFactory/taskchampion-sync-server.

The integration-tests repo used the sync server to test integration
between taskchampion and the sync-server. We should do that again, but
after taskchampion moves to its own repo (#3209). In the interim, the
cross-sync integration test can simply test syncing between local
servers, but the snapshot test is no longer useful as the local server
does not support snapshots.
2024-04-20 12:44:06 +00:00
dependabot[bot]
304b84e4da Bump rustls from 0.21.7 to 0.21.11 (#3379)
Bumps [rustls](https://github.com/rustls/rustls) from 0.21.7 to 0.21.11.
- [Release notes](https://github.com/rustls/rustls/releases)
- [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustls/rustls/compare/v/0.21.7...v/0.21.11)

---
updated-dependencies:
- dependency-name: rustls
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-19 19:32:49 -04:00
Dustin J. Mitchell
f86a069faf Fix paths generated from origin (#3372)
These mistakenly doubled the initial `/` character. This was broken in #3361.
2024-04-16 22:05:45 -04:00
ryneeverett
0944c73716 Recommend LSP's in development docs (#3370)
* Recommend LSP's in development docs

Per conversation in #3338.

There are already a lot of documented compile options so I think we're
better off suggesting that everybody create a compile_commands.json
whether or not they're using an LSP because it doesn't cost much.

While I was at it it seemed reasonable to mention rust LSP too. Now that
rls is deprecated I'm not sure there is any competitor to rust-analyzer
worth mentioning.

* Export compile commands by default.

Thanks to @felixschurk for the idea and telling me how to do it.

It took me a minute to figure out that this places the
compile_commands.json in the build directory rather than the root of the
project. But clangd still finds it there and that's a better place for
it anyway.
2024-04-16 08:19:58 -04:00
Dustin J. Mitchell
10cec507cb Check that sync.server.origin is a URL (#3361) 2024-04-16 02:11:55 +00:00
Dustin J. Mitchell
4d9bb20bdd Update task news to support 3.0.0 (#3342)
* Introduce Version, use it to check current version in custom reports
* Support multiple versions in 'task news'
2024-04-15 22:04:16 -04:00
dependabot[bot]
d243d000eb Bump env_logger from 0.10.0 to 0.10.2 (#3336)
Bumps [env_logger](https://github.com/rust-cli/env_logger) from 0.10.0 to 0.10.2.
- [Release notes](https://github.com/rust-cli/env_logger/releases)
- [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/env_logger/compare/v0.10.0...v0.10.2)

---
updated-dependencies:
- dependency-name: env_logger
  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>
2024-04-15 21:49:56 -04:00
Dustin J. Mitchell
9040a7eb79 Throw std::strings on sync server errors (#3362) 2024-04-15 21:49:17 -04:00
Dustin J. Mitchell
0a491f36ad Store all modified tasks for use by on-exit hook (#3352)
The on-exit hook gets all modified tasks as input, but this was omitted
in the previous release. This adds a test for the desired behavior, and
updates TDB2 to correctly store the required information.
2024-04-15 21:14:25 -04:00
dependabot[bot]
7578768d9b Bump peaceiris/actions-gh-pages from 3 to 4 (#3367)
Bumps [peaceiris/actions-gh-pages](https://github.com/peaceiris/actions-gh-pages) from 3 to 4.
- [Release notes](https://github.com/peaceiris/actions-gh-pages/releases)
- [Changelog](https://github.com/peaceiris/actions-gh-pages/blob/main/CHANGELOG.md)
- [Commits](https://github.com/peaceiris/actions-gh-pages/compare/v3...v4)

---
updated-dependencies:
- dependency-name: peaceiris/actions-gh-pages
  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-04-15 21:12:20 -04:00
dependabot[bot]
cb0d21f96e Bump peaceiris/actions-mdbook from 1 to 2 (#3366)
Bumps [peaceiris/actions-mdbook](https://github.com/peaceiris/actions-mdbook) from 1 to 2.
- [Release notes](https://github.com/peaceiris/actions-mdbook/releases)
- [Changelog](https://github.com/peaceiris/actions-mdbook/blob/main/CHANGELOG.md)
- [Commits](https://github.com/peaceiris/actions-mdbook/compare/v1...v2)

---
updated-dependencies:
- dependency-name: peaceiris/actions-mdbook
  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-04-15 21:11:17 -04:00
dependabot[bot]
3b414cd9bb Bump sigstore/cosign-installer from 3.4.0 to 3.5.0 (#3365)
Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 3.4.0 to 3.5.0.
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/v3.4.0...v3.5.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-04-15 21:10:52 -04:00
Dustin J. Mitchell
c90eb8f71d remove reference to NEWS (#3357) 2024-04-12 22:46:09 -04:00
Dustin J. Mitchell
7c465ceb8f Remove NEWS, as it is redundant to 'task news' (#3354) 2024-04-07 21:58:27 -04:00
Dustin J. Mitchell
a6b721853b Remove 'sync' from default verbose flags (#3319)
Do not give sync feedback when not doing remote sync
2024-04-08 01:02:07 +00:00
ryneeverett
fd306712b8 Install corrosion as submodule. (#3348)
This will enable nixpkgs -- and any other distribution that builds in a
network sandbox and/or wants to use their own corrosion package rather
than building another one -- to do so without patching taskwarrior.

Since we're already using submodules for libshared I don't think this
should make the build process any more complicated for anyone else.

See
https://github.com/NixOS/nixpkgs/issues/300679#issuecomment-2041252688
for context.
2024-04-07 12:10:54 -04:00
Felix Schurk
b5aa7c6ae2 change order of hook invocation and setting task id (#3339)
this prevents that the task id is always returned as zero after a hook
is run on it
closes #3312
2024-04-05 19:45:55 -04:00
Dustin J. Mitchell
933885f21c Merge pull request #3341 from ryneeverett/sync-config-man-warning
sync: Point to manpage if unconfigured
2024-04-05 19:01:50 -04:00
ryneeverett
587f8910ef sync: Point to manpage if unconfigured
See #3340.
2024-04-05 10:35:15 -04:00
dependabot[bot]
de42c8ba34 Bump strum_macros from 0.25.0 to 0.25.3 (#3335)
Bumps [strum_macros](https://github.com/Peternator7/strum) from 0.25.0 to 0.25.3.
- [Release notes](https://github.com/Peternator7/strum/releases)
- [Changelog](https://github.com/Peternator7/strum/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Peternator7/strum/commits)

---
updated-dependencies:
- dependency-name: strum_macros
  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>
2024-04-04 18:16:31 +00:00
Dustin J. Mitchell
8a0a98d3ef Issue a warning if .data files remain (#3321)
This will help users realize that they have updated to an incompatible
version and must export and import their tasks.
2024-03-31 18:55:30 -04:00
Felix Schurk
5a56cff88b prefix regex strings to be treated as raw strings (#3322)
Source for documentation
https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals.
Closes #3316.
2024-03-31 09:08:56 -04:00
Dustin J. Mitchell
c91cef43b0 add note about ENABLE_SYNC to changelog (#3317) 2024-03-31 00:59:21 +00:00
Felix Schurk
8c2c629a4d use CMake project settings (#3315)
This adds a description as well as the homepage to the CMake settings.
Further it would also now use the numbering cheme as supposed to in
CMake, this way other people could now pin a specific version if
taskwarrior is included in another project.
Documentation from CMake is https://cmake.org/cmake/help/latest/command/project.html
2024-03-30 10:33:49 -04:00
dependabot[bot]
dfaf3dfcb2 Bump tokio from 1.36.0 to 1.37.0 (#3310)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.36.0 to 1.37.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.36.0...tokio-1.37.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-03-29 09:25:44 -04:00
Dustin J. Mitchell
06fdfc5af3 Edit Cargo.toml during release (#3302) 2024-03-27 22:56:13 +00:00
dependabot[bot]
19f2c0d7b4 Bump uuid from 1.7.0 to 1.8.0 (#3290)
* Bump uuid from 1.7.0 to 1.8.0

Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.7.0 to 1.8.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.7.0...1.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>

* use as_bytes

---------

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 <djmitche@google.com>
2024-03-24 22:19:27 +00:00
Dustin J. Mitchell
8bb08bf01d Add releasing docs (#3292)
add releasing docs
2024-03-24 21:32:38 +00:00
740 changed files with 30753 additions and 57303 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,7 +10,7 @@ RUN if [ "${REINSTALL_CMAKE_VERSION_FROM_SOURCE}" != "none" ]; then \
fi \ fi \
&& rm -f /tmp/reinstall-cmake.sh && 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. # [Optional] Uncomment this section to install additional vcpkg ports.
# RUN su vscode -c "${VCPKG_ROOT}/vcpkg install <your-port-name-here>" # RUN su vscode -c "${VCPKG_ROOT}/vcpkg install <your-port-name-here>"

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

@@ -9,4 +9,4 @@
* Clearly describe the feature. * 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. * 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

@@ -29,7 +29,10 @@ jobs:
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
toolchain: "1.70.0" # MSRV # 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
override: true override: true
- uses: actions-rs/cargo@v1.0.3 - uses: actions-rs/cargo@v1.0.3
@@ -44,22 +47,6 @@ jobs:
args: --all-features args: --all-features
name: "Clippy Results" name: "Clippy Results"
mdbook:
runs-on: ubuntu-latest
name: "Documentation"
steps:
- uses: actions/checkout@v4
- name: Setup mdBook
uses: peaceiris/actions-mdbook@v1
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: fmt:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: "Formatting" name: "Formatting"
@@ -78,38 +65,18 @@ jobs:
command: fmt command: fmt
args: --all -- --check args: --all -- --check
codegen: cargo-metadata:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: "codegen" name: "Cargo Metadata"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ 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 - uses: actions-rs/toolchain@v1
with: with:
toolchain: "1.70.0" # MSRV profile: minimal
components: rustfmt
toolchain: stable
override: true override: true
- uses: actions-rs/cargo@v1.0.3 - name: "Check metadata"
with: run: ".github/workflows/metadata-check.sh"
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

View File

@@ -23,31 +23,35 @@ jobs:
id-token: write id-token: write
steps: steps:
- name: Create lowercase repository name
run: |
GHCR_REPOSITORY="${{ github.repository_owner }}"
echo "REPOSITORY=${GHCR_REPOSITORY,,}" >> ${GITHUB_ENV}
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
submodules: "recursive" submodules: "recursive"
- name: Install cosign - name: Install cosign
uses: sigstore/cosign-installer@v3.4.0 uses: sigstore/cosign-installer@v3.7.0
- name: Log into registry ${{ env.REGISTRY }} - name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@v3.1.0 uses: docker/login-action@v3.3.0
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Taskwarrior Docker image - name: Build and push Taskwarrior Docker image
id: build-and-push id: build-and-push
uses: docker/build-push-action@v5.3.0 uses: docker/build-push-action@v6.10.0
with: with:
context: . context: .
file: "./docker/task.dockerfile" file: "./docker/task.dockerfile"
push: true 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 - name: Sign the published Docker image
env: env:
COSIGN_EXPERIMENTAL: "true" 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@v1
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@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./taskchampion/docs/book
destination_dir: taskchampion

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

@@ -0,0 +1,30 @@
name: release-tests
on: [push, pull_request]
jobs:
check-tarball:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/toolchain@v1
with:
toolchain: "stable"
override: true
- 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,10 +1,42 @@
## Run the Taskwarrior tests, using stable rust to build TaskChampion. ## Run the Taskwarrior tests, using stable rust to build TaskChampion.
name: tests name: tests
on: [push, pull_request] on: [push, pull_request]
jobs: 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 # MacOS tests do not run in Docker, and use the actions-rs Rust installaction
tests-macos-12: tests-macos-12:
needs: coverage
name: tests (Mac OS 12.latest) name: tests (Mac OS 12.latest)
if: false # see #3242 if: false # see #3242
runs-on: macos-latest runs-on: macos-latest
@@ -33,6 +65,7 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tests-macos-13: tests-macos-13:
needs: coverage
name: tests (Mac OS 13.latest) name: tests (Mac OS 13.latest)
if: false # see #3242 if: false # see #3242
runs-on: macos-13 runs-on: macos-13
@@ -61,17 +94,50 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
cargo-test:
runs-on: ubuntu-latest
name: "Cargo Test"
steps:
- uses: actions/checkout@v4
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ 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:
# 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.
# This should match the MSRV in `src/taskchampion-cpp/Cargo.toml`.
toolchain: "1.81.0" # MSRV
override: true
- uses: actions-rs/cargo@v1.0.3
with:
command: test
tests: tests:
needs: coverage
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- name: "Fedora 38" - name: "Fedora 40"
runner: ubuntu-latest runner: ubuntu-latest
dockerfile: fedora38 dockerfile: fedora40
- name: "Fedora 39" - name: "Fedora 41"
runner: ubuntu-latest runner: ubuntu-latest
dockerfile: fedora39 dockerfile: fedora41
- name: "Debian Testing" - name: "Debian Testing"
runner: ubuntu-latest runner: ubuntu-latest
dockerfile: debiantesting dockerfile: debiantesting
@@ -99,10 +165,10 @@ jobs:
GITHUB_USER: ${{ github.actor }} GITHUB_USER: ${{ github.actor }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONTAINER: ${{ matrix.dockerfile }} CONTAINER: ${{ matrix.dockerfile }}
run: docker-compose build test-$CONTAINER run: docker compose build test-${{ env.CONTAINER }}
- name: Test ${{ matrix.name }} - name: Test ${{ matrix.name }}
run: docker-compose run test-$CONTAINER run: docker compose run test-${{ env.CONTAINER }}
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONTAINER: ${{ matrix.dockerfile }} CONTAINER: ${{ matrix.dockerfile }}

6
.gitignore vendored
View File

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

3
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "src/libshared"] [submodule "src/libshared"]
path = src/libshared path = src/libshared
url = https://github.com/GothenburgBitFactory/libshared.git url = https://github.com/GothenburgBitFactory/libshared.git
[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: v19.1.6
hooks:
- id: clang-format
types_or: [c++, c]
- repo: https://github.com/psf/black
rev: 24.10.0
hooks:
- id: black

View File

@@ -1,4 +1,13 @@
cmake_minimum_required (VERSION 3.22) cmake_minimum_required (VERSION 3.22)
enable_testing()
set (CMAKE_EXPORT_COMPILE_COMMANDS ON)
project (task
VERSION 3.3.0
DESCRIPTION "Taskwarrior - a command-line TODO list manager"
HOMEPAGE_URL https://taskwarrior.org/)
set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
include (FetchContent) include (FetchContent)
@@ -7,11 +16,8 @@ include (CheckStructHasMember)
set (HAVE_CMAKE true) set (HAVE_CMAKE true)
project (task)
include (CXXSniffer) include (CXXSniffer)
set (PROJECT_VERSION "3.0.0")
OPTION (ENABLE_WASM "Enable 'wasm' support" OFF) OPTION (ENABLE_WASM "Enable 'wasm' support" OFF)
if (ENABLE_WASM) if (ENABLE_WASM)
@@ -19,19 +25,19 @@ if (ENABLE_WASM)
set(CMAKE_EXECUTABLE_SUFFIX ".js") set(CMAKE_EXECUTABLE_SUFFIX ".js")
endif (ENABLE_WASM) endif (ENABLE_WASM)
message ("-- Looking for libshared") message ("-- Looking for git submodules")
if (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src) if (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src AND EXISTS ${CMAKE_SOURCE_DIR}/src/taskchampion-cpp/corrosion)
message ("-- Found libshared") message ("-- Found git submodules")
else (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src) else (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src)
message ("-- Cloning libshared") message ("-- Cloning git submodules")
execute_process (COMMAND git submodule update --init execute_process (COMMAND git submodule update --init
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) 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") message ("-- Looking for SHA1 references")
if (EXISTS ${CMAKE_SOURCE_DIR}/.git/index) if (EXISTS ${CMAKE_SOURCE_DIR}/.git/index)
set (HAVE_COMMIT true) 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} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE COMMIT) OUTPUT_VARIABLE COMMIT)
configure_file ( ${CMAKE_SOURCE_DIR}/commit.h.in configure_file ( ${CMAKE_SOURCE_DIR}/commit.h.in
@@ -136,39 +142,22 @@ configure_file (
add_subdirectory (src) add_subdirectory (src)
add_subdirectory (src/commands) add_subdirectory (src/commands)
add_subdirectory (src/tc) add_subdirectory (src/taskchampion-cpp)
add_subdirectory (src/columns) add_subdirectory (src/columns)
add_subdirectory (doc) add_subdirectory (doc)
add_subdirectory (scripts) add_subdirectory (scripts)
if (EXISTS ${CMAKE_SOURCE_DIR}/test) if (EXISTS ${CMAKE_SOURCE_DIR}/test)
add_subdirectory (test EXCLUDE_FROM_ALL) add_subdirectory (test EXCLUDE_FROM_ALL)
endif (EXISTS ${CMAKE_SOURCE_DIR}/test) endif (EXISTS ${CMAKE_SOURCE_DIR}/test)
if (EXISTS performance) if (EXISTS ${CMAKE_SOURCE_DIR}/performance)
add_subdirectory (performance EXCLUDE_FROM_ALL) add_subdirectory (performance EXCLUDE_FROM_ALL)
endif (EXISTS performance) endif (EXISTS ${CMAKE_SOURCE_DIR}/performance)
set (doc_FILES NEWS ChangeLog README.md INSTALL AUTHORS COPYING LICENSE) set (doc_FILES ChangeLog README.md INSTALL AUTHORS COPYING LICENSE)
foreach (doc_FILE ${doc_FILES}) foreach (doc_FILE ${doc_FILES})
install (FILES ${doc_FILE} DESTINATION ${TASK_DOCDIR}) install (FILES ${doc_FILE} DESTINATION ${TASK_DOCDIR})
endforeach (doc_FILE) 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_GENERATOR "TGZ")

3189
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,47 +1,7 @@
[workspace] [workspace]
members = [ members = [
"taskchampion/taskchampion", "src/taskchampion-cpp",
"taskchampion/sync-server",
"taskchampion/lib",
"taskchampion/integration-tests",
"taskchampion/xtask",
] ]
resolver = "2" 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]
actix-rt = "2"
actix-web = "^4.3.1"
anyhow = "1.0"
byteorder = "1.5"
cc = "1.0.73"
chrono = { version = "^0.4.22", features = ["serde"] }
clap = { version = "^4.3.0", features = ["string"] }
env_logger = "^0.10.0"
ffizz-header = "0.5"
flate2 = "1"
futures = "^0.3.25"
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.7.0", features = ["serde", "v4"] }

View File

@@ -1,4 +1,96 @@
------ current release --------------------------- ------ current release ---------------------------
- Sync now supports AWS S3 as a backend.
- A new `task import-v2` command allows importing Taskwarrior-2.x
data files directly.
3.3.0 -
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
------ old releases ------------------------------
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)
- Improvements to `task news`, including notes for the 3.0.0 release
- Minor improvements to documentation and error handling
- Fix incorrect task ID of 0 when using hooks (#3339)
- Issue a warning if .data files remain (#3321)
3.0.0 - 3.0.0 -
- [BREAKING CHANGE] the sync functionality has been rewritten entirely, and - [BREAKING CHANGE] the sync functionality has been rewritten entirely, and
@@ -19,7 +111,9 @@
- `taskd.server` - `taskd.server`
- `taskd.trust` - `taskd.trust`
The Taskwarrior build no longer requires GnuTLS. The Taskwarrior build no longer requires GnuTLS. The build option
`ENABLE_SYNC=OFF` is also no longer supported; sync support is always built
in.
Deep thanks to the following for contributions to this work: Deep thanks to the following for contributions to this work:
@@ -229,8 +323,6 @@
Thanks to bharatvaj for contributing. Thanks to bharatvaj for contributing.
- TW #2581 Config entry with a trailing comment cannot be modified - TW #2581 Config entry with a trailing comment cannot be modified
------ old releases ------------------------------
2.5.3 (2021-01-05) - 2f47226f91f0b02f7617912175274d9eed85924f 2.5.3 (2021-01-05) - 2f47226f91f0b02f7617912175274d9eed85924f
- #2375 task hangs then dies when certain tasks are present in a report - #2375 task hangs then dies when certain tasks are present in a report
@@ -2768,4 +2860,3 @@ regular usage to determine which features were needed or unnecessary.]
- Usage. - Usage.
------ start ----------------------------------- ------ start -----------------------------------

56
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: You will need the following libraries:
- libuuid (not needed for OSX) - libuuid (not needed for OSX)
You will need a Rust toolchain of the Minimum Supported Rust Version (MSRV):
- rust 1.81.0
Basic Installation Basic Installation
------------------ ------------------
Briefly, these shell commands will unpack, build and install Taskwarrior: Briefly, these shell commands will unpack, build and install Taskwarrior:
$ tar xzf task-X.Y.Z.tar.gz [1] $ tar xzf task-X.Y.Z.tar.gz [1]
$ cd task-X.Y.Z [2] $ cd task-X.Y.Z [2]
$ cmake -DCMAKE_BUILD_TYPE=release . [3] $ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release . [3]
$ make [4] $ cmake --build build [4]
$ sudo make install [5] $ sudo cmake --install build [5]
$ cd .. ; rm -r task-X.Y.Z [6] $ cd .. ; rm -r task-X.Y.Z [6]
These commands are explained below: 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_MAN1DIR /usr/local/share/man/man1
CMAKE_INSTALL_PREFIX/TASK_MAN5DIR /usr/local/share/man/man5 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 Uninstallation
-------------- --------------
@@ -108,6 +115,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 System notes below. If this doesn't help, then go to the Troubleshooting
section, which includes instructions on how to contact us for help. 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 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, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.

152
NEWS
View File

@@ -1,152 +0,0 @@
New Features in Taskwarrior 2.6.0
- The logic behind new-uuid verbosity option changed. New-uuid now overrides
new-id if set and will cause Taskwarrior to display UUIDs instead of IDs
for new tasks (machine friendly).
- If ~/.taskrc is not found, Taskwarrior will look for its configuration in
$XDG_CONFIG_HOME/task/taskrc (defaulting to ~/.config/task/taskrc). This
allows users to setup their Taskwarrior to follow XDG standard without
using config overrides.
- Newer Unicode characters, such as emojis are correctly handled and displayed.
Taskwarrior now supports all Unicode characters up to Unicode 12.
- Datetime values until year 9999 are now supported.
Duration values of up to 1 000 000 years are now supported.
- 64-bit numeric values (up to 9,223,372,036,854,775,807) are now supported.
- Later/someday named datetime values now resolve to 9999-12-30 (instead of
2038-01-18).
- Calendar now supports displaying due dates until year 9999.
- Calendar now displays waiting tasks with due dates on the calendar.
- Calendar supports highlighting days with scheduled tasks.
- Multi-day holidays are now supported.
- Holiday data files for fr-CA, hu-HU, pt-BR, sk-SK and sv-FI locales are now
generated and shipped with Taskwarrior.
- The task edit command can now handle multi-line annotations and UDAs in a
user friendly way, withouth having to handle with JSON escaping of special
chars.
- A large portion of currently known parser-related issues was fixed.
- The taskrc file now supports relative paths, which are evaluated with
respect to (a) current directory, (b) taskrc directory and (c) now also the
installation directory of configuration files.
- The currently selected context is now applied for "task add" and "task log"
commands. Section on contexts in the manpage was updated to describe this
functionality.
- Users can specify per-context specific overrides of configuration variables.
- The `task import` command can now accept annotations with missing entry
values. Current time will be assumed.
- The new 'by' filter attribute modifier compares using '<=' rather than '<'
as 'before' uses. This allows the last second of the day to match with
'due.by:eod', which it would not otherwise. It also works with
whole units like days, e.g. 'add test due:2021-07-17' would not match
'due.before:tomorrow' (on the 16th), but would match 'due.by:tomorrow'.
- Waiting is now an entirely "virtual" concept, based on a task's
'wait' property and the current time. Task is considered "waiting" if its
wait attribute is in the future. TaskWarrior no longer explicitly
"unwaits" a task (the wait attribute is not removed once its value is in
the past), so the "unwait' verbosity token is no longer available.
This allows for filtering for tasks that were waiting in the past
intervals, but are not waiting anymore.
- The configuration file now supports environment variables.
- Taskwarrior can now handle displaying tasks in windows with limited width,
even if columns contain long strings (like URLs).
- The nag message is emitted at most once per task command, even with bulk
operations. Additionally, the urgency of the task considered is taken
before the completion, not after.
- The export command now takes an optional argument that references an
existing report. As such, "task export <report>" command will export
the same tasks that "task <report>" prints, and in the same order.
- The burndown command now supports non-cumulative display, where tasks only
get plotted within the interval segment when they got completed.
New Commands in Taskwarrior 2.6.0
- The 'news' command will guide the user through important release notes
anytime a new version of Taskwarrior is installed. It provides personalized
feedback, deprecation warnings and usage advice, where applicable.
New Configuration Options in Taskwarrior 2.6.0
- The context definitions for reporting commmands are now stored in
"context.<name>.read". Context definitions for write commands are now
supported using "context.<name>.write" configuration variable.
- The context-specific configuration overrides are now supported. Use
context.<name>.rc.<key>=<value> to override, such as
context.work.rc.urgency.blocking=5.0 to override the value of urgency.blocking
when the 'work' context is active.
- Each report (and the timesheet command) can explicitly opt-out from the
currently active context by setting the report.<name>.context variable to 0
(defaults to 1). Useful for defining universal reports that ignore
currently set context, such as 'inbox' report for GTD methodology.
- Multi-day holidays are now supported. Use holiday.<name>.start=<date> and
holiday.<name>.end=<date> to specify a range-based holiday, such as a
vacation.
- Verbosity token 'default' was introduced in order to display information
about default actions.
- The new burndown.cumulative option can be used to toggle between
non-cumulative and cumulative version of the burndown command.
- The new color.calendar.scheduled setting can be used to control the
highlighting color of days in the calendar that have scheduled tasks.
Newly Deprecated Features in Taskwarrior 2.6.0
- The 'PARENT' and 'CHILD' virtual tags are replaced by 'TEMPLATE' and 'INSTANCE'.
- The 'waiting' status is now deprecated. We recommend using +WAITING virtual tag
or wait-attribute based filters, such as 'wait.before:eow' instead.
- The configuration variable 'monthsperline' is deprecated. Please use
'calendar.monthsperline' instead.
Fixed regressions in 2.6.0
- The "end of <date>" named dates ('eod', 'eow', ...) were pointing to the
first second of the next day, instead of last second of the referenced
interval. This was a regression introduced in 2.5.2.
- The "eow" and "eonw" were using a different weekday as a reference. This
was a regeression introduced in 2.5.2.
- The rc.verbose=<value> configuration override was applied only if it were
the first configuration override. In #2247, this manifested itself as
inability to supress footnotes about the overrides, and in #1953 as failure
to force task to display UUIDs of on task add. This was a regression
introduced in 2.5.2.
- The attribute values of the form "<attribute name>-<arbitrary string>", for
example "due-nextweek" or "scheduled-work" would fail to parse (see
#1913). This was a regression introduced in 2.5.1.
- The capitalized versions of named dates (such as Monday, February or
Tomorrow) are again supported. This was a regression introduced in 2.5.2.
- The duration periods are converted to datetime values using the
current time as the anchor, as opposed to the beginning of unix time.
This was a regression in 2.5.2.
- Filtering for attribute values containing dashes and numbers (such as
'vs.2021-01', see #2392) or spaces (such as "Home renovation", see #2388)
is again supported. This was a regression introduced in 2.4.0.
Removed Features in 2.6.0
-
Other notable changes in 2.6.0
- C++17 compatible compiler is now required (GCC 7.1 or older / clang 5.0 or older).
Known Issues
- https://github.com/GothenburgBitFactory/taskwarrior
Taskwarrior 2.6.0 has been built and tested on the following configurations:
* Archlinux
* OpenSUSE
* macOS 10.15
* Fedora (31, 32, 33, 34)
* Ubuntu (18.04, 20.04, 21.04)
* Debian (Stable, Testing)
* CentOS (7, 8)
However, we expect Taskwarrior to work on other platforms as well.
---
While Taskwarrior has undergone testing, bugs are sure to remain. If you
encounter a bug, please enter a new issue at:
https://github.com/GothenburgBitFactory/taskwarrior

View File

@@ -2,9 +2,11 @@
<img src="https://avatars.githubusercontent.com/u/36100920?s=200&u=24da05914c20c4ccfe8485310f7b83049407fa9a&v=4"></br> <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) [![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](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) [![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/) [![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> </br>
</div> </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). more are available on our website, [taskwarrior.org](https://taskwarrior.org).
## Community ## 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) [![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) [![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. Taskwarrior has a lively community on many places on the internet.

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

@@ -58,4 +58,3 @@
/* Undefine this to eliminate the execute command */ /* Undefine this to eliminate the execute command */
#define HAVE_EXECUTE 1 #define HAVE_EXECUTE 1

View File

@@ -6,7 +6,8 @@ include (CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("-std=c++17" _HAS_CXX17) CHECK_CXX_COMPILER_FLAG("-std=c++17" _HAS_CXX17)
if (_HAS_CXX17) if (_HAS_CXX17)
set (_CXX14_FLAGS "-std=c++17") set (CMAKE_CXX_STANDARD 17)
set (CMAKE_CXX_EXTENSIONS OFF)
else (_HAS_CXX17) 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.") 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) endif (_HAS_CXX17)
@@ -32,7 +33,7 @@ elseif (${CMAKE_SYSTEM_NAME} STREQUAL "GNU")
set (GNUHURD true) set (GNUHURD true)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "CYGWIN") elseif (${CMAKE_SYSTEM_NAME} STREQUAL "CYGWIN")
set (CYGWIN true) set (CYGWIN true)
set (_CXX14_FLAGS "-std=gnu++17") set (CMAKE_CXX_EXTENSIONS ON)
else (${CMAKE_SYSTEM_NAME} MATCHES "Linux") else (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
set (UNKNOWN true) set (UNKNOWN true)
endif (${CMAKE_SYSTEM_NAME} MATCHES "Linux") 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. As of the 3.0 release, Taskwarrior uses TaskChampion to manage task data.
Find documentation of TaskChampion here: Find documentation of TaskChampion here:
* [TaskChampion README](../../taskchampion) * [TaskChampion Repository](https://github.com/GothenburgBitFactory/taskchampion/)
* [TaskChampion CONTRIBUTING guide](../../taskchampion/CONTRIBUTING.md) * [TaskChampion Book](https://gothenburgbitfactory.github.io/taskchampion/)
* [TaskChampion Book](../../taskchampion/docs/src/SUMMARY.md)
* [TaskChampion API Documentation](https://docs.rs/taskchampion) * [TaskChampion API Documentation](https://docs.rs/taskchampion)
TaskChampion will [become its own
project](https://github.com/GothenburgBitFactory/taskwarrior/issues/3209) soon.

View File

@@ -5,3 +5,4 @@
* [Coding Style](coding_style.md) * [Coding Style](coding_style.md)
* [Branching Model](branching.md) * [Branching Model](branching.md)
* [Rust and C++](rust-and-c++.md) * [Rust and C++](rust-and-c++.md)
* [Releasing Taskwarrior](releasing.md)

View File

@@ -1,30 +1,18 @@
# Coding Style # Coding Style
The coding style used for the Taskwarrior, Taskserver, and other codebases is deliberately kept simple and a little vague. 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.
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.
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: 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.
## 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.
## Rust ## Rust

View File

@@ -1,20 +1,18 @@
# Developing Taskwarrior # 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: ## 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 * gcc 7.0 or later, clang 6.0 or later, or a compiler with full C++17 support
* libuuid (if not on macOS) * libuuid (if not on macOS)
* python 3 (optional, for running the test suite)
* Rust 1.64.0 or higher (hint: use https://rustup.rs/ instead of using your system's package manager) * 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)
## Obtain and Build Code: ## Obtain and Build Code:
The following documentation works with CMake 3.14 and later. The following documentation works with CMake 3.14 and later.
Here are the minimal steps to get started, using an out of source build directory and calling the underlying build tool over the CMake interface. Here are the minimal steps to get started, using an out of source build directory and calling the underlying build tool over the CMake interface.
@@ -24,7 +22,7 @@ See the general CMake man pages or the [cmake-documentation](https://cmake.org/c
```sh ```sh
git clone https://github.com/GothenburgBitFactory/taskwarrior git clone https://github.com/GothenburgBitFactory/taskwarrior
cd taskwarrior cd taskwarrior
cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo
cmake --build build cmake --build build
``` ```
Other possible build types can be `Release` and `Debug`. Other possible build types can be `Release` and `Debug`.
@@ -52,24 +50,57 @@ cmake --build build-clang
``` ```
## Run the Test Suite: ## 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`:
``` ### Repeating a test case
$ ./run_all In order to find sporadic test failures the `--repeat` flag can be used.
``` ```sh
Either way, you can get a summary of any test failures with: ctest --test-dir build -R cpp --repeat-until-fail 10
```
$ ./problems
``` ```
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. 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. 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. 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

@@ -0,0 +1,26 @@
# Releasing Taskwarrior
To release Taskwarrior, follow this process:
- Examine the changes since the last version, and update `src/commands/CmdNews.cpp` accordingly.
There are instructions at the top of the file.
- Create a release PR
- Update version in CMakeLists.txt
- Update Changelog
- get this merged
- On `develop` after that PR merges, create a release tarball:
- `git clone . release-tarball`
- `cd release-tarball/`
- `cmake -S. -Bbuild`
- `make -Cbuild package_source`
- copy build/task-*.tar.gz elsewhere and delete the `release-tarball` dir
- NOTE: older releases had a `test-*.tar.gz` but it's unclear how to generate this
- Update `stable` to the released commit and push upstream
- Tag the commit as vX.Y.Z and push the tag upstream
- 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
- 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`.

View File

@@ -1,42 +1,18 @@
# Rust and C++ # 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. 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.
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.
## TaskChampion ## TaskChampion
TaskChampion implements storage and access to "replicas" containing a user's tasks. 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 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. 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. 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. 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 use of TaskChampion
Taskwarrior's interface to TaskChampion has a few layers: 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.
* The skeletal Rust crate in [`src/tc/rust`](../../src/tc/rust) brings the symbols from `taskchampion-lib` under CMake's management. That API is defined, and documented, in `src/taskchampion-cpp/src/lib.rs`, and available in the `tc` namespace in C++ code.
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.

View File

@@ -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. 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. 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 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: a terminal, color is desirable, but consider the following command:
.nf
$ task list > file.txt $ task list > file.txt
.fi
Do we really want all those color control codes in the file? Taskwarrior 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 assumes that you do not, and temporarily sets color to 'off' while generating
the output. This explains the output from the following command: the output. This explains the output from the following command:
.nf
$ task show | grep '^color ' $ task show | grep '^color '
color off color off
.fi
it always returns 'off', no matter what the setting, because the output is being it always returns 'off', no matter what the setting, because the output is being
sent to a pipe. 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 If you wanted those color codes, you can override this behavior by setting the
_forcecolor variable to on, like this: _forcecolor variable to on, like this:
.nf
$ task config _forcecolor on $ task config _forcecolor on
$ task config | grep '^color ' $ task config | grep '^color '
color on color on
.fi
or by temporarily overriding it like this: or by temporarily overriding it like this:
.nf
$ task rc._forcecolor=on config | grep '^color ' $ task rc._forcecolor=on config | grep '^color '
color on color on
.fi
.SH AVAILABLE COLORS .SH AVAILABLE COLORS
Taskwarrior has a 'color' command that will show all the colors it is capable of Taskwarrior has a 'color' command that will show all the colors it is capable of
displaying. Try this: displaying. Try this:
.nf
$ task color $ task color
.fi
The output cannot be replicated here in a man page, but you should see a set of 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 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 .SH 16-COLOR SUPPORT
The basic color support is provided through named colors: The basic color support is provided through named colors:
.nf
black, red, blue, green, magenta, cyan, yellow, white black, red, blue, green, magenta, cyan, yellow, white
.fi
Foreground color (for text) is simply specified as one of the above colors, or 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. 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 Background color is specified by using the word 'on', and one of the above
colors. Some examples: colors. Some examples:
.nf
green # green text, default background color green # green text, default background color
green on yellow # green text, yellow background green on yellow # green text, yellow background
on yellow # default text color, yellow background on yellow # default text color, yellow background
.fi
These colors can be modified further, by making the foreground bold, or by These colors can be modified further, by making the foreground bold, or by
making the background bright. Some examples: making the background bright. Some examples:
.nf
bold green bold green
bold white on bright red bold white on bright red
on bright cyan on bright cyan
.fi
The order of the words is not important, so the following are equivalent: The order of the words is not important, so the following are equivalent:
.nf
bold green bold green
green bold green bold
.fi
But the 'on' is important - colors before the 'on' are foreground, and colors But the 'on' is important - colors before the 'on' are foreground, and colors
after 'on' are background. after 'on' are background.
There is an additional 'underline' attribute that may be used: There is an additional 'underline' attribute that may be used:
.nf
underline bold red on black underline bold red on black
.fi
And an 'inverse' attribute: And an 'inverse' attribute:
.nf
inverse red inverse red
.fi
Taskwarrior has a command that helps you visualize these color combinations. Taskwarrior has a command that helps you visualize these color combinations.
Try this: Try this:
.nf
$ task color underline bold red on black $ task color underline bold red on black
.fi
You can use this command to see how the various color combinations work. You 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 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, colors can be referenced in different ways. First there is by color ordinal,
which is like this: which is like this:
.nf
color0 color0
color1 color1
color2 color2
... ...
color255 color255
.fi
This gives you access to all 256 colors, but doesn't help you much. This range 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 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 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: component color. For example, a bright red is specified as:
.nf
rgb500 rgb500
.fi
And a darker red would be: And a darker red would be:
.nf
rgb300 rgb300
.fi
Note that the three digits represent the three component values, so in this 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 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: with no green and no blue yields red. Similarly, blue and green are:
.nf
rgb005 rgb005
rgb050 rgb050
.fi
Another example - bright yellow - is a mix of bright red and bright green, but Another example - bright yellow - is a mix of bright red and bright green, but
no blue component, so bright yellow is addressed as: no blue component, so bright yellow is addressed as:
.nf
rgb550 rgb550
.fi
A soft pink would be addressed as: A soft pink would be addressed as:
.nf
rgb515 rgb515
.fi
See if you agree, by running: See if you agree, by running:
.nf
$ task color black on rgb515 $ task color black on rgb515
.fi
You may notice that the large color block is represented as 6 squares. All 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 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 There is some limited color mapping - for example, if you were to specify this
combination: combination:
.nf
red on gray3 red on gray3
.fi
you are mixing a 16-color and 256-color specification. Taskwarrior will map red 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. 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 Taskwarrior will show examples of all defined colors used in your .taskrc, or
theme, if you run this command: theme, if you run this command:
.nf
$ task color legend $ task color legend
.fi
This gives you an example of each of the colors, so you can see the effect, 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. 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, specify a color, and the conditions under which that color is used. By example,
let us add a few tasks: let us add a few tasks:
.nf
$ task add project:Home priority:H pay the bills (1) $ task add project:Home priority:H pay the bills (1)
$ task add project:Home clean the rug (2) $ task add project:Home clean the rug (2)
$ task add project:Garden clean out the garage (3) $ 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 We can add a color rule that uses a blue background for all tasks in the Home
project: project:
.nf
$ task config color.project.Home 'on blue' $ task config color.project.Home 'on blue'
.fi
We use quotes around 'on blue' because there are two words, but they represent 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 one value in the .taskrc file. Now suppose we wish to use a bold yellow text
color for all cleaning work: color for all cleaning work:
.nf
$ task config color.keyword.clean 'bold yellow' $ task config color.keyword.clean 'bold yellow'
.fi
Now what happens to task 2, which belongs to project Home (blue background), and 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 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 The precedence for the color rules is determined by the configuration
variable 'rule.precedence.color', which by default contains: 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. 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 These are just the color rules with the 'color.' prefix removed. The
rule 'color.deleted' has the highest precedence, and 'color.uda.' the lowest. 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. meaning 'color.keyword.*', or in other words all the keyword rules.
There is also 'color.project.none', 'color.tag.none' and 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 .SH THEMES
Taskwarrior supports themes. What this really means is that with the ability to 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 To get a good idea of what a color theme looks like, try adding this entry to
your .taskrc file: your .taskrc file:
.RS .nf
include dark-256.theme include dark-256.theme
.RE .fi
You can use any of the standard Taskwarrior themes: You can use any of the standard Taskwarrior themes:
.RS .nf
dark-16.theme dark-16.theme
.br dark-256.theme
dark-256.theme dark-blue-256.theme
.br dark-gray-256.theme
dark-blue-256.theme dark-green-256.theme
.br dark-red-256.theme
dark-gray-256.theme dark-violets-256.theme
.br dark-yellow-green.theme
dark-green-256.theme light-16.theme
.br light-256.theme
dark-red-256.theme solarized-dark-256.theme
.br solarized-light-256.theme
dark-violets-256.theme dark-default-16.theme
.br dark-gray-blue-256.theme
dark-yellow-green.theme no-color.theme
.br .fi
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
Bear in mind that if you are using a terminal with a dark background, you will Bear in mind that if you are using a terminal with a dark background, you will
see better results using a dark theme. see better results using a dark theme.
You can also see how the theme will color the various tasks with the command: You can also see how the theme will color the various tasks with the command:
.nf
$ task color legend $ task color legend
.fi
Better yet, create your own, and share it. We will gladly host the theme file Better yet, create your own, and share it. We will gladly host the theme file
on <https://taskwarrior.org>. on <https://taskwarrior.org>.

View File

@@ -30,12 +30,11 @@ the existing replica, and run `task sync`.
.SS When to Synchronize .SS When to Synchronize
Taskwarrior can perform a sync operation at every garbage collection (gc) run. For synchronization to a server, a common solution is to run
This is the default, and is appropriate for local synchronization.
For synchronization to a server, a better solution is to run
.nf
$ task sync $ task sync
.fi
periodically, such as via periodically, such as via
.BR cron (8) . .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 decrypt your tasks. This can be any secret string, and must match for all
replicas sharing tasks. replicas sharing tasks.
.nf
$ task config sync.encryption_secret <encryption_secret> $ task config sync.encryption_secret <encryption_secret>
.fi
Tools such as Tools such as
.BR pwgen (1) .BR pwgen (1)
@@ -64,14 +65,22 @@ To synchronize your tasks to a sync server, you will need the following
information from the server administrator: information from the server administrator:
.br .br
- The server's URL ("origin", such as "https://tw.example.com") - The server's URL (such as "https://tw.example.com/path")
.br .br
- A client ID ("client_id") identifying your tasks - A client ID ("client_id") identifying your tasks
Configure Taskwarrior with these details: 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> $ task config sync.server.client_id <client_id>
.fi
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 .SS Google Cloud Platform
@@ -81,12 +90,16 @@ the bucket are adequate.
Authenticate to the project with: Authenticate to the project with:
.nf
$ gcloud config set project $PROJECT_NAME $ gcloud config set project $PROJECT_NAME
$ gcloud auth application-default login $ gcloud auth application-default login
.fi
Then configure Taskwarrior with: Then configure Taskwarrior with:
.nf
$ task config sync.gcp.bucket <bucket-name> $ 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 `application-default` is already being used by some other application
@@ -103,26 +116,105 @@ Select the following permissions:
- storage.buckets.get - storage.buckets.get
- storage.buckets.update - storage.buckets.update
- storage.objects.create - storage.objects.create
- storage.objects.delete
- storage.objects.get - storage.objects.get
- storage.objects.list - storage.objects.list
- storage.objects.update - 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." 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. Provide an appropriate name and description for the new service account.
Select the role you just created and complete the service account creation process. 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).
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: Then configure Taskwarrior with:
.nf
$ task config sync.gcp.bucket <bucket-name> $ 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.
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 .SS Local Synchronization
@@ -130,7 +222,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. space without setting up a remote server, it is possible to sync tasks locally.
To configure local sync: To configure local sync:
.nf
$ task config sync.local.server_dir /path/to/sync $ task config sync.local.server_dir /path/to/sync
.fi
The default configuration is to sync to a database in the task directory The default configuration is to sync to a database in the task directory
("data.location"). ("data.location").
@@ -142,16 +236,8 @@ Users are identified by a client ID, and users with different client IDs are
entirely independent. Task data is encrypted by Taskwarrior, and the sync entirely independent. Task data is encrypted by Taskwarrior, and the sync
server never sees un-encrypted data. server never sees un-encrypted data.
To start the server, run it in your preferred HTTP hosting environment, using The server is developed in
`--port` to set the TCP port on which it should listen. It is recommended to https://github.com/GothenburgBitFactory/taskchampion-sync-server.
use TLS to protect communications with the server, but this is not required.
The server stores its data in a database, the path to which is given by the
`--data-dir` argument, defaulting to "/var/lib/taskchampion-sync-server".
For example:
$ taskchampion-sync-server --port 8443 --data-dir /storage/taskdata
.SS Adding a New User .SS Adding a New User
@@ -159,7 +245,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 `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 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 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". invent their own "encryption_secret".
.SH AVOIDING DUPLICATE RECURRING TASKS .SH AVOIDING DUPLICATE RECURRING TASKS
@@ -167,11 +253,15 @@ invent their own "encryption_secret".
If you run multiple clients that sync to the same server, you will need to run 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): this command on your primary client (the one you use most often):
.nf
$ task config recurrence on $ task config recurrence on
.fi
And on the other clients, run: And on the other clients, run:
.nf
$ task config recurrence off $ task config recurrence off
.fi
This protects you against the effects of a sync/duplication bug. This protects you against the effects of a sync/duplication bug.
@@ -190,7 +280,9 @@ modifying the same task on two machines, without an intervening sync.
Setup simply involves creating the directory and modifying your data.location Setup simply involves creating the directory and modifying your data.location
configuration variable like this: configuration variable like this:
.nf
$ task config data.location /path/to/shared/directory $ task config data.location /path/to/shared/directory
.fi
Strengths: Strengths:
.br .br

View File

@@ -24,18 +24,24 @@ descriptors), project groups, etc.
The <filter> consists of zero or more search criteria that select tasks. For The <filter> consists of zero or more search criteria that select tasks. For
example, to list all pending tasks belonging to the 'Home' project: example, to list all pending tasks belonging to the 'Home' project:
.nf
task project:Home list task project:Home list
.fi
You can specify multiple filter terms, each of which further restricts the You can specify multiple filter terms, each of which further restricts the
result: result:
.nf
task project:Home +weekend garden list task project:Home +weekend garden list
.fi
This example applies three filters: the 'Home' project, the 'weekend' tag, and This example applies three filters: the 'Home' project, the 'weekend' tag, and
the description or annotations must contain the character sequence 'garden'. the description or annotations must contain the character sequence 'garden'.
In this example, 'garden' is translated internally to: In this example, 'garden' is translated internally to:
.nf
description.contains:garden description.contains:garden
.fi
as a convenient shortcut. The 'contains' here is an attribute modifier, which 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. 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 command. This can be dangerous, and this special case is confirmed, and
cannot be overridden. For example, this command: cannot be overridden. For example, this command:
.nf
task modify +work task modify +work
This command has no filter, and will modify all tasks. Are you sure? (yes/no) 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. will add the 'work' tag to all tasks, but only after confirmation.
More filter examples: More filter examples:
.nf
task <command> <mods> task <command> <mods>
task 28 <command> <mods> task 28 <command> <mods>
task +weekend <command> <mods> task +weekend <command> <mods>
task +bills due.by:eom <command> <mods> task +bills due.by:eom <command> <mods>
task project:Home due.before:today <command> <mods> task project:Home due.before:today <command> <mods>
task ebeeab00-ccf8-464b-8b58-f7f2d606edfb <command> <mods> task ebeeab00-ccf8-464b-8b58-f7f2d606edfb <command> <mods>
.fi
By default filter elements are combined with an implicit 'and' operator, By default filter elements are combined with an implicit 'and' operator,
but 'or' and 'xor' may also be used, provided parentheses are included: but 'or' and 'xor' may also be used, provided parentheses are included:
.nf
task '( /[Cc]at|[Dd]og/ or /[0-9]+/ )' <command> <mods> task '( /[Cc]at|[Dd]og/ or /[0-9]+/ )' <command> <mods>
.fi
The parentheses isolate the logical term from any default command filter or The parentheses isolate the logical term from any default command filter or
implicit report filter which would be combined with an implicit 'and'. 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, multiple tasks use one of these forms (space-separated list of ID numbers,
UUID numbers or ID ranges): UUID numbers or ID ranges):
.nf
task 1 2 3 delete task 1 2 3 delete
task 1-3 info task 1-3 info
task 1 2-5 19 modify pri:H task 1 2-5 19 modify pri:H
task 4-7 ebeeab00-ccf8-464b-8b58-f7f2d606edfb info task 4-7 ebeeab00-ccf8-464b-8b58-f7f2d606edfb info
.fi
Note that it may be necessary to properly escape special characters as well as 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 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 The <mods> consist of zero or more changes to apply to the selected tasks, such
as: as:
.nf
task <filter> <command> project:Home task <filter> <command> project:Home
task <filter> <command> +weekend +garden due:tomorrow task <filter> <command> +weekend +garden due:tomorrow
task <filter> <command> Description/annotation text task <filter> <command> Description/annotation text
task <filter> <command> /from/to/ <- replace first match task <filter> <command> /from/to/ <- replace first match
task <filter> <command> /from/to/g <- replace all matches task <filter> <command> /from/to/g <- replace all matches
.fi
.SH SUBCOMMANDS .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 another format. You'll find these example scripts online at
<https://taskwarrior.org/tools/>: <https://taskwarrior.org/tools/>:
.nf
export-csv.pl export-csv.pl
export-sql.py export-sql.py
export-xml.py export-xml.py
@@ -198,6 +215,7 @@ another format. You'll find these example scripts online at
export-ical.pl export-ical.pl
export-xml.pl export-xml.pl
export-yad.pl export-yad.pl
.fi
.TP .TP
.B task <filter> ghistory.annual .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 a space-separated list. This is useful as input to a task command, to achieve
this: this:
.nf
task $(task project:Home ids) modify priority:H task $(task project:Home ids) modify priority:H
.fi
This example first gets the IDs for the project:Home filter, then sets 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: the priority to H for each of those tasks. This can also be achieved directly:
.nf
task project:Home modify priority:H task project:Home modify priority:H
.fi
This command is mainly of use to external scripts. 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. 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: This is useful as input to a task command, to achieve this:
.nf
task $(task project:Home status:completed uuids) modify status:pending task $(task project:Home status:completed uuids) modify status:pending
.fi
This example first gets the UUIDs for the project:Home and status:completed This example first gets the UUIDs for the project:Home and status:completed
filters, then makes each of those tasks pending again. 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 For importing other file formats, the standard task release comes with a
few example scripts, such as: few example scripts, such as:
.nf
import-todo.sh.pl import-todo.sh.pl
import-yaml.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 .TP
.B task log <mods> .B task log <mods>
@@ -401,6 +432,15 @@ Modifies the existing task with provided information.
.B task <filter> prepend <mods> .B task <filter> prepend <mods>
Prepends description text to an existing task. Is affected by the context. 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 .TP
.B task <filter> start <mods> .B task <filter> start <mods>
Marks the specified tasks as started. Is affected by the context. 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: Examples:
.nf
task calc 1 + 1 task calc 1 + 1
2 2
.fi
.nf
task calc now + 8d task calc now + 8d
2015-03-26T18:06:57 2015-03-26T18:06:57
.fi
.nf
task calc eom task calc eom
2015-03-31T23:59:59 2015-03-31T23:59:59
.fi
.TP .TP
.B task config [<name> [<value> | '']] .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', This command either modifies the 'name' setting with a new value of 'value',
or adds a new entry that is equivalent to 'name=value': or adds a new entry that is equivalent to 'name=value':
.nf
task config name value task config name value
.fi
This command sets a blank value. This has the effect of suppressing any This command sets a blank value. This has the effect of suppressing any
default value: default value:
.nf
task config name '' task config name ''
.fi
Finally, this command removes any 'name=...' entry from the .taskrc file: Finally, this command removes any 'name=...' entry from the .taskrc file:
.nf
task config name task config name
.fi
.TP .TP
.B task context <name> .B task context <name>
@@ -455,7 +507,9 @@ Sets the currently active context. See the CONTEXT section.
Example: Example:
.nf
task context work task context work
.fi
.TP .TP
.B task context delete <name> .B task context delete <name>
@@ -464,7 +518,9 @@ set as active, it will be unset.
Example: Example:
.nf
task context delete work task context delete work
.fi
.TP .TP
.B task context define <name> <filter> .B task context define <name> <filter>
@@ -473,9 +529,11 @@ does not affect the currently set context, just adds a new context definition.
Examples: Examples:
.nf
task context define work project:Work task context define work project:Work
task context define home project:Home or +home task context define home project:Home or +home
task context define superurgent due:today and +urgent task context define superurgent due:today and +urgent
.fi
.TP .TP
.B task context list .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. that should accompany a bug report.
It includes compiler, library and software information. It does not include It includes compiler, library and software information. It does not include
any personal information, other than the location and size of your task data any personal information, other than the location of your task data.
files.
This command also performs a diagnostic scan of your data files looking for This command also performs a diagnostic scan of your data looking for common
common problems, such as duplicate UUIDs. problems, such as duplicate UUIDs.
.TP .TP
.B task execute <external command> .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) Note: If you use multiple sync clients, make sure this setting (which is the default)
is on your primary client: is on your primary client:
.nf
recurrence=on recurrence=on
.fi
and on all other clients (this is not the default): and on all other clients (this is not the default):
.nf
recurrence=off recurrence=off
.fi
This is a workaround to avoid a recurrence bug that duplicates recurring tasks. 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 Reports a unique set of attribute values. For example, to see all the active
projects: projects:
.nf
task +PENDING _unique project task +PENDING _unique project
.fi
.TP .TP
.B task <filter> _uuids .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 Accesses and displays the DOM reference(s). Used to extract individual values
from tasks, or the system. Supported DOM references are: from tasks, or the system. Supported DOM references are:
.nf
rc.<name> rc.<name>
tw.syncneeded tw.syncneeded
tw.program tw.program
@@ -670,6 +734,7 @@ from tasks, or the system. Supported DOM references are:
system.os system.os
<id>.<attribute> <id>.<attribute>
<uuid>.<attribute> <uuid>.<attribute>
.fi
Note that the 'rc.<name>' reference may need to be escaped using '--' to prevent Note that the 'rc.<name>' reference may need to be escaped using '--' to prevent
the reference from being interpreted as an override. 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 Additionally, some components of the attributes of particular types may be
extracted by DOM references. extracted by DOM references.
.nf
$ task _get 2.due.year $ task _get 2.due.year
2015 2015
.fi
For a full list of supported attribute-specific DOM references, consult For a full list of supported attribute-specific DOM references, consult
the online documentation at: the online documentation at:
@@ -691,14 +758,16 @@ the online documentation at:
.TP .TP
.B ID .B ID
Tasks can be specified uniquely by IDs, which are simply the indexes of the Tasks can be specified uniquely by IDs, which are the indexes of the "working
tasks in the data file. The ID of a task may therefore change, but only when set" of tasks (mostly pending and recurrent tasks). The ID of a task may
a command is run that displays IDs. When modifying tasks, it is safe to therefore change, but only when a report that displays IDs is run. When
rely on the last displayed ID. Always run a report to check you have the right modifying tasks, it is safe to rely on the last displayed ID. Always run a
ID for a task. IDs can be given to task as a sequence, for example, report to check you have the right ID for a task. IDs can be given to task as a
.br sequence, for example:
.B
.nf
task 1,4-10,19 delete task 1,4-10,19 delete
.fi
.TP .TP
.B +tag|-tag .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 treated. For example, if a task has the special tag 'nocolor', then it is
exempt from all color rules. The supported special tags are: exempt from all color rules. The supported special tags are:
.nf
+nocolor Disable color rules processing for this task +nocolor Disable color rules processing for this task
+nonag Completion of this task suppresses all nag messages +nonag Completion of this task suppresses all nag messages
+nocal This task will not appear on the calendar +nocal This task will not appear on the calendar
+next Elevates task so it appears on 'next' report +next Elevates task so it appears on 'next' report
.fi
There are also virtual tags, which represent task metadata in tag form. These 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 tags do not exist, but can be used to filter tasks. The supported virtual tags
are: are:
.nf
ACTIVE Matches if the task is started ACTIVE Matches if the task is started
ANNOTATED Matches if the task has annotations ANNOTATED Matches if the task has annotations
BLOCKED Matches if the task is blocked 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) CHILD Matches if the task has a parent (deprecated in 2.6.0)
COMPLETED Matches if the task has completed status COMPLETED Matches if the task has completed status
DELETED Matches if the task has deleted 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 INSTANCE Matches if the task is a recurrent instance
LATEST Matches if the task is the newest added task LATEST Matches if the task is the newest added task
MONTH Matches if the task is due this month MONTH Matches if the task is due this month
@@ -749,6 +821,7 @@ are:
WEEK Matches if the task is due this week WEEK Matches if the task is due this week
YEAR Matches if the task is due this year YEAR Matches if the task is due this year
YESTERDAY Matches if the task was due sometime yesterday 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. .\" 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> .B project:<project-name>
Specifies the project to which a task is related to. 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 .TP
.B priority:H|M|L or priority: .B priority:H|M|L or priority:
Specifies High, Medium, Low and no priority for a task. 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> .B entry:<entry-date>
For report purposes, specifies the date that a task was created. 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 .SH ATTRIBUTE MODIFIERS
Attribute modifiers improve filters. Supported modifiers are: Attribute modifiers improve filters. Supported modifiers are:
@@ -846,9 +927,9 @@ calculated attributes:
For example: For example:
.RS .nf
task due.before:eom priority.not:L list task due.before:eom priority.not:L list
.RE .fi
The The
.I before .I before
@@ -868,15 +949,21 @@ The
modifier is the same as 'before', except it also includes the moment in modifier is the same as 'before', except it also includes the moment in
question. For example: question. For example:
.nf
task add test due:eoy task add test due:eoy
.fi
will be found when using the inclusive filter 'by': will be found when using the inclusive filter 'by':
.nf
task due.by:eoy task due.by:eoy
.fi
but not when the non-inclusive filter 'before' is used: but not when the non-inclusive filter 'before' is used:
.nf
task due.before:eoy task due.before:eoy
.fi
this applies equally to other named dates such as 'eom', 'eod', etc; the this applies equally to other named dates such as 'eom', 'eod', etc; the
modifier compares using '<=' rather than '<' like 'before' does. modifier compares using '<=' rather than '<' like 'before' does.
@@ -885,8 +972,10 @@ The
.I none .I none
modifier requires that the attribute does not have a value. For example: modifier requires that the attribute does not have a value. For example:
.nf
task priority: list task priority: list
task priority.none: list task priority.none: list
.fi
are equivalent, and list tasks that do not have a priority. are equivalent, and list tasks that do not have a priority.
@@ -908,8 +997,10 @@ The
.I has .I has
modifier is used to search for a substring, such as: modifier is used to search for a substring, such as:
.nf
task description.has:foo list task description.has:foo list
task foo list task foo list
.fi
These are equivalent and will return any task that has 'foo' in the description These are equivalent and will return any task that has 'foo' in the description
or annotations. or annotations.
@@ -924,13 +1015,17 @@ The
.I startswith .I startswith
modifier matches against the left, or beginning of an attribute, such that: modifier matches against the left, or beginning of an attribute, such that:
.nf
task project.startswith:H list task project.startswith:H list
task project:H list task project:H list
.fi
are equivalent and will match any project starting with 'H'. Matching all are equivalent and will match any project starting with 'H'. Matching all
projects not starting with 'H' is done with: projects not starting with 'H' is done with:
.nf
task project.not:H list task project.not:H list
.fi
The The
.I endswith .I endswith
@@ -941,7 +1036,9 @@ The
modifier requires that the attribute contain the whole word specified, such modifier requires that the attribute contain the whole word specified, such
that this: that this:
.nf
task description.word:bar list task description.word:bar list
.fi
Will match the description 'foo bar baz' but does not match 'dog food'. 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: You can use the following operators in filter expressions:
.nf
and or xor ! Logical operators and or xor ! Logical operators
< <= = == != !== >= > Relational operators < <= = == != !== >= > Relational operators
( ) Precedence ( ) Precedence
.fi
For example: For example:
.nf
task due.before:eom priority.not:L list task due.before:eom priority.not:L list
task '( due < eom or priority != L )' list task '( due < eom or priority != L )' list
task '! ( project:Home or project:Garden )' list task '! ( project:Home or project:Garden )' list
.fi
The The
.I = .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 the 'and' operator. The reason is that some reports contain filters that must
be combined with the command line. Consider this example: be combined with the command line. Consider this example:
.nf
task project:Home or project:Garden list task project:Home or project:Garden list
.fi
While this looks correct, it is not. The 'list' report contains a filter of: While this looks correct, it is not. The 'list' report contains a filter of:
.nf
task show report.list.filter task show report.list.filter
Config Variable Value Config Variable Value
----------------- -------------- ----------------- --------------
report.list.filter status:pending report.list.filter status:pending
.fi
Which means the example is really: Which means the example is really:
.nf
task status:pending project:Home or project:Garden list task status:pending project:Home or project:Garden list
.fi
The implied 'and' operator makes it: The implied 'and' operator makes it:
.nf
task status:pending and project:Home or project:Garden list 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 This is a precedence error - the 'and' and 'or' need to be grouped using
parentheses, like this: parentheses, like this:
.nf
task status:pending and ( project:Home or project:Garden ) list task status:pending and ( project:Home or project:Garden ) list
.fi
The original example therefore must be entered as: The original example therefore must be entered as:
.nf
task '( project:Home or project:Garden )' list task '( project:Home or project:Garden )' list
.fi
This includes quotes to escape the parentheses, so that the shell doesn't This includes quotes to escape the parentheses, so that the shell doesn't
interpret them and hide them from Taskwarrior. 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 There is redundancy between operators, attribute modifiers and other syntactic
sugar. For example, the following are all equivalent: sugar. For example, the following are all equivalent:
.nf
task foo list task foo list
task /foo/ list task /foo/ list
task description.contains:foo list task description.contains:foo list
task description.has:foo list task description.has:foo list
task 'description ~ foo' list task 'description ~ foo' list
.fi
.SH SPECIFYING DATES AND FREQUENCIES .SH SPECIFYING DATES AND FREQUENCIES
@@ -1038,92 +1153,83 @@ configuration variable
.RS .RS
.TP .TP
Exact specification Exact specification
task ... due:7/14/2008 .nf
task ... due:7/14/2008
.fi
.TP .TP
ISO-8601 ISO-8601
task ... due:2013-03-14T22:30:00Z .nf
task ... due:2013-03-14T22:30:00Z
.fi
.TP .TP
Relative wording Relative wording
task ... due:now .nf
.br task ... due:now
task ... due:today task ... due:today
.br task ... due:yesterday
task ... due:yesterday task ... due:tomorrow
.br .fi
task ... due:tomorrow
.TP .TP
Day number with ordinal Day number with ordinal
task ... due:23rd .nf
.br task ... due:23rd
task ... due:3wks task ... due:3wks
.br task ... due:1day
task ... due:1day task ... due:9hrs
.br .fi
task ... due:9hrs
.TP .TP
Start of next (work) week (Monday), calendar week (Sunday or Monday), month, quarter and year Start of next (work) week (Monday), calendar week (Sunday or Monday), month, quarter and year
.br .nf
task ... due:sow task ... due:sow
.br task ... due:soww
task ... due:soww task ... due:socw
.br task ... due:som
task ... due:socw task ... due:soq
.br task ... due:soy
task ... due:som .fi
.br
task ... due:soq
.br
task ... due:soy
.TP .TP
End of current (work) week (Friday), calendar week (Saturday or Sunday), month, quarter and year End of current (work) week (Friday), calendar week (Saturday or Sunday), month, quarter and year
.br .nf
task ... due:eow task ... due:eow
.br task ... due:eoww
task ... due:eoww task ... due:eocw
.br task ... due:eom
task ... due:eocw task ... due:eoq
.br task ... due:eoy
task ... due:eom .fi
.br
task ... due:eoq
.br
task ... due:eoy
.TP .TP
At some point or later At some point or later
.br .nf
task ... wait:later task ... wait:later
.br task ... wait:someday
task ... wait:someday .fi
This sets the wait date to 12/30/9999. This sets the wait date to 12/30/9999.
.TP .TP
Next occurring weekday Next occurring weekday
task ... due:fri .nf
task ... due:fri
.fi
.TP .TP
Predictable holidays Predictable holidays
task ... due:goodfriday .nf
.br task ... due:goodfriday
task ... due:easter task ... due:easter
.br task ... due:eastermonday
task ... due:eastermonday task ... due:ascension
.br task ... due:pentecost
task ... due:ascension task ... due:midsommar
.br task ... due:midsommarafton
task ... due:pentecost task ... due:juhannus
.br .fi
task ... due:midsommar
.br
task ... due:midsommarafton
.br
task ... due:juhannus
.RE .RE
.SS FREQUENCIES .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 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 example, any report command will have its result affected by the current
active context. Here is a list of the commands that are affected: active context. Here is a list of the commands that are affected:
.IP
.nf
add add
burndown burndown
count count
@@ -1187,38 +1294,50 @@ active context. Here is a list of the commands that are affected:
log log
prepend prepend
projects projects
purge
start start
stats stats
stop stop
summary summary
tags tags
.fi
All other commands are NOT affected by the context. All other commands are NOT affected by the context.
.nf
$ task list $ task list
ID Age Project Description Urg ID Age Project Description Urg
1 2d Sport Run 5 miles 1.42 1 2d Sport Run 5 miles 1.42
2 1d Home Clean the dishes 1.14 2 1d Home Clean the dishes 1.14
.fi
.nf
$ task context home $ task context home
Context 'home' set. Use 'task context none' to remove. Context 'home' set. Use 'task context none' to remove.
.fi
.nf
$ task list $ task list
ID Age Project Description Urg ID Age Project Description Urg
2 1d Home Clean the dishes 1.14 2 1d Home Clean the dishes 1.14
Context 'home' set. Use 'task context none' to remove. Context 'home' set. Use 'task context none' to remove.
.fi
Task list got automatically filtered for project:Home. Task list got automatically filtered for project:Home.
.nf
$ task add Vaccuum the carpet $ task add Vaccuum the carpet
Created task 3. Created task 3.
Context 'home' set. Use 'task context none' to remove. Context 'home' set. Use 'task context none' to remove.
.fi
.nf
$ task list $ task list
ID Age Project Description Urg ID Age Project Description Urg
2 1d Home Clean the dishes 1.14 2 1d Home Clean the dishes 1.14
3 5s Home Vaccuum the carpet 1.14 3 5s Home Vaccuum the carpet 1.14
Context 'home' set. Use 'task context none' to remove. Context 'home' set. Use 'task context none' to remove.
.fi
Note that the newly added task "Vaccuum the carpet" has "project:Home" set Note that the newly added task "Vaccuum the carpet" has "project:Home" set
automatically. automatically.
@@ -1229,22 +1348,28 @@ new context's name to the 'context' command.
To unset any context, use the 'none' subcommand. To unset any context, use the 'none' subcommand.
.nf
$ task context none $ task context none
Context unset. Context unset.
.fi
.nf
$ task list $ task list
ID Age Project Description Urg ID Age Project Description Urg
1 2d Sport Run 5 miles 1.42 1 2d Sport Run 5 miles 1.42
2 1d Home Clean the dishes 1.14 2 1d Home Clean the dishes 1.14
3 7s Home Vaccuum the carpet 1.14 3 7s Home Vaccuum the carpet 1.14
.fi
Context can be defined using the 'define' subcommand, specifying both the name Context can be defined using the 'define' subcommand, specifying both the name
of the new context, and it's assigned filter. of the new context, and it's assigned filter.
.nf
$ task context define home project:Home $ 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.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 Are you sure you want to add 'context.home.write' with a value of 'project:Home'? (yes/no) yes
Context 'home' successfully defined. Context 'home' successfully defined.
.fi
Note that you were separately prompted to set the 'read' and 'write' context. 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 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. To remove the definition, use the 'delete' subcommand.
.nf
$ task context delete home $ 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.read'? (yes/no) yes
Are you sure you want to remove 'context.home.write'? (yes/no) yes Are you sure you want to remove 'context.home.write'? (yes/no) yes
Context 'home' deleted. Context 'home' deleted.
.fi
To check what is the currently active context, use the 'show' subcommand. To check what is the currently active context, use the 'show' subcommand.
.nf
$ task context show $ task context show
Context 'home' with Context 'home' with
@@ -1266,13 +1394,16 @@ To check what is the currently active context, use the 'show' subcommand.
* write filter: '+home' * write filter: '+home'
is currently applied. is currently applied.
.fi
Contexts can store arbitrarily complex filters. Contexts can store arbitrarily complex filters.
.nf
$ task context define family project:Family or +paul or +nancy $ 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.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 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. Context 'family' successfully defined.
.fi
Contexts are permanent, and the currently set context name is stored in the Contexts are permanent, and the currently set context name is stored in the
"context" configuration variable. The context definition 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, 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: if such a context is used as a writeable context, the following happens:
.nf
$ task add Call Paul $ task add Call Paul
Created task 4. Created task 4.
Context 'family' set. Use 'task context none' to remove. Context 'family' set. Use 'task context none' to remove.
.fi
.nf
$ task 4 list $ task 4 list
ID Age Project Tags Description Urg ID Age Project Tags Description Urg
4 9min Family nancy paul or or Call Paul 0 4 9min Family nancy paul or or Call Paul 0
.fi
There is no clear mapping between the complex filter used and the modifications 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 the user intention here, and instead, the user is expected to set the
"context.<name>.write" variable to make their intention explicit, for example: "context.<name>.write" variable to make their intention explicit, for example:
.nf
$ task config context.family.write project:Family $ 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 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. Config file /home/tbabej/.config/task/taskrc modified.
.fi
.nf
$ task context $ task context
Name Type Definition Active Name Type Definition Active
family read project:Family or +paul or +nancy yes family read project:Family or +paul or +nancy yes
write project:Family yes write project:Family yes
home read +home no home read +home no
write +home no write +home no
.fi
Note how read and write contexts differ for context "family", while for context Note how read and write contexts differ for context "family", while for context
"home" they stay the same. "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 context, by specifying context.<name>.rc.<parameter>. For example, if the default
command for the family context should be displaying the family_report: command for the family context should be displaying the family_report:
.nf
$ task config context.family.rc.default.command family_report $ task config context.family.rc.default.command family_report
.fi
.SH COMMAND ABBREVIATION .SH COMMAND ABBREVIATION
All Taskwarrior commands may be abbreviated as long as a unique prefix is used, All Taskwarrior commands may be abbreviated as long as a unique prefix is used,
for example: for example:
.RS .nf
$ task li $ task li
.RE .fi
is an unambiguous abbreviation for is an unambiguous abbreviation for
.RS .nf
$ task list $ task list
.RE .fi
but but
.RS .nf
$ task l $ task l
.RE .fi
could be list, ls or long. could be list, ls or long.
Note that you can restrict the minimum abbreviation size using the configuration Note that you can restrict the minimum abbreviation size using the configuration
setting: setting:
.RS .nf
abbreviation.minimum=3 abbreviation.minimum=3
.RE .fi
.SH SPECIFYING DESCRIPTIONS .SH SPECIFYING DESCRIPTIONS
Some task descriptions need to be escaped because of the shell and the special 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 meaning of some characters to the shell. This can be done either by adding
quotes to the description or escaping the special character: quotes to the description or escaping the special character:
.RS .nf
$ task add "quoted ' quote" $ task add "quoted ' quote"
.br $ task add escaped \\' quote
$ task add escaped \\' quote .fi
.RE
The argument \-\- (a double dash) tells Taskwarrior to treat all other args The argument \-\- (a double dash) tells Taskwarrior to treat all other args
as description: as description:
.RS .nf
$ task add -- project:Home needs scheduling $ task add -- project:Home needs scheduling
.RE .fi
In other situations, the shell sees spaces and breaks up arguments. For In other situations, the shell sees spaces and breaks up arguments. For
example, this command: example, this command:
.RS .nf
$ task 123 modify /from this/to that/ $ task 123 modify /from this/to that/
.RE .fi
is broken up into several arguments, which is corrected with quotes: is broken up into several arguments, which is corrected with quotes:
.RS .nf
$ task 123 modify "/from this/to that/" $ task 123 modify "/from this/to that/"
.RE .fi
It is sometimes necessary to force the shell to pass quotes to Taskwarrior It is sometimes necessary to force the shell to pass quotes to Taskwarrior
intact, so you can use: intact, so you can use:
.RS .nf
$ task add project:\\'Three Word Project\\' description $ task add project:\\'Three Word Project\\' description
.RE .fi
Taskwarrior supports Unicode using only the UTF8 encoding, with no Byte Order Taskwarrior supports Unicode using only the UTF8 encoding.
Marks in the data files.
.SH CONFIGURATION FILE AND OVERRIDE OPTIONS .SH CONFIGURATION FILE AND OVERRIDE OPTIONS
Taskwarrior stores its configuration in a file in the user's home directory: 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 .TP
~/.task ~/.task
The default directory where task stores its data files. The location The default directory where task stores its data. The location can be
can be configured in the configuration variable 'data.location', or configured in the configuration variable 'data.location', or overridden with
overridden with the TASKDATA environment variable.. the TASKDATA environment variable.
.TP .TP
~/.task/pending.data ~/.task/taskchampion.sqlite3
The file that contains the tasks that are not yet done. The database file.
.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.
.SH "CREDITS & COPYRIGHTS" .SH "CREDITS & COPYRIGHTS"
Copyright (C) 2006 \- 2021 T. Babej, P. Beckingham, F. Hernandez. 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 .I .taskrc
\&. This file is normally located in the user's home directory: \&. This file is normally located in the user's home directory:
.RS .nf
$HOME/.taskrc $HOME/.taskrc
.RE .fi
The default location can be overridden using the The default location can be overridden using the
.I rc: .I rc:
attribute when running task: attribute when running task:
.RS .nf
$ task rc:<directory-path>/.taskrc ... $ task rc:<directory-path>/.taskrc ...
.RE .fi
or using the TASKRC environment variable: or using the TASKRC environment variable:
.RS .nf
$ TASKRC=/tmp/.taskrc task ... $ TASKRC=/tmp/.taskrc task ...
.RE .fi
Additionally, if no ~/.taskrc exists, taskwarrior will check if the XDG_CONFIG_HOME environment variable is defined: Additionally, if no ~/.taskrc exists, taskwarrior will check if the XDG_CONFIG_HOME environment variable is defined:
.RS .nf
$ XDG_CONFIG_HOME=~/.config task ... $ XDG_CONFIG_HOME=~/.config task ...
.RE .fi
Individual options can be overridden by using the Individual options can be overridden by using the
.I rc.<name>: .I rc.<name>:
attribute when running task: attribute when running task:
.RS .nf
$ task rc.<name>:<value> ... $ task rc.<name>:<value> ...
.RE .fi
or or
.RS .nf
$ task rc.<name>=<value> ... $ task rc.<name>=<value> ...
.RE .fi
If If
.B Taskwarrior .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: The .taskrc file follows a very simple syntax defining name/value pairs:
.RS .nf
<name> = <value> <name> = <value>
.RE .fi
There may be whitespace around <name>, '=' and <value>, and it is ignored. There may be whitespace around <name>, '=' and <value>, and it is ignored.
Whitespace within the <value> is left intact. 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 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. 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 .nf
include <file> include <file>
.RE .fi
There may be whitespace around 'include' and <file>. The file may be an 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 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 Note that environment variables are also expanded in paths (and any other
taskrc variables). taskrc variables).
.RS .nf
# <comment> # <comment>
.RE .fi
A comment consists of the character '#', and extends from the '#' to the end 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 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 represent overrides of the default values. To remove a default value completely
there must be an entry like this: there must be an entry like this:
.RS .nf
<name> = <name> =
.RE .fi
This entry overrides the default value with a blank value. 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' 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: command. To permanently set a value in your .taskrc file, use this command:
.RS .nf
$ task config nag "You have more urgent tasks." $ task config nag "You have more urgent tasks."
.RE .fi
To delete an entry, use this command: To delete an entry, use this command:
.RS .nf
$ task config nag $ task config nag
.RE .fi
Taskwarrior will then use the default value. To explicitly set a value to Taskwarrior will then use the default value. To explicitly set a value to
blank, and therefore avoid using the default value, use this command: blank, and therefore avoid using the default value, use this command:
.RS .nf
$ task config nag "" $ task config nag ""
.RE .fi
Taskwarrior will also display all your settings with this command: Taskwarrior will also display all your settings with this command:
.RS .nf
$ task show $ task show
.RE .fi
and in addition, will also perform a check of all the values in the file, and in addition, will also perform a check of all the values in the file,
warning you of anything it finds amiss. 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 .B include
statement: statement:
.RS .nf
include <path/to/the/configuration/file/to/be/included> include <path/to/the/configuration/file/to/be/included>
.RE .fi
By using include files you can divide your main configuration file into several By using include files you can divide your main configuration file into several
ones containing just the relevant configuration data like colors, etc. ones containing just the relevant configuration data like colors, etc.
There are two excellent uses of includes in your .taskrc, shown here: There are two excellent uses of includes in your .taskrc, shown here:
.RS .nf
include holidays.en-US.rc include holidays.en-US.rc
.br include dark-16.theme
include dark-16.theme .fi
.RE
This includes two standard files that are distributed with Taskwarrior, which 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 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 .TP
.B TASKDATA=~/.task .B TASKDATA=~/.task
This overrides the default path for the Taskwarrior data files. This overrides the default path for the Taskwarrior data.
.TP .TP
.B TASKRC=~/.taskrc .B TASKRC=~/.taskrc
@@ -197,7 +196,7 @@ Valid variable names and their default values are:
.TP .TP
.B data.location=$HOME/.task .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 default, it is set up to be ~/.task, for example: /home/paul/.task
Note that you can use the Note that you can use the
@@ -211,19 +210,17 @@ Note that the TASKDATA environment variable overrides this setting.
This is a path to the hook scripts directory. By default it is ~/.task/hooks. This is a path to the hook scripts directory. By default it is ~/.task/hooks.
.TP .TP
.B locking=1 .B gc=1
Determines whether to use file locking when accessing the pending.data and Can be used to temporarily suspend rebuilding, so that task IDs don't change.
completed.data files. Defaults to "1". Solaris users who store the data Note that this should be used in the form of a command line override (task
files on an NFS mount may need to set locking to "0". Note that there is rc.gc=0 ...), and not permanently used in the .taskrc file, as this
danger in setting this value to "0" - another program (or another instance of significantly affects performance in the long term.
task) may write to the task.pending file at the same time.
.TP .TP
.B gc=1 .B purge.on-sync=0
Can be used to temporarily suspend garbage collection (gc), so that task IDs If set, old tasks will be purged automatically after each synchronization.
don't change. Note that this should be used in the form of a command line Tasks are identified as "old" when they have status "Deleted" and have not
override (task rc.gc=0 ...), and not permanently used in the .taskrc file, been modified for 180 days.
as this significantly affects performance in the long term.
.TP .TP
.B hooks=1 .B hooks=1
@@ -241,6 +238,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 Determines whether to use ioctl to establish the size of the window you are
using, for text wrapping. 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 .TP
.B defaultwidth=80 .B defaultwidth=80
The width of output used when auto-detection support is not available. Defaults The width of output used when auto-detection support is not available. Defaults
@@ -292,12 +296,14 @@ is most readily parsed and used by shell scripts.
Alternatively, you can specify a comma-separated list of verbosity tokens that Alternatively, you can specify a comma-separated list of verbosity tokens that
control specific occasions when output is generated. This list may contain: control specific occasions when output is generated. This list may contain:
.nf
blank Inserts extra blank lines in output, for clarity 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) 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) footnote Messages that appear after report output (mostly status messages and change descriptions)
label Column labels on tabular reports 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-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. 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 affected Reports 'N tasks affected' and similar
edit Used the verbose template for the 'edit' command edit Used the verbose template for the 'edit' command
special Feedback when applying special tags special Feedback when applying special tags
@@ -308,6 +314,7 @@ control specific occasions when output is generated. This list may contain:
override Notification when configuration options are overridden override Notification when configuration options are overridden
recur Notification when a new recurring task instance is created recur Notification when a new recurring task instance is created
default Notifications about taskwarrior choosing to perform a default action. default Notifications about taskwarrior choosing to perform a default action.
.fi
The tokens "affected", "new-id", "new-uuid", "project", "override" and "recur" The tokens "affected", "new-id", "new-uuid", "project", "override" and "recur"
imply "footnote". imply "footnote".
@@ -319,14 +326,20 @@ and the "nothing" setting is equivalent to none of the tokens being specified.
Here are the shortcut equivalents: Here are the shortcut equivalents:
.nf
verbose=on 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=0
verbose=blank,label,new-id,edit verbose=blank,label,new-id,edit
.fi
.nf
verbose=nothing verbose=nothing
verbose= verbose=
.fi
Those additional comments are sent to the standard error for header, footnote Those additional comments are sent to the standard error for header, footnote
and project. The others are sent to standard output. and project. The others are sent to standard output.
@@ -495,13 +508,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, is set to 2, then a report will list 2 pending recurring tasks, one for tomorrow,
and one for a week from 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 .TP
.B abbreviation.minimum=2 .B abbreviation.minimum=2
Minimum length of any abbreviated command/value. This means that "ve", "ver", Minimum length of any abbreviated command/value. This means that "ve", "ver",
@@ -577,51 +583,29 @@ are formatted according to dateformat.
The default value is the ISO-8601 standard: Y-M-D. The string can contain the The default value is the ISO-8601 standard: Y-M-D. The string can contain the
characters: characters:
.RS .nf
.RS m minimal-digit month, for example 1 or 12
m minimal-digit month, for example 1 or 12 d minimal-digit day, for example 1 or 30
.br y two-digit year, for example 09 or 12
d minimal-digit day, for example 1 or 30 D two-digit day, for example 01 or 30
.br M two-digit month, for example 01 or 12
y two-digit year, for example 09 or 12 Y four-digit year, for example 2009 or 2015
.br a short name of weekday, for example Mon or Wed
D two-digit day, for example 01 or 30 A long name of weekday, for example Monday or Wednesday
.br b short name of month, for example Jan or Aug
M two-digit month, for example 01 or 12 B long name of month, for example January or August
.br v minimal-digit week, for example 3 or 37
Y four-digit year, for example 2009 or 2015 V two-digit week, for example 03 or 37
.br h minimal-digit hour, for example 3 or 21
a short name of weekday, for example Mon or Wed n minimal-digit minutes, for example 5 or 42
.br s minimal-digit seconds, for example 7 or 47
A long name of weekday, for example Monday or Wednesday H two-digit hour, for example 03 or 21
.br N two-digit minutes, for example 05 or 42
b short name of month, for example Jan or Aug S two-digit seconds, for example 07 or 47
.br J three-digit Julian day, for example 023 or 365
B long name of month, for example January or August j Julian day, for example 23 or 365
.br w Week day, for example 0 for Monday, 5 for Friday
v minimal-digit week, for example 3 or 37 .fi
.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
.RS .RS
The characters 'v', 'V', 'a' and 'A' can only be used for formatting printed The characters 'v', 'V', 'a' and 'A' can only be used for formatting printed
@@ -633,37 +617,24 @@ The string may also contain other characters to act as spacers, or formatting.
Examples for other values of dateformat: Examples for other values of dateformat:
.RE .RE
.RS .nf
.RS d/m/Y would use for input and output 24/7/2009
.br yMD would use for input and output 090724
d/m/Y would use for input and output 24/7/2009 M-D-Y would use for input and output 07-24-2009
.br .fi
yMD would use for input and output 090724
.br
M-D-Y would use for input and output 07-24-2009
.RE
.RE
.RS .RS
Examples for other values of dateformat.report: Examples for other values of dateformat.report:
.RE .RE
.RS .nf
.RS a D b Y (V) would emit "Fri 24 Jul 2009 (30)"
.br A, B D, Y would emit "Friday, July 24, 2009"
a D b Y (V) would emit "Fri 24 Jul 2009 (30)" wV a Y-M-D would emit "w30 Fri 2009-07-24"
.br yMD.HN would emit "110124.2342"
A, B D, Y would emit "Friday, July 24, 2009" 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"
wV a Y-M-D would emit "w30 Fri 2009-07-24" .fi
.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
.RS .RS
Undefined fields are put to their minimal valid values (1 for month and day and Undefined fields are put to their minimal valid values (1 for month and day and
@@ -672,14 +643,10 @@ field that is set. Otherwise, they are set to the corresponding values of
"now". For example: "now". For example:
.RE .RE
.RS .nf
.RS 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
8/1/2013 with m/d/Y implies August 1, 2013 at midnight (inferred) .fi
.br
8/1 20:40 with m/d H:N implies August 1, 2013 (inferred) at 20:40
.RE
.RE
.TP .TP
.B date.iso=1 .B date.iso=1
@@ -773,28 +740,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 that is specified in .taskrc. For single-day holidays the name and the date is
required to be given: required to be given:
.RS .nf
.RS holiday.towel.name=Day of the towel
.br holiday.towel.date=20100525
holiday.towel.name=Day of the towel .fi
.br
holiday.towel.date=20100525
.RE
.RE
For holidays that span a range of days (i.e. vacation), you can use a start date For holidays that span a range of days (i.e. vacation), you can use a start date
and an end date: and an end date:
.RS .nf
.RS holiday.sysadmin.name=System Administrator Appreciation Week
.br holiday.sysadmin.start=20100730
holiday.sysadmin.name=System Administrator Appreciation Week holiday.sysadmin.end=20100805
.br .fi
holiday.sysadmin.start=20100730
.br
holiday.sysadmin.end=20100805
.RE
.RE
.RS .RS
Dates are to be entered according to the setting in the dateformat.holiday Dates are to be entered according to the setting in the dateformat.holiday
@@ -807,24 +765,17 @@ Easter (easter), Easter Monday (eastermonday), Ascension (ascension), Pentecost
(pentecost). The date for these holidays is the given keyword: (pentecost). The date for these holidays is the given keyword:
.RE .RE
.RS .nf
.RS holiday.eastersunday.name=Easter
.br holiday.eastersunday.date=easter
holiday.eastersunday.name=Easter .fi
.br
holiday.eastersunday.date=easter
.RE
.RE
Note that the Taskwarrior distribution contains example holiday files that can Note that the Taskwarrior distribution contains example holiday files that can
be included like this: be included like this:
.RS .nf
.RS include holidays.en-US.rc
.br .fi
include holidays.en-US.rc
.RE
.RE
.SS DEPENDENCIES .SS DEPENDENCIES
@@ -912,10 +863,9 @@ Task is deleted.
.RS .RS
To disable a coloration rule for which there is a default, set the value to To disable a coloration rule for which there is a default, set the value to
nothing, for example: nothing, for example:
.RS .nf
.B color.tagged= color.tagged=
.RE .fi
.RE
.RS .RS
By default, colors produced by rules blend. This has the advantage of By default, colors produced by rules blend. This has the advantage of
@@ -1092,6 +1042,9 @@ yellow bars.
.RS .RS
Colors used by the undo command, to indicate the values both before and after Colors used by the undo command, to indicate the values both before and after
a change that is to be reverted. a change that is to be reverted.
Currently not supported.
.RE .RE
.TP .TP
@@ -1250,30 +1203,23 @@ default.command=next
Provides a default command that is run every time Taskwarrior is invoked with no Provides a default command that is run every time Taskwarrior is invoked with no
arguments. For example, if set to: arguments. For example, if set to:
.RS .nf
.RS default.command=project:foo list
default.command=project:foo list .fi
.RE
.RE
.RS .RS
then Taskwarrior will run the "project:foo list" command if no command is then Taskwarrior will run the "project:foo list" command if no command is
specified. This means that by merely typing specified. This means that by merely typing
.RE .RE
.RS .nf
.RS $ task
$ task [task project:foo list]
.br
[task project:foo list] ID Project Pri Description
.br 1 foo H Design foo
\& 2 foo Build foo
.br .fi
ID Project Pri Description
1 foo H Design foo
2 foo Build foo
.RE
.RE
.SS REPORTS .SS REPORTS
@@ -1312,12 +1258,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. or a "-" for descending sort order. The sort IDs are separated by commas.
For example: For example:
.nf
report.list.sort=due+,priority-,start.active-,project+ report.list.sort=due+,priority-,start.active-,project+
.fi
Additionally, after the "+" or "-", there can be a solidus "/" which indicates Additionally, after the "+" or "-", there can be a solidus "/" which indicates
that there are breaks after the column values change. For example: that there are breaks after the column values change. For example:
.nf
report.minimal.sort=project+/,description+ report.minimal.sort=project+/,description+
.fi
This sort order now specifies that there is a listing break between each 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 project. A listing break is simply a blank line, which provides a visual

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.due.today=bold white on red
color.calendar.holiday=black on bright yellow color.calendar.holiday=black on bright yellow
color.calendar.overdue=black on bright red color.calendar.overdue=black on bright red
color.calendar.scheduled=
color.calendar.today=bold white on bright blue color.calendar.today=bold white on bright blue
color.calendar.weekend=white on bright black color.calendar.weekend=white on bright black
color.calendar.weeknumber=bold blue color.calendar.weeknumber=bold blue
@@ -98,4 +99,3 @@ color.sync.rejected=red
# Command: undo # Command: undo
color.undo.after=green color.undo.after=green
color.undo.before=red 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.today=color15 on color1
color.calendar.due=color0 on color1 color.calendar.due=color0 on color1
color.calendar.holiday=color0 on color11 color.calendar.holiday=color0 on color11
color.calendar.scheduled=
color.calendar.overdue=color0 on color9 color.calendar.overdue=color0 on color9
color.calendar.today=color15 on rgb013 color.calendar.today=color15 on rgb013
color.calendar.weekend=on color235 color.calendar.weekend=on color235
@@ -95,4 +96,3 @@ color.sync.rejected=color9
# Command: undo # Command: undo
color.undo.after=color2 color.undo.after=color2
color.undo.before=color1 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.due=color0 on color249
color.calendar.holiday=color255 on rgb013 color.calendar.holiday=color255 on rgb013
color.calendar.overdue=color0 on color255 color.calendar.overdue=color0 on color255
color.calendar.scheduled=
color.calendar.today=color0 on rgb115 color.calendar.today=color0 on rgb115
color.calendar.weekend=on color235 color.calendar.weekend=on color235
color.calendar.weeknumber=rgb015 color.calendar.weeknumber=rgb015
@@ -95,4 +96,3 @@ color.sync.rejected=rgb004
# Command: undo # Command: undo
color.undo.after=rgb035 color.undo.after=rgb035
color.undo.before=rgb013 color.undo.before=rgb013

View File

@@ -83,6 +83,7 @@ color.calendar.due=on gray8
color.calendar.due.today=black on gray15 color.calendar.due.today=black on gray15
color.calendar.holiday=black on gray20 color.calendar.holiday=black on gray20
color.calendar.overdue=gray2 on gray10 color.calendar.overdue=gray2 on gray10
color.calendar.scheduled=
color.calendar.today=bold white color.calendar.today=bold white
color.calendar.weekend=on gray2 color.calendar.weekend=on gray2
color.calendar.weeknumber=gray6 color.calendar.weeknumber=gray6
@@ -95,4 +96,3 @@ color.sync.rejected=gray5 on gray23
# Command: undo # Command: undo
color.undo.before=white on black color.undo.before=white on black
color.undo.after=black on white 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.due.today=color0 on gray15
color.calendar.holiday=color15 on rgb005 color.calendar.holiday=color15 on rgb005
color.calendar.overdue=color0 on gray20 color.calendar.overdue=color0 on gray20
color.calendar.scheduled=
color.calendar.today=underline black on color15 color.calendar.today=underline black on color15
color.calendar.weekend=on gray4 color.calendar.weekend=on gray4
color.calendar.weeknumber=gray10 color.calendar.weeknumber=gray10
@@ -95,4 +96,3 @@ color.sync.rejected=gray23
# Command: undo # Command: undo
color.undo.before=rgb013 color.undo.before=rgb013
color.undo.after=rgb035 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.due=color0 on color249
color.calendar.holiday=rgb151 on rgb020 color.calendar.holiday=rgb151 on rgb020
color.calendar.overdue=color0 on color255 color.calendar.overdue=color0 on color255
color.calendar.scheduled=
color.calendar.today=color0 on rgb151 color.calendar.today=color0 on rgb151
color.calendar.weekend=on color235 color.calendar.weekend=on color235
color.calendar.weeknumber=rgb010 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.due=color0 on color249
color.calendar.holiday=rgb522 on rgb300 color.calendar.holiday=rgb522 on rgb300
color.calendar.overdue=color0 on color255 color.calendar.overdue=color0 on color255
color.calendar.scheduled=
color.calendar.today=color0 on rgb511 color.calendar.today=color0 on rgb511
color.calendar.weekend=on color235 color.calendar.weekend=on color235
color.calendar.weeknumber=rgb100 color.calendar.weeknumber=rgb100
@@ -95,4 +96,3 @@ color.sync.rejected=rgb200
# Command: undo # Command: undo
color.undo.after=rgb511 color.undo.after=rgb511
color.undo.before=rgb200 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.due.today=color0 on rgb404
color.calendar.holiday=color15 on rgb102 color.calendar.holiday=color15 on rgb102
color.calendar.overdue=color0 on color5 color.calendar.overdue=color0 on color5
color.calendar.scheduled=
color.calendar.today=color15 on rgb103 color.calendar.today=color15 on rgb103
color.calendar.weekend=gray12 on gray3 color.calendar.weekend=gray12 on gray3
color.calendar.weeknumber=rgb104 color.calendar.weeknumber=rgb104
@@ -95,4 +96,3 @@ color.sync.rejected=rgb103
# Command: undo # Command: undo
color.undo.before=rgb103 color.undo.before=rgb103
color.undo.after=rgb305 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.due.today=color0 on rgb430
color.calendar.holiday=rgb151 on rgb020 color.calendar.holiday=rgb151 on rgb020
color.calendar.overdue=color0 on rgb420 color.calendar.overdue=color0 on rgb420
color.calendar.scheduled=
color.calendar.today=color15 on rgb110 color.calendar.today=color15 on rgb110
color.calendar.weekend=on color235 color.calendar.weekend=on color235
color.calendar.weeknumber=rgb110 color.calendar.weeknumber=rgb110
@@ -95,4 +96,3 @@ color.sync.rejected=rgb110
# Command: undo # Command: undo
color.undo.before=rgb021 color.undo.before=rgb021
color.undo.after=rgb042 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.due.today=blue on bright yellow
color.calendar.holiday=on yellow color.calendar.holiday=on yellow
color.calendar.overdue=on bright red color.calendar.overdue=on bright red
color.calendar.scheduled=
color.calendar.today=blue color.calendar.today=blue
color.calendar.weekend=on white color.calendar.weekend=on white
color.calendar.weeknumber=blue color.calendar.weeknumber=blue
@@ -95,4 +96,3 @@ color.sync.rejected=red
# Command: undo # Command: undo
color.undo.before=yellow color.undo.before=yellow
color.undo.after=green color.undo.after=green

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,17 +1,17 @@
version: '3' version: '3'
services: services:
test-fedora38: test-fedora40:
build: build:
context: . context: .
dockerfile: test/docker/fedora38 dockerfile: test/docker/fedora40
network_mode: "host" network_mode: "host"
security_opt: security_opt:
- label=type:container_runtime_t - label=type:container_runtime_t
tty: true tty: true
test-fedora39: test-fedora41:
build: build:
context: . context: .
dockerfile: test/docker/fedora39 dockerfile: test/docker/fedora41
network_mode: "host" network_mode: "host"
security_opt: security_opt:
- label=type:container_runtime_t - 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 Note that for the solarized themes, the terminal color palette needs to be set
to specific colors. 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 14 mod depends:13
task rc:x 15 delete 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) cmake_minimum_required (VERSION 3.22)
add_custom_target (performance ./run_perf configure_file(compare_runs.py compare_runs.py COPYONLY)
DEPENDS task_executable configure_file(load load)
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/performance) 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] = [] tests[command] = []
# Parse concatenated run_perf output # Parse concatenated run_perf output
for i in re.findall("^ - task %s\.\.\.\n" for i in re.findall(
"Perf task ([^ ]+) ([^ ]+) ([^ ]+) (.+)$" "^ - task %s\\.\\.\\.\n"
% command, input, re.MULTILINE): "Perf task ([^ ]+) ([^ ]+) ([^ ]+) (.+)$" % command,
info = i[0:3] + ({k:v for k, v in (i.split(":") for i in i[-1].split())},) input,
re.MULTILINE,
):
info = i[0:3] + ({k: v for k, v in (i.split(":") for i in i[-1].split())},)
pt = TaskPerf(*info) pt = TaskPerf(*info)
tests[command].append(pt) tests[command].append(pt)
return tests return tests
@@ -61,8 +64,14 @@ with open(sys.argv[2], "r") as fh:
tests_cur = parse_perf(fh.read()) tests_cur = parse_perf(fh.read())
best_cur = get_best(tests_cur) best_cur = get_best(tests_cur)
print("Previous: %s (%s)" % (tests_prev[COMMANDS[0]][0].version, tests_prev[COMMANDS[0]][0].commit)) print(
print("Current: %s (%s)" % (tests_cur[COMMANDS[0]][0].version, tests_cur[COMMANDS[0]][0].commit)) "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: for test in COMMANDS:
print("# %s:" % test) print("# %s:" % test)
@@ -76,7 +85,9 @@ for test in COMMANDS:
else: else:
percentage = "0%" 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[0] += " %s" % k.rjust(pad)
out[1] += " %s" % best_prev[test][k].rjust(pad) out[1] += " %s" % best_prev[test][k].rjust(pad)
out[2] += " %s" % best_cur[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; close $fh;
} }
my $filename = 'sample-text.txt'; my $filename = '${CMAKE_SOURCE_DIR}/performance/sample-text.txt';
open(my $fh, '<:encoding(UTF-8)', $filename) open(my $fh, '<:encoding(UTF-8)', $filename)
or die "Could not open file '$filename' $!"; or die "Could not open file '$filename' $!";
@@ -31,18 +31,18 @@ while (my $line = <$fh>)
if ($. % 20 == 19) if ($. % 20 == 19)
{ {
my $anno_id = $id - 1; 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 $?; print "[$.] task rc:perf.rc rc.gc=off $anno_id annotate $line\n" if $?;
} }
elsif ($. % 4 == 1) 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 $?; print "[$.] task rc:perf.rc rc.gc=off add $line\n" if $?;
++$id; ++$id;
} }
else 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 $?; print "[$.] task rc:perf.rc rc.gc=off log $line\n" if $?;
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,9 +22,8 @@ SHADOW_FILE=$(task _get rc.shadow.file)
# rc.detection=off Disables terminal size detection # rc.detection=off Disables terminal size detection
# rc.gc=off Disables GC, thus not changing IDs unexpectedly # rc.gc=off Disables GC, thus not changing IDs unexpectedly
# rc.color=off Disable color in the shadow file # 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 # 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 ]] if [[ $? != 0 ]]
then then
echo Could not create $SHADOW_FILE echo Could not create $SHADOW_FILE
@@ -33,4 +32,3 @@ fi
echo Shadow file $SHADOW_FILE updated. echo Shadow file $SHADOW_FILE updated.
exit 0 exit 0

View File

@@ -14,4 +14,3 @@ echo 'on-launch'
# - 0: JSON ignored, non-JSON is feedback. # - 0: JSON ignored, non-JSON is feedback.
# - non-0: JSON ignored, non-JSON is error. # - non-0: JSON ignored, non-JSON is error.
exit 0 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

@@ -132,6 +132,7 @@ syn match taskrcGoodKey '^\s*\Vexpressions='he=e-1
syn match taskrcGoodKey '^\s*\Vextensions='he=e-1 syn match taskrcGoodKey '^\s*\Vextensions='he=e-1
syn match taskrcGoodKey '^\s*\Vfontunderline='he=e-1 syn match taskrcGoodKey '^\s*\Vfontunderline='he=e-1
syn match taskrcGoodKey '^\s*\Vgc='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='he=e-1
syn match taskrcGoodKey '^\s*\Vhooks.location='he=e-1 syn match taskrcGoodKey '^\s*\Vhooks.location='he=e-1
syn match taskrcGoodKey '^\s*\Vhyphenate='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.start.annotation='he=e-1
syn match taskrcGoodKey '^\s*\Vjournal.time.stop.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*\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.projects='he=e-1
syn match taskrcGoodKey '^\s*\Vlist.all.tags='he=e-1 syn match taskrcGoodKey '^\s*\Vlist.all.tags='he=e-1
syn match taskrcGoodKey '^\s*\Vlocale='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*\Vsearch.case.sensitive='he=e-1
syn match taskrcGoodKey '^\s*\Vsummary.all.projects='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*\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*\Vtag.indicator='he=e-1
syn match taskrcGoodKey '^\s*\Vuda.\S\{-}.\(default\|type\|label\|values\|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.active.coefficient='he=e-1
syn match taskrcGoodKey '^\s*\Vurgency.age.coefficient='he=e-1 syn match taskrcGoodKey '^\s*\Vurgency.age.coefficient='he=e-1
syn match taskrcGoodKey '^\s*\Vurgency.age.max='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' local word=$'[^\0]#\0'
# projects # projects
local _task_projects=($(task _projects)) local _task_projects=($(task rc.hooks=0 _projects))
local task_projects=( local task_projects=(
/"$word"/ /"$word"/
":values:task projects:compadd -a _task_projects" ":values:task projects:compadd -a _task_projects"
@@ -157,7 +157,7 @@ _task_filter() {
local uda_name uda_label uda_values local uda_name uda_label uda_values
local -a udas_spec local -a udas_spec
task _udas | while read uda_name; do 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 # 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 # but that can become extremly slow with a lot of udas
#uda_values=(${(@s:,:)"$(task _get rc.uda."$uda_name".values)"}) #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[@]}" _regex_words -t ':' default 'task attributes' "${_task_all_attributes[@]}"
local task_attributes=("$reply[@]") local task_attributes=("$reply[@]")
local _task_tags=($(task _tags)) local _task_tags=($(task rc.hooks=0 _tags))
local _task_config=($(task _config)) local _task_config=($(task rc.hooks=0 _config))
local _task_modifiers=( local _task_modifiers=(
'before' 'before'
'after' 'after'
@@ -213,12 +213,12 @@ _task_filter() {
# id-only completion # id-only completion
(( $+functions[_task_ids] )) || (( $+functions[_task_ids] )) ||
_task_ids() { _task_ids() {
local _ids=( ${(f)"$(task _zshids)"} ) local _ids=( ${(f)"$(task rc.hooks=0 _zshids)"} )
_describe 'task ids' _ids _describe 'task ids' _ids
} }
(( $+functions[_task_aliases] )) || (( $+functions[_task_aliases] )) ||
_task_aliases() { _task_aliases() {
local _aliases=( ${(f)"$(task _aliases)"} ) local _aliases=( ${(f)"$(task rc.hooks=0 _aliases)"} )
_describe 'task aliases' _aliases _describe 'task aliases' _aliases
} }
@@ -230,7 +230,7 @@ _task_subcommands() {
local cmd category desc local cmd category desc
local lastcategory='' local lastcategory=''
# The list is sorted by category, in the right order. # 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 for _zshcmd in "$_task_zshcmds[@]"; do
# Parse out the three fields # Parse out the three fields
cmd=${_zshcmd%%:*} cmd=${_zshcmd%%:*}
@@ -254,7 +254,7 @@ _task_subcommands() {
## contexts ## contexts
(( $+functions[_task_context] )) || (( $+functions[_task_context] )) ||
_task_context() { _task_context() {
local _contexts=(${(f)"$(task _context)"}) local _contexts=(${(f)"$(task rc.hooks=0 _context)"})
_describe 'task contexts' _contexts _describe 'task contexts' _contexts
} }
@@ -264,7 +264,7 @@ _task_default() {
local cmd ret=1 local cmd ret=1
integer i=1 integer i=1
local _task_cmds=($(task _commands; task _aliases)) local _task_cmds=($(task rc.hooks=0 _commands; task _aliases))
while (( i < $#words )) while (( i < $#words ))
do do
cmd="${_task_cmds[(r)$words[$i]]}" cmd="${_task_cmds[(r)$words[$i]]}"

5
src/.gitignore vendored
View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,11 +1,9 @@
cmake_minimum_required (VERSION 3.22) cmake_minimum_required (VERSION 3.22)
include_directories (${CMAKE_SOURCE_DIR} include_directories (${CMAKE_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/src/tc
${CMAKE_SOURCE_DIR}/src/commands ${CMAKE_SOURCE_DIR}/src/commands
${CMAKE_SOURCE_DIR}/src/columns ${CMAKE_SOURCE_DIR}/src/columns
${CMAKE_SOURCE_DIR}/src/libshared/src ${CMAKE_SOURCE_DIR}/src/libshared/src
${CMAKE_SOURCE_DIR}/taskchampion/lib
${TASK_INCLUDE_DIRS}) ${TASK_INCLUDE_DIRS})
add_library (task STATIC CLI2.cpp CLI2.h add_library (task STATIC CLI2.cpp CLI2.h
@@ -15,9 +13,12 @@ add_library (task STATIC CLI2.cpp CLI2.h
Filter.cpp Filter.h Filter.cpp Filter.h
Hooks.cpp Hooks.h Hooks.cpp Hooks.h
Lexer.cpp Lexer.h Lexer.cpp Lexer.h
Operation.cpp Operation.h
TF2.cpp TF2.h
TDB2.cpp TDB2.h TDB2.cpp TDB2.h
Task.cpp Task.h Task.cpp Task.h
Variant.cpp Variant.h Variant.cpp Variant.h
Version.cpp Version.h
ViewTask.cpp ViewTask.h ViewTask.cpp ViewTask.h
dependency.cpp dependency.cpp
feedback.cpp feedback.cpp
@@ -27,6 +28,7 @@ add_library (task STATIC CLI2.cpp CLI2.h
rules.cpp rules.cpp
sort.cpp sort.cpp
util.cpp util.h) util.cpp util.h)
target_link_libraries(task taskchampion-cpp)
add_library (libshared STATIC libshared/src/Color.cpp libshared/src/Color.h add_library (libshared STATIC libshared/src/Color.cpp libshared/src/Color.h
libshared/src/Configuration.cpp libshared/src/Configuration.h libshared/src/Configuration.cpp libshared/src/Configuration.h
@@ -51,10 +53,9 @@ add_executable (calc_executable calc.cpp)
add_executable (lex_executable lex.cpp) add_executable (lex_executable lex.cpp)
# Yes, 'task' (and hence libshared) is included twice, otherwise linking fails on assorted OSes. # Yes, 'task' (and hence libshared) is included twice, otherwise linking fails on assorted OSes.
# Similarly for `tc`. target_link_libraries (task_executable task commands columns libshared task libshared ${TASK_LIBRARIES})
target_link_libraries (task_executable task tc commands tc columns libshared task libshared ${TASK_LIBRARIES}) target_link_libraries (calc_executable task commands 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 commands columns libshared task libshared ${TASK_LIBRARIES})
target_link_libraries (lex_executable task tc commands tc columns libshared task libshared ${TASK_LIBRARIES})
if (DARWIN) if (DARWIN)
# SystemConfiguration is required by Rust libraries like reqwest, to get proxy configuration. # SystemConfiguration is required by Rust libraries like reqwest, to get proxy configuration.
target_link_libraries (task_executable "-framework CoreFoundation -framework Security -framework SystemConfiguration") 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 #ifndef INCLUDED_CONTEXT
#define 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 <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 <Timer.h>
#include <set> #include <set>
class CurrentTask; class CurrentTask;
class Context class Context {
{ public:
public: Context() = default; // Default constructor
Context () = default; // Default constructor ~Context(); // Destructor
~Context (); // Destructor
Context (const Context&); Context(const Context &);
Context& operator= (const Context&); Context &operator=(const Context &);
static Context& getContext (); static Context &getContext();
static void setContext (Context*); static void setContext(Context *);
int initialize (int, const char**); // all startup int initialize(int, const char **); // all startup
int run (); int run();
int dispatch (std::string&); // command handler dispatch int dispatch(std::string &); // command handler dispatch
int getWidth (); // determine terminal width int getWidth(); // determine terminal width
int getHeight (); // determine terminal height 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; const std::vector<std::string> getColumns() const;
void getLimits (int&, int&); void getLimits(int &, int &);
bool color (); // TTY or <other>? bool color(); // TTY or <other>?
bool verbose (const std::string&); // Verbosity control bool verbose(const std::string &); // Verbosity control
void header (const std::string&); // Header message sink void header(const std::string &); // Header message sink
void footnote (const std::string&); // Footnote message sink void footnote(const std::string &); // Footnote message sink
void debug (const std::string&); // Debug message sink void debug(const std::string &); // Debug message sink
void error (const std::string&); // Error message sink - non-maskable void error(const std::string &); // Error message sink - non-maskable
void decomposeSortField (const std::string&, std::string&, bool&, bool&); void decomposeSortField(const std::string &, std::string &, bool &, bool &);
void debugTiming (const std::string&, const Timer&); void debugTiming(const std::string &, const Timer &);
CurrentTask withCurrentTask (const Task *); CurrentTask withCurrentTask(const Task *);
friend class CurrentTask; friend class CurrentTask;
private: private:
void staticInitialization (); void staticInitialization();
void createDefaultConfig (); void createDefaultConfig();
void updateXtermTitle (); void updateXtermTitle();
void updateVerbosity (); void updateVerbosity();
void loadAliases (); void loadAliases();
void propagateDebug (); void propagateDebug();
static Context* context; static Context *context;
public: public:
CLI2 cli2 {}; CLI2 cli2{};
std::string home_dir {}; std::string home_dir{};
File rc_file {"~/.taskrc"}; File rc_file{"~/.taskrc"};
Path data_dir {"~/.task"}; Path data_dir{"~/.task"};
Configuration config {}; Configuration config{};
TDB2 tdb2 {}; TDB2 tdb2{};
Hooks hooks {}; Hooks hooks{};
bool determine_color_use {true}; bool determine_color_use{true};
bool use_color {true}; bool use_color{true};
bool run_gc {true}; bool verbosity_legacy{false};
bool verbosity_legacy {false}; std::set<std::string> verbosity{};
std::set <std::string> verbosity {}; std::vector<std::string> headers{};
std::vector <std::string> headers {}; std::vector<std::string> footnotes{};
std::vector <std::string> footnotes {}; std::vector<std::string> errors{};
std::vector <std::string> errors {}; std::vector<std::string> debugMessages{};
std::vector <std::string> debugMessages {}; std::map<std::string, Command *> commands{};
std::map <std::string, Command*> commands {}; std::map<std::string, Column *> columns{};
std::map <std::string, Column*> columns {}; int terminal_width{0};
int terminal_width {0}; int terminal_height{0};
int terminal_height {0};
Timer timer_total {}; Timer timer_total{};
long time_total_us {0}; long time_total_us{0};
long time_init_us {0}; long time_init_us{0};
long time_load_us {0}; long time_load_us{0};
long time_gc_us {0}; long time_gc_us{0};
long time_filter_us {0}; long time_filter_us{0};
long time_commit_us {0}; long time_commit_us{0};
long time_sort_us {0}; long time_sort_us{0};
long time_render_us {0}; long time_render_us{0};
long time_hooks_us {0}; long time_hooks_us{0};
// the current task for DOM references, or NULL if there is no task // 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 // 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. // that this context value is restored when exiting the scope where the context was applied.
class CurrentTask { class CurrentTask {
public: public:
~CurrentTask(); ~CurrentTask();
private: private:
CurrentTask(Context &context, const Task *previous); CurrentTask(Context &context, const Task *previous);
Context &context; Context &context;

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -27,50 +27,51 @@
#ifndef INCLUDED_EVAL #ifndef INCLUDED_EVAL
#define INCLUDED_EVAL #define INCLUDED_EVAL
#include <vector>
#include <string>
#include <Lexer.h> #include <Lexer.h>
#include <Variant.h> #include <Variant.h>
bool domSource (const std::string&, Variant&); #include <string>
#include <vector>
class Eval bool domSource(const std::string &, Variant &);
{
public:
Eval ();
void addSource (bool (*fn)(const std::string&, Variant&)); class Eval {
void evaluateInfixExpression (const std::string&, Variant&) const; public:
void evaluatePostfixExpression (const std::string&, Variant&) const; Eval();
void compileExpression (const std::vector <std::pair <std::string, Lexer::Type>>&);
void evaluateCompiledExpression (Variant&);
void debug (bool);
static std::vector <std::string> getOperators (); void addSource(bool (*fn)(const std::string &, Variant &));
static std::vector <std::string> getBinaryOperators (); 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: static std::vector<std::string> getOperators();
void evaluatePostfixStack (const std::vector <std::pair <std::string, Lexer::Type>>&, Variant&) const; static std::vector<std::string> getBinaryOperators();
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;
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::string dump(std::vector<std::pair<std::string, Lexer::Type>> &) const;
std::vector <bool (*)(const std::string&, Variant&)> _sources {};
bool _debug {false}; private:
std::vector <std::pair <std::string, Lexer::Type>> _compiled {}; std::vector<bool (*)(const std::string &, Variant &)> _sources{};
bool _debug{false};
std::vector<std::pair<std::string, Lexer::Type>> _compiled{};
}; };
#endif #endif

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -27,95 +27,108 @@
#ifndef INCLUDED_LEXER #ifndef INCLUDED_LEXER
#define INCLUDED_LEXER #define INCLUDED_LEXER
#include <string>
#include <map>
#include <vector>
#include <cstddef> #include <cstddef>
#include <map>
#include <string>
#include <vector>
// Lexer: A UTF8 lexical analyzer for every construct used on the Taskwarrior // Lexer: A UTF8 lexical analyzer for every construct used on the Taskwarrior
// command line, with additional recognized types for disambiguation. // command line, with additional recognized types for disambiguation.
class Lexer class Lexer {
{ public:
public:
// These are overridable. // These are overridable.
static std::string dateFormat; static std::string dateFormat;
static std::string::size_type minimumMatchLength; 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, enum class Type {
string, uuid,
url, pair, set, separator, number,
tag, hex,
path, string,
substitution, pattern, url,
op, pair,
dom, identifier, word, set,
date, duration }; separator,
tag,
path,
substitution,
pattern,
op,
dom,
identifier,
word,
date,
duration
};
Lexer (const std::string&); Lexer(const std::string&);
~Lexer () = default; ~Lexer() = default;
bool token (std::string&, Lexer::Type&); bool token(std::string&, Lexer::Type&);
static std::vector <std::string> split (const std::string&); static std::vector<std::string> split(const std::string&);
static std::string typeToString (Lexer::Type); static std::string typeToString(Lexer::Type);
// Static helpers. // Static helpers.
static const std::string typeName (const Lexer::Type&); static const std::string typeName(const Lexer::Type&);
static bool isIdentifierStart (int); static bool isIdentifierStart(int);
static bool isIdentifierNext (int); static bool isIdentifierNext(int);
static bool isSingleCharOperator (int); static bool isSingleCharOperator(int);
static bool isDoubleCharOperator (int, int, int); static bool isDoubleCharOperator(int, int, int);
static bool isTripleCharOperator (int, int, int, int); static bool isTripleCharOperator(int, int, int, int);
static bool isBoundary (int, int); static bool isBoundary(int, int);
static bool isHardBoundary (int, int); static bool isHardBoundary(int, int);
static bool isPunctuation (int); static bool isPunctuation(int);
static bool isAllDigits (const std::string&); static bool isAllDigits(const std::string&);
static bool isDOM (const std::string&); static bool isDOM(const std::string&);
static void dequote (std::string&, const std::string& quotes = "'\""); static void dequote(std::string&, const std::string& quotes = "'\"");
static bool wasQuoted (const std::string&); 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&, const std::string&, std::string::size_type&,
static bool readWord (const std::string&, std::string::size_type&, std::string&); std::string&);
static bool decomposePair (const std::string&, std::string&, std::string&, std::string&, std::string&); static bool readWord(const std::string&, std::string::size_type&, std::string&);
static bool decomposeSubstitution (const std::string&, std::string&, std::string&, std::string&); static bool decomposePair(const std::string&, std::string&, std::string&, std::string&,
static bool decomposePattern (const std::string&, std::string&, std::string&); std::string&);
static int hexToInt (int); static bool decomposeSubstitution(const std::string&, std::string&, std::string&, std::string&);
static int hexToInt (int, int); static bool decomposePattern(const std::string&, std::string&, std::string&);
static int hexToInt (int, int, int, int); static int hexToInt(int);
static std::string::size_type commonLength (const std::string&, const std::string&); static int hexToInt(int, int);
static std::string::size_type commonLength (const std::string&, std::string::size_type, const std::string&, std::string::size_type); static int hexToInt(int, int, int, int);
static std::string commify (const std::string&); static std::string::size_type commonLength(const std::string&, const std::string&);
static std::string lowerCase (const std::string&); static std::string::size_type commonLength(const std::string&, std::string::size_type,
static std::string ucFirst (const std::string&); const std::string&, std::string::size_type);
static std::string trimLeft (const std::string& in, const std::string& t = " "); static std::string commify(const std::string&);
static std::string trimRight (const std::string& in, const std::string& t = " "); static std::string lowerCase(const std::string&);
static std::string trim (const std::string& in, const std::string& t = " "); 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. // Stream Classifiers.
bool isEOS () const; bool isEOS() const;
bool isString (std::string&, Lexer::Type&, const std::string&); bool isString(std::string&, Lexer::Type&, const std::string&);
bool isDate (std::string&, Lexer::Type&); bool isDate(std::string&, Lexer::Type&);
bool isDuration (std::string&, Lexer::Type&); bool isDuration(std::string&, Lexer::Type&);
bool isUUID (std::string&, Lexer::Type&, bool); bool isUUID(std::string&, Lexer::Type&, bool);
bool isNumber (std::string&, Lexer::Type&); bool isNumber(std::string&, Lexer::Type&);
bool isInteger (std::string&, Lexer::Type&); bool isInteger(std::string&, Lexer::Type&);
bool isHexNumber (std::string&, Lexer::Type&); bool isHexNumber(std::string&, Lexer::Type&);
bool isSeparator (std::string&, Lexer::Type&); bool isSeparator(std::string&, Lexer::Type&);
bool isURL (std::string&, Lexer::Type&); bool isURL(std::string&, Lexer::Type&);
bool isPair (std::string&, Lexer::Type&); bool isPair(std::string&, Lexer::Type&);
bool isSet (std::string&, Lexer::Type&); bool isSet(std::string&, Lexer::Type&);
bool isTag (std::string&, Lexer::Type&); bool isTag(std::string&, Lexer::Type&);
bool isPath (std::string&, Lexer::Type&); bool isPath(std::string&, Lexer::Type&);
bool isSubstitution (std::string&, Lexer::Type&); bool isSubstitution(std::string&, Lexer::Type&);
bool isPattern (std::string&, Lexer::Type&); bool isPattern(std::string&, Lexer::Type&);
bool isOperator (std::string&, Lexer::Type&); bool isOperator(std::string&, Lexer::Type&);
bool isDOM (std::string&, Lexer::Type&); bool isDOM(std::string&, Lexer::Type&);
bool isIdentifier (std::string&, Lexer::Type&); bool isIdentifier(std::string&, Lexer::Type&);
bool isWord (std::string&, Lexer::Type&); bool isWord(std::string&, Lexer::Type&);
bool isLiteral (const std::string&, bool, bool); bool isLiteral(const std::string&, bool, bool);
bool isOneOf (const std::vector <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 isOneOf(const std::map<std::string, std::string>&, bool, bool);
private: private:
std::string _text; std::string _text;
std::size_t _cursor; std::size_t _cursor;
std::size_t _eos; 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 // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -25,57 +25,49 @@
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
#include <cmake.h> #include <cmake.h>
#include <format.h> // cmake.h include header must come first
#include <assert.h>
#include "tc/Replica.h"
#include "tc/Task.h"
using namespace tc::ffi; #include <Operation.h>
#include <taskchampion-cpp/lib.h>
#include <vector>
namespace tc {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
TCString string2tc (const std::string& str) Operation::Operation(const tc::Operation& op) : op(&op) {}
{
return tc_string_clone_with_len (str.data (), str.size ()); ////////////////////////////////////////////////////////////////////////////////
std::vector<Operation> Operation::operations(const rust::Vec<tc::Operation>& operations) {
return {operations.begin(), operations.end()};
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
std::string tc2string_clone (const TCString& str) Operation& Operation::operator=(const Operation& other) {
{ op = other.op;
size_t len; return *this;
auto ptr = tc_string_content_with_len (&str, &len);
auto rv = std::string (ptr, len);
return rv;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
std::string tc2string (TCString& str) bool Operation::operator<(const Operation& other) const {
{ if (is_create()) {
auto rv = tc2string_clone(str); return !other.is_create();
tc_string_free (&str); } else if (is_update()) {
return rv; if (other.is_create()) {
} return false;
} else if (other.is_update()) {
//////////////////////////////////////////////////////////////////////////////// return get_timestamp() < other.get_timestamp();
TCUuid uuid2tc(const std::string& str) } else {
{ return true;
TCString tcstr = tc_string_borrow(str.c_str()); }
TCUuid rv; } else if (is_delete()) {
if (TC_RESULT_OK != tc_uuid_from_str(tcstr, &rv)) { if (other.is_create() || other.is_update() || other.is_delete()) {
throw std::string ("invalid UUID"); 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;
}
////////////////////////////////////////////////////////////////////////////////
}

88
src/Operation.h Normal file
View File

@@ -0,0 +1,88 @@
////////////////////////////////////////////////////////////////////////////////
//
// 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_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,97 +25,77 @@
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
#include <cmake.h> #include <cmake.h>
#include <TDB2.h> // cmake.h include header must come first
#include <iostream>
#include <sstream>
#include <algorithm>
#include <list>
#include <unordered_set>
#include <stdlib.h>
#include <signal.h>
#include <Context.h>
#include <Color.h> #include <Color.h>
#include <Context.h>
#include <Datetime.h> #include <Datetime.h>
#include <TDB2.h>
#include <Table.h> #include <Table.h>
#include <shared.h>
#include <format.h> #include <format.h>
#include <main.h> #include <main.h>
#include <shared.h>
#include <signal.h>
#include <stdlib.h>
#include <util.h> #include <util.h>
#include "tc/Server.h"
#include "tc/util.h" #include <algorithm>
#include <iostream>
#include <list>
#include <sstream>
#include <unordered_set>
#include <vector>
bool TDB2::debug_mode = false; bool TDB2::debug_mode = false;
static void dependency_scan (std::vector<Task> &); static void dependency_scan(std::vector<Task>&);
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
TDB2::TDB2 () void TDB2::open_replica(const std::string& location, bool create_if_missing) {
: replica {tc::Replica()} // in-memory Replica _replica = tc::new_replica_on_disk(location, create_if_missing);
, _working_set {}
{
}
////////////////////////////////////////////////////////////////////////////////
void TDB2::open_replica (const std::string& location, bool create_if_missing)
{
replica = tc::Replica(location, create_if_missing);
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Add the new task to the replica. // Add the new task to the replica.
void TDB2::add (Task& task) void TDB2::add(Task& task) {
{ // Validate a task for addition. This is stricter than `task.validate`, as any
// inconsistency is probably user error.
task.validate_add();
// Ensure the task is consistent, and provide defaults if necessary. // Ensure the task is consistent, and provide defaults if necessary.
// bool argument to validate() is "applyDefault", to apply default values for // bool argument to validate() is "applyDefault", to apply default values for
// properties not otherwise given. // properties not otherwise given.
task.validate (true); task.validate(true);
std::string uuid = task.get ("uuid"); rust::Vec<tc::Operation> ops;
auto innertask = replica.import_task_with_uuid (uuid); maybe_add_undo_point(ops);
{ auto uuid = task.get("uuid");
auto guard = replica.mutate_task(innertask); changes[uuid] = task;
tc::Uuid tcuuid = tc::uuid_from_string(uuid);
// 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));
if (id.has_value ()) {
task.id = id.value();
}
// run hooks for this new task // run hooks for this new task
Context::getContext ().hooks.onAdd (task); Context::getContext().hooks.onAdd(task);
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;
}
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@@ -133,29 +113,34 @@ void TDB2::add (Task& task)
// this method. In this case, this method throws an error that will make sense // 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 // to the user. This is especially unlikely since tasks are only deleted when
// they have been unmodified for a long time. // 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 // All locally modified tasks are timestamped, implicitly overwriting any
// changes the user or hooks tried to apply to the "modified" attribute. // changes the user or hooks tried to apply to the "modified" attribute.
task.setAsNow ("modified"); task.setAsNow("modified");
task.validate (false); task.validate(false);
auto uuid = task.get ("uuid"); auto uuid = task.get("uuid");
// invoke the hook and allow it to modify the task before updating 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
Task original; Task original;
get (uuid, original); get(uuid, original);
Context::getContext ().hooks.onModify (original, task); Context::getContext().hooks.onModify(original, task);
auto maybe_tctask = replica.get_task (uuid); tc::Uuid tcuuid = tc::uuid_from_string(uuid);
if (!maybe_tctask.has_value ()) { auto maybe_tctask = replica()->get_task_data(tcuuid);
throw std::string ("task no longer exists"); if (maybe_tctask.is_none()) {
throw std::string("task no longer exists");
} }
auto tctask = std::move (maybe_tctask.value ()); auto tctask = maybe_tctask.take();
auto guard = replica.mutate_task(tctask);
auto tctask_map = tctask.get_taskmap ();
// Perform the necessary `update` operations to set all keys in `tctask`
// equal to those in `task`.
std::unordered_set<std::string> seen; std::unordered_set<std::string> seen;
for (auto k : task.all ()) { for (auto k : task.all()) {
// ignore task keys that aren't stored // ignore task keys that aren't stored
if (k == "uuid") { if (k == "uuid") {
continue; continue;
@@ -163,221 +148,184 @@ void TDB2::modify (Task& task)
seen.insert(k); seen.insert(k);
bool update = false; bool update = false;
auto v_new = task.get(k); auto v_new = task.get(k);
try { std::string v_tctask;
auto v_tctask = tctask_map.at(k); if (tctask->get(k, v_tctask)) {
update = v_tctask != v_new; update = v_tctask != v_new;
} catch (const std::out_of_range& oor) { } else {
// tctask_map does not contain k, so update it // tctask does not contain k, so update it
update = true; update = true;
} }
if (update) { if (update) {
// An empty string indicates the value should be removed. // An empty string indicates the value should be removed.
if (v_new == "") { if (v_new == "") {
tctask.set_value(k, {}); tctask->update_remove(k, ops);
} else { } 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 // we've now added and updated properties; but must find any deleted properties
for (auto kv : tctask_map) { for (auto k : tctask->properties()) {
if (seen.find (kv.first) == seen.end ()) { auto kstr = static_cast<std::string>(k);
tctask.set_value (kv.first, {}); 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 () void TDB2::purge(Task& task) {
{ auto uuid = tc::uuid_from_string(task.get("uuid"));
if (!_working_set.has_value ()) { rust::Vec<tc::Operation> ops;
_working_set = std::make_optional (replica.working_set ()); 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() {
{ // Create a replica in-memory if `open_replica` has not been called. This
// TODO: changes in an invocation of `task` are not currently tracked, so this // occurs in tests.
// list is always empty. if (!_replica) {
_replica = tc::new_replica_in_memory();
}
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(); changes.clear();
std::transform(changes_map.begin(), changes_map.end(), std::back_inserter(changes),
[](const auto& kv) { return kv.second; });
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void TDB2::revert () void TDB2::gc() {
{
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 ()
{
Timer timer; Timer timer;
// Allowed as an override, but not recommended. // Allowed as an override, but not recommended.
if (Context::getContext ().config.getBoolean ("gc")) if (Context::getContext().config.getBoolean("gc")) {
{ replica()->rebuild_working_set(true);
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. // Latest ID is that of the last pending task.
int TDB2::latest_id () int TDB2::latest_id() {
{ auto& ws = working_set();
const tc::WorkingSet &ws = working_set (); return (int)ws->largest_index();
return (int)ws.largest_index ();
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
const std::vector <Task> TDB2::all_tasks () const std::vector<Task> TDB2::all_tasks() {
{ Timer timer;
auto all_tctasks = replica.all_tasks(); auto all_tctasks = replica()->all_task_data();
std::vector <Task> all; std::vector<Task> all;
for (auto& tctask : all_tctasks) for (auto& maybe_tctask : all_tctasks) {
all.push_back (Task (std::move (tctask))); 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; return all;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
const std::vector <Task> TDB2::pending_tasks () const std::vector<Task> TDB2::pending_tasks() {
{ if (!_pending_tasks) {
const tc::WorkingSet &ws = working_set (); Timer timer;
auto largest_index = ws.largest_index ();
std::vector <Task> result; auto pending_tctasks = replica()->pending_task_data();
for (size_t i = 0; i <= largest_index; i++) { std::vector<Task> result;
auto maybe_uuid = ws.by_index (i); for (auto& maybe_tctask : pending_tctasks) {
if (maybe_uuid.has_value ()) { auto tctask = maybe_tctask.take();
auto maybe_task = replica.get_task (maybe_uuid.value ()); result.push_back(Task(std::move(tctask)));
if (maybe_task.has_value ()) {
result.push_back (Task (std::move (maybe_task.value ())));
}
} }
dependency_scan(result);
Context::getContext().time_load_us += timer.total_us();
_pending_tasks = result;
} }
dependency_scan(result); return *_pending_tasks;
return result;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
const std::vector <Task> TDB2::completed_tasks () const std::vector<Task> TDB2::completed_tasks() {
{ if (!_completed_tasks) {
auto all_tctasks = replica.all_tasks(); auto all_tctasks = replica()->all_task_data();
const tc::WorkingSet &ws = working_set (); auto& ws = working_set();
std::vector <Task> result; std::vector<Task> result;
for (auto& tctask : all_tctasks) { for (auto& maybe_tctask : all_tctasks) {
// if this task is _not_ in the working set, return it. auto tctask = maybe_tctask.take();
if (!ws.by_uuid (tctask.get_uuid ())) { // if this task is _not_ in the working set, return it.
result.push_back (Task (std::move (tctask))); 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. // Locate task by ID, wherever it is.
bool TDB2::get (int id, Task& task) bool TDB2::get(int id, Task& task) {
{ auto& ws = working_set();
const tc::WorkingSet &ws = working_set (); const auto tcuuid = ws->by_index(id);
const auto maybe_uuid = ws.by_index (id); if (!tcuuid.is_nil()) {
if (maybe_uuid) { std::string uuid = static_cast<std::string>(tcuuid.to_string());
auto maybe_task = replica.get_task(*maybe_uuid); // Load all pending tasks in order to get dependency data, and in particular
if (maybe_task) { // `task.is_blocking` and `task.is_blocked`, set correctly.
task = Task{std::move(*maybe_task)}; std::vector<Task> pending = pending_tasks();
return true; for (auto& pending_task : pending) {
if (pending_task.get("uuid") == uuid) {
task = pending_task;
return true;
}
} }
} }
@@ -386,25 +334,25 @@ bool TDB2::get (int id, Task& task)
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Locate task by UUID, including by partial ID, wherever it is. // 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 // try by raw uuid, if the length is right
if (uuid.size () == 36) { for (auto& pending_task : pending) {
try { if (closeEnough(pending_task.get("uuid"), uuid, uuid.length())) {
auto maybe_task = replica.get_task (uuid); task = pending_task;
if (maybe_task) { return true;
task = Task{std::move (*maybe_task)};
return true;
}
} catch (const std::string &err) {
return false;
} }
} }
// Nothing to do but iterate over all tasks and check whether it's closeEnough // Nothing to do but iterate over all tasks and check whether it's closeEnough.
for (auto& tctask : replica.all_tasks ()) { for (auto& maybe_tctask : replica()->all_task_data()) {
if (closeEnough (tctask.get_uuid (), uuid, uuid.length ())) { auto tctask = maybe_tctask.take();
task = Task{std::move (tctask)}; 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; return true;
} }
} }
@@ -414,34 +362,25 @@ bool TDB2::get (const std::string& uuid, Task& task)
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Locate task by UUID, wherever it is. // Locate task by UUID, wherever it is.
bool TDB2::has (const std::string& uuid) bool TDB2::has(const std::string& uuid) {
{
Task task; Task task;
return get(uuid, task); return get(uuid, task);
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
const std::vector <Task> TDB2::siblings (Task& task) const std::vector<Task> TDB2::siblings(Task& task) {
{ std::vector<Task> results;
std::vector <Task> results; if (task.has("parent")) {
if (task.has ("parent")) std::string parent = task.get("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. // Do not include self in results.
if (i.id != task.id) if (i.id != task.id) {
{
// Do not include completed or deleted tasks. // Do not include completed or deleted tasks.
if (i.getStatus () != Task::completed && if (i.getStatus() != Task::completed && i.getStatus() != Task::deleted) {
i.getStatus () != Task::deleted)
{
// If task has the same parent, it is a sibling. // If task has the same parent, it is a sibling.
if (i.has ("parent") && if (i.has("parent") && i.get("parent") == parent) {
i.get ("parent") == parent) results.push_back(i);
{
results.push_back (i);
} }
} }
} }
@@ -452,82 +391,67 @@ 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 // scan _pending_ tasks for those with `parent` equal to this task
std::vector <Task> results; std::vector<Task> results;
std::string this_uuid = parent.get ("uuid"); std::string this_uuid = parent.get("uuid");
const tc::WorkingSet &ws = working_set (); auto& ws = working_set();
size_t end_idx = ws.largest_index (); size_t end_idx = ws->largest_index();
for (size_t i = 0; i <= end_idx; i++) { for (size_t i = 0; i <= end_idx; i++) {
auto uuid_opt = ws.by_index (i); auto uuid = ws->by_index(i);
if (!uuid_opt) { if (uuid.is_nil()) {
continue; continue;
} }
auto uuid = uuid_opt.value ();
// skip self-references // skip self-references
if (uuid == this_uuid) { if (uuid.to_string() == this_uuid) {
continue; continue;
} }
auto task_opt = replica.get_task (uuid_opt.value ()); auto task_opt = replica()->get_task_data(uuid);
if (!task_opt) { if (task_opt.is_none()) {
continue; continue;
} }
auto task = std::move (task_opt.value ()); auto task = task_opt.take();
auto parent_uuid_opt = task.get_value ("parent"); std::string parent_uuid;
if (!parent_uuid_opt) { if (!task->get("parent", parent_uuid)) {
continue; continue;
} }
auto parent_uuid = parent_uuid_opt.value ();
if (parent_uuid == this_uuid) { if (parent_uuid == this_uuid) {
results.push_back (Task (std::move (task))); results.push_back(Task(std::move(task)));
} }
} }
return results; return results;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
std::string TDB2::uuid (int id) std::string TDB2::uuid(int id) {
{ auto& ws = working_set();
const tc::WorkingSet &ws = working_set (); auto uuid = ws->by_index(id);
return ws.by_index ((size_t)id).value_or (""); if (uuid.is_nil()) {
return "";
}
return static_cast<std::string>(uuid.to_string());
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
int TDB2::id (const std::string& uuid) int TDB2::id(const std::string& uuid) {
{ auto& ws = working_set();
const tc::WorkingSet &ws = working_set (); return ws->by_uuid(tc::uuid_from_string(uuid));
return (int)ws.by_uuid (uuid).value_or (0);
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
int TDB2::num_local_changes () int TDB2::num_local_changes() { return (int)replica()->num_local_operations(); }
{
return (int)replica.num_local_operations ();
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
int TDB2::num_reverts_possible () int TDB2::num_reverts_possible() { return (int)replica()->num_undo_points(); }
{
return (int)replica.num_undo_points ();
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void TDB2::sync (tc::Server server, bool avoid_snapshots) void TDB2::dump() {
{
replica.sync(std::move(server), avoid_snapshots);
}
////////////////////////////////////////////////////////////////////////////////
void TDB2::dump ()
{
// TODO // TODO
} }
@@ -535,24 +459,16 @@ void TDB2::dump ()
// For any task that has depenencies, follow the chain of dependencies until the // 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 // end. Along the way, update the Task::is_blocked and Task::is_blocking data
// cache. // cache.
static void dependency_scan (std::vector<Task> &tasks) static void dependency_scan(std::vector<Task>& tasks) {
{ for (auto& left : tasks) {
for (auto& left : tasks) for (auto& dep : left.getDependencyUUIDs()) {
{ for (auto& right : tasks) {
for (auto& dep : left.getDependencyUUIDs ()) if (right.get("uuid") == dep) {
{
for (auto& right : tasks)
{
if (right.get ("uuid") == dep)
{
// GC hasn't run yet, check both tasks for their current status // GC hasn't run yet, check both tasks for their current status
Task::status lstatus = left.getStatus (); Task::status lstatus = left.getStatus();
Task::status rstatus = right.getStatus (); Task::status rstatus = right.getStatus();
if (lstatus != Task::completed && if (lstatus != Task::completed && lstatus != Task::deleted &&
lstatus != Task::deleted && rstatus != Task::completed && rstatus != Task::deleted) {
rstatus != Task::completed &&
rstatus != Task::deleted)
{
left.is_blocked = true; left.is_blocked = true;
right.is_blocking = true; right.is_blocking = true;
} }

View File

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

184
src/TF2.cpp Normal file
View File

@@ -0,0 +1,184 @@
////////////////////////////////////////////////////////////////////////////////
//
// 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>
#include <main.h>
#include <shared.h>
#include <signal.h>
#include <stdlib.h>
#include <util.h>
#include <algorithm>
#include <iostream>
#include <list>
#include <set>
#include <sstream>
#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

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 // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -24,28 +24,43 @@
// //
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_TC_UTIL #ifndef INCLUDED_TF2
#define INCLUDED_TC_UTIL #define INCLUDED_TF2
#include <FS.h>
#include <Task.h>
#include <stdio.h>
#include <map>
#include <string> #include <string>
#include "tc/ffi.h" #include <unordered_map>
#include <unordered_set>
#include <vector>
namespace tc { // TF2 Class represents a single 2.x-style file in the task database.
// convert a std::string into a TCString, copying the contained data //
tc::ffi::TCString string2tc(const std::string&); // 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();
// convert a TCString into a std::string, leaving the TCString as-is void target(const std::string&);
std::string tc2string_clone(const tc::ffi::TCString&);
// convert a TCString into a std::string, freeing the TCString const std::vector<std::map<std::string, std::string>>& get_tasks();
std::string tc2string(tc::ffi::TCString&);
// convert a TCUuid into a std::string std::map<std::string, std::string> load_task(const std::string&);
std::string tc2uuid(tc::ffi::TCUuid&); void load_tasks();
void load_lines();
const std::string decode(const std::string& value) const;
// parse a std::string into a TCUuid (throwing if parse fails) bool _loaded_tasks;
tc::ffi::TCUuid uuid2tc(const std::string&); bool _loaded_lines;
} std::vector<std::map<std::string, std::string>> _tasks;
std::vector<std::string> _lines;
File _file;
};
#endif #endif
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

File diff suppressed because it is too large Load Diff

View File

@@ -27,27 +27,27 @@
#ifndef INCLUDED_TASK #ifndef INCLUDED_TASK
#define INCLUDED_TASK #define INCLUDED_TASK
#include <vector> #include <Datetime.h>
#include <map>
#include <string>
#include <stdio.h>
#include <time.h>
#include <JSON.h> #include <JSON.h>
#include <Table.h> #include <Table.h>
#include <Datetime.h> #include <stdio.h>
#include <tc/Task.h> #include <taskchampion-cpp/lib.h>
#include <time.h>
class Task #include <map>
{ #include <string>
public: #include <vector>
class Task {
public:
static std::string defaultProject; static std::string defaultProject;
static std::string defaultDue; static std::string defaultDue;
static std::string defaultScheduled; static std::string defaultScheduled;
static bool searchCaseSensitive; static bool searchCaseSensitive;
static bool regex; static bool regex;
static std::map <std::string, std::string> attributes; // name -> type static std::map<std::string, std::string> attributes; // name -> type
static std::map <std::string, float> coefficients; static std::map<std::string, float> coefficients;
static std::map <std::string, std::vector <std::string>> customOrder; static std::map<std::string, std::vector<std::string>> customOrder;
static float urgencyProjectCoefficient; static float urgencyProjectCoefficient;
static float urgencyActiveCoefficient; static float urgencyActiveCoefficient;
static float urgencyScheduledCoefficient; static float urgencyScheduledCoefficient;
@@ -60,160 +60,155 @@ public:
static float urgencyAgeCoefficient; static float urgencyAgeCoefficient;
static float urgencyAgeMax; static float urgencyAgeMax;
public: public:
Task () = default; Task() = default;
bool operator== (const Task&); bool operator==(const Task&);
bool operator!= (const Task&); bool operator!=(const Task&);
Task (const std::string&); Task(const std::string&);
Task (const json::object*); Task(const json::object*);
Task (tc::Task); Task(rust::Box<tc::TaskData>);
void parse (const std::string&); void parse(const std::string&);
std::string composeF4 () const; std::string composeJSON(bool decorate = false) const;
std::string composeJSON (bool decorate = false) const;
// Status values. // Status values.
enum status {pending, completed, deleted, recurring, waiting}; enum status { pending, completed, deleted, recurring, waiting };
// Date state values. // Date state values.
enum dateState {dateNotDue, dateAfterToday, dateLaterToday, dateEarlierToday, dateBeforeToday}; enum dateState { dateNotDue, dateAfterToday, dateLaterToday, dateEarlierToday, dateBeforeToday };
// Public data. // Public data.
int id {0}; int id{0};
float urgency_value {0.0}; float urgency_value{0.0};
bool recalc_urgency {true}; bool recalc_urgency{true};
bool is_blocked {false}; bool is_blocked{false};
bool is_blocking {false}; bool is_blocking{false};
int annotation_count {0}; int annotation_count{0};
// Series of helper functions. // Series of helper functions.
static status textToStatus (const std::string&); static status textToStatus(const std::string&);
static std::string statusToText (status); static std::string statusToText(status);
static tc::Status status2tc (const Task::status);
static Task::status tc2status (const tc::Status);
void setAsNow (const std::string&); void setAsNow(const std::string&);
bool has (const std::string&) const; bool has(const std::string&) const;
std::vector <std::string> all () const; std::vector<std::string> all() const;
const std::string identifier (bool shortened = false) const; const std::string identifier(bool shortened = false) const;
const std::string get (const std::string&) const; const std::string get(const std::string&) const;
const std::string& get_ref (const std::string&) const; const std::string& get_ref(const std::string&) const;
int get_int (const std::string&) const; int get_int(const std::string&) const;
unsigned long get_ulong (const std::string&) const; unsigned long get_ulong(const std::string&) const;
float get_float (const std::string&) const; float get_float(const std::string&) const;
time_t get_date (const std::string&) const; time_t get_date(const std::string&) const;
void set (const std::string&, const std::string&); void set(const std::string&, const std::string&);
void set (const std::string&, long long); void set(const std::string&, long long);
void remove (const std::string&); void remove(const std::string&);
bool is_empty () const; bool is_empty() const;
#ifdef PRODUCT_TASKWARRIOR #ifdef PRODUCT_TASKWARRIOR
bool is_ready () const; bool is_ready() const;
bool is_due () const; bool is_due() const;
bool is_dueyesterday () const; bool is_dueyesterday() const;
bool is_duetoday () const; bool is_duetoday() const;
bool is_duetomorrow () const; bool is_duetomorrow() const;
bool is_dueweek () const; bool is_dueweek() const;
bool is_duemonth () const; bool is_duemonth() const;
bool is_duequarter () const; bool is_duequarter() const;
bool is_dueyear () const; bool is_dueyear() const;
bool is_overdue () const; bool is_overdue() const;
bool is_udaPresent () const; bool is_udaPresent() const;
bool is_orphanPresent () const; bool is_orphanPresent() const;
static bool isTagAttr (const std::string&); static bool isTagAttr(const std::string&);
static bool isDepAttr (const std::string&); static bool isDepAttr(const std::string&);
static bool isAnnotationAttr (const std::string&); static bool isAnnotationAttr(const std::string&);
#endif #endif
bool is_waiting () const; bool is_waiting() const;
status getStatus () const; status getStatus() const;
void setStatus (status); void setStatus(status);
#ifdef PRODUCT_TASKWARRIOR #ifdef PRODUCT_TASKWARRIOR
dateState getDateState (const std::string&) const; dateState getDateState(const std::string&) const;
#endif #endif
int getTagCount () const; int getTagCount() const;
bool hasTag (const std::string&) const; bool hasTag(const std::string&) const;
void addTag (const std::string&); void addTag(const std::string&);
void setTags (const std::vector <std::string>&); void setTags(const std::vector<std::string>&);
std::vector <std::string> getTags () const; std::vector<std::string> getTags() const;
void removeTag (const std::string&); void removeTag(const std::string&);
int getAnnotationCount () const; int getAnnotationCount() const;
bool hasAnnotations () const; bool hasAnnotations() const;
std::map <std::string, std::string> getAnnotations () const; std::map<std::string, std::string> getAnnotations() const;
void setAnnotations (const std::map <std::string, std::string>&); void setAnnotations(const std::map<std::string, std::string>&);
void addAnnotation (const std::string&); void addAnnotation(const std::string&);
void removeAnnotations (); void removeAnnotations();
#ifdef PRODUCT_TASKWARRIOR #ifdef PRODUCT_TASKWARRIOR
void addDependency (int); void addDependency(int);
#endif #endif
void addDependency (const std::string&); void addDependency(const std::string&);
#ifdef PRODUCT_TASKWARRIOR #ifdef PRODUCT_TASKWARRIOR
void removeDependency (int); void removeDependency(int);
void removeDependency (const std::string&); void removeDependency(const std::string&);
bool hasDependency (const std::string&) const; bool hasDependency(const std::string&) const;
std::vector <int> getDependencyIDs () const; std::vector<int> getDependencyIDs() const;
std::vector <std::string> getDependencyUUIDs () const; std::vector<std::string> getDependencyUUIDs() const;
std::vector <Task> getBlockedTasks () const; std::vector<Task> getBlockedTasks() const;
std::vector <Task> getDependencyTasks () const; std::vector<Task> getDependencyTasks() const;
std::vector <std::string> getUDAOrphans () const; std::vector<std::string> getUDAOrphans() const;
void substitute (const std::string&, const std::string&, const std::string&); void substitute(const std::string&, const std::string&, const std::string&);
#endif #endif
void validate (bool applyDefault = true); static std::string tag2Attr(const std::string&);
static std::string attr2Tag(const std::string&);
static std::string dep2Attr(const std::string&);
static std::string attr2Dep(const std::string&);
float urgency_c () const; void validate_add();
float urgency (); void validate(bool applyDefault = true);
float urgency_c() const;
float urgency();
#ifdef PRODUCT_TASKWARRIOR #ifdef PRODUCT_TASKWARRIOR
enum modType {modReplace, modPrepend, modAppend, modAnnotate}; enum modType { modReplace, modPrepend, modAppend, modAnnotate };
void modify (modType, bool text_required = false); void modify(modType, bool text_required = false);
#endif #endif
std::string diff (const Task& after) const; std::string diff(const Task& after) const;
std::string diffForInfo (const Task& after, const std::string& dateformat, long& last_timestamp, const long current_timestamp) const;
Table diffForUndoSide (const Task& after) const;
Table diffForUndoPatch (const Task& after, const Datetime& lastChange) const;
private:
int determineVersion(const std::string&);
void parseJSON(const std::string&);
void parseJSON(const json::object*);
void parseTC(rust::Box<tc::TaskData>);
void parseLegacy(const std::string&);
void validate_before(const std::string&, const std::string&);
const std::string encode(const std::string&) const;
const std::string decode(const std::string&) const;
void fixDependsAttribute();
void fixTagsAttribute();
private: protected:
int determineVersion (const std::string&); std::map<std::string, std::string> data{};
void parseJSON (const std::string&);
void parseJSON (const json::object*);
void parseTC (const tc::Task&);
void parseLegacy (const std::string&);
void validate_before (const std::string&, const std::string&);
const std::string encode (const std::string&) const;
const std::string decode (const std::string&) const;
const std::string tag2Attr (const std::string&) const;
const std::string attr2Tag (const std::string&) const;
const std::string dep2Attr (const std::string&) const;
const std::string attr2Dep (const std::string&) const;
void fixDependsAttribute ();
void fixTagsAttribute ();
protected: public:
std::map <std::string, std::string> data {}; float urgency_project() const;
float urgency_active() const;
public: float urgency_scheduled() const;
float urgency_project () const; float urgency_waiting() const;
float urgency_active () const; float urgency_blocked() const;
float urgency_scheduled () const; float urgency_inherit() const;
float urgency_waiting () const; float urgency_annotations() const;
float urgency_blocked () const; float urgency_tags() const;
float urgency_inherit () const; float urgency_due() const;
float urgency_annotations () const; float urgency_blocking() const;
float urgency_tags () const; float urgency_age() const;
float urgency_due () const;
float urgency_blocking () const;
float urgency_age () const;
}; };
#endif #endif

File diff suppressed because it is too large Load Diff

View File

@@ -27,94 +27,94 @@
#ifndef INCLUDED_VARIANT #ifndef INCLUDED_VARIANT
#define INCLUDED_VARIANT #define INCLUDED_VARIANT
#include <Task.h>
#include <time.h>
#include <map> #include <map>
#include <string> #include <string>
#include <time.h>
#include <Task.h>
class Variant class Variant {
{ public:
public:
static std::string dateFormat; static std::string dateFormat;
static bool searchCaseSensitive; static bool searchCaseSensitive;
static bool searchUsingRegex; static bool searchUsingRegex;
static bool isoEnabled; static bool isoEnabled;
enum type {type_boolean, type_integer, type_real, type_string, type_date, type_duration}; enum type { type_boolean, type_integer, type_real, type_string, type_date, type_duration };
Variant () = default; Variant() = default;
Variant (const Variant&); Variant(const Variant&);
Variant (const bool); Variant(const bool);
Variant (const int); Variant(const int);
Variant (const double); Variant(const double);
Variant (const std::string&); Variant(const std::string&);
Variant (const char*); Variant(const char*);
Variant (const time_t, const enum type); Variant(const time_t, const enum type);
void source (const std::string&); void source(const std::string&);
const std::string& source () const; const std::string& source() const;
Variant& operator= (const Variant&); Variant& operator=(const Variant&);
bool operator&& (const Variant&) const; bool operator&&(const Variant&) const;
bool operator|| (const Variant&) const; bool operator||(const Variant&) const;
bool operator_xor (const Variant&) const; bool operator_xor(const Variant&) const;
bool operator< (const Variant&) const; bool operator<(const Variant&) const;
bool operator<= (const Variant&) const; bool operator<=(const Variant&) const;
bool operator> (const Variant&) const; bool operator>(const Variant&) const;
bool operator>= (const Variant&) const; bool operator>=(const Variant&) const;
bool operator== (const Variant&) const; bool operator==(const Variant&) const;
bool operator!= (const Variant&) const; bool operator!=(const Variant&) const;
bool operator_match (const Variant&, const Task&) const; bool operator_match(const Variant&, const Task&) const;
bool operator_nomatch (const Variant&, const Task&) const; bool operator_nomatch(const Variant&, const Task&) const;
bool operator_partial (const Variant&) const; bool operator_partial(const Variant&) const;
bool operator_nopartial (const Variant&) const; bool operator_nopartial(const Variant&) const;
bool operator_hastag (const Variant&, const Task&) const; bool operator_hastag(const Variant&, const Task&) const;
bool operator_notag (const Variant&, const Task&) const; bool operator_notag(const Variant&, const Task&) const;
bool operator! () const; bool operator!() const;
Variant& operator^= (const Variant&); Variant& operator^=(const Variant&);
Variant operator^ (const Variant&) const; Variant operator^(const Variant&) const;
Variant& operator-= (const Variant&); Variant& operator-=(const Variant&);
Variant operator- (const Variant&) const; Variant operator-(const Variant&) const;
Variant& operator+= (const Variant&); Variant& operator+=(const Variant&);
Variant operator+ (const Variant&) const; Variant operator+(const Variant&) const;
Variant& operator*= (const Variant&); Variant& operator*=(const Variant&);
Variant operator* (const Variant&) const; Variant operator*(const Variant&) const;
Variant& operator/= (const Variant&); Variant& operator/=(const Variant&);
Variant operator/ (const Variant&) const; Variant operator/(const Variant&) const;
Variant& operator%= (const Variant&); Variant& operator%=(const Variant&);
Variant operator% (const Variant&) const; Variant operator%(const Variant&) const;
operator std::string () const; operator std::string() const;
void sqrt (); void sqrt();
void cast (const enum type); void cast(const enum type);
int type (); int type();
bool trivial () const; bool trivial() const;
bool get_bool () const; bool get_bool() const;
long long get_integer () const; long long get_integer() const;
double get_real () const; double get_real() const;
const std::string& get_string () const; const std::string& get_string() const;
time_t get_date () const; time_t get_date() const;
time_t get_duration () const; time_t get_duration() const;
private: private:
enum type _type {type_boolean}; enum type _type { type_boolean };
bool _bool {false}; bool _bool{false};
long long _integer {0}; long long _integer{0};
double _real {0.0}; double _real{0.0};
std::string _string {""}; std::string _string{""};
time_t _date {0}; time_t _date{0};
time_t _duration {0}; time_t _duration{0};
std::string _source {""}; std::string _source{""};
}; };
#endif #endif

113
src/Version.cpp Normal file
View File

@@ -0,0 +1,113 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2024, Dustin Mitchell.
//
// 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 <Version.h>
#include <cmake.h>
// cmake.h include header must come first
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
////////////////////////////////////////////////////////////////////////////////
Version::Version(std::string version) {
std::vector<int> parts;
std::string part;
std::istringstream input(version);
while (std::getline(input, part, '.')) {
int value;
// Try converting string to integer
if (std::stringstream(part) >> value && value >= 0) {
parts.push_back(value);
} else {
return;
}
}
if (parts.size() != 3) {
return;
}
major = parts[0];
minor = parts[1];
patch = parts[2];
}
////////////////////////////////////////////////////////////////////////////////
Version Version::Current() { return Version(PACKAGE_VERSION); }
////////////////////////////////////////////////////////////////////////////////
bool Version::is_valid() const { return major >= 0; }
////////////////////////////////////////////////////////////////////////////////
bool Version::operator<(const Version &other) const {
return std::tie(major, minor, patch) < std::tie(other.major, other.minor, other.patch);
}
////////////////////////////////////////////////////////////////////////////////
bool Version::operator<=(const Version &other) const {
return std::tie(major, minor, patch) <= std::tie(other.major, other.minor, other.patch);
}
////////////////////////////////////////////////////////////////////////////////
bool Version::operator>(const Version &other) const {
return std::tie(major, minor, patch) > std::tie(other.major, other.minor, other.patch);
}
////////////////////////////////////////////////////////////////////////////////
bool Version::operator>=(const Version &other) const {
return std::tie(major, minor, patch) >= std::tie(other.major, other.minor, other.patch);
}
////////////////////////////////////////////////////////////////////////////////
bool Version::operator==(const Version &other) const {
return std::tie(major, minor, patch) == std::tie(other.major, other.minor, other.patch);
}
////////////////////////////////////////////////////////////////////////////////
bool Version::operator!=(const Version &other) const {
return std::tie(major, minor, patch) != std::tie(other.major, other.minor, other.patch);
}
////////////////////////////////////////////////////////////////////////////////
Version::operator std::string() const {
std::ostringstream output;
if (is_valid()) {
output << major << '.' << minor << '.' << patch;
} else {
output << "(invalid version)";
}
return output.str();
}
////////////////////////////////////////////////////////////////////////////////
std::ostream &operator<<(std::ostream &os, const Version &version) {
os << std::string(version);
return os;
}

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