diff --git a/.github/workflows/docker-image.yaml b/.github/workflows/docker-image.yaml index c63bd8d2..6e23e16c 100644 --- a/.github/workflows/docker-image.yaml +++ b/.github/workflows/docker-image.yaml @@ -33,7 +33,7 @@ jobs: submodules: "recursive" - name: Install cosign - uses: sigstore/cosign-installer@v3.8.1 + uses: sigstore/cosign-installer@v3.9.1 - name: Log into registry ${{ env.REGISTRY }} uses: docker/login-action@v3.4.0 @@ -44,7 +44,7 @@ jobs: - name: Build and push Timewarrior Docker image id: build-and-push - uses: docker/build-push-action@v6.15.0 + uses: docker/build-push-action@v6.18.0 with: context: . file: "./docker/timew.dockerfile" diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 391fc2fc..fe1829de 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -24,21 +24,18 @@ jobs: - name: "Debian Testing" runner: ubuntu-latest container: debiantesting - - name: "Fedora 40" - runner: ubuntu-latest - container: fedora40 - name: "Fedora 41" runner: ubuntu-latest container: fedora41 + - name: "Fedora 42" + runner: ubuntu-latest + container: fedora42 - name: "OpenSUSE Leap" runner: ubuntu-latest container: opensuseleap - name: "OpenSUSE Tumbleweed" runner: ubuntu-latest container: opensusetumbleweed - - name: "Ubuntu 20.04" - runner: ubuntu-latest - container: ubuntu2004 - name: "Ubuntu 22.04" runner: ubuntu-latest container: ubuntu2204 @@ -51,6 +48,9 @@ jobs: - name: "macOS 14" runner: macos-14 container: osx-14 + - name: "macOS 15" + runner: macos-15 + container: osx-15 runs-on: ${{ matrix.runner }} continue-on-error: ${{ matrix.continue-on-error == true }} steps: diff --git a/AUTHORS b/AUTHORS index c93d4a43..9c69d2b5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -129,3 +129,4 @@ Thanks to the following, who submitted detailed bug reports and excellent sugges Eugene Morozov Stefan Herold Sebastian Carlos + ftambara diff --git a/CMakeLists.txt b/CMakeLists.txt index 036d17ad..715c847f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ project (timew) include (CXXSniffer) include (FindAsciidoctor) -set (PROJECT_VERSION "1.8.0") +set (PROJECT_VERSION "1.8.0-dev") string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) diff --git a/ChangeLog b/ChangeLog index 5f830d7b..9e0bcdaa 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,12 @@ +- #677 Extension names starting with 'timew' cause problems + (thanks to ftambara) +- #661 Make display of ids and annotations the default in summary report for new users +- #669 id filtering for charts and reports +- #660 Fix man page section numbers and reference formatting + ------ current release --------------------------- -1.8.0 (2025-04-20) - +1.8.0 (2025-04-20) - 2257084710247189231118bc9257180a815ef21a - #658 Add sub-command 'range' to command 'modify' (thanks to Sebastian Carlos) diff --git a/README.md b/README.md index 24c6030a..effd5b58 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Please visit [timewarrior.net](https://timewarrior.net/docs/) for extensive docu ### From Package -Thanks to the community, there are binary packages available [here](https://timewarrior.net/docs/install.html#distributions). +Thanks to the community, there are binary packages available [here](https://timewarrior.net/docs/install/#distributions). ### Building Timewarrior @@ -93,7 +93,7 @@ For other support options, take a look at [timewarrior.net/support](https://time Contributions are greatly appreciated. Whether in the form of code patches, ideas, discussion, bug reports, encouragement or criticism, we need you! -For support options, take a look at [CONTRIBUTING.md](CONTRIBUTING.md) or visit [timewarior.net](https://timewarrior.net/support). +For support options, take a look at [CONTRIBUTING.md](CONTRIBUTING.md) or visit [timewarrior.net](https://timewarrior.net/support). Visit [GitHub](https://github.com/GothenburgBitFactory/timewarrior) and participate in the future of Timewarrior. diff --git a/doc/man1/timew-chart.1.adoc b/doc/man1/timew-chart.1.adoc index e727787b..386dd266 100644 --- a/doc/man1/timew-chart.1.adoc +++ b/doc/man1/timew-chart.1.adoc @@ -103,14 +103,13 @@ This can be used to see the exclusions. The ':ids' hint causes the intervals to be displayed with their ids == EXAMPLES -Charts accept date ranges and tags for filtering, or shortcut hints: +Charts accept date ranges and/or tags, or ids for filtering: $ timew month 1st - today $ timew week FOO BAR - $ timew day :week + $ timew day @3 @4 + +See **timew-ranges**(7) and **timew-hints**(7) on the different ways to provide date ranges. == SEE ALSO -**timew-day**(1), -**timew-month**(1), **timew-summary**(1), -**timew-week**(1) diff --git a/doc/man1/timew-config.1.adoc b/doc/man1/timew-config.1.adoc index b5bc0efa..31f0f03c 100644 --- a/doc/man1/timew-config.1.adoc +++ b/doc/man1/timew-config.1.adoc @@ -33,5 +33,5 @@ If no arguments are provided, all configuration settings are shown: ... == SEE ALSO -**timew-hints**(1), +**timew-hints**(7), **timew-show**(1) diff --git a/doc/man1/timew-delete.1.adoc b/doc/man1/timew-delete.1.adoc index 072f98b5..66eecbbf 100644 --- a/doc/man1/timew-delete.1.adoc +++ b/doc/man1/timew-delete.1.adoc @@ -23,4 +23,4 @@ Then having selected '@2' as the interval you wish to delete: == SEE ALSO -*timew-cancel*(1) +**timew-cancel**(1) diff --git a/doc/man1/timew-export.1.adoc b/doc/man1/timew-export.1.adoc index 08b8a98a..da85b9c3 100644 --- a/doc/man1/timew-export.1.adoc +++ b/doc/man1/timew-export.1.adoc @@ -10,7 +10,7 @@ timew-export - export tracked time in JSON == DESCRIPTION Exports all the tracked time in JSON format. -Supply either a list of interval IDs (e.g. `@1 @2`), or optional filters (see **timew-ranges(7)** and/or **timew-tags(1)**) +Supply either a list of interval IDs (e.g. `@1 @2`), or optional filters (see **timew-ranges**(7) and/or **timew-tags**(1)) == EXAMPLES diff --git a/doc/man1/timew-extensions.1.adoc b/doc/man1/timew-extensions.1.adoc index e8063a27..33254740 100644 --- a/doc/man1/timew-extensions.1.adoc +++ b/doc/man1/timew-extensions.1.adoc @@ -11,4 +11,4 @@ timew-extensions - list available extensions Displays the directory containing the extension programs and a table showing each extension and its status. == SEE ALSO -**timew-diagnostics**(1) \ No newline at end of file +**timew-diagnostics**(1) diff --git a/doc/man1/timew-fill.1.adoc b/doc/man1/timew-fill.1.adoc index 3fe89fbd..713570bb 100644 --- a/doc/man1/timew-fill.1.adoc +++ b/doc/man1/timew-fill.1.adoc @@ -27,4 +27,4 @@ Note that you can fill multiple intervals: == SEE ALSO -**timew-hints**(1) +**timew-hints**(7) diff --git a/doc/man1/timew-get.1.adoc b/doc/man1/timew-get.1.adoc index e958cb4e..09b5746d 100644 --- a/doc/man1/timew-get.1.adoc +++ b/doc/man1/timew-get.1.adoc @@ -19,4 +19,4 @@ For example: It is an error to reference an interval or tag that does not exist. == SEE ALSO -*timew-DOM* \ No newline at end of file +**timew-dom**(7) diff --git a/doc/man1/timew-modify.1.adoc b/doc/man1/timew-modify.1.adoc index 1fe3b6c4..96aadcaa 100644 --- a/doc/man1/timew-modify.1.adoc +++ b/doc/man1/timew-modify.1.adoc @@ -15,7 +15,7 @@ Using the 'start' or 'end' subcommand, one can either specify a new start or end The interval to be modified is specified via its id. If the resulting interval overlaps with an existing interval, the command will return an error. -One can the ':adjust' hint to force an overwrite in this case. +One can add the ':adjust' hint to force an overwrite in this case. See **timew-summary**(1) on how to retrieve the interval id. @@ -46,6 +46,6 @@ As in the examples above, the date portion can be omitted, if the date of the in == SEE ALSO **timew-lengthen**(1), **timew-move**(1), -**timew-resize**(1) +**timew-resize**(1), **timew-shorten**(1), **timew-summary**(1) \ No newline at end of file diff --git a/doc/man1/timew-report.1.adoc b/doc/man1/timew-report.1.adoc index e3bbae90..1c1aa776 100644 --- a/doc/man1/timew-report.1.adoc +++ b/doc/man1/timew-report.1.adoc @@ -6,6 +6,7 @@ timew-report - run an extension report == SYNOPSIS [verse] *timew* [*report*] __ [__] [__**...**] +*timew* [*report*] __ __**...** == DESCRIPTION Runs an extension report, and supports filtering data. @@ -19,14 +20,17 @@ This does however assume there is a 'foo' extension installed. The return code is the return code of the extension. If the extension produces no output and a non-zero rc, then 255 is returned. +Filtering is either possible by range and/or tags, or by ids. + == CONFIGURATION **reports.range**:: Sets the default date range for all reports. -The value has to correspond to a range hint, see timew-hints(7). +The value has to correspond to a range hint, see **timew-hints**(7). Defaults to `all` **reports.**____**.range**:: Set the date range for report _name_, used if no _range_ is given on the command line. Here, _name_ is the name of the report executable without its extension (i.e. a report executable 'foo.py' is referred to by 'foo'). -The value has to correspond to a range hint, see timew-hints(7). +The value has to correspond to a range hint, see **timew-hints**(7). +Defaults to the value of **reports.range**. diff --git a/doc/man1/timew-summary.1.adoc b/doc/man1/timew-summary.1.adoc index 7e3a26b4..8e4dadaa 100644 --- a/doc/man1/timew-summary.1.adoc +++ b/doc/man1/timew-summary.1.adoc @@ -67,7 +67,7 @@ Default value is 'yes'. **reports.summary.range**:: Set the date range for the summary report. -The value has to correspond to a range hint, see timew-hints(7). +The value has to correspond to a range hint, see **timew-hints**(7). Default value is 'day' **reports.summary.weekdays**:: diff --git a/docker-compose.yml b/docker-compose.yml index f4377def..76a89d1d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -47,14 +47,6 @@ services: security_opt: - label=type:container_runtime_t tty: true - test-fedora40: - build: - context: . - dockerfile: test/docker/fedora40 - network_mode: "host" - security_opt: - - label=type:container_runtime_t - tty: true test-fedora41: build: context: . @@ -63,6 +55,14 @@ services: security_opt: - label=type:container_runtime_t tty: true + test-fedora42: + build: + context: . + dockerfile: test/docker/fedora42 + network_mode: "host" + security_opt: + - label=type:container_runtime_t + tty: true test-opensuseleap: build: context: . @@ -79,14 +79,6 @@ services: security_opt: - label=type:container_runtime_t tty: true - test-ubuntu2004: - build: - context: . - dockerfile: test/docker/ubuntu2004 - network_mode: "host" - security_opt: - - label=type:container_runtime_t - tty: true test-ubuntu2204: build: context: . diff --git a/src/CLI.cpp b/src/CLI.cpp index 25a5a125..cacaec9d 100644 --- a/src/CLI.cpp +++ b/src/CLI.cpp @@ -460,6 +460,12 @@ void CLI::canonicalizeNames () for (auto& a : _args) { + // Do not canonicalize the BINARY + if (a.hasTag ("BINARY")) + { + continue; + } + auto raw = a.attribute ("raw"); std::string canonical = raw; diff --git a/src/commands/CmdChart.cpp b/src/commands/CmdChart.cpp index 5cbf487f..0c5b99f4 100644 --- a/src/commands/CmdChart.cpp +++ b/src/commands/CmdChart.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -82,16 +83,57 @@ int renderChart ( Range default_range = {}; expandIntervalHint (":" + report_hint, default_range); - auto range = cli.getRange (default_range); + auto ids = cli.getIds (); auto tags = cli.getTags (); - // Load the data. - IntervalFilterAndGroup filtering ({ - std::make_shared (range), - std::make_shared (tags) - }); + if (! ids.empty () && ! tags.empty ()) + { + throw std::string ("You cannot filter intervals by both, ids and tags."); + } - auto tracked = getTracked (database, rules, filtering); + Range range; + + std::vector tracked; + + if (! ids.empty ()) + { + auto filtering = IntervalFilterAllWithIds (ids); + tracked = getTracked (database, rules, filtering); + + if (tracked.size () != ids.size ()) + { + for (auto& id: ids) + { + bool found = false; + + for (auto& interval: tracked) + { + if (interval.id == id) + { + found = true; + break; + } + } + if (! found) + { + throw format ("ID '@{1}' does not correspond to any tracking.", id); + } + } + } + + range = cli.getRange (default_range); + } + else + { + range = cli.getRange (default_range); + + IntervalFilterAndGroup filtering ({ + std::make_shared (range), + std::make_shared (tags) + }); + + tracked = getTracked (database, rules, filtering); + } if (tracked.empty ()) { diff --git a/src/commands/CmdExport.cpp b/src/commands/CmdExport.cpp index 2adca876..86f53a37 100644 --- a/src/commands/CmdExport.cpp +++ b/src/commands/CmdExport.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -39,33 +40,52 @@ int CmdExport ( Database& database) { auto ids = cli.getIds (); - auto range = cli.getRange (); auto tags = cli.getTags (); - std::shared_ptr filtering; + if (! ids.empty () && ! tags.empty ()) + { + throw std::string ("You cannot specify both id and tags/range to export intervals."); + } + + std::vector intervals; if (! ids.empty ()) { - if (! range.is_empty ()) - { - throw std::string ("You cannot specify both id and tags/range to export intervals."); - } + auto filtering = IntervalFilterAllWithIds (ids); + intervals = getTracked (database, rules, filtering); - filtering = std::make_shared (ids); + if (intervals.size () != ids.size ()) + { + for (auto& id: ids) + { + bool found = false; + + for (auto& interval: intervals) + { + if (interval.id == id) + { + found = true; + break; + } + } + if (! found) + { + throw format ("ID '@{1}' does not correspond to any tracking.", id); + } + } + } } else { - filtering = std::make_shared ( - std::vector > ( - { - std::make_shared (range), - std::make_shared (tags), - } - ) - ); - } + auto range = cli.getRange (); - auto intervals = getTracked (database, rules, *filtering); + IntervalFilterAndGroup filtering ({ + std::make_shared (range), + std::make_shared (tags) + }); + + intervals = getTracked (database, rules, filtering); + } std::cout << jsonFromIntervals (intervals); diff --git a/src/commands/CmdReport.cpp b/src/commands/CmdReport.cpp index c4871c3a..3050fb02 100644 --- a/src/commands/CmdReport.cpp +++ b/src/commands/CmdReport.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -133,16 +134,57 @@ int CmdReport ( Range default_range = {}; expandIntervalHint (":" + report_hint, default_range); - // Create a filter, and if empty, choose the current week. + auto ids = cli.getIds (); auto tags = cli.getTags (); - auto range = cli.getRange (default_range); - IntervalFilterAndGroup filtering ({ - std::make_shared (range), - std::make_shared (tags) - }); + if (! ids.empty () && ! tags.empty ()) + { + throw std::string ("You cannot filter intervals by both, ids and tags."); + } - auto tracked = getTracked (database, rules, filtering); + Range range; + + std::vector tracked; + + if (! ids.empty ()) + { + auto filtering = IntervalFilterAllWithIds (ids); + tracked = getTracked (database, rules, filtering); + + if (tracked.size () != ids.size ()) + { + for (auto& id: ids) + { + bool found = false; + + for (auto& interval: tracked) + { + if (interval.id == id) + { + found = true; + break; + } + } + if (! found) + { + throw format ("ID '@{1}' does not correspond to any tracking.", id); + } + } + } + + range = Range {tracked.begin ()->end, tracked.end ()->start}; + } + else + { + range = cli.getRange (default_range); + + IntervalFilterAndGroup filtering ({ + std::make_shared (range), + std::make_shared (tags) + }); + + tracked = getTracked (database, rules, filtering); + } // Compose Header info. rules.set ("temp.report.start", range.is_started () ? range.start.toISO () : ""); diff --git a/src/libshared b/src/libshared index 2aa844cb..121f757c 160000 --- a/src/libshared +++ b/src/libshared @@ -1 +1 @@ -Subproject commit 2aa844cb9b015fca81b947c57fde07999ede002b +Subproject commit 121f757c3ec1b1f548f7835208b8c72d85d141a7 diff --git a/src/paths.cpp b/src/paths.cpp index 6c4620a5..283f8fdc 100644 --- a/src/paths.cpp +++ b/src/paths.cpp @@ -148,7 +148,13 @@ void initializeDirs (const CLI& cli, Rules& rules) if (! configFileLocation.exists ()) { - File (configFileLocation).create (0600); + File configFile (configFileLocation); + configFile.create (0600); + std::vector defaultConfig = { + "reports.summary.ids = yes\n", + "reports.summary.annotations = yes\n", + }; + configFile.append(defaultConfig); } // Load the configuration data. diff --git a/test/docker/fedora40 b/test/docker/fedora42 similarity index 98% rename from test/docker/fedora40 rename to test/docker/fedora42 index 3479e481..f7647fce 100644 --- a/test/docker/fedora40 +++ b/test/docker/fedora42 @@ -1,4 +1,4 @@ -FROM fedora:40 +FROM fedora:42 RUN dnf update -y RUN dnf install -y \ diff --git a/test/docker/ubuntu2004 b/test/docker/ubuntu2004 deleted file mode 100644 index 680df589..00000000 --- a/test/docker/ubuntu2004 +++ /dev/null @@ -1,42 +0,0 @@ -FROM ubuntu:20.04 - -ENV DEBIAN_FRONTEND noninteractive - -RUN apt-get update && yes | unminimize -RUN apt-get install -y \ - asciidoctor \ - cmake \ - g++ \ - git \ - locales \ - man \ - man-db \ - python3 \ - python3-dateutil \ - tzdata - -# Setup environment -RUN update-alternatives --install /usr/bin/python python /usr/bin/python3 10 -RUN ln -fs /usr/share/zoneinfo/Europe/Berlin /etc/localtime -RUN dpkg-reconfigure -f noninteractive tzdata - -ENV LC_ALL en_US.UTF-8 -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US.UTF-8 -RUN locale-gen "en_US.UTF-8" - -# Setup timewarrior -ADD . /root/code/ -WORKDIR /root/code/ -RUN git clean -dfx -RUN git submodule init -RUN git submodule update -RUN cmake -DCMAKE_BUILD_TYPE=debug . -RUN make -j2 -RUN make install - -# Setup tests -WORKDIR /root/code/test/ -RUN make -j2 - -CMD ["bash", "-c", "./run_all -v ; cat all.log | grep 'not ok' ; ./problems ; FAILED=$? ; echo timew $( timew --version ) ; python --version ; cmake --version ; gcc --version ; asciidoctor --version ; exit $FAILED"] diff --git a/test/summary.t b/test/summary.t index bc8d8c64..90275661 100755 --- a/test/summary.t +++ b/test/summary.t @@ -58,10 +58,10 @@ class TestSummary(TestCase): code, out, err = self.t("summary :ids {:%Y-%m-%d} - {:%Y-%m-%d}".format(yesterday, tomorrow)) self.assertRegex(out, r""" -Wk ?Date Day ID Tags ?Start ?End Time Total +Wk ?Date Day ID Tags Annotation ?Start ?End Time Total [ -]+ -W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 ?\d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2}( -W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 ?\d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2})? +W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 ?\d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2}( +W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 ?\d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2})? [ ]+1:00:0[01] """) @@ -77,10 +77,10 @@ W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 ?\d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2 code, out, err = self.t("summary :ids :all".format(yesterday, tomorrow)) self.assertRegex(out, r""" -Wk ?Date Day ID Tags ?Start ?End Time Total +Wk ?Date Day ID Tags Annotation ?Start ?End Time Total [ -]+ -W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 ?\d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2}( -W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 ?\d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2})? +W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 ?\d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2}( +W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 ?\d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2})? [ ]+1:00:0[01] """) @@ -96,10 +96,10 @@ W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 ?\d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2 code, out, err = self.t("summary :ids {:%Y-%m-%d} - {:%Y-%m-%d}".format(yesterday, tomorrow)) self.assertRegex(out, r""" -Wk ?Date Day ID Tags ?Start End Time Total +Wk ?Date Day ID Tags Annotation ?Start End Time Total [ -]+ -W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 ?\d{1,2}:\d{2}:\d{2}[ ]+- \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2}( -W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 ?\d{1,2}:\d{2}:\d{2}[ ]+- \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2})? +W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 ?\d{1,2}:\d{2}:\d{2}[ ]+- \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2}( +W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 ?\d{1,2}:\d{2}:\d{2}[ ]+- \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2})? [ ]+1:00:0[01] """) @@ -115,10 +115,10 @@ W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 ?\d{1,2}:\d{2}:\d{2}[ ]+- \d{1,2}:\d{2} code, out, err = self.t("summary :ids :all".format(yesterday, tomorrow)) self.assertRegex(out, r""" -Wk ?Date Day ID Tags ?Start End Time Total +Wk ?Date Day ID Tags Annotation ?Start End Time Total [ -]+ -W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 ?\d{1,2}:\d{2}:\d{2}[ ]+- \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2}( -W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 ?\d{1,2}:\d{2}:\d{2}[ ]+- \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2})? +W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 ?\d{1,2}:\d{2}:\d{2}[ ]+- \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2}( +W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 ?\d{1,2}:\d{2}:\d{2}[ ]+- \d{1,2}:\d{2}:\d{2} \d{1,2}:\d{2}:\d{2})? [ ]+1:00:0[01] """) @@ -133,12 +133,12 @@ W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 ?\d{1,2}:\d{2}:\d{2}[ ]+- \d{1,2}:\d{2} code, out, err = self.t("summary 2017-03-09T11:00 - 2017-03-09T12:00 :ids") self.assertIn(""" -Wk Date Day ID Tags Start End Time Total ---- ---------- --- -- ---------- -------- -------- ------- ------- -W10 2017-03-09 Thu @3 Tag2 11:38:39 11:45:35 0:06:56 - @2 Tag2, Tag3 11:46:21 12:00:17 0:13:56 0:20:52 +Wk Date Day ID Tags Annotation Start End Time Total +--- ---------- --- -- ---------- ---------- -------- -------- ------- ------- +W10 2017-03-09 Thu @3 Tag2 11:38:39 11:45:35 0:06:56 + @2 Tag2, Tag3 11:46:21 12:00:17 0:13:56 0:20:52 - 0:20:52 + 0:20:52 """, out) def test_with_date_filter(self): @@ -150,11 +150,11 @@ W10 2017-03-09 Thu @3 Tag2 11:38:39 11:45:35 0:06:56 code, out, err = self.t("summary 2017-03-10 :ids") self.assertIn(""" -Wk Date Day ID Tags Start End Time Total ---- ---------- --- -- ---- -------- -------- ------- ------- -W10 2017-03-10 Fri @2 10:00:00 11:00:00 1:00:00 1:00:00 +Wk Date Day ID Tags Annotation Start End Time Total +--- ---------- --- -- ---- ---------- -------- -------- ------- ------- +W10 2017-03-10 Fri @2 10:00:00 11:00:00 1:00:00 1:00:00 - 1:00:00 + 1:00:00 """, out) def test_with_tag_filter(self): @@ -167,11 +167,11 @@ W10 2017-03-10 Fri @2 10:00:00 11:00:00 1:00:00 1:00:00 code, out, err = self.t("summary 2017-03-09 Tag1 :ids") self.assertIn(""" -Wk Date Day ID Tags Start End Time Total ---- ---------- --- -- ---- ------- ------- ------- ------- -W10 2017-03-09 Thu @4 Tag1 8:43:08 9:38:15 0:55:07 0:55:07 +Wk Date Day ID Tags Annotation Start End Time Total +--- ---------- --- -- ---- ---------- ------- ------- ------- ------- +W10 2017-03-09 Thu @4 Tag1 8:43:08 9:38:15 0:55:07 0:55:07 - 0:55:07 + 0:55:07 """, out) def test_with_id_filter(self): @@ -184,12 +184,12 @@ W10 2017-03-09 Thu @4 Tag1 8:43:08 9:38:15 0:55:07 0:55:07 code, out, err = self.t("summary @2 @4 :ids") self.assertIn(""" -Wk Date Day ID Tags Start End Time Total ---- ---------- --- -- ---------- -------- -------- ------- ------- -W10 2017-03-09 Thu @4 Tag1 8:43:08 9:38:15 0:55:07 - @2 Tag2, Tag3 11:46:21 12:00:17 0:13:56 1:09:03 +Wk Date Day ID Tags Annotation Start End Time Total +--- ---------- --- -- ---------- ---------- -------- -------- ------- ------- +W10 2017-03-09 Thu @4 Tag1 8:43:08 9:38:15 0:55:07 + @2 Tag2, Tag3 11:46:21 12:00:17 0:13:56 1:09:03 - 1:09:03 + 1:09:03 """, out) def test_non_contiguous_with_tag_filter(self): @@ -202,12 +202,12 @@ W10 2017-03-09 Thu @4 Tag1 8:43:08 9:38:15 0:55:07 code, out, err = self.t("summary 2017-03-09 Tag1 :ids") self.assertIn(""" -Wk Date Day ID Tags Start End Time Total ---- ---------- --- -- ---------- -------- -------- ------- ------- -W10 2017-03-09 Thu @4 Tag1 8:43:08 9:38:15 0:55:07 - @2 Tag1, Tag3 11:46:21 12:00:17 0:13:56 1:09:03 +Wk Date Day ID Tags Annotation Start End Time Total +--- ---------- --- -- ---------- ---------- -------- -------- ------- ------- +W10 2017-03-09 Thu @4 Tag1 8:43:08 9:38:15 0:55:07 + @2 Tag1, Tag3 11:46:21 12:00:17 0:13:56 1:09:03 - 1:09:03 + 1:09:03 """, out) def test_with_all_hint(self): @@ -229,13 +229,13 @@ W10 2017-03-09 Thu @4 Tag1 8:43:08 9:38:15 0:55:07 week_col_padding = 2 if (week_yesterday > 9 or week_now > 9 or week_tomorrow > 9) else 1 self.assertIn(""" -Wk{7: <{width}}Date Day ID Tags Start End Time Total --{6:-<{width}} ---------- --- -- ---- -------- -------- ------- ------- -W{3: <{width}} {0:%Y-%m-%d} {0:%a} @3 FOO 10:00:00 11:00:00 1:00:00 1:00:00 -W{4: <{width}} {1:%Y-%m-%d} {1:%a} @2 BAR 10:00:00 11:00:00 1:00:00 1:00:00 -W{5: <{width}} {2:%Y-%m-%d} {2:%a} @1 BAZ 10:00:00 11:00:00 1:00:00 1:00:00 +Wk{7: <{width}}Date Day ID Tags Annotation Start End Time Total +-{6:-<{width}} ---------- --- -- ---- ---------- -------- -------- ------- ------- +W{3: <{width}} {0:%Y-%m-%d} {0:%a} @3 FOO 10:00:00 11:00:00 1:00:00 1:00:00 +W{4: <{width}} {1:%Y-%m-%d} {1:%a} @2 BAR 10:00:00 11:00:00 1:00:00 1:00:00 +W{5: <{width}} {2:%Y-%m-%d} {2:%a} @1 BAZ 10:00:00 11:00:00 1:00:00 1:00:00 - {7: <{width}} 3:00:00 + {7: <{width}} 3:00:00 """.format(yesterday, now, tomorrow, week_yesterday, week_now, week_tomorrow, "-", " ", width=week_col_padding), out) @@ -271,11 +271,11 @@ W{5: <{width}} {2:%Y-%m-%d} {2:%a} @1 BAZ 10:00:00 11:00:00 1:00:00 1:00:00 two_digit_week = (week_yesterday > 9) self.assertIn(""" -Wk{2} Date Day ID Tags Start End Time Total ---{3} ---------- --- -- ---- -------- -------- ------- ------- -W{1} {0:%Y-%m-%d} {0:%a} @3 FOO 10:00:00 11:00:00 1:00:00 1:00:00 +Wk{2} Date Day ID Tags Annotation Start End Time Total +--{3} ---------- --- -- ---- ---------- -------- -------- ------- ------- +W{1} {0:%Y-%m-%d} {0:%a} @3 FOO 10:00:00 11:00:00 1:00:00 1:00:00 -{2} 1:00:00 +{2} 1:00:00 """.format(yesterday, week_yesterday, " " if two_digit_week is True else "", "-" if two_digit_week is True else ""), out) def test_with_named_date_today(self): @@ -295,11 +295,11 @@ W{1} {0:%Y-%m-%d} {0:%a} @3 FOO 10:00:00 11:00:00 1:00:00 1:00:00 two_digit_week = (week_now > 9) self.assertIn(""" -Wk{2} Date Day ID Tags Start End Time Total ---{3} ---------- --- -- ---- -------- -------- ------- ------- -W{1} {0:%Y-%m-%d} {0:%a} @2 BAR 10:00:00 11:00:00 1:00:00 1:00:00 +Wk{2} Date Day ID Tags Annotation Start End Time Total +--{3} ---------- --- -- ---- ---------- -------- -------- ------- ------- +W{1} {0:%Y-%m-%d} {0:%a} @2 BAR 10:00:00 11:00:00 1:00:00 1:00:00 -{2} 1:00:00 +{2} 1:00:00 """.format(now, week_now, " " if two_digit_week is True else "", "-" if two_digit_week is True else ""), out) def test_with_day_gap(self): @@ -310,12 +310,12 @@ W{1} {0:%Y-%m-%d} {0:%a} @2 BAR 10:00:00 11:00:00 1:00:00 1:00:00 code, out, err = self.t("summary 2017-03-09 - 2017-03-12 :ids") self.assertIn(""" -Wk Date Day ID Tags Start End Time Total ---- ---------- --- -- ---- -------- -------- ------- ------- -W10 2017-03-09 Thu @2 10:00:00 11:00:00 1:00:00 1:00:00 -W10 2017-03-11 Sat @1 10:00:00 11:00:00 1:00:00 1:00:00 +Wk Date Day ID Tags Annotation Start End Time Total +--- ---------- --- -- ---- ---------- -------- -------- ------- ------- +W10 2017-03-09 Thu @2 10:00:00 11:00:00 1:00:00 1:00:00 +W10 2017-03-11 Sat @1 10:00:00 11:00:00 1:00:00 1:00:00 - 2:00:00 + 2:00:00 """, out) def test_with_week_change(self): @@ -326,12 +326,12 @@ W10 2017-03-11 Sat @1 10:00:00 11:00:00 1:00:00 1:00:00 code, out, err = self.t("summary 2017-03-11 - 2017-03-14 :ids") self.assertIn(""" -Wk Date Day ID Tags Start End Time Total ---- ---------- --- -- ---- -------- -------- ------- ------- -W10 2017-03-11 Sat @2 10:00:00 11:00:00 1:00:00 1:00:00 -W11 2017-03-13 Mon @1 10:00:00 11:00:00 1:00:00 1:00:00 +Wk Date Day ID Tags Annotation Start End Time Total +--- ---------- --- -- ---- ---------- -------- -------- ------- ------- +W10 2017-03-11 Sat @2 10:00:00 11:00:00 1:00:00 1:00:00 +W11 2017-03-13 Mon @1 10:00:00 11:00:00 1:00:00 1:00:00 - 2:00:00 + 2:00:00 """, out) def test_with_several_tracks_per_day(self): @@ -346,16 +346,16 @@ W11 2017-03-13 Mon @1 10:00:00 11:00:00 1:00:00 1:00:00 code, out, err = self.t("summary 2017-03-09 - 2017-03-12 :ids") self.assertIn(""" -Wk Date Day ID Tags Start End Time Total ---- ---------- --- -- ---- -------- -------- ------- ------- -W10 2017-03-09 Thu @6 FOO 10:00:00 11:00:00 1:00:00 - @5 BAR 12:00:00 13:00:00 1:00:00 - @4 BAZ 14:00:00 15:00:00 1:00:00 3:00:00 -W10 2017-03-11 Sat @3 FOO 10:00:00 11:00:00 1:00:00 - @2 BAR 12:00:00 13:00:00 1:00:00 - @1 BAZ 14:00:00 15:00:00 1:00:00 3:00:00 +Wk Date Day ID Tags Annotation Start End Time Total +--- ---------- --- -- ---- ---------- -------- -------- ------- ------- +W10 2017-03-09 Thu @6 FOO 10:00:00 11:00:00 1:00:00 + @5 BAR 12:00:00 13:00:00 1:00:00 + @4 BAZ 14:00:00 15:00:00 1:00:00 3:00:00 +W10 2017-03-11 Sat @3 FOO 10:00:00 11:00:00 1:00:00 + @2 BAR 12:00:00 13:00:00 1:00:00 + @1 BAZ 14:00:00 15:00:00 1:00:00 3:00:00 - 6:00:00 + 6:00:00 """, out) def test_with_empty_interval_at_start_of_day(self): @@ -363,9 +363,9 @@ W10 2017-03-11 Sat @3 FOO 10:00:00 11:00:00 1:00:00 self.t("track sod - sod") code, out, err = self.t("summary :year") self.assertRegex(out, r""" -Wk ?Date Day Tags ?Start ?End Time Total +Wk ?Date Day ID Tags Annotation Start End Time Total [ -]+ -W\d{1,2} \d{4}-\d{2}-\d{2} .{3} ?0:00:00 0:00:00 0:00:00 0:00:00 +W\d{1,2} \d{4}-\d{2}-\d{2} .{3} @1 0:00:00 0:00:00 0:00:00 0:00:00 [ ]+0:00:00 """) diff --git a/test/undo.t b/test/undo.t index 21cdbcdb..e963ffa6 100755 --- a/test/undo.t +++ b/test/undo.t @@ -118,6 +118,9 @@ class TestUndo(TestCase): def test_undo_config_add_name(self): """Test undo of command 'config' (add name)""" + self.t("config") + initial = [x for x in self.t.timewrc_content if x != '\n'] + self.t("config foo bar :yes") before = [x for x in self.t.timewrc_content if x != '\n'] @@ -127,7 +130,7 @@ class TestUndo(TestCase): after = [x for x in self.t.timewrc_content if x != '\n'] self.assertNotEqual(before, after) - self.assertEqual([], after) + self.assertEqual(initial, after) def test_undo_config_remove_name(self): """Test undo of command 'config' (remove name)"""