mirror of
https://github.com/GothenburgBitFactory/timewarrior.git
synced 2025-07-07 20:06:39 +02:00
Compare commits
18 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d925553116 | ||
![]() |
d43e962c6c | ||
![]() |
ae06f0526a | ||
![]() |
1fd82ec137 | ||
![]() |
1d3dd8f440 | ||
![]() |
191ad1ec8a | ||
![]() |
817023f75d | ||
![]() |
9b91a7ac71 | ||
![]() |
331433c688 | ||
![]() |
09ed1d1e22 | ||
![]() |
6c74f7aa3c | ||
![]() |
9cc2844595 | ||
![]() |
ad838354a1 | ||
![]() |
dafced21ef | ||
![]() |
7dc7f54d3c | ||
![]() |
7158687398 | ||
![]() |
f36ad01788 | ||
![]() |
e092041a38 |
27 changed files with 270 additions and 191 deletions
4
.github/workflows/docker-image.yaml
vendored
4
.github/workflows/docker-image.yaml
vendored
|
@ -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"
|
||||
|
|
12
.github/workflows/tests.yaml
vendored
12
.github/workflows/tests.yaml
vendored
|
@ -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:
|
||||
|
|
1
AUTHORS
1
AUTHORS
|
@ -129,3 +129,4 @@ Thanks to the following, who submitted detailed bug reports and excellent sugges
|
|||
Eugene Morozov
|
||||
Stefan Herold
|
||||
Sebastian Carlos
|
||||
ftambara
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -23,4 +23,4 @@ Then having selected '@2' as the interval you wish to delete:
|
|||
|
||||
|
||||
== SEE ALSO
|
||||
*timew-cancel*(1)
|
||||
**timew-cancel**(1)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
**timew-diagnostics**(1)
|
||||
|
|
|
@ -27,4 +27,4 @@ Note that you can fill multiple intervals:
|
|||
|
||||
|
||||
== SEE ALSO
|
||||
**timew-hints**(1)
|
||||
**timew-hints**(7)
|
||||
|
|
|
@ -19,4 +19,4 @@ For example:
|
|||
It is an error to reference an interval or tag that does not exist.
|
||||
|
||||
== SEE ALSO
|
||||
*timew-DOM*
|
||||
**timew-dom**(7)
|
||||
|
|
|
@ -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)
|
|
@ -6,6 +6,7 @@ timew-report - run an extension report
|
|||
== SYNOPSIS
|
||||
[verse]
|
||||
*timew* [*report*] _<report>_ [_<range>_] [_<tag>_**...**]
|
||||
*timew* [*report*] _<report>_ _<id>_**...**
|
||||
|
||||
== 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.**__<name>__**.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**.
|
||||
|
|
|
@ -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**::
|
||||
|
|
|
@ -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: .
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include <ChartConfig.h>
|
||||
#include <Duration.h>
|
||||
#include <IntervalFilterAllInRange.h>
|
||||
#include <IntervalFilterAllWithIds.h>
|
||||
#include <IntervalFilterAllWithTags.h>
|
||||
#include <IntervalFilterAndGroup.h>
|
||||
#include <Range.h>
|
||||
|
@ -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 <IntervalFilterAllInRange> (range),
|
||||
std::make_shared <IntervalFilterAllWithTags> (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 <Interval> 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 <IntervalFilterAllInRange> (range),
|
||||
std::make_shared <IntervalFilterAllWithTags> (tags)
|
||||
});
|
||||
|
||||
tracked = getTracked (database, rules, filtering);
|
||||
}
|
||||
|
||||
if (tracked.empty ())
|
||||
{
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <IntervalFilterAllWithTags.h>
|
||||
#include <IntervalFilterAndGroup.h>
|
||||
#include <commands.h>
|
||||
#include <format.h>
|
||||
#include <iostream>
|
||||
#include <timew.h>
|
||||
|
||||
|
@ -39,33 +40,52 @@ int CmdExport (
|
|||
Database& database)
|
||||
{
|
||||
auto ids = cli.getIds ();
|
||||
auto range = cli.getRange ();
|
||||
auto tags = cli.getTags ();
|
||||
|
||||
std::shared_ptr <IntervalFilter> filtering;
|
||||
if (! ids.empty () && ! tags.empty ())
|
||||
{
|
||||
throw std::string ("You cannot specify both id and tags/range to export intervals.");
|
||||
}
|
||||
|
||||
std::vector <Interval> 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 <IntervalFilterAllWithIds> (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 <IntervalFilterAndGroup> (
|
||||
std::vector <std::shared_ptr <IntervalFilter>> (
|
||||
{
|
||||
std::make_shared <IntervalFilterAllInRange> (range),
|
||||
std::make_shared <IntervalFilterAllWithTags> (tags),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
auto range = cli.getRange ();
|
||||
|
||||
auto intervals = getTracked (database, rules, *filtering);
|
||||
IntervalFilterAndGroup filtering ({
|
||||
std::make_shared <IntervalFilterAllInRange> (range),
|
||||
std::make_shared <IntervalFilterAllWithTags> (tags)
|
||||
});
|
||||
|
||||
intervals = getTracked (database, rules, filtering);
|
||||
}
|
||||
|
||||
std::cout << jsonFromIntervals (intervals);
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#include <FS.h>
|
||||
#include <IntervalFilterAllInRange.h>
|
||||
#include <IntervalFilterAllWithIds.h>
|
||||
#include <IntervalFilterAllWithTags.h>
|
||||
#include <IntervalFilterAndGroup.h>
|
||||
#include <cmake.h>
|
||||
|
@ -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 <IntervalFilterAllInRange> (range),
|
||||
std::make_shared <IntervalFilterAllWithTags> (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 <Interval> 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 <IntervalFilterAllInRange> (range),
|
||||
std::make_shared <IntervalFilterAllWithTags> (tags)
|
||||
});
|
||||
|
||||
tracked = getTracked (database, rules, filtering);
|
||||
}
|
||||
|
||||
// Compose Header info.
|
||||
rules.set ("temp.report.start", range.is_started () ? range.start.toISO () : "");
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 2aa844cb9b015fca81b947c57fde07999ede002b
|
||||
Subproject commit 121f757c3ec1b1f548f7835208b8c72d85d141a7
|
|
@ -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<std::string> defaultConfig = {
|
||||
"reports.summary.ids = yes\n",
|
||||
"reports.summary.annotations = yes\n",
|
||||
};
|
||||
configFile.append(defaultConfig);
|
||||
}
|
||||
|
||||
// Load the configuration data.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM fedora:40
|
||||
FROM fedora:42
|
||||
|
||||
RUN dnf update -y
|
||||
RUN dnf install -y \
|
|
@ -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"]
|
140
test/summary.t
140
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
|
||||
""")
|
||||
|
|
|
@ -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)"""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue