Compare commits

...

125 Commits

Author SHA1 Message Date
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
698 changed files with 27348 additions and 50326 deletions

1
.cargo/config.toml Symbolic link
View File

@@ -0,0 +1 @@
../config.toml

5
.clang-format Normal file
View File

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

View File

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

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 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

@@ -44,22 +44,6 @@ jobs:
args: --all-features
name: "Clippy Results"
mdbook:
runs-on: ubuntu-latest
name: "Documentation"
steps:
- uses: actions/checkout@v4
- name: Setup mdBook
uses: peaceiris/actions-mdbook@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:
runs-on: ubuntu-latest
name: "Formatting"
@@ -103,8 +87,8 @@ jobs:
- uses: actions-rs/cargo@v1.0.3
with:
command: run
args: --package xtask -- codegen
command: xtask
args: codegen
- name: check for changes
run: |

View File

@@ -29,10 +29,10 @@ jobs:
submodules: "recursive"
- name: Install cosign
uses: sigstore/cosign-installer@v3.4.0
uses: sigstore/cosign-installer@v3.5.0
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@v3.1.0
uses: docker/login-action@v3.3.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -40,7 +40,7 @@ jobs:
- name: Build and push Taskwarrior Docker image
id: build-and-push
uses: docker/build-push-action@v5.3.0
uses: docker/build-push-action@v6.5.0
with:
context: .
file: "./docker/task.dockerfile"

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

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

@@ -0,0 +1,27 @@
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: 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.
name: tests
on: [push, pull_request]
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- name: Install apt packages
run: sudo apt-get install -y build-essential cmake git uuid-dev faketime locales python3 curl gcovr ninja-build
- name: Check out this repository
uses: actions/checkout@v4.1.6
- name: Configure project
run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS=--coverage
- name: Build project
run: cmake --build build --target build_tests
- name: Test project
run: ctest --test-dir build -j 8 --output-on-failure
- name: Generate a code coverage report
uses: threeal/gcovr-action@v1.0.0
with:
coveralls-out: coverage.coveralls.json
excludes: |
build
- name: Sent to Coveralls
uses: coverallsapp/github-action@v2
with:
file: coverage.coveralls.json
# MacOS tests do not run in Docker, and use the actions-rs Rust installaction
tests-macos-12:
needs: coverage
name: tests (Mac OS 12.latest)
if: false # see #3242
runs-on: macos-latest
@@ -33,6 +65,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tests-macos-13:
needs: coverage
name: tests (Mac OS 13.latest)
if: false # see #3242
runs-on: macos-13
@@ -62,13 +95,14 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tests:
needs: coverage
strategy:
fail-fast: false
matrix:
include:
- name: "Fedora 38"
- name: "Fedora 40"
runner: ubuntu-latest
dockerfile: fedora38
dockerfile: fedora40
- name: "Fedora 39"
runner: ubuntu-latest
dockerfile: fedora39
@@ -99,10 +133,10 @@ jobs:
GITHUB_USER: ${{ github.actor }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONTAINER: ${{ matrix.dockerfile }}
run: docker-compose build test-$CONTAINER
run: docker compose build test-${{ env.CONTAINER }}
- name: Test ${{ matrix.name }}
run: docker-compose run test-$CONTAINER
run: docker compose run test-${{ env.CONTAINER }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONTAINER: ${{ matrix.dockerfile }}

6
.gitignore vendored
View File

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

3
.gitmodules vendored
View File

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

View File

@@ -1,4 +1,13 @@
cmake_minimum_required (VERSION 3.22)
enable_testing()
set (CMAKE_EXPORT_COMPILE_COMMANDS ON)
project (task
VERSION 3.1.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")
include (FetchContent)
@@ -7,11 +16,8 @@ include (CheckStructHasMember)
set (HAVE_CMAKE true)
project (task)
include (CXXSniffer)
set (PROJECT_VERSION "3.0.0")
OPTION (ENABLE_WASM "Enable 'wasm' support" OFF)
if (ENABLE_WASM)
@@ -19,14 +25,14 @@ if (ENABLE_WASM)
set(CMAKE_EXECUTABLE_SUFFIX ".js")
endif (ENABLE_WASM)
message ("-- Looking for libshared")
if (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src)
message ("-- Found libshared")
message ("-- Looking for git submodules")
if (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src AND EXISTS ${CMAKE_SOURCE_DIR}/src/tc/corrosion)
message ("-- Found git submodules")
else (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src)
message ("-- Cloning libshared")
message ("-- Cloning git submodules")
execute_process (COMMAND git submodule update --init
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
endif (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src)
endif (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src AND EXISTS ${CMAKE_SOURCE_DIR}/src/tc/corrosion)
message ("-- Looking for SHA1 references")
if (EXISTS ${CMAKE_SOURCE_DIR}/.git/index)
@@ -143,32 +149,15 @@ add_subdirectory (scripts)
if (EXISTS ${CMAKE_SOURCE_DIR}/test)
add_subdirectory (test EXCLUDE_FROM_ALL)
endif (EXISTS ${CMAKE_SOURCE_DIR}/test)
if (EXISTS performance)
if (EXISTS ${CMAKE_SOURCE_DIR}/performance)
add_subdirectory (performance EXCLUDE_FROM_ALL)
endif (EXISTS performance)
endif (EXISTS ${CMAKE_SOURCE_DIR}/performance)
set (doc_FILES NEWS ChangeLog README.md INSTALL AUTHORS COPYING LICENSE)
set (doc_FILES ChangeLog README.md INSTALL AUTHORS COPYING LICENSE)
foreach (doc_FILE ${doc_FILES})
install (FILES ${doc_FILE} DESTINATION ${TASK_DOCDIR})
endforeach (doc_FILE)
add_custom_command(OUTPUT run-review
COMMAND docker build -q --build-arg PR=$(PR) --build-arg LIBPR=$(LIBPR) -t taskwarrior-review:$(PR)s$(LIBPR) - < scripts/review-dockerfile
COMMAND docker run --rm --memory 1g --hostname pr-$(PR)s$(LIBPR) -it taskwarrior-review:$(PR)s$(LIBPR) bash || :
)
add_custom_target(review DEPENDS run-review)
add_custom_command(OUTPUT run-reproduce
COMMAND docker build -q --build-arg RELEASE=$(RELEASE) -t taskwarrior-reproduce:$(RELEASE) - < scripts/reproduce-dockerfile
COMMAND docker run --rm --memory 1g --hostname tw-$(RELEASE) -it taskwarrior-reproduce:$(RELEASE) bash || :
)
add_custom_target(reproduce DEPENDS run-reproduce)
add_custom_command(OUTPUT show-problems
COMMAND cd test && ./problems
)
add_custom_target(problems DEPENDS show-problems)
# ---
set (CPACK_SOURCE_GENERATOR "TGZ")

1775
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,47 +1,18 @@
[workspace]
members = [
"taskchampion/taskchampion",
"taskchampion/sync-server",
"taskchampion/lib",
"taskchampion/integration-tests",
"taskchampion/xtask",
"src/tc/lib",
"xtask",
]
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"] }
regex = "^1.10.2"
taskchampion = "0.6"

View File

@@ -1,4 +1,55 @@
------ current release ---------------------------
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
------ old releases ------------------------------
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 -
- [BREAKING CHANGE] the sync functionality has been rewritten entirely, and
@@ -19,13 +70,15 @@
- `taskd.server`
- `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:
- Akash Shanmugaraj
- Andrew Savchenko
- Dathan Bennett
- Dathan Bennett
- Dustin Mitchell
- dbr/Ben
- Felix Schurk
@@ -229,8 +282,6 @@
Thanks to bharatvaj for contributing.
- TW #2581 Config entry with a trailing comment cannot be modified
------ old releases ------------------------------
2.5.3 (2021-01-05) - 2f47226f91f0b02f7617912175274d9eed85924f
- #2375 task hangs then dies when certain tasks are present in a report
@@ -2768,4 +2819,3 @@ regular usage to determine which features were needed or unnecessary.]
- Usage.
------ start -----------------------------------

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,20 +1,18 @@
# Developing Taskwarrior
The following describes the process for developing Taskwarrior. If you are only
changing TaskChampion (Rust code), you can simply treat it like any other Rust
project: modify the source under `taskchampion/` and use `cargo test` to run
the TaskChampion tests.
See the [TaskChampion CONTRIBUTING guide](../../../taskchampion/CONTRIBUTING.md) for more.
## Satisfy the Requirements:
* CMake 3.0 or later
* CMake 3.22 or later
* gcc 7.0 or later, clang 6.0 or later, or a compiler with full C++17 support
* libuuid (if not on macOS)
* 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)
## 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:
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.
@@ -24,7 +22,7 @@ See the general CMake man pages or the [cmake-documentation](https://cmake.org/c
```sh
git clone https://github.com/GothenburgBitFactory/taskwarrior
cd taskwarrior
cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo
cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo
cmake --build build
```
Other possible build types can be `Release` and `Debug`.
@@ -52,24 +50,47 @@ cmake --build build-clang
```
## Run the Test Suite:
First switch to the test directory:
For running the test suite [ctest](https://cmake.org/cmake/help/latest/manual/ctest.1.html) is used.
Before one can run the test suite the `task_executable` must be built.
After that also the `build_tests` target must be build, which can be done over:
```sh
cmake --build build --target build_tests
```
Again you may also use the `-j <number-of-jobs>` option for parallel builds.
Now one can invoke `ctest` to run the tests.
```sh
ctest --test-dir build
```
$ cd build/test
This would run all the test in serial and might take some time.
### Running tests in parallel
```sh
ctest --test-dir build -j <number-of-jobs>
```
Then you can run all tests, showing details, with
Further it is adviced to add the `--output-on-failure` option to `ctest`, to recieve a verbose output if a test is failing as well as the `--rerun-failed` flag, to invoke in subsequent runs only the failed ones.
### Running specific tests
For this case one can use the `-R <regex>` or `--tests-regex <regex>` option to run only the tests matching the regular expression.
Running only the `cpp` tests can then be achieved over
```sh
ctest --test-dir build -R cpp
```
$ make VERBOSE=1
or running the `variant_*` tests
```sh
ctest --test-dir build -R variant
```
Alternately, run the tests with the details hidden in `all.log`:
```
$ ./run_all
```
Either way, you can get a summary of any test failures with:
```
$ ./problems
### Repeating a test case
In order to find sporadic test failures the `--repeat` flag can be used.
```sh
ctest --test-dir build -R cpp --repeat-until-fail 10
```
There are more options to `ctest` such as `--progress`, allowing to have a less verbose output.
They can be found in the [ctest](https://cmake.org/cmake/help/latest/manual/ctest.1.html) man page.
Note that any development should be performed using a git clone, and the current development branch.
The source tarballs do not reflect HEAD, and do not contain the test suite.
Follow the [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow) for creating a pull request.

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,21 @@
# Rust and C++
Taskwarrior has historically been a C++ project, but as part of an [ongoing effort to replace its storage backend](https://github.com/GothenburgBitFactory/taskwarrior/issues/2770) it now includes a Rust library called TaskChampion.
To develop Taskwarrior, you will need both a [C++ compiler and tools](./development.md), and a [Rust toolchain](https://www.rust-lang.org/tools/install).
However, most tasks will only require you to be familiar with one of the two languages.
Taskwarrior has historically been a C++ project, but as of taskwarrior-3.0.0, the storage backend is now provided by a Rust library called TaskChampion.
## TaskChampion
TaskChampion implements storage and access to "replicas" containing a user's tasks.
It defines an abstract model for this data, and also provides a simple Rust API for manipulating replicas.
It also defines a method of synchronizing replicas and provides an implementation of that method in the form of a sync server.
TaskChampion provides a C interface via the `taskchampion-lib` crate.
TaskChampion provides a C interface via the `taskchampion-lib` crate, at `src/tc/lib`.
Other applications, besides Taskwarrior, can use TaskChampion to manage tasks.
Applications written in Rust can use the `taskchampion` crate, while those in other languages may use the `taskchampion-lib` crate.
Taskwarrior is just one application using the TaskChampion interface.
You can build TaskChampion locally by simply running `cargo build` in the root of this repository.
The implementation, including more documentation, is in the [`rust`](../../rust) subdirectory.
## Taskwarrior's use of TaskChampion
Taskwarrior's interface to TaskChampion has a few layers:
* The skeletal Rust crate in [`src/tc/rust`](../../src/tc/rust) brings the symbols from `taskchampion-lib` under CMake's management.
The corresponding header file is included from [`taskchampion/lib`](../../taskchampion/lib).
All of these symbols are placed in the C++ namespace, `tc::ffi`.
* C++ wrappers for the types from `taskchampion-lib` are defined in [`src/tc`](../../src/tc), ensuring memory safety (with `unique_ptr`) and adding methods corresponding to the Rust API's methods.
* A Rust library, `takschampion-lib`, that presents `extern "C"` functions for use from C++, essentially defining a C interface to TaskChampion.
* C++ wrappers for the types from `taskchampion-lib`, 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

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

View File

@@ -466,4 +466,3 @@ Given that the configuration is not present in the JSON format of a task, any fi
This means that if a task contains a UDA, unless the meaning of it is understood, it MUST be preserved.
UDAs may have one of four types: string, numeric, date and duration.

4
doc/man/.gitignore vendored
View File

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

View File

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

View File

@@ -30,12 +30,11 @@ the existing replica, and run `task sync`.
.SS When to Synchronize
Taskwarrior can perform a sync operation at every garbage collection (gc) run.
This is the default, and is appropriate for local synchronization.
For synchronization to a server, a better solution is to run
For synchronization to a server, a common solution is to run
.nf
$ task sync
.fi
periodically, such as via
.BR cron (8) .
@@ -52,7 +51,9 @@ For most of these, you will need an encryption secret used to encrypt and
decrypt your tasks. This can be any secret string, and must match for all
replicas sharing tasks.
.nf
$ task config sync.encryption_secret <encryption_secret>
.fi
Tools such as
.BR pwgen (1)
@@ -64,14 +65,22 @@ To synchronize your tasks to a sync server, you will need the following
information from the server administrator:
.br
- The server's URL ("origin", such as "https://tw.example.com")
- The server's URL (such as "https://tw.example.com/path")
.br
- A client ID ("client_id") identifying your tasks
Configure Taskwarrior with these details:
$ task config sync.server.origin <origin>
.nf
$ task config sync.server.url <url>
$ task config sync.server.client_id <client_id>
.fi
Note that the 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
@@ -81,14 +90,18 @@ the bucket are adequate.
Authenticate to the project with:
.nf
$ gcloud config set project $PROJECT_NAME
$ gcloud auth application-default login
.fi
Then configure Taskwarrior with:
.nf
$ task config sync.gcp.bucket <bucket-name>
.fi
However you can bring your own service account credentials if your
However you can bring your own service account credentials if your
`application-default` is already being used by some other application
To begin, navigate to the "IAM and Admin" section in the Navigation Menu, then select "Roles."
@@ -99,30 +112,32 @@ Provide an appropriate name and description for the new role.
Add permissions to your new role using the filter "Service:storage" (not the "Filter permissions by role" input box).
Select the following permissions:
- storage.buckets.create
- storage.buckets.get
- storage.buckets.create
- storage.buckets.get
- storage.buckets.update
- storage.objects.create
- storage.objects.delete
- storage.objects.get
- storage.objects.list
- storage.objects.update
Create your new role.
Create your new role.
On the left sidebar, navigate to "Service accounts."
On the left sidebar, navigate to "Service accounts."
On the top menu bar within the "Service accounts" section, click "CREATE SERVICE ACCOUNT."
Provide an appropriate name and description for the new service account.
Select the role you just created and complete the service account creation process.
Now, in the Service Account dashboard, click into the new service account and select "keys" on the top menu bar.
Click on "ADD KEY" to create and download a new key (a JSON key).
On the top menu bar within the "Service accounts" section, click "CREATE SERVICE ACCOUNT."
Provide an appropriate name and description for the new service account.
Select the role you just created and complete the service account creation process.
Now, in the Service Account dashboard, click into the new service account and select "keys" on the top menu bar.
Click on "ADD KEY" to create and download a new key (a JSON key).
Then configure Taskwarrior with:
.nf
$ task config sync.gcp.bucket <bucket-name>
$ task config sync.gcp.credential_path <absolute-path-to-downloaded-credentials>
$ task config sync.gcp.credential_path <absolute-path-to-downloaded-credentials>
.fi
.SS Local Synchronization
@@ -130,7 +145,9 @@ In order to take advantage of synchronization's side effect of saving disk
space without setting up a remote server, it is possible to sync tasks locally.
To configure local sync:
.nf
$ task config sync.local.server_dir /path/to/sync
.fi
The default configuration is to sync to a database in the task directory
("data.location").
@@ -142,16 +159,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
server never sees un-encrypted data.
To start the server, run it in your preferred HTTP hosting environment, using
`--port` to set the TCP port on which it should listen. It is recommended to
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
The server is developed in
https://github.com/GothenburgBitFactory/taskchampion-sync-server.
.SS Adding a New User
@@ -159,7 +168,7 @@ To add a new user to the server, invent a new client ID with a tool like
`uuidgen` or an online UUID generator. There is no need to configure the server
for this new client ID: the sync server will automatically create a new user
whenever presented with a new client ID. Supply the ID, along with the
origin, to the user for inclusion in their Taskwarrior config. The user should
URL, to the user for inclusion in their Taskwarrior config. The user should
invent their own "encryption_secret".
.SH AVOIDING DUPLICATE RECURRING TASKS
@@ -167,11 +176,15 @@ invent their own "encryption_secret".
If you run multiple clients that sync to the same server, you will need to run
this command on your primary client (the one you use most often):
.nf
$ task config recurrence on
.fi
And on the other clients, run:
.nf
$ task config recurrence off
.fi
This protects you against the effects of a sync/duplication bug.
@@ -190,7 +203,9 @@ modifying the same task on two machines, without an intervening sync.
Setup simply involves creating the directory and modifying your data.location
configuration variable like this:
.nf
$ task config data.location /path/to/shared/directory
.fi
Strengths:
.br

View File

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

View File

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

View File

@@ -0,0 +1,98 @@
###############################################################################
#
# 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.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

@@ -98,4 +98,3 @@ color.sync.rejected=red
# Command: undo
color.undo.after=green
color.undo.before=red

View File

@@ -95,4 +95,3 @@ color.sync.rejected=color9
# Command: undo
color.undo.after=color2
color.undo.before=color1

View File

@@ -95,4 +95,3 @@ color.sync.rejected=rgb004
# Command: undo
color.undo.after=rgb035
color.undo.before=rgb013

View File

@@ -95,4 +95,3 @@ color.sync.rejected=gray5 on gray23
# Command: undo
color.undo.before=white on black
color.undo.after=black on white

View File

@@ -95,4 +95,3 @@ color.sync.rejected=gray23
# Command: undo
color.undo.before=rgb013
color.undo.after=rgb035

View File

@@ -95,4 +95,3 @@ color.sync.rejected=rgb200
# Command: undo
color.undo.after=rgb511
color.undo.before=rgb200

View File

@@ -95,4 +95,3 @@ color.sync.rejected=rgb103
# Command: undo
color.undo.before=rgb103
color.undo.after=rgb305

View File

@@ -95,4 +95,3 @@ color.sync.rejected=rgb110
# Command: undo
color.undo.before=rgb021
color.undo.after=rgb042

View File

@@ -95,4 +95,3 @@ color.sync.rejected=red
# Command: undo
color.undo.before=yellow
color.undo.after=green

View File

@@ -65,7 +65,7 @@ color.due.today=on rgb353
color.overdue=on rgb544
# Report: burndown
color.burndown.pending=on rgb411
color.burndown.pending=on rgb411
color.burndown.started=on rgb550
color.burndown.done=on rgb151
@@ -95,4 +95,3 @@ color.sync.rejected=red
# Command: undo
color.undo.before=yellow
color.undo.after=green

View File

@@ -98,4 +98,3 @@ color.sync.rejected=
# Command: undo
color.undo.after=
color.undo.before=

View File

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

View File

@@ -112,4 +112,3 @@ color.sync.rejected=color13
# Command: undo
color.undo.after=color2
color.undo.before=color1

View File

@@ -112,4 +112,3 @@ color.sync.rejected=color13
# Command: undo
color.undo.after=color2
color.undo.before=color1

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

5
src/.gitignore vendored
View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -2,10 +2,10 @@ cmake_minimum_required (VERSION 3.22)
include_directories (${CMAKE_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/src/tc
${CMAKE_SOURCE_DIR}/src/tc/lib
${CMAKE_SOURCE_DIR}/src/commands
${CMAKE_SOURCE_DIR}/src/columns
${CMAKE_SOURCE_DIR}/src/libshared/src
${CMAKE_SOURCE_DIR}/taskchampion/lib
${TASK_INCLUDE_DIRS})
add_library (task STATIC CLI2.cpp CLI2.h
@@ -18,6 +18,7 @@ add_library (task STATIC CLI2.cpp CLI2.h
TDB2.cpp TDB2.h
Task.cpp Task.h
Variant.cpp Variant.h
Version.cpp Version.h
ViewTask.cpp ViewTask.h
dependency.cpp
feedback.cpp

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -25,97 +25,101 @@
////////////////////////////////////////////////////////////////////////////////
#include <cmake.h>
#include <TDB2.h>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <list>
#include <unordered_set>
#include <stdlib.h>
#include <signal.h>
#include <Context.h>
// cmake.h include header must come first
#include <Color.h>
#include <Context.h>
#include <Datetime.h>
#include <TDB2.h>
#include <Table.h>
#include <shared.h>
#include <format.h>
#include <main.h>
#include <shared.h>
#include <signal.h>
#include <stdlib.h>
#include <util.h>
#include <algorithm>
#include <iostream>
#include <list>
#include <sstream>
#include <unordered_set>
#include <vector>
#include "tc/Server.h"
#include "tc/util.h"
bool TDB2::debug_mode = false;
static void dependency_scan (std::vector<Task> &);
static void dependency_scan(std::vector<Task>&);
////////////////////////////////////////////////////////////////////////////////
TDB2::TDB2 ()
: replica {tc::Replica()} // in-memory Replica
, _working_set {}
{
}
TDB2::TDB2()
: replica{tc::Replica()} // in-memory Replica
,
_working_set{} {}
////////////////////////////////////////////////////////////////////////////////
void TDB2::open_replica (const std::string& location, bool create_if_missing)
{
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.
void TDB2::add (Task& task)
{
void TDB2::add(Task& task) {
// Ensure the task is consistent, and provide defaults if necessary.
// bool argument to validate() is "applyDefault", to apply default values for
// properties not otherwise given.
task.validate (true);
task.validate(true);
std::string uuid = task.get ("uuid");
auto innertask = replica.import_task_with_uuid (uuid);
std::string uuid = task.get("uuid");
changes[uuid] = task;
// run hooks for this new task
Context::getContext().hooks.onAdd(task);
auto innertask = replica.import_task_with_uuid(uuid);
{
auto guard = replica.mutate_task(innertask);
// add the task attributes
for (auto& attr : task.all ()) {
for (auto& attr : task.all()) {
// TaskChampion does not store uuid or id in the taskmap
if (attr == "uuid" || attr == "id") {
continue;
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))));
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);
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)));
innertask.set_value(attr, std::make_optional(task.get(attr)));
}
}
}
auto ws = replica.working_set ();
auto ws = replica.working_set();
// get the ID that was assigned to this task
auto id = ws.by_uuid (uuid);
auto id = ws.by_uuid(uuid);
// update the cached working set with the new information
_working_set = std::make_optional (std::move (ws));
_working_set = std::make_optional(std::move(ws));
if (id.has_value ()) {
if (id.has_value()) {
task.id = id.value();
}
// run hooks for this new task
Context::getContext ().hooks.onAdd (task);
}
////////////////////////////////////////////////////////////////////////////////
@@ -133,29 +137,30 @@ void TDB2::add (Task& task)
// this method. In this case, this method throws an error that will make sense
// to the user. This is especially unlikely since tasks are only deleted when
// they have been unmodified for a long time.
void TDB2::modify (Task& task)
{
void TDB2::modify(Task& task) {
// All locally modified tasks are timestamped, implicitly overwriting any
// changes the user or hooks tried to apply to the "modified" attribute.
task.setAsNow ("modified");
task.validate (false);
auto uuid = task.get ("uuid");
task.setAsNow("modified");
task.validate(false);
auto uuid = task.get("uuid");
// invoke the hook and allow it to modify the task before updating
changes[uuid] = task;
// invoke the hook and allow it to modify the task before updating
Task original;
get (uuid, original);
Context::getContext ().hooks.onModify (original, task);
get(uuid, original);
Context::getContext().hooks.onModify(original, task);
auto maybe_tctask = replica.get_task (uuid);
if (!maybe_tctask.has_value ()) {
throw std::string ("task no longer exists");
auto maybe_tctask = replica.get_task(uuid);
if (!maybe_tctask.has_value()) {
throw std::string("task no longer exists");
}
auto tctask = std::move (maybe_tctask.value ());
auto tctask = std::move(maybe_tctask.value());
auto guard = replica.mutate_task(tctask);
auto tctask_map = tctask.get_taskmap ();
auto tctask_map = tctask.get_taskmap();
std::unordered_set<std::string> seen;
for (auto k : task.all ()) {
for (auto k : task.all()) {
// ignore task keys that aren't stored
if (k == "uuid") {
continue;
@@ -175,39 +180,43 @@ void TDB2::modify (Task& task)
if (v_new == "") {
tctask.set_value(k, {});
} else {
tctask.set_value(k, make_optional (v_new));
tctask.set_value(k, make_optional(v_new));
}
}
}
// we've now added and updated properties; but must find any deleted properties
for (auto kv : tctask_map) {
if (seen.find (kv.first) == seen.end ()) {
tctask.set_value (kv.first, {});
if (seen.find(kv.first) == seen.end()) {
tctask.set_value(kv.first, {});
}
}
}
////////////////////////////////////////////////////////////////////////////////
const tc::WorkingSet &TDB2::working_set ()
{
if (!_working_set.has_value ()) {
_working_set = std::make_optional (replica.working_set ());
void TDB2::purge(Task& task) {
auto uuid = task.get("uuid");
replica.delete_task(uuid);
}
////////////////////////////////////////////////////////////////////////////////
const tc::WorkingSet& TDB2::working_set() {
if (!_working_set.has_value()) {
_working_set = std::make_optional(replica.working_set());
}
return _working_set.value ();
return _working_set.value();
}
////////////////////////////////////////////////////////////////////////////////
void TDB2::get_changes (std::vector <Task>& changes)
{
// TODO: changes in an invocation of `task` are not currently tracked, so this
// list is always empty.
void TDB2::get_changes(std::vector<Task>& changes) {
std::map<std::string, Task>& changes_map = this->changes;
changes.clear();
std::transform(changes_map.begin(), changes_map.end(), std::back_inserter(changes),
[](const auto& kv) { return kv.second; });
}
////////////////////////////////////////////////////////////////////////////////
void TDB2::revert ()
{
void TDB2::revert() {
auto undo_ops = replica.get_undo_ops();
if (undo_ops.len == 0) {
std::cout << "No operations to undo.";
@@ -219,19 +228,18 @@ void TDB2::revert ()
} else {
replica.free_replica_ops(undo_ops);
}
replica.rebuild_working_set (false);
replica.rebuild_working_set(false);
}
////////////////////////////////////////////////////////////////////////////////
bool TDB2::confirm_revert (struct tc::ffi::TCReplicaOpList undo_ops)
{
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) {
switch (op.operation_type) {
case tc::ffi::TCReplicaOpType::Create:
std::cout << "Create " << replica.get_op_uuid(op);
break;
@@ -240,107 +248,100 @@ bool TDB2::confirm_revert (struct tc::ffi::TCReplicaOpList undo_ops)
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));
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.");
throw std::string("Can't undo UndoPoint.");
break;
default:
throw std::string ("Can't undo non-operation.");
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?");
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;
}
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));
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") : "");
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")
{
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 ())
<< format("The last modification was made {1}", lastChange.toString()) << '\n'
<< '\n'
<< '\n'
<< view.render ()
<< '\n';
<< view.render() << '\n';
}
else if (Context::getContext ().config.get ("undo.style") == "diff")
{
else if (Context::getContext().config.get("undo.style") == "diff") {
Table view = before.diffForUndoPatch(after, lastChange);
std::cout << '\n'
<< view.render ()
<< '\n';
std::cout << '\n' << view.render() << '\n';
}
}
void TDB2::gc ()
{
////////////////////////////////////////////////////////////////////////////////
void TDB2::gc() {
Timer timer;
// Allowed as an override, but not recommended.
if (Context::getContext ().config.getBoolean ("gc"))
{
replica.rebuild_working_set (true);
if (Context::getContext().config.getBoolean("gc")) {
replica.rebuild_working_set(true);
}
Context::getContext ().time_gc_us += timer.total_us ();
Context::getContext().time_gc_us += timer.total_us();
}
////////////////////////////////////////////////////////////////////////////////
void TDB2::expire_tasks() { replica.expire_tasks(); }
////////////////////////////////////////////////////////////////////////////////
// Latest ID is that of the last pending task.
int TDB2::latest_id ()
{
const tc::WorkingSet &ws = working_set ();
return (int)ws.largest_index ();
int TDB2::latest_id() {
const tc::WorkingSet& ws = working_set();
return (int)ws.largest_index();
}
////////////////////////////////////////////////////////////////////////////////
const std::vector <Task> TDB2::all_tasks ()
{
const std::vector<Task> TDB2::all_tasks() {
auto all_tctasks = replica.all_tasks();
std::vector <Task> all;
for (auto& tctask : all_tctasks)
all.push_back (Task (std::move (tctask)));
std::vector<Task> all;
for (auto& tctask : all_tctasks) all.push_back(Task(std::move(tctask)));
return all;
}
////////////////////////////////////////////////////////////////////////////////
const std::vector <Task> TDB2::pending_tasks ()
{
const tc::WorkingSet &ws = working_set ();
auto largest_index = ws.largest_index ();
const std::vector<Task> TDB2::pending_tasks() {
const tc::WorkingSet& ws = working_set();
auto largest_index = ws.largest_index();
std::vector <Task> result;
std::vector<Task> result;
for (size_t i = 0; i <= largest_index; i++) {
auto maybe_uuid = ws.by_index (i);
if (maybe_uuid.has_value ()) {
auto maybe_task = replica.get_task (maybe_uuid.value ());
if (maybe_task.has_value ()) {
result.push_back (Task (std::move (maybe_task.value ())));
auto maybe_uuid = ws.by_index(i);
if (maybe_uuid.has_value()) {
auto maybe_task = replica.get_task(maybe_uuid.value());
if (maybe_task.has_value()) {
result.push_back(Task(std::move(maybe_task.value())));
}
}
}
@@ -351,16 +352,15 @@ const std::vector <Task> TDB2::pending_tasks ()
}
////////////////////////////////////////////////////////////////////////////////
const std::vector <Task> TDB2::completed_tasks ()
{
const std::vector<Task> TDB2::completed_tasks() {
auto all_tctasks = replica.all_tasks();
const tc::WorkingSet &ws = working_set ();
const tc::WorkingSet& ws = working_set();
std::vector <Task> result;
std::vector<Task> result;
for (auto& tctask : all_tctasks) {
// if this task is _not_ in the working set, return it.
if (!ws.by_uuid (tctask.get_uuid ())) {
result.push_back (Task (std::move (tctask)));
if (!ws.by_uuid(tctask.get_uuid())) {
result.push_back(Task(std::move(tctask)));
}
}
@@ -369,10 +369,9 @@ const std::vector <Task> TDB2::completed_tasks ()
////////////////////////////////////////////////////////////////////////////////
// Locate task by ID, wherever it is.
bool TDB2::get (int id, Task& task)
{
const tc::WorkingSet &ws = working_set ();
const auto maybe_uuid = ws.by_index (id);
bool TDB2::get(int id, Task& task) {
const tc::WorkingSet& ws = working_set();
const auto maybe_uuid = ws.by_index(id);
if (maybe_uuid) {
auto maybe_task = replica.get_task(*maybe_uuid);
if (maybe_task) {
@@ -386,25 +385,24 @@ bool TDB2::get (int id, Task& task)
////////////////////////////////////////////////////////////////////////////////
// Locate task by UUID, including by partial ID, wherever it is.
bool TDB2::get (const std::string& uuid, Task& task)
{
bool TDB2::get(const std::string& uuid, Task& task) {
// try by raw uuid, if the length is right
if (uuid.size () == 36) {
if (uuid.size() == 36) {
try {
auto maybe_task = replica.get_task (uuid);
auto maybe_task = replica.get_task(uuid);
if (maybe_task) {
task = Task{std::move (*maybe_task)};
task = Task{std::move(*maybe_task)};
return true;
}
} catch (const std::string &err) {
} catch (const std::string& err) {
return false;
}
}
// Nothing to do but iterate over all tasks and check whether it's closeEnough
for (auto& tctask : replica.all_tasks ()) {
if (closeEnough (tctask.get_uuid (), uuid, uuid.length ())) {
task = Task{std::move (tctask)};
for (auto& tctask : replica.all_tasks()) {
if (closeEnough(tctask.get_uuid(), uuid, uuid.length())) {
task = Task{std::move(tctask)};
return true;
}
}
@@ -414,34 +412,25 @@ bool TDB2::get (const std::string& uuid, Task& task)
////////////////////////////////////////////////////////////////////////////////
// Locate task by UUID, wherever it is.
bool TDB2::has (const std::string& uuid)
{
bool TDB2::has(const std::string& uuid) {
Task task;
return get(uuid, task);
}
////////////////////////////////////////////////////////////////////////////////
const std::vector <Task> TDB2::siblings (Task& task)
{
std::vector <Task> results;
if (task.has ("parent"))
{
std::string parent = task.get ("parent");
const std::vector<Task> TDB2::siblings(Task& task) {
std::vector<Task> results;
if (task.has("parent")) {
std::string parent = task.get("parent");
for (auto& i : this->pending_tasks())
{
for (auto& i : this->pending_tasks()) {
// Do not include self in results.
if (i.id != task.id)
{
if (i.id != task.id) {
// Do not include completed or deleted tasks.
if (i.getStatus () != Task::completed &&
i.getStatus () != Task::deleted)
{
if (i.getStatus() != Task::completed && i.getStatus() != Task::deleted) {
// If task has the same parent, it is a sibling.
if (i.has ("parent") &&
i.get ("parent") == parent)
{
results.push_back (i);
if (i.has("parent") && i.get("parent") == parent) {
results.push_back(i);
}
}
}
@@ -452,41 +441,40 @@ const std::vector <Task> TDB2::siblings (Task& task)
}
////////////////////////////////////////////////////////////////////////////////
const std::vector <Task> TDB2::children (Task& parent)
{
const std::vector<Task> TDB2::children(Task& parent) {
// scan _pending_ tasks for those with `parent` equal to this task
std::vector <Task> results;
std::string this_uuid = parent.get ("uuid");
std::vector<Task> results;
std::string this_uuid = parent.get("uuid");
const tc::WorkingSet &ws = working_set ();
size_t end_idx = ws.largest_index ();
const tc::WorkingSet& ws = working_set();
size_t end_idx = ws.largest_index();
for (size_t i = 0; i <= end_idx; i++) {
auto uuid_opt = ws.by_index (i);
auto uuid_opt = ws.by_index(i);
if (!uuid_opt) {
continue;
}
auto uuid = uuid_opt.value ();
auto uuid = uuid_opt.value();
// skip self-references
if (uuid == this_uuid) {
continue;
}
auto task_opt = replica.get_task (uuid_opt.value ());
auto task_opt = replica.get_task(uuid_opt.value());
if (!task_opt) {
continue;
}
auto task = std::move (task_opt.value ());
auto task = std::move(task_opt.value());
auto parent_uuid_opt = task.get_value ("parent");
auto parent_uuid_opt = task.get_value("parent");
if (!parent_uuid_opt) {
continue;
}
auto parent_uuid = parent_uuid_opt.value ();
auto parent_uuid = parent_uuid_opt.value();
if (parent_uuid == this_uuid) {
results.push_back (Task (std::move (task)));
results.push_back(Task(std::move(task)));
}
}
@@ -494,40 +482,30 @@ const std::vector <Task> TDB2::children (Task& parent)
}
////////////////////////////////////////////////////////////////////////////////
std::string TDB2::uuid (int id)
{
const tc::WorkingSet &ws = working_set ();
return ws.by_index ((size_t)id).value_or ("");
std::string TDB2::uuid(int id) {
const tc::WorkingSet& ws = working_set();
return ws.by_index((size_t)id).value_or("");
}
////////////////////////////////////////////////////////////////////////////////
int TDB2::id (const std::string& uuid)
{
const tc::WorkingSet &ws = working_set ();
return (int)ws.by_uuid (uuid).value_or (0);
int TDB2::id(const std::string& uuid) {
const tc::WorkingSet& ws = working_set();
return (int)ws.by_uuid(uuid).value_or(0);
}
////////////////////////////////////////////////////////////////////////////////
int TDB2::num_local_changes ()
{
return (int)replica.num_local_operations ();
}
int TDB2::num_local_changes() { return (int)replica.num_local_operations(); }
////////////////////////////////////////////////////////////////////////////////
int TDB2::num_reverts_possible ()
{
return (int)replica.num_undo_points ();
}
int TDB2::num_reverts_possible() { return (int)replica.num_undo_points(); }
////////////////////////////////////////////////////////////////////////////////
void TDB2::sync (tc::Server server, bool avoid_snapshots)
{
void TDB2::sync(tc::Server server, bool avoid_snapshots) {
replica.sync(std::move(server), avoid_snapshots);
}
////////////////////////////////////////////////////////////////////////////////
void TDB2::dump ()
{
void TDB2::dump() {
// TODO
}
@@ -535,24 +513,16 @@ void TDB2::dump ()
// For any task that has depenencies, follow the chain of dependencies until the
// end. Along the way, update the Task::is_blocked and Task::is_blocking data
// cache.
static void dependency_scan (std::vector<Task> &tasks)
{
for (auto& left : tasks)
{
for (auto& dep : left.getDependencyUUIDs ())
{
for (auto& right : tasks)
{
if (right.get ("uuid") == dep)
{
static void dependency_scan(std::vector<Task>& tasks) {
for (auto& left : tasks) {
for (auto& dep : left.getDependencyUUIDs()) {
for (auto& right : tasks) {
if (right.get("uuid") == dep) {
// GC hasn't run yet, check both tasks for their current status
Task::status lstatus = left.getStatus ();
Task::status rstatus = right.getStatus ();
if (lstatus != Task::completed &&
lstatus != Task::deleted &&
rstatus != Task::completed &&
rstatus != Task::deleted)
{
Task::status lstatus = left.getStatus();
Task::status rstatus = right.getStatus();
if (lstatus != Task::completed && lstatus != Task::deleted &&
rstatus != Task::completed && rstatus != Task::deleted) {
left.is_blocked = true;
right.is_blocking = true;
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

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

72
src/Version.h Normal file
View File

@@ -0,0 +1,72 @@
////////////////////////////////////////////////////////////////////////////////
//
// 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
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_VERSION
#define INCLUDED_VERSION
#include <string>
// A utility class for handling Taskwarrior versions.
class Version {
public:
// Parse a version from a string. This must be of the format
// digits.digits.digits.
explicit Version(std::string version);
// Create an invalid version.
Version() = default;
Version(const Version &other) = default;
Version(Version &&other) = default;
Version &operator=(const Version &) = default;
Version &operator=(Version &&) = default;
// Return a version representing the release being built.
static Version Current();
bool is_valid() const;
// Compare versions.
bool operator<(const Version &) const;
bool operator<=(const Version &) const;
bool operator>(const Version &) const;
bool operator>=(const Version &) const;
bool operator==(const Version &) const;
bool operator!=(const Version &) const;
// Convert back to a string.
operator std::string() const;
friend std::ostream &operator<<(std::ostream &os, const Version &version);
private:
int major = -1;
int minor = -1;
int patch = -1;
};
#endif
////////////////////////////////////////////////////////////////////////////////

View File

@@ -25,42 +25,41 @@
////////////////////////////////////////////////////////////////////////////////
#include <cmake.h>
#include <ViewTask.h>
#include <numeric>
// cmake.h include header must come first
#include <Context.h>
#include <ViewTask.h>
#include <format.h>
#include <util.h>
#include <utf8.h>
#include <main.h>
#include <utf8.h>
#include <util.h>
#include <numeric>
////////////////////////////////////////////////////////////////////////////////
ViewTask::ViewTask ()
: _width (0)
, _left_margin (0)
, _header (0)
, _sort_header (0)
, _odd (0)
, _even (0)
, _intra_padding (1)
, _intra_odd (0)
, _intra_even (0)
, _extra_padding (0)
, _extra_odd (0)
, _extra_even (0)
, _truncate_lines (0)
, _truncate_rows (0)
, _lines (0)
, _rows (0)
{
}
ViewTask::ViewTask()
: _width(0),
_left_margin(0),
_header(0),
_sort_header(0),
_odd(0),
_even(0),
_intra_padding(1),
_intra_odd(0),
_intra_even(0),
_extra_padding(0),
_extra_odd(0),
_extra_even(0),
_truncate_lines(0),
_truncate_rows(0),
_lines(0),
_rows(0) {}
////////////////////////////////////////////////////////////////////////////////
ViewTask::~ViewTask ()
{
for (auto& col : _columns)
delete col;
ViewTask::~ViewTask() {
for (auto& col : _columns) delete col;
_columns.clear ();
_columns.clear();
}
////////////////////////////////////////////////////////////////////////////////
@@ -106,64 +105,55 @@ ViewTask::~ViewTask ()
// the larger fields. If the widest field is W0, and the second widest
// field is W1, then a solution may be achievable by reducing W0 --> W1.
//
std::string ViewTask::render (std::vector <Task>& data, std::vector <int>& sequence)
{
std::string ViewTask::render(std::vector<Task>& data, std::vector<int>& sequence) {
Timer timer;
bool const obfuscate = Context::getContext ().config.getBoolean ("obfuscate");
bool const print_empty_columns = Context::getContext ().config.getBoolean ("print.empty.columns");
std::vector <Column*> nonempty_columns;
std::vector <bool> nonempty_sort;
bool const obfuscate = Context::getContext().config.getBoolean("obfuscate");
bool const print_empty_columns = Context::getContext().config.getBoolean("print.empty.columns");
std::vector<Column*> nonempty_columns;
std::vector<bool> nonempty_sort;
// Determine minimal, ideal column widths.
std::vector <int> minimal;
std::vector <int> ideal;
std::vector<int> minimal;
std::vector<int> ideal;
for (unsigned int i = 0; i < _columns.size (); ++i)
{
for (unsigned int i = 0; i < _columns.size(); ++i) {
// Headers factor in to width calculations.
unsigned int global_min = 0;
unsigned int global_ideal = global_min;
for (unsigned int s = 0; s < sequence.size (); ++s)
{
if ((int)s >= _truncate_lines && _truncate_lines != 0)
break;
for (unsigned int s = 0; s < sequence.size(); ++s) {
if ((int)s >= _truncate_lines && _truncate_lines != 0) break;
if ((int)s >= _truncate_rows && _truncate_rows != 0)
break;
if ((int)s >= _truncate_rows && _truncate_rows != 0) break;
// Determine minimum and ideal width for this column.
unsigned int min = 0;
unsigned int ideal = 0;
_columns[i]->measure (data[sequence[s]], min, ideal);
_columns[i]->measure(data[sequence[s]], min, ideal);
if (min > global_min) global_min = min;
if (min > global_min) global_min = min;
if (ideal > global_ideal) global_ideal = ideal;
// If a fixed-width column was just measured, there is no point repeating
// the measurement for all tasks.
if (_columns[i]->is_fixed_width ())
break;
if (_columns[i]->is_fixed_width()) break;
}
if (print_empty_columns || global_min != 0)
{
unsigned int label_length = utf8_width (_columns[i]->label ());
if (label_length > global_min) global_min = label_length;
if (print_empty_columns || global_min != 0) {
unsigned int label_length = utf8_width(_columns[i]->label());
if (label_length > global_min) global_min = label_length;
if (label_length > global_ideal) global_ideal = label_length;
minimal.push_back (global_min);
ideal.push_back (global_ideal);
minimal.push_back(global_min);
ideal.push_back(global_ideal);
}
if (! print_empty_columns)
{
if (global_min != 0) // Column is nonempty
if (!print_empty_columns) {
if (global_min != 0) // Column is nonempty
{
nonempty_columns.push_back (_columns[i]);
nonempty_sort.push_back (_sort[i]);
}
else // Column is empty, drop it
nonempty_columns.push_back(_columns[i]);
nonempty_sort.push_back(_sort[i]);
} else // Column is empty, drop it
{
// Note: This is safe to do because we set _columns = nonempty_columns
// after iteration over _columns is finished.
@@ -172,51 +162,40 @@ std::string ViewTask::render (std::vector <Task>& data, std::vector <int>& seque
}
}
if (! print_empty_columns)
{
if (!print_empty_columns) {
_columns = nonempty_columns;
_sort = nonempty_sort;
}
int all_extra = _left_margin
+ (2 * _extra_padding)
+ ((_columns.size () - 1) * _intra_padding);
int all_extra = _left_margin + (2 * _extra_padding) + ((_columns.size() - 1) * _intra_padding);
// Sum the widths.
int sum_minimal = std::accumulate (minimal.begin (), minimal.end (), 0);
int sum_ideal = std::accumulate (ideal.begin (), ideal.end (), 0);
int sum_minimal = std::accumulate(minimal.begin(), minimal.end(), 0);
int sum_ideal = std::accumulate(ideal.begin(), ideal.end(), 0);
// Calculate final column widths.
int overage = _width - sum_minimal - all_extra;
Context::getContext ().debug (format ("ViewTask::render min={1} ideal={2} overage={3} width={4}",
sum_minimal + all_extra,
sum_ideal + all_extra,
overage,
_width));
Context::getContext().debug(format("ViewTask::render min={1} ideal={2} overage={3} width={4}",
sum_minimal + all_extra, sum_ideal + all_extra, overage,
_width));
std::vector <int> widths;
std::vector<int> widths;
// Ideal case. Everything fits.
if (_width == 0 || sum_ideal + all_extra <= _width)
{
if (_width == 0 || sum_ideal + all_extra <= _width) {
widths = ideal;
}
// Not enough for minimum. Decrease certain columns.
else if (overage < 0)
{
else if (overage < 0) {
// Determine which columns are the longest.
unsigned int longest = 0;
unsigned int second_longest = 0;
for (unsigned int j = 0; j < minimal.size(); j++)
{
if (minimal[j] > minimal[longest])
{
for (unsigned int j = 0; j < minimal.size(); j++) {
if (minimal[j] > minimal[longest]) {
second_longest = longest;
longest = j;
}
else if (minimal[j] > minimal[second_longest])
{
} else if (minimal[j] > minimal[second_longest]) {
second_longest = j;
}
}
@@ -224,53 +203,46 @@ std::string ViewTask::render (std::vector <Task>& data, std::vector <int>& seque
// Case 1: Shortening longest column still keeps it longest. Let it bear
// all the shortening.
widths = minimal;
if (minimal[longest] + overage >= minimal[second_longest])
widths[longest] += overage;
if (minimal[longest] + overage >= minimal[second_longest]) widths[longest] += overage;
// Case 2: Shorten the longest column to second longest length. Try to
// split shortening them evenly.
else
{
else {
int decrease = minimal[second_longest] - minimal[longest];
widths[longest] += decrease;
overage = overage - decrease;
// Attempt to decrease the two longest columns (at most to two characters)
if (-overage <= widths[longest] + widths[second_longest] - 4)
{
if (-overage <= widths[longest] + widths[second_longest] - 4) {
// Compute half of the overage, rounding up
int half_overage = overage / 2 + overage % 2;
// Decrease both larges columns by this amount
widths[longest] += half_overage;
widths[second_longest] += half_overage;
}
else
} else
// If reducing two of the longest solumns to 2 characters is not sufficient, then give up.
Context::getContext ().error (format ("The report has a minimum width of {1} and does not fit in the available width of {2}.", sum_minimal + all_extra, _width));
Context::getContext().error(format(
"The report has a minimum width of {1} and does not fit in the available width of {2}.",
sum_minimal + all_extra, _width));
}
}
// Perfect minimal width.
else if (overage == 0)
{
else if (overage == 0) {
widths = minimal;
}
// Extra space to share.
else if (overage > 0)
{
else if (overage > 0) {
widths = minimal;
// Spread 'overage' among columns where width[i] < ideal[i]
bool needed = true;
while (overage && needed)
{
while (overage && needed) {
needed = false;
for (unsigned int i = 0; i < _columns.size () && overage; ++i)
{
if (widths[i] < ideal[i])
{
for (unsigned int i = 0; i < _columns.size() && overage; ++i) {
if (widths[i] < ideal[i]) {
++widths[i];
--overage;
needed = true;
@@ -281,39 +253,34 @@ std::string ViewTask::render (std::vector <Task>& data, std::vector <int>& seque
// Compose column headers.
unsigned int max_lines = 0;
std::vector <std::vector <std::string>> headers;
for (unsigned int c = 0; c < _columns.size (); ++c)
{
headers.emplace_back ();
_columns[c]->renderHeader (headers[c], widths[c], _sort[c] ? _sort_header : _header);
std::vector<std::vector<std::string>> headers;
for (unsigned int c = 0; c < _columns.size(); ++c) {
headers.emplace_back();
_columns[c]->renderHeader(headers[c], widths[c], _sort[c] ? _sort_header : _header);
if (headers[c].size () > max_lines)
max_lines = headers[c].size ();
if (headers[c].size() > max_lines) max_lines = headers[c].size();
}
// Render column headers.
std::string left_margin = std::string (_left_margin, ' ');
std::string extra = std::string (_extra_padding, ' ');
std::string intra = std::string (_intra_padding, ' ');
std::string left_margin = std::string(_left_margin, ' ');
std::string extra = std::string(_extra_padding, ' ');
std::string intra = std::string(_intra_padding, ' ');
std::string extra_odd = Context::getContext ().color () ? _extra_odd.colorize (extra) : extra;
std::string extra_even = Context::getContext ().color () ? _extra_even.colorize (extra) : extra;
std::string intra_odd = Context::getContext ().color () ? _intra_odd.colorize (intra) : intra;
std::string intra_even = Context::getContext ().color () ? _intra_even.colorize (intra) : intra;
std::string extra_odd = Context::getContext().color() ? _extra_odd.colorize(extra) : extra;
std::string extra_even = Context::getContext().color() ? _extra_even.colorize(extra) : extra;
std::string intra_odd = Context::getContext().color() ? _intra_odd.colorize(intra) : intra;
std::string intra_even = Context::getContext().color() ? _intra_even.colorize(intra) : intra;
std::string out;
_lines = 0;
for (unsigned int i = 0; i < max_lines; ++i)
{
for (unsigned int i = 0; i < max_lines; ++i) {
out += left_margin + extra;
for (unsigned int c = 0; c < _columns.size (); ++c)
{
if (c)
out += intra;
for (unsigned int c = 0; c < _columns.size(); ++c) {
if (c) out += intra;
if (headers[c].size () < max_lines - i)
out += _header.colorize (std::string (widths[c], ' '));
if (headers[c].size() < max_lines - i)
out += _header.colorize(std::string(widths[c], ' '));
else
out += headers[c][i];
}
@@ -321,60 +288,50 @@ std::string ViewTask::render (std::vector <Task>& data, std::vector <int>& seque
out += extra;
// Trim right.
out.erase (out.find_last_not_of (' ') + 1);
out.erase(out.find_last_not_of(' ') + 1);
out += "\n";
// Stop if the line limit is exceeded.
if (++_lines >= _truncate_lines && _truncate_lines != 0)
{
Context::getContext ().time_render_us += timer.total_us ();
if (++_lines >= _truncate_lines && _truncate_lines != 0) {
Context::getContext().time_render_us += timer.total_us();
return out;
}
}
// Compose, render columns, in sequence.
_rows = 0;
std::vector <std::vector <std::string>> cells;
for (unsigned int s = 0; s < sequence.size (); ++s)
{
std::vector<std::vector<std::string>> cells;
for (unsigned int s = 0; s < sequence.size(); ++s) {
max_lines = 0;
// Apply color rules to task.
Color rule_color;
autoColorize (data[sequence[s]], rule_color);
autoColorize(data[sequence[s]], rule_color);
// Alternate rows based on |s % 2|
bool odd = (s % 2) ? true : false;
Color row_color;
if (Context::getContext ().color ())
{
if (Context::getContext().color()) {
row_color = odd ? _odd : _even;
row_color.blend (rule_color);
row_color.blend(rule_color);
}
for (unsigned int c = 0; c < _columns.size (); ++c)
{
cells.emplace_back ();
_columns[c]->render (cells[c], data[sequence[s]], widths[c], row_color);
for (unsigned int c = 0; c < _columns.size(); ++c) {
cells.emplace_back();
_columns[c]->render(cells[c], data[sequence[s]], widths[c], row_color);
if (cells[c].size () > max_lines)
max_lines = cells[c].size ();
if (cells[c].size() > max_lines) max_lines = cells[c].size();
if (obfuscate)
if (_columns[c]->type () == "string")
for (auto& line : cells[c])
line = obfuscateText (line);
if (_columns[c]->type() == "string")
for (auto& line : cells[c]) line = obfuscateText(line);
}
// Listing breaks are simply blank lines inserted when a column value
// changes.
if (s > 0 &&
_breaks.size () > 0)
{
for (const auto& b : _breaks)
{
if (data[sequence[s - 1]].get (b) != data[sequence[s]].get (b))
{
if (s > 0 && _breaks.size() > 0) {
for (const auto& b : _breaks) {
if (data[sequence[s - 1]].get(b) != data[sequence[s]].get(b)) {
out += "\n";
++_lines;
@@ -384,51 +341,46 @@ std::string ViewTask::render (std::vector <Task>& data, std::vector <int>& seque
}
}
for (unsigned int i = 0; i < max_lines; ++i)
{
for (unsigned int i = 0; i < max_lines; ++i) {
out += left_margin + (odd ? extra_odd : extra_even);
for (unsigned int c = 0; c < _columns.size (); ++c)
{
if (c)
{
if (row_color.nontrivial ())
row_color._colorize (out, intra);
for (unsigned int c = 0; c < _columns.size(); ++c) {
if (c) {
if (row_color.nontrivial())
row_color._colorize(out, intra);
else
out += (odd ? intra_odd : intra_even);
}
if (i < cells[c].size ())
if (i < cells[c].size())
out += cells[c][i];
else
row_color._colorize (out, std::string (widths[c], ' '));
row_color._colorize(out, std::string(widths[c], ' '));
}
out += (odd ? extra_odd : extra_even);
// Trim right.
out.erase (out.find_last_not_of (' ') + 1);
out.erase(out.find_last_not_of(' ') + 1);
out += "\n";
// Stop if the line limit is exceeded.
if (++_lines >= _truncate_lines && _truncate_lines != 0)
{
Context::getContext ().time_render_us += timer.total_us ();
if (++_lines >= _truncate_lines && _truncate_lines != 0) {
Context::getContext().time_render_us += timer.total_us();
return out;
}
}
cells.clear ();
cells.clear();
// Stop if the row limit is exceeded.
if (++_rows >= _truncate_rows && _truncate_rows != 0)
{
Context::getContext ().time_render_us += timer.total_us ();
if (++_rows >= _truncate_rows && _truncate_rows != 0) {
Context::getContext().time_render_us += timer.total_us();
return out;
}
}
Context::getContext ().time_render_us += timer.total_us ();
Context::getContext().time_render_us += timer.total_us();
return out;
}

View File

@@ -27,63 +27,68 @@
#ifndef INCLUDED_VIEWTASK
#define INCLUDED_VIEWTASK
#include <string>
#include <vector>
#include <Task.h>
#include <Color.h>
#include <Column.h>
#include <Task.h>
class ViewTask
{
public:
ViewTask ();
~ViewTask ();
#include <string>
#include <vector>
class ViewTask {
public:
ViewTask();
~ViewTask();
// View specifications.
void add (Column* column, bool sort = false) { _columns.push_back (column); _sort.push_back (sort); }
void width (int width) { _width = width; }
void leftMargin (int margin) { _left_margin = margin; }
void colorHeader (Color& c) { _header = c; if (!_sort_header) _sort_header = c; }
void colorSortHeader (Color& c) { _sort_header = c; }
void colorOdd (Color& c) { _odd = c; }
void colorEven (Color& c) { _even = c; }
void intraPadding (int padding) { _intra_padding = padding; }
void intraColorOdd (Color& c) { _intra_odd = c; }
void intraColorEven (Color& c) { _intra_even = c; }
void extraPadding (int padding) { _extra_padding = padding; }
void extraColorOdd (Color& c) { _extra_odd = c; }
void extraColorEven (Color& c) { _extra_even = c; }
void truncateLines (int n) { _truncate_lines = n; }
void truncateRows (int n) { _truncate_rows = n; }
void addBreak (const std::string& attr) { _breaks.push_back (attr); }
int lines () { return _lines; }
int rows () { return _rows; }
void add(Column* column, bool sort = false) {
_columns.push_back(column);
_sort.push_back(sort);
}
void width(int width) { _width = width; }
void leftMargin(int margin) { _left_margin = margin; }
void colorHeader(Color& c) {
_header = c;
if (!_sort_header) _sort_header = c;
}
void colorSortHeader(Color& c) { _sort_header = c; }
void colorOdd(Color& c) { _odd = c; }
void colorEven(Color& c) { _even = c; }
void intraPadding(int padding) { _intra_padding = padding; }
void intraColorOdd(Color& c) { _intra_odd = c; }
void intraColorEven(Color& c) { _intra_even = c; }
void extraPadding(int padding) { _extra_padding = padding; }
void extraColorOdd(Color& c) { _extra_odd = c; }
void extraColorEven(Color& c) { _extra_even = c; }
void truncateLines(int n) { _truncate_lines = n; }
void truncateRows(int n) { _truncate_rows = n; }
void addBreak(const std::string& attr) { _breaks.push_back(attr); }
int lines() { return _lines; }
int rows() { return _rows; }
// View rendering.
std::string render (std::vector <Task>&, std::vector <int>&);
std::string render(std::vector<Task>&, std::vector<int>&);
private:
std::vector <Column*> _columns;
std::vector <bool> _sort;
std::vector <std::string> _breaks;
int _width;
int _left_margin;
Color _header;
Color _sort_header;
Color _odd;
Color _even;
int _intra_padding;
Color _intra_odd;
Color _intra_even;
int _extra_padding;
Color _extra_odd;
Color _extra_even;
int _truncate_lines;
int _truncate_rows;
int _lines;
int _rows;
private:
std::vector<Column*> _columns;
std::vector<bool> _sort;
std::vector<std::string> _breaks;
int _width;
int _left_margin;
Color _header;
Color _sort_header;
Color _odd;
Color _even;
int _intra_padding;
Color _intra_odd;
Color _intra_even;
int _extra_padding;
Color _extra_odd;
Color _extra_even;
int _truncate_lines;
int _truncate_rows;
int _lines;
int _rows;
};
#endif
////////////////////////////////////////////////////////////////////////////////

View File

@@ -1 +0,0 @@
task

View File

@@ -25,57 +25,55 @@
////////////////////////////////////////////////////////////////////////////////
#include <cmake.h>
#include <iostream>
#include <string>
#include <stdlib.h>
#include <string.h>
#include <Eval.h>
// cmake.h include header must come first
#include <Context.h>
#include <Task.h>
#include <Datetime.h>
#include <Duration.h>
#include <shared.h>
#include <Eval.h>
#include <Task.h>
#include <format.h>
#include <shared.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
////////////////////////////////////////////////////////////////////////////////
// Constants.
bool get (const std::string&, Variant&)
{
/*
// An example, although a bad one because this is supported by default.
if (name == "pi") {value = Variant (3.14159165); return true;}
*/
bool get(const std::string&, Variant&) {
/*
// An example, although a bad one because this is supported by default.
if (name == "pi") {value = Variant (3.14159165); return true;}
*/
return false;
}
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
int main(int argc, char** argv) {
int status = 0;
try
{
try {
Context globalContext;
Context::setContext (&globalContext);
Context::setContext(&globalContext);
// Same operating parameters as Context::staticInitialization.
Datetime::standaloneDateEnabled = false;
Datetime::standaloneTimeEnabled = false;
Datetime::standaloneDateEnabled = false;
Datetime::standaloneTimeEnabled = false;
Duration::standaloneSecondsEnabled = false;
bool infix {true};
bool infix{true};
// Add a source for constants.
Eval e;
e.addSource (get);
e.addSource(get);
// Combine all the arguments into one expression string.
std::string expression;
for (int i = 1; i < argc; i++)
{
if (!strcmp (argv[i], "-h") || ! strcmp (argv[i], "--help"))
{
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
std::cout << '\n'
<< "Usage: " << argv[0] << " [options] '<expression>'\n"
<< '\n'
@@ -85,56 +83,47 @@ int main (int argc, char** argv)
<< " -i|--infix Infix expression (default)\n"
<< " -p|--postfix Postfix expression\n"
<< '\n';
exit (1);
}
else if (!strcmp (argv[i], "-v") || !strcmp (argv[i], "--version"))
{
exit(1);
} else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--version")) {
std::cout << '\n'
<< format ("calc {1} built for ", VERSION)
<< osName ()
<< format("calc {1} built for ", VERSION) << osName() << '\n'
<< "Copyright (C) 2006 - 2021 T. Babej, P. Beckingham, F. Hernandez." << '\n'
<< '\n'
<< "Copyright (C) 2006 - 2021 T. Babej, P. Beckingham, F. Hernandez."
<< '\n'
<< '\n'
<< "Taskwarrior may be copied only under the terms of the MIT license, which may be found in the Taskwarrior source kit."
<< "Taskwarrior may be copied only under the terms of the MIT license, which may "
"be found in the Taskwarrior source kit."
<< '\n'
<< '\n';
exit (1);
}
else if (!strcmp (argv[i], "-d") || !strcmp (argv[i], "--debug"))
e.debug (true);
else if (!strcmp (argv[i], "-i") || !strcmp (argv[i], "--infix"))
exit(1);
} else if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug"))
e.debug(true);
else if (!strcmp(argv[i], "-i") || !strcmp(argv[i], "--infix"))
infix = true;
else if (!strcmp (argv[i], "-p") || !strcmp (argv[i], "--postfix"))
else if (!strcmp(argv[i], "-p") || !strcmp(argv[i], "--postfix"))
infix = false;
else
expression += std::string (argv[i]) + ' ';
expression += std::string(argv[i]) + ' ';
}
Variant result;
if (infix)
e.evaluateInfixExpression (expression, result);
e.evaluateInfixExpression(expression, result);
else
e.evaluatePostfixExpression (expression, result);
e.evaluatePostfixExpression(expression, result);
// Show any debug output.
for (const auto& i : Context::getContext ().debugMessages)
std::cout << i << '\n';
for (const auto& i : Context::getContext().debugMessages) std::cout << i << '\n';
// Show the result in string form.
std::cout << (std::string) result
<< '\n';
std::cout << (std::string)result << '\n';
}
catch (const std::string& error)
{
catch (const std::string& error) {
std::cerr << error << '\n';
status = -1;
}
catch (...)
{
catch (...) {
std::cerr << "Unknown error occured. Oops.\n";
status = -2;
}

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