Compare commits

...

92 Commits
v1.4.1 ... main

Author SHA1 Message Date
Abraham Toriz 08ff8254d1
derive some defaults to make clippy happy 2023-03-14 16:04:57 -05:00
Abraham Toriz 22d16ff5ba separate cli definition into its own file 2023-03-11 19:29:09 -06:00
Abraham Toriz 530dd8dc15
simpler way of specifying no command 2023-02-13 19:58:13 -06:00
Abraham Toriz 858302839c
upgrade clap to v3 2023-02-13 18:17:47 -06:00
Abraham Toriz 56393636c9
change wording in readme 2023-02-03 12:17:09 -06:00
Abraham Toriz 518fe26c82
fix changelog 2022-11-26 21:16:46 -06:00
Abraham Toriz dd6b9f9a8d
Redefined CI and smaller features 2022-11-26 21:01:52 -06:00
Abraham Toriz 6afb517535
changelog and docs for 1.6 2022-11-26 21:00:11 -06:00
Abraham Toriz 3f83485769
another two paths were incorrect 2022-11-26 19:56:51 -06:00
Abraham Toriz 5d8d80fcfd
condense podman command lines in scripts 2022-11-26 19:43:38 -06:00
Abraham Toriz 4d1e3537df
fix package name again in archlinux-bin 2022-11-26 19:26:40 -06:00
Abraham Toriz a8990533bc
changelog for new release 2022-11-26 18:57:12 -06:00
Abraham Toriz 9cc6a88de6
a task for later 2022-11-26 18:54:26 -06:00
Abraham Toriz 5ae5e32cfb
fix variable names in CI 2022-11-26 18:54:17 -06:00
Abraham Toriz d56ef65f73
add a (un)install script to package 2022-11-26 18:52:41 -06:00
Abraham Toriz c9f5782f59
fix path to artifacts in CI 2022-11-26 18:23:55 -06:00
Abraham Toriz 889295fd58
finish restructure of the CI pipelines 2022-11-26 18:00:47 -06:00
Abraham Toriz b3959782b1
dont run test and build in different steps 2022-11-26 01:20:02 -06:00
Abraham Toriz ec87086b26
container accomplished 2022-11-26 01:15:48 -06:00
Abraham Toriz c939ae5753
next task 2022-11-26 01:07:56 -06:00
Abraham Toriz 1e68467d36
a container image capable of building tiempo consistently 2022-11-26 01:07:47 -06:00
Abraham Toriz 2dc6cda4f2
move my TODO list to the readme 2022-11-26 00:07:02 -06:00
Abraham Toriz 709cfda05b
actually display the timesheets after the charts so it is always visible 2022-11-25 09:09:23 -06:00
Abraham Toriz 83498ac654
display sheet name(s) in chart formatter 2022-11-25 09:03:40 -06:00
Abraham Toriz b75b412c23
adjust release steps 2022-11-10 11:45:00 -06:00
Abraham Toriz 28c91053be
update changelog 2022-11-09 14:22:40 -06:00
Abraham Toriz 9b07bedf55
display a nicer message when no entries are running with t now 2022-11-09 14:19:11 -06:00
Abraham Toriz 57ec60226d
path in pipeline definition was wrong, again 2022-11-07 00:41:23 -06:00
Abraham Toriz c588db6b45
fix paths in aur-git package 2022-11-07 00:39:24 -06:00
Abraham Toriz e7d261f75f
aur-bin archive was missing the verification sum 2022-11-07 00:33:39 -06:00
Abraham Toriz 2f56a6ff79
bash completions were in the wrong place 2022-11-07 00:04:47 -06:00
Abraham Toriz 1c9527f3a0
put artifacts in a folder because gitlab is not rendering my variables anymore 2022-11-06 23:23:41 -06:00
Abraham Toriz 2948adac15
fix som paths in the debian archive 2022-11-06 22:02:41 -06:00
Abraham Toriz c6225f3821
fix latest ci pipeline 2022-11-06 21:29:33 -06:00
Abraham Toriz 425df3aeca
fix directory name in release CI 2022-11-06 21:28:43 -06:00
Abraham Toriz 37a779b0f1
publish completions 2022-11-06 21:11:53 -06:00
Abraham Toriz ec7375d33e
document --flat as a feature released in 1.5.3 2022-11-06 21:09:44 -06:00
Abraham Toriz 48a8fa22f2
explain how to install the binary in linux 2022-11-06 21:05:59 -06:00
Abraham Toriz 6496d684ff
add completions to debian, binary and arch packages 2022-11-06 20:46:45 -06:00
Abraham Toriz bc2b96d425
clippy lints 2022-11-05 20:34:35 -06:00
Abraham Toriz 3fa0509319
document --flat in changelog and man page 2022-11-04 22:46:19 -06:00
Abraham Toriz 9433903cdf
add zsh completion 2022-11-04 22:00:41 -06:00
Abraham Toriz cfb2989853
use list's --flat option for bash completion 2022-11-04 22:00:18 -06:00
Abraham Toriz 762520797a
add a --flat option to list and fish completion 2022-11-04 21:56:41 -06:00
Abraham Toriz a689a68267
normalize family -> variants in the docs 2022-10-31 12:29:25 -06:00
Abraham Toriz 540e5ce53f
deb and aur-git release fixes 2022-10-31 01:05:36 -06:00
Abraham Toriz 2da1708885
add missing python-tomlkit dependency to aur-git package 2022-10-31 01:05:14 -06:00
Abraham Toriz ca2ddbb8de
add man page to deb package 2022-10-31 01:04:42 -06:00
Abraham Toriz 3d587c9c61
fix ci steps ordering 2022-10-31 00:32:31 -06:00
Abraham Toriz 44137b835a
build-doc must be a step prior to build 2022-10-31 00:32:11 -06:00
Abraham Toriz 7d4e4645fc
shorten publish command 2022-10-31 00:21:47 -06:00
Abraham Toriz bcc929dbc0
First release to include man page 2022-10-31 00:15:00 -06:00
Abraham Toriz 1b6077b1a0
some clippy lints 2022-10-31 00:03:11 -06:00
Abraham Toriz aaf5f89c86
theres no master branch 2022-10-30 23:40:41 -06:00
Abraham Toriz f4e733f9b8
set pipeline to build docs on master 2022-10-30 23:38:19 -06:00
Abraham Toriz f3bdc85d4c
link to new docs in crates.io 2022-10-30 23:30:57 -06:00
Abraham Toriz 0eb601b466
remove old section of why rewriting 2022-10-30 23:25:49 -06:00
Abraham Toriz 610e3e7f9d
mention in readme that man page is not installed by cargo 2022-10-30 23:25:16 -06:00
Abraham Toriz 16970f7557
finish reshaping of the tutorial 2022-10-30 23:13:14 -06:00
Abraham Toriz aa6fca4245
remove duplicated word 2022-10-30 11:11:29 -06:00
Abraham Toriz 7c9334da5c
remove dashes from subcommand titles as they might be confusing 2022-10-30 11:11:11 -06:00
Abraham Toriz 45f83113ff
more style fixes 2022-10-29 10:05:55 -05:00
Abraham Toriz 4d96688b9b
normalize style, sorry :( 2022-10-28 18:50:50 -05:00
Abraham Toriz ed3d8a7ab5
command for running autobuild was wrong 2022-10-28 18:50:28 -05:00
perro tuerto 7ede6ff1da
Tutorial writing 2022-10-28 18:10:36 -05:00
Abraham Toriz 805bdd2de1
shorten sample url to not disturb mobile view 2022-09-30 11:16:54 -04:00
Abraham Toriz f032216e16
document files, paths and environment variables 2022-09-30 07:42:38 -04:00
Abraham Toriz e52aafaf7b
finish documenting settings 2022-09-30 07:19:40 -04:00
Abraham Toriz 3d63a581a4
improve example of custom formatter 2022-09-28 23:33:45 -04:00
Abraham Toriz 37893a9e9a
make subcommand table reference each subcommand in html version 2022-09-28 23:33:20 -04:00
Abraham Toriz d15f120ef9
dont suggest to file an issue if there is one 2022-09-28 15:00:40 -04:00
Abraham Toriz b515b0a317
document most settings 2022-09-27 22:47:54 -04:00
Abraham Toriz f3b4c99760
t-out, t-resume and t-sheet documented 2022-09-27 21:42:49 -04:00
Abraham Toriz 22510dfcd2
docs on how to build the docs and autoreload 2022-09-27 21:42:19 -04:00
Abraham Toriz 41a414d6d2
document t-list and t-now 2022-09-26 23:45:51 -04:00
Abraham Toriz bd1785058e
document t-archive and t-kill 2022-09-26 10:24:26 -04:00
Abraham Toriz 7115ddfe54
more docs 2022-09-25 23:35:09 -04:00
Abraham Toriz 5f7fc63ef4
document all display commands 2022-09-25 21:33:01 -04:00
Abraham Toriz 4c486ae888
add logo to docs 2022-09-25 21:32:43 -04:00
Abraham Toriz 49e068c5a3
more docs for some commands 2022-09-25 18:14:06 -04:00
Abraham Toriz 5e4fd1cb37
enhance description in the docs 2022-09-25 16:09:10 -04:00
Abraham Toriz 61f80f2be6
install all requirements.txt for building docs 2022-09-24 23:32:53 -04:00
Abraham Toriz 023f78bdae
dont run cargo in the man-page branch for now 2022-09-24 23:30:46 -04:00
Abraham Toriz a997e25a5d
use correct pipeline syntax 2022-09-24 23:29:03 -04:00
Abraham Toriz f742264e29
attempt to publish the docs 2022-09-24 23:25:29 -04:00
Abraham Toriz 6be61a26d0
explain how to test the man page 2022-09-20 10:52:42 -04:00
Abraham Toriz b617257f0c
define the structure of tiempo's man page 2022-09-20 10:45:27 -04:00
Abraham Toriz 78abfa16e6
adapt CI to include man file 2022-09-20 10:43:43 -04:00
Abraham Toriz 0bd576523b
list subcommands in docs 2022-09-18 23:24:44 -04:00
Abraham Toriz 2ca3189fe6
read version from Cargo.toml 2022-09-18 22:55:46 -04:00
Abraham Toriz efc5055262
move current docs from readme to docs 2022-09-18 22:54:56 -04:00
Abraham Toriz a41817f438
restart the docs 2022-09-17 23:22:22 -04:00
66 changed files with 2535 additions and 1853 deletions

4
.gitignore vendored
View File

@ -4,3 +4,7 @@
/*.sqlite3
docs/*/build/
dev_config.toml
Pipfile*
artifacts/
build/
debian-package/

View File

@ -1,5 +1,6 @@
stages:
- test
- build-doc
- build
- upload
- release
@ -13,33 +14,45 @@ test:cargo:
- rustup component add clippy
- cargo clippy --all-targets --all-features -- -D warnings
- cargo test
rules:
- if: $CI_COMMIT_BRANCH == "main"
build-doc:
stage: build-doc
image: python:3.10
script:
- cd docs/
- pip install -r requirements.txt
- make html
- make man
- gzip build/man/tiempo.1
rules:
- if: $CI_COMMIT_BRANCH == "main"
artifacts:
paths:
- docs/build/html
- docs/build/man/tiempo.1.gz
publish-doc:
stage: release
image: kroniak/ssh-client
script:
- mkdir -p ~/.ssh
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
- eval $(ssh-agent -s)
- ssh-add <(echo "$PRIVATE_KEY")
- scp -r docs/build/html/* $SERVER_USER@$SERVER_HOST:$APP_PATH
rules:
- if: $CI_COMMIT_BRANCH == "main"
build:
stage: build
image: categulario/rust-cli-image:latest
image: categulario/tiempo-build-env:1.65
script:
# build the binary
- cargo build --locked --release
# create the tar package
- mkdir -p build
# move things to the build directory
- cp target/release/t build/t
- cp CHANGELOG.md build/CHANGELOG.md
- cp README.md build/README.md
- cp LICENSE build/LICENSE
# compress the tar file
- tar -cvzf tiempo-${CI_COMMIT_TAG:1}-x86_64.tar.gz build/
# makes the debian archive
- ./debpackage.sh
# computes the sums
- sha256sum tiempo-${CI_COMMIT_TAG:1}-x86_64.tar.gz > tiempo-${CI_COMMIT_TAG:1}-x86_64.tar.gz.sum
- sha256sum debian-package/tiempo_${CI_COMMIT_TAG:1}_amd64.deb > tiempo_${CI_COMMIT_TAG:1}_amd64.deb.sum
- ./scripts/build.sh
artifacts:
paths:
- tiempo-${CI_COMMIT_TAG:1}-x86_64.tar.gz
- debian-package/tiempo_${CI_COMMIT_TAG:1}_amd64.deb
- tiempo-${CI_COMMIT_TAG:1}-x86_64.tar.gz.sum
- tiempo_${CI_COMMIT_TAG:1}_amd64.deb.sum
- artifacts/
rules:
- if: $CI_COMMIT_BRANCH
when: never
@ -58,10 +71,10 @@ upload:
when: never
- if: $CI_COMMIT_TAG =~ /^v*/
script:
- 'curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file tiempo-${CI_COMMIT_TAG:1}-x86_64.tar.gz ${PACKAGE_REGISTRY_URL}/tiempo-${CI_COMMIT_TAG:1}-x86_64.tar.gz'
- 'curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file tiempo-${CI_COMMIT_TAG:1}-x86_64.tar.gz.sum ${PACKAGE_REGISTRY_URL}/tiempo-${CI_COMMIT_TAG:1}-x86_64.tar.gz.sum'
- 'curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file debian-package/tiempo_${CI_COMMIT_TAG:1}_amd64.deb ${PACKAGE_REGISTRY_URL}/tiempo_${CI_COMMIT_TAG:1}_amd64.deb'
- 'curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file tiempo_${CI_COMMIT_TAG:1}_amd64.deb.sum ${PACKAGE_REGISTRY_URL}/tiempo_${CI_COMMIT_TAG:1}_amd64.deb.sum'
- 'curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file artifacts/tiempo-${CI_COMMIT_TAG}-x86_64.tar.gz ${PACKAGE_REGISTRY_URL}/tiempo-${CI_COMMIT_TAG}-x86_64.tar.gz'
- 'curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file artifacts/tiempo-${CI_COMMIT_TAG}-x86_64.tar.gz.sum ${PACKAGE_REGISTRY_URL}/tiempo-${CI_COMMIT_TAG}-x86_64.tar.gz.sum'
- 'curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file artifacts/tiempo_${CI_COMMIT_TAG}_amd64.deb ${PACKAGE_REGISTRY_URL}/tiempo_${CI_COMMIT_TAG}_amd64.deb'
- 'curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file artifacts/tiempo_${CI_COMMIT_TAG}_amd64.deb.sum ${PACKAGE_REGISTRY_URL}/tiempo_${CI_COMMIT_TAG}_amd64.deb.sum'
release:
stage: release
@ -80,13 +93,13 @@ release:
assets:
links:
- name: 'Any linux binary'
url: '${PACKAGE_REGISTRY_URL}/tiempo-${CI_COMMIT_TAG:1}-x86_64.tar.gz'
url: '${PACKAGE_REGISTRY_URL}/tiempo-${CI_COMMIT_TAG}-x86_64.tar.gz'
- name: 'Any linux binary sha256 sum'
url: '${PACKAGE_REGISTRY_URL}/tiempo-${CI_COMMIT_TAG:1}-x86_64.tar.gz.sum'
url: '${PACKAGE_REGISTRY_URL}/tiempo-${CI_COMMIT_TAG}-x86_64.tar.gz.sum'
- name: 'Debian archive'
url: '${PACKAGE_REGISTRY_URL}/tiempo_${CI_COMMIT_TAG:1}_amd64.deb'
url: '${PACKAGE_REGISTRY_URL}/tiempo_${CI_COMMIT_TAG}_amd64.deb'
- name: 'Debian archive sha256 sum'
url: '${PACKAGE_REGISTRY_URL}/tiempo_${CI_COMMIT_TAG:1}_amd64.deb.sum'
url: '${PACKAGE_REGISTRY_URL}/tiempo_${CI_COMMIT_TAG}_amd64.deb.sum'
deploy:arch-bin:
stage: aur
@ -100,8 +113,17 @@ deploy:arch-bin:
# setup git, because we'll commit
- git config --global user.name "$COMMITER_NAME"
- git config --global user.email "$COMMITER_EMAIL"
# finally run the script
# Clone the repo
- git clone $BIN_REPO_URL tiempo-bin
# generate the PKGBUILD in the current directory
- scripts/release-aur-bin.sh
- mv PKGBUILD tiempo-bin/
- mv .SRCINFO tiempo-bin/
# commit
- cd tiempo-bin
- git add .
- git commit -m "Release version $CI_COMMIT_TAG"
- git push
rules:
- if: $CI_COMMIT_BRANCH
when: never
@ -119,23 +141,18 @@ deploy:arch-git:
# setup git, because we'll commit
- git config --global user.name "$COMMITER_NAME"
- git config --global user.email "$COMMITER_EMAIL"
# clone the repo
- git clone $GIT_REPO_URL tiempo-git
# finally run the script
- scripts/release-aur-git.sh
- mv PKGBUILD tiempo-git/
- mv .SRCINFO tiempo-git/
# and commit
- cd tiempo-git
- git add .
- git commit -m "Release version $VERSION"
- git push
rules:
- if: $CI_COMMIT_BRANCH
when: never
- if: $CI_COMMIT_TAG =~ /^v*/
# pages:
# image: python:3.8-alpine
# stage: deploy
# script:
# - pip install -U sphinx
# - mkdir -p public/{es,en}
# - sphinx-build -b html ./docs/es/source/ public/es
# - sphinx-build -b html ./docs/en/source/ public/en
# artifacts:
# paths:
# - public
# rules:
# - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH

View File

@ -1,5 +1,23 @@
# Changes
## 1.6
- `t now` displays a nicer message if no entries are running.
- `chart` formatter now shows the sheet name(s) that are being displayed.
- any-linux package now includes install script.
## 1.5.3
- added `--flat` option to `list` that displays only available sheet names one
per line
- completions for bash, fish and zsh added to the package
- binary package structure changed
## 1.5
- First release to include the man page. Documentation is now published in
https://tiempo.categulario.xyz
## 1.4.1
- Fix config not being passed to custom formatters

555
Cargo.lock generated
View File

@ -4,9 +4,9 @@ version = 3
[[package]]
name = "ahash"
version = "0.7.4"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43bb833f0bf979d8475d38fbf09ed3b8a55e1885fe93ad3f93239fc6a4f17b98"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom",
"once_cell",
@ -15,20 +15,20 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "0.7.18"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"winapi",
"libc",
]
[[package]]
@ -68,15 +68,15 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.2.1"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bstr"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"lazy_static",
"memchr",
@ -85,10 +85,16 @@ dependencies = [
]
[[package]]
name = "cc"
version = "1.0.69"
name = "bumpalo"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
@ -98,31 +104,43 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.19"
version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
dependencies = [
"libc",
"iana-time-zone",
"js-sys",
"num-integer",
"num-traits",
"serde",
"time",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "clap"
version = "2.33.3"
version = "3.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
dependencies = [
"ansi_term 0.11.0",
"atty",
"bitflags",
"clap_lex",
"indexmap",
"once_cell",
"strsim",
"textwrap 0.11.0",
"unicode-width",
"vec_map",
"termcolor",
"textwrap 0.16.0",
]
[[package]]
name = "clap_lex"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
[[package]]
@ -134,6 +152,22 @@ dependencies = [
"bitflags",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "csv"
version = "1.1.6"
@ -142,7 +176,7 @@ checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
dependencies = [
"bstr",
"csv-core",
"itoa 0.4.7",
"itoa 0.4.8",
"ryu",
"serde",
]
@ -158,19 +192,63 @@ dependencies = [
[[package]]
name = "ctor"
version = "0.1.20"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d"
checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "diff"
version = "0.1.12"
name = "cxx"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
checksum = "90d59d9acd2a682b4e40605a242f6670eaa58c5957471cbf85e8aa6a0b97a5e8"
dependencies = [
"cc",
"cxxbridge-flags",
"cxxbridge-macro",
"link-cplusplus",
]
[[package]]
name = "cxx-build"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebfa40bda659dd5c864e65f4c9a2b0aff19bea56b017b9b77c73d3766a453a38"
dependencies = [
"cc",
"codespan-reporting",
"once_cell",
"proc-macro2",
"quote",
"scratch",
"syn",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "457ce6757c5c70dc6ecdbda6925b958aae7f959bda7d8fb9bde889e34a09dc03"
[[package]]
name = "cxxbridge-macro"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebf883b7aacd7b2aeb2a7b338648ee19f57c140d4ee8e52c68979c6b2f7f2263"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]]
name = "directories"
@ -183,9 +261,9 @@ dependencies = [
[[package]]
name = "dirs-sys"
version = "0.3.6"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780"
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [
"libc",
"redox_users",
@ -194,9 +272,9 @@ dependencies = [
[[package]]
name = "either"
version = "1.6.1"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "fallible-iterator"
@ -210,6 +288,15 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fastrand"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
dependencies = [
"instant",
]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
@ -218,13 +305,13 @@ checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "getrandom"
version = "0.2.3"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
@ -238,9 +325,9 @@ dependencies = [
[[package]]
name = "hashlink"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d452c155cb93fecdfb02a73dd57b5d8e442c2063bd7aac72f1bc5e4263a43086"
checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa"
dependencies = [
"hashbrown",
]
@ -266,15 +353,48 @@ dependencies = [
]
[[package]]
name = "indexmap"
version = "1.9.1"
name = "iana-time-zone"
version = "0.1.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
dependencies = [
"cxx",
"cxx-build",
]
[[package]]
name = "indexmap"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg 1.1.0",
"hashbrown",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "isolang"
version = "1.0.0"
@ -287,24 +407,33 @@ dependencies = [
[[package]]
name = "itertools"
version = "0.10.1"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.7"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "itoa"
version = "1.0.3"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]]
name = "js-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
@ -314,21 +443,39 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.98"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "libsqlite3-sys"
version = "0.25.1"
version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f0455f2c1bc9a7caa792907026e469c1d91761fb0ea37cbb16427c77280cf35"
checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "link-cplusplus"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
dependencies = [
"cc",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "match_cfg"
version = "0.1.0"
@ -337,15 +484,15 @@ checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
[[package]]
name = "memchr"
version = "2.4.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "num-integer"
version = "0.1.44"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg 1.1.0",
"num-traits",
@ -353,24 +500,30 @@ dependencies = [
[[package]]
name = "num-traits"
version = "0.2.14"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "once_cell"
version = "1.8.0"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]]
name = "os_str_bytes"
version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "output_vt100"
version = "0.1.2"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66"
dependencies = [
"winapi",
]
@ -401,7 +554,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662"
dependencies = [
"phf_shared",
"rand 0.6.5",
"rand",
]
[[package]]
@ -415,42 +568,36 @@ dependencies = [
[[package]]
name = "pkg-config"
version = "0.3.19"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
[[package]]
name = "ppv-lite86"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]]
name = "pretty_assertions"
version = "1.2.1"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c89f989ac94207d048d92db058e4f6ec7342b0971fc58d1271ca148b799b3563"
checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755"
dependencies = [
"ansi_term 0.12.1",
"ctor",
"diff",
"output_vt100",
"yansi",
]
[[package]]
name = "proc-macro2"
version = "1.0.43"
version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.9"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
@ -463,9 +610,9 @@ checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
dependencies = [
"autocfg 0.1.8",
"libc",
"rand_chacha 0.1.1",
"rand_chacha",
"rand_core 0.4.2",
"rand_hc 0.1.0",
"rand_hc",
"rand_isaac",
"rand_jitter",
"rand_os",
@ -474,18 +621,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "rand"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_core 0.6.3",
"rand_hc 0.3.1",
]
[[package]]
name = "rand_chacha"
version = "0.1.1"
@ -496,16 +631,6 @@ dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.3",
]
[[package]]
name = "rand_core"
version = "0.3.1"
@ -521,15 +646,6 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.1.0"
@ -539,15 +655,6 @@ dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_hc"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
dependencies = [
"rand_core 0.6.3",
]
[[package]]
name = "rand_isaac"
version = "0.1.1"
@ -612,28 +719,29 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.2.9"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.0"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom",
"redox_syscall",
"thiserror",
]
[[package]]
name = "regex"
version = "1.5.4"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
"aho-corasick",
"memchr",
@ -648,9 +756,9 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
[[package]]
name = "regex-syntax"
version = "0.6.25"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "remove_dir_all"
@ -678,24 +786,30 @@ dependencies = [
[[package]]
name = "ryu"
version = "1.0.5"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
[[package]]
name = "scratch"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
[[package]]
name = "serde"
version = "1.0.144"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.144"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [
"proc-macro2",
"quote",
@ -704,23 +818,23 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.65"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28c5e91e4240b46c4c19219d6cc84784444326131a4210f496f948d5cc827a29"
checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
dependencies = [
"itoa 0.4.7",
"itoa 1.0.5",
"ryu",
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.9.11"
version = "0.9.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89f31df3f50926cdf2855da5fd8812295c34752cb20438dae42a67f79e021ac3"
checksum = "8fb06d4b6cdaef0e0c51fa881acb721bed3c924cfaa71d9c94a3b771dfdf6567"
dependencies = [
"indexmap",
"itoa 1.0.3",
"itoa 1.0.5",
"ryu",
"serde",
"unsafe-libyaml",
@ -734,9 +848,9 @@ checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
[[package]]
name = "smallvec"
version = "1.6.1"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "smawk"
@ -746,15 +860,15 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
[[package]]
name = "strsim"
version = "0.8.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.99"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
@ -763,25 +877,25 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.2.0"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
dependencies = [
"cfg-if",
"fastrand",
"libc",
"rand 0.8.4",
"redox_syscall",
"remove_dir_all",
"winapi",
]
[[package]]
name = "textwrap"
version = "0.11.0"
name = "termcolor"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"unicode-width",
"winapi-util",
]
[[package]]
@ -796,19 +910,25 @@ dependencies = [
]
[[package]]
name = "thiserror"
version = "1.0.26"
name = "textwrap"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
[[package]]
name = "thiserror"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.26"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
@ -817,9 +937,9 @@ dependencies = [
[[package]]
name = "tiempo"
version = "1.4.1"
version = "1.6.0"
dependencies = [
"ansi_term 0.12.1",
"ansi_term",
"atty",
"chrono",
"clap",
@ -843,11 +963,12 @@ dependencies = [
[[package]]
name = "time"
version = "0.1.43"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
@ -863,39 +984,40 @@ dependencies = [
[[package]]
name = "toml"
version = "0.5.8"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]]
name = "unicode-ident"
version = "1.0.3"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "unicode-linebreak"
version = "0.1.2"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f"
checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
dependencies = [
"hashbrown",
"regex",
]
[[package]]
name = "unicode-width"
version = "0.1.8"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "unsafe-libyaml"
version = "0.2.2"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "931179334a56395bcf64ba5e0ff56781381c1a5832178280c7d7f91d1679aeb0"
checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2"
[[package]]
name = "vcpkg"
@ -903,23 +1025,77 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.3"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
[[package]]
name = "winapi"
@ -937,8 +1113,23 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "yansi"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"

View File

@ -1,6 +1,6 @@
[[bin]]
name = "t"
path = "src/main.rs"
path = "src/bin/t.rs"
[package]
name = "tiempo"
@ -9,12 +9,12 @@ authors = ["Abraham Toriz <categulario@gmail.com>"]
edition = "2021"
description = "A command line time tracker"
license = "GPL-3.0"
documentation = "https://gitlab.com/categulario/tiempo-rs"
documentation = "https://tiempo.categulario.xyz"
homepage = "https://gitlab.com/categulario/tiempo-rs"
version = "1.4.1"
version = "1.6.0"
default-run = "t"
[dependencies]
clap = "2"
thiserror = "1"
directories = "3"
serde_yaml = "0.9"
@ -31,6 +31,10 @@ hostname = "0.3"
atty = "0.2"
timeago = "0.3"
[dependencies.clap]
version = "3"
features = ["cargo"]
[dependencies.chrono]
version = "0.4"
features = ["serde"]

12
Containerfile Normal file
View File

@ -0,0 +1,12 @@
# This Containerfile builds the tiempo-build-env container image used to build
# tiempo in CI and local environments.
FROM docker.io/rust:1.65
RUN apt-get update && apt-get upgrade -y && \
apt-get install -y --no-install-recommends \
fakeroot \
python3-minimal \
python3-sphinx \
python3-tomlkit \
&& apt-get -q clean \
&& rm -rf /var/lib/apt/lists/*

444
README.md
View File

@ -1,29 +1,35 @@
# Tiempo
A [timetrap](https://github.com/samg/timetrap/) compatible command line time
tracking application.
tracking application. [Read the fine manual](https://tiempo.categulario.xyz).
## Installation
### For Archlinux (and derivatives) users
There are a binary and a source package in the AUR:
There are both binary and source packages in the AUR:
* [tiempo-bin](https://aur.archlinux.org/packages/tiempo-bin)
* [tiempo-git](https://aur.archlinux.org/packages/tiempo-git)
### For every other linux users
### For all other linux users
Go to [gitlab releases page](https://gitlab.com/categulario/tiempo-rs/-/releases)
and grab the latest binary for your linux. There is a `.deb` file for Debian and
Ubuntu as well as a binary for any `x86_64` Linux.
In the case of the tar archive you just need to run the included `install.sh`
script.
### For Rust developers
You have `cargo`! you can run:
cargo install tiempo
However that will not install the beautiful man page. Although you can still see
it at https://tiempo.categulario.xyz .
### For everyone else
You need to compile `tiempo` by yourself. But don't worry! It is not that hard.
@ -36,358 +42,6 @@ inside the repository. The binary will be named `t` (or `t.exe` if you use
windows) and it is located inside the `target/release` directory that was
created during compilation.
## Tutorial
First of all, you can abbreviate all commands to their first letter, so `t in`
and `t i` are equivalent.
### Managing entries
Register the start of an activity in the default timesheet with:
t in 'Doing some coding'
which sets the activity's start time to the current time. Later when you're done
use
t out
to mark it as finished. If you forgot to start the activity before you can do so
with:
t i --at '20 min ago'
the same applies for `t out`.
Edit an entry with
t edit [options]
where the options are
-i, --id <id:i> Alter entry with id <id> instead of the running entry
-s, --start <time:qs> Change the start time to <time>
-e, --end <time:qs> Change the end time to <time>
-a, --append Append to the current note instead of replacing it
the delimiter between appended notes is
configurable (see configure)
-m, --move <sheet> Move to another sheet
You can remove an entry with
t kill --id 123
or an entire timesheet with
t kill somesheet
check bellow to see how to get the ids.
### Displaying entries
At any point in time you can check your time spent in the current or other
timesheet with:
t display [options] [SHEET | all | full]
the available options are
-v, --ids Print database ids (for use with edit)
-s, --start <date:qs> Include entries that start on this date or later
-e, --end <date:qs> Include entries that start on this date or earlier
-f, --format <format> The output format. Valid built-in formats are
chart, text (default), ical, csv, json and ids.
Check the docs on defining custom formats bellow.
-g, --grep <regexp> Include entries where the note matches this regexp
Some shortcuts available are:
`today` - Display entries that started today
t today [--ids] [--format FMT] [SHEET | all]
`yesterday` - Display entries that started yesterday
t yesterday [--ids] [--format FMT] [SHEET | all]
`week` - Entries of this week so far. The default start of the week is Monday
(configurable).
t week [--ids] [--end DATE] [--format FMT] [SHEET | all]
`month` - Entries of this month or a specified one.
t month [--ids] [--start MONTH] [--format FMT] [SHEET | all]
#### Per command default formatters
**New in 1.4.0**
It might be the case that you want to use one default formatter for when you use
`t week` and a different one for `t month` or `t today`. That's what per-command
default formatters are for. To use them just add a `commands` section to your
config file and set `default_formatter` on some formatters. This is what it
would look like:
**In toml**
```toml
[commands.week]
default_formatter = "chart"
[commands.month]
default_formatter = "ical"
```
**In yaml**
```yaml
commands:
week:
default_formatter: chart
month:
default_formatter: ical
```
### Using different timesheets
You can organize your activities in different timesheets by first switching to
an existing one, then starting an activity:
t sheet somename
t in 'some activity'
which will also create the timesheet if it doesn't exist.
List all existing timesheets using
t list [all]
(defaults to not showing archive timesheets with names preceded by an
underscore)
### Advanced management
You can archive entries from a timesheet using:
t archive [OPTIONS] [SHEET]
which defaults to archiving all entries in the current sheet, or you can be more
specific using these options:
-s, --start <date:qs> Include entries that start on this date or later.
-e, --end <date:qs> Include entries that start on this date or earlier.
-g, --grep <regexp> Include entries where the note matches this regexp.
-t, --time <hours> Only archive up to `hours` hours.
This subcommand will move the selected entries to a hidden timesheet named
`_[SHEET]` (the name of the timesheet preceded by an underscore).
It is possible to access directly the sqlite database using
t backend
### Configuration
`tiempo` keeps a config file, whose location you can learn usign `t configure`.
It is also possible to edit the config file in-place passing arguments to
`t configure` like this:
t c --append-notes-delimiter ';'
it will print the resulting config file. Beware that it wont keep comments added
to the file.
## Specifying times
Some arguments accept a time as value, like `t in`'s `--at` or `t d --start`.
These are the accepted formats:
**Something similar to ISO format** will be parsed as a time in the computer's
timezone.
* `2021-01-13` a date
* `2019-05-03 11:13` a date with portions of a time
**ISO format with offset or UTC** will be parsed as a time in the specified
timezone. Use `Z` for `UTC` and an offset for everything else
* `2021-01-13Z`
* `2005-10-14 19:20:35+05:00`
**something that looks like an hour** will be parsed as a time in the current
day in the computer's timezone. Add `Z` or an offset to specify the timezone.
* `11:30`
* `23:50:45` (with seconds)
**some human times**, for now restricted to time ago:
* `an hour ago`
* `a minute ago`
* `50 min ago`
* `1h30m ago`
* `two hours thirty minutes ago`
## Default formatters
Tiempo comes with a handful of formatters that display your entries in different
ways. Here's the full list.
### chart
(New in 1.4.0) Displays a nice chart of the weekly progress.
**Settings**
By default this formatter will only display a nice chart of the progress divided
by week and respecting the `week_start` setting. However you may specify a daily
goal and a weekly goal to customize the chart, as well as how many minutes each
character represents:
In toml:
```toml
[formatters.chart]
daily_goal_hours = 4
weekly_goal_hours = 20
character_equals_minutes = 30
```
In yaml:
```yaml
formatters:
chart:
daily_goal_hours: 4
weekly_goal_hours: 20
character_equals_minutes: 30
```
### text
Displays nicely the entries grouped by sheet and day.
### ical
This formatter's output can be redirected to a file and then uploaded to a
calendar app.
### csv
Basically dump the entries as CSV to stdout. Useful for passing entries to a
different tool.
### json
Dump the entries in JSON format.
### ids
Dump only the ids in a single line
## Custom formatters
You can implement your own formatters for all subcommands that display entries
(like `t display`, `t week` etc.). It is as easy as creating an executable file
written in any programming language (interpreted or compiled) and placing it in
a path listed in the config value for `formatter_search_paths`.
This executable will be given as standard input a csv stream with each row
representing a time entry with the same structure as the `csv` formatter output.
It will also be given a command line argument representing user settings for
this formatter stored in the config file and formatted as JSON.
### Example
Suppose we have this config file:
```toml
database_file = "/home/user/.config/tiempo/database.sqlite3"
round_in_seconds = 900
append_notes_delimiter = " "
formatter_search_paths = ["/home/user/.config/tiempo/formatters"]
default_formatter = "text"
auto_sheet = "dotfiles"
auto_sheet_search_paths = ["/home/user/.config/tiempo/auto_sheets"]
auto_checkout = false
require_note = true
week_start = "Monday"
[formatters.earnings]
hourly_rate = 300
currency = "USD"
```
then we can create the `earnings` formatter by placing the following file in
`/home/user/.config/tiempo/formatters/earnings`:
```python
#!/usr/bin/env python3
import sys
import json
import csv
from datetime import datetime, timezone
from datetime import timedelta
from math import ceil
config = json.loads(sys.argv[1])
reader = csv.DictReader(
sys.stdin,
fieldnames=['id', 'start', 'end', 'note', 'sheet'],
)
total = timedelta(seconds=0)
for line in reader:
start = datetime.strptime(line['start'], '%Y-%m-%dT%H:%M:%S.%fZ')
if not line['end']:
end = datetime.utcnow()
else:
end = datetime.strptime(line['end'], '%Y-%m-%dT%H:%M:%S.%fZ')
total += end - start
hours = total.total_seconds() / 3600
earnings = hours * config['hourly_rate']
currency = config['currency']
print(f'You have earned: ${earnings:.2f} {currency}')
```
Now if you run `t display -f earnings` you will get something like:
```
You have earned: 2400 USD
```
## Why did you write this instead of improving timetrap?
* timetrap is [hard to install](https://github.com/samg/timetrap/issues/176),
hard to keep [updated](https://github.com/samg/timetrap/issues/174) (because
of ruby). With tiempo you can get (or build) a binary, put it somewhere in
your `PATH`, and it will just work forever in that machine. I'm bundling
sqlite.
* timetrap is slow (no way around it, because of ruby), some commands take up to
a second. Tiempo always feels snappy.
* needed major refactor to fix the timezone problem (in a language I'm not
proficient with). I was aware of this problem and designed tiempo to store
timestamps in UTC while at the same time being able to work with a database
made by timetrap without messing up. And there are a lot of tests backing this
assertions.
### Other advantages
* Columns in the output are always aligned.
* Fixed some input inconsistencies.
* CLI interface is easier to discover (ask -h for any sub-command)
* End times are printed with +1d to indicate that the activity ended the next
day in the 'text' formatter.
* Solved some old issues in timetrap.
* Added new features!
## How to build
You need [rust](https://rustup.rs), then clone the repo and simply run
@ -422,45 +76,89 @@ and when I want to test some commands against such config file I just source it:
### Documentation
The docs are written using [sphinx](https://www.sphinx-doc.org/en/master/), so
first you need to install it somehow. Two options I can offer are:
The docs are written using [sphinx](https://www.sphinx-doc.org/en/master/). To
install the required dependencies enter the `docs` directory and create a virual
environment:
* using your computer's package manager. Install a package with a name similar
to `python-sphinx`.
* using [pipenv](https://duckduckgo.com/?t=ffab&q=pipenv&ia=web). Just ensure
you have python 3.9 on your computer, enter the `docs` directory and do
`pipenv install`.
virtualenv .venv
To build the docs just enter the `docs` directory and some of the language
directories (currently `es` or `en`) and run:
then activate it and install the dependencies:
source .venv/bin/activate
pip install -r requirements.txt
To build the docs just do:
make html
for the html version (output located at `docs/<lang>/build/html`), or
for the html version (output located at `docs/build/html`), or
make man
for the man page (output located at `docs/<lang>/build/man/tiempo.1`). If you
are using pipenv just prefix the commands with `pipenv run` or run `pipenv
shell` before running any command.
for the man page (output located at `docs/build/man/tiempo.1`). To test the man
page you can do:
The contents of the docs are located in `docs/<lang>/source/index.rst`,
man -l build/man/tiempo.1
To get a live-reloaded server with the html docs do:
sphinx-autobuild source/ build/html/
The contents of the man page are located in `docs/source/index.rst`,
formatted as
[reStructuredText](https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html).
### Building the packages like in CI but locally
First pull the image:
podman pull tiempo-build-env
Or build it yourself from this repo:
podman build -t tiempo-build-env .
Then build the artifacts:
./scripts/podman-build.sh
To build the archlinux PKGBUILDs (depends on the artifacts folder created by the
previous command):
./scripts/podman-build-aur-bin.sh
./scripts/podman-build-aur-git.sh
Both of the previous commands produce PKGBUILDs in the current directory so if
you run both sequentially you'll lose the PKGBUILD of the first command.
## Special Thanks
To [timetrap](https://github.com/samg/timetrap) for existing, to
[samg](https://github.com/samg) for creating it. It is the tool I was looking
for and whose design I took as reference, keeping compatibility when possible.
## What I'm working on
(more or less ordered by priority)
* fix the pipeline because it's broken. The last release and its artifacts are
wrong.
* add an install/uninstall script to the any-linux package.
* finish the `summary` formatter.
* match formatters by prefix (so there's no need to type all of its name if the
prefix is unambiguous).
* let resume --interactive receive a string as the text for a new entry.
* add a command that opens the doc in the browser just like fish
* compile a package for windows in CI
## Release checklist
(mostly to remind myself)
* [ ] Ensure tests pass and that clippy doesn't complain
* [ ] Add documentation to the readme on the new features
* [ ] Create an entry in `CHANGELOG.md` with the target version, stage it but
don't commit
* [ ] Add documentation about the new features
* [ ] Create an entry in `CHANGELOG.md` with the target version, commit it
* [ ] run `vbump`
* [ ] git push && git push --tags && cargo publish && cargo build --release && cp target/release/t ~/.local/bin/t
* [ ] git push && git push --tags && cargo publish
* [ ] wait for release and then test the releases (aur bin and git and
packaged).

View File

@ -5,7 +5,7 @@ _tiempo ()
cmd="${COMP_WORDS[1]}"
if [[ ( $cmd = s* || $cmd = d* || $cmd = k* ) && "$COMP_CWORD" = 2 ]]; then
COMPREPLY=($(compgen -W "$(echo "select distinct sheet from entries where sheet not like '\_%';" | t b)" $cur))
COMPREPLY=($(compgen -W "$(t l --all --flat)" $cur))
return
elif [[ "$COMP_CWORD" = 1 ]]; then
CMDS="archive backend configure display edit in kill list now out resume sheet week month"

16
completions/fish/t.fish Normal file
View File

@ -0,0 +1,16 @@
# tiempo does not accept files as arguments
complete --command t --no-files
# define all subcommands
set -l sheetcommandslong display kill sheet week month
set -l sheetcommandsshort d k s w m
set -l sheetcommandsall $sheetcommandslong $sheetcommandsshort
set -l commandslong archive backend configure edit in list now out resume
set -l commandsshort a b c e i l n o r
set -l subcommands $sheetcommandslong $commandslong $sheetcommandsshort $commandsshort
# add subcommands
complete --command t --condition "not __fish_seen_subcommand_from $subcommands" -a "$subcommands"
# complete sheet name
complete --command t --condition "__fish_seen_subcommand_from $sheetcommandsall" -a "(t l --all --flat)"

30
completions/zsh/_t Normal file
View File

@ -0,0 +1,30 @@
#compdef t
_t() {
local curcontext="$curcontext" state line
typeset -A opt_args
_arguments \
'1: :->t_command'\
'2: :->first_arg'
case $state in
t_command)
compadd "$@" archive backend configure display edit in kill\
list now out resume sheet week month
;;
first_arg)
# If the first argument starts with s or d (sheet or display),
# the second argument can be autocompleted to one of the existing
# non-archived sheets.
if [[ $words[2] == s* || $words[2] == d* ]]; then
t l --all --flat | while read sheet; do
compadd "$@" $sheet
done
fi
;;
esac
}
_t "$@"

1
docs/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build/

View File

@ -1,12 +0,0 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
sphinx = "*"
[dev-packages]
[requires]
python_version = "3.9"

268
docs/Pipfile.lock generated
View File

@ -1,268 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "e879b252b55733ab98d1dcd005f029f322788f15b4ee5dd35a2a3d7730680920"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.9"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"alabaster": {
"hashes": [
"sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359",
"sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"
],
"version": "==0.7.12"
},
"babel": {
"hashes": [
"sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9",
"sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.9.1"
},
"certifi": {
"hashes": [
"sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872",
"sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"
],
"version": "==2021.10.8"
},
"charset-normalizer": {
"hashes": [
"sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0",
"sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"
],
"markers": "python_version >= '3'",
"version": "==2.0.7"
},
"docutils": {
"hashes": [
"sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125",
"sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==0.17.1"
},
"idna": {
"hashes": [
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
],
"markers": "python_version >= '3'",
"version": "==3.3"
},
"imagesize": {
"hashes": [
"sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1",
"sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.2.0"
},
"jinja2": {
"hashes": [
"sha256:827a0e32839ab1600d4eb1c4c33ec5a8edfbc5cb42dafa13b81f182f97784b45",
"sha256:8569982d3f0889eed11dd620c706d39b60c36d6d25843961f33f77fb6bc6b20c"
],
"markers": "python_version >= '3.6'",
"version": "==3.0.2"
},
"markupsafe": {
"hashes": [
"sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298",
"sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64",
"sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b",
"sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194",
"sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567",
"sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff",
"sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724",
"sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74",
"sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646",
"sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35",
"sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6",
"sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a",
"sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6",
"sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad",
"sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26",
"sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38",
"sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac",
"sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7",
"sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6",
"sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047",
"sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75",
"sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f",
"sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b",
"sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135",
"sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8",
"sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a",
"sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a",
"sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1",
"sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9",
"sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864",
"sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914",
"sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee",
"sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f",
"sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18",
"sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8",
"sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2",
"sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d",
"sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b",
"sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b",
"sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86",
"sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6",
"sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f",
"sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb",
"sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833",
"sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28",
"sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e",
"sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415",
"sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902",
"sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f",
"sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d",
"sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9",
"sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d",
"sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145",
"sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066",
"sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c",
"sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1",
"sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a",
"sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207",
"sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f",
"sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53",
"sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd",
"sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134",
"sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85",
"sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9",
"sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5",
"sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94",
"sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509",
"sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51",
"sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"
],
"markers": "python_version >= '3.6'",
"version": "==2.0.1"
},
"packaging": {
"hashes": [
"sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7",
"sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"
],
"markers": "python_version >= '3.6'",
"version": "==21.0"
},
"pygments": {
"hashes": [
"sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380",
"sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"
],
"markers": "python_version >= '3.5'",
"version": "==2.10.0"
},
"pyparsing": {
"hashes": [
"sha256:84196357aa3566d64ad123d7a3c67b0e597a115c4934b097580e5ce220b91531",
"sha256:fd93fc45c47893c300bd98f5dd1b41c0e783eaeb727e7cea210dcc09d64ce7c3"
],
"markers": "python_version >= '3.6'",
"version": "==3.0.1"
},
"pytz": {
"hashes": [
"sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c",
"sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"
],
"version": "==2021.3"
},
"requests": {
"hashes": [
"sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
"sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==2.26.0"
},
"snowballstemmer": {
"hashes": [
"sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2",
"sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"
],
"version": "==2.1.0"
},
"sphinx": {
"hashes": [
"sha256:94078db9184491e15bce0a56d9186e0aec95f16ac20b12d00e06d4e36f1058a6",
"sha256:98a535c62a4fcfcc362528592f69b26f7caec587d32cd55688db580be0287ae0"
],
"index": "pypi",
"version": "==4.2.0"
},
"sphinxcontrib-applehelp": {
"hashes": [
"sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a",
"sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"
],
"markers": "python_version >= '3.5'",
"version": "==1.0.2"
},
"sphinxcontrib-devhelp": {
"hashes": [
"sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e",
"sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"
],
"markers": "python_version >= '3.5'",
"version": "==1.0.2"
},
"sphinxcontrib-htmlhelp": {
"hashes": [
"sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07",
"sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"
],
"markers": "python_version >= '3.6'",
"version": "==2.0.0"
},
"sphinxcontrib-jsmath": {
"hashes": [
"sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178",
"sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"
],
"markers": "python_version >= '3.5'",
"version": "==1.0.1"
},
"sphinxcontrib-qthelp": {
"hashes": [
"sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72",
"sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"
],
"markers": "python_version >= '3.5'",
"version": "==1.0.3"
},
"sphinxcontrib-serializinghtml": {
"hashes": [
"sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd",
"sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"
],
"markers": "python_version >= '3.5'",
"version": "==1.1.5"
},
"urllib3": {
"hashes": [
"sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
"sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==1.26.7"
}
},
"develop": {}
}

View File

@ -1,35 +0,0 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

View File

@ -1,12 +0,0 @@
Advanced Usage
==============
Subcommands
-----------
bla
Settings
--------
bla

View File

@ -1,62 +0,0 @@
Intro
=====
What is Tiempo
--------------
Tiempo is a command line time tracker. It helps you keep track of the time spent
in different activities optionally organized in projects or `sheets`. Tiempo is
also a `Timetrap`_ compatible command line time tracking application.
Why another time tracking instead of improving Timetrap?
* Timetrap is `hard to install`_, hard to keep `updated`_ (because of Ruby).
With Tiempo you can get or build a binary, put it somewhere, and it will
just work forever in that machine. I'm bundling SQLite.
* Timetrap is slow (no way around it, because of Ruby), some commands take up to
a second. Tiempo always feels snappy.
* Timetrap needed major refactor to fix the timezone problem (in a language I'm not
proficient with). I was aware of this problem and designed Tiempo to store
timestamps in UTC while at the same time being able to work with a database
made by Timetrap without messing up. And there are a lot of tests backing this
assertions.
Tiempo has other advantages:
* Fixed some input inconsistencies.
* Solved some old issues in Timetrap.
* Columns in the output are always aligned.
* CLI interface is easier to discover (ask ``-h`` for any sub-command).
* End times are printed with +1d to indicate that the activity ended the next
day in the 'text' formatter.
Installation
------------
Just do:
.. code:: bash
cargo install tiempo
If you use Arch Linux, install `tiempo-git` package from the `AUR`_, for example:
.. code:: bash
git clone https://aur.archlinux.org/tiempo-git.git && cd tiempo-git && makepkg -si
.. NOTE::
You need to install `Rust`_ and `Cargo`_. For more info `click here`_.
Quickstart
----------
TODO
.. _Timetrap: https://github.com/samg/timetrap/
.. _hard to install: https://github.com/samg/timetrap/issues/176
.. _updated: https://github.com/samg/timetrap/issues/174
.. _click here: https://doc.rust-lang.org/cargo/getting-started/installation.html
.. _Rust: https://rust-lang.org
.. _Cargo: https://doc.rust-lang.org/book/ch01-03-hello-cargo.html
.. _AUR: https://aur.archlinux.org/packages/tiempo-git/

View File

@ -1,55 +0,0 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = 'tiempo'
copyright = '2021, Abbraham Toriz'
author = 'Abbraham Toriz'
# The full version, including alpha/beta/rc tags
release = 'v1.1.1'
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

View File

@ -1,52 +0,0 @@
Tiempo docs
===========
.. toctree::
:maxdepth: 2
:caption: Contents:
basic_usage
advanced_usage
Tiempo is a command line time tracker. It helps you keep track of the time spent
in different activities optionally organized in projects or `sheets`. Tiempo is
also compatible with `Timetrap`_, its predecesor.
Installation
------------
Provided you have a `Rust`_ toolchain installed, just do:
.. code:: bash
cargo install tiempo
If you use Arch Linux, install ``tiempo-git`` package from the `AUR`_, for
example:
.. code:: bash
git clone https://aur.archlinux.org/tiempo-git.git && cd tiempo-git && makepkg -si
.. NOTE::
You need to install `Rust`_ and `Cargo`_. For more info `click here`_.
Quickstart
----------
Go `here`_...
Special Thanks
--------------
To `Timetrap`_ for existing, to `samg`_ for creating it. It is the tool I was
looking for and whose design I took as reference, keeping compatibility when
possible.
.. _Timetrap: https://github.com/samg/timetrap/
.. _samg: https://github.com/samg
.. _here: basic_usage.html#quickstart
.. _click here: https://doc.rust-lang.org/cargo/getting-started/installation.html
.. _Rust: https://rust-lang.org
.. _Cargo: https://doc.rust-lang.org/book/ch01-03-hello-cargo.html
.. _AUR: https://aur.archlinux.org/packages/tiempo-git/

View File

@ -1,20 +0,0 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View File

@ -1,12 +0,0 @@
Advanced Usage
==============
Subcommands
-----------
bla
Settings
--------
bla

View File

@ -1,62 +0,0 @@
Introducción
============
¿Qué es Tiempo?
---------------
Tiempo es una herramienta de línea de comandos para rastrear el tiempo. Te ayuda a grabar
el tiempo empleado en diferentes actividades y de manera opcional te permite organizarlas
en proyectos o `sheets` (hojas). Tiempo es compatible con `Timetrap`_, su predecesor.
¿Por qué otro rastreador de tiempo en lugar de mejorar Timetrap?
* Timetrap es `difícil de instalar`_, difícil de mantenerlo `actualizado`_ (debido a Ruby).
Con Tiempo puedes obtener o compilar un binario, ponerlo donde sea y simplemente funcionará
en la máquina. Estoy embebiendo SQLite.
* Timetrap es lento (no hay modo de sacarle la vuelta, es por Ruby), algunos comandos
tardan hasta un segundo. Tiempo siempre se siente rápido.
* Timetrap necesitaba una refactorización mayor para arreglar un problema con el huso
horario (en un lenguaje en el que no soy experto). Estaba al tanto de este problema
y diseñé Tiempo para guardar marcas de tiempo en UTC así como es posible
trabajar con la base de datos hecha por Timetrap sin estropearla. Y muchas pruebas
se han realizado que respaldan esta afirmación.
Tiempo tiene otras ventajas:
* Arreglé algunas inconsistencias en el input.
* Resolví algunas incidencias antiguas de Timetrap.
* Las columnas del output están siempre alineadas.
* La interfaz de línea de comandos es más fácil de descubrir (pregunta ``-h`` para cualquier subcomando).
* Los tiempos de conclusión se imprimen con un +1d para indicar que la actividad acabó al siguiente
día.
Instalación
-----------
Si tienes instaladas las herramientas de `Rust`_, solo ejecuta:
.. code:: bash
cargo install tiempo
Si usas Arch Linux, instala el paquete `tiempo-git` desde `AUR`_, por ejemplo:
.. code:: bash
git clone https://aur.archlinux.org/tiempo-git.git && cd tiempo-git && makepkg -si
.. NOTE::
Requieres tener instalado `Rust`_ y `Cargo`_. Para más información haz `clic aquí`_.
Inicio rápido
-------------
TODO
.. _Timetrap: https://github.com/samg/timetrap/
.. _difícil de instalar: https://github.com/samg/timetrap/issues/176
.. _actualizado: https://github.com/samg/timetrap/issues/174
.. _clic aquí: https://doc.rust-lang.org/cargo/getting-started/installation.html
.. _Rust: https://rust-lang.org
.. _Cargo: https://doc.rust-lang.org/book/ch01-03-hello-cargo.html
.. _AUR: https://aur.archlinux.org/packages/tiempo-git/

View File

@ -1,62 +0,0 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = 'tiempo'
copyright = '2021, Abraham Toriz'
author = 'Abraham Toriz'
# The full version, including alpha/beta/rc tags
release = 'v1.1.1'
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = 'es'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

View File

@ -1,50 +0,0 @@
Documentación de Tiempo
=======================
.. toctree::
:maxdepth: 2
:caption: Contenidos:
basic_usage
advanced_usage
Tiempo es una herramienta de línea de comandos para rastrear el tiempo. Te ayuda a grabar
el tiempo empleado en diferentes actividades y de manera opcional te permite organizarlas
en proyectos o `sheets` (hojas). Tiempo es compatible con `Timetrap`_, su predecesor.
Instalación
-----------
Si tienes instaladas las herramientas de `Rust`_, solo ejecuta:
.. code:: bash
cargo install tiempo
Si usas Arch Linux, instala el paquete `tiempo-git` desde `AUR`_, por ejemplo:
.. code:: bash
git clone https://aur.archlinux.org/tiempo-git.git && cd tiempo-git && makepkg -si
.. NOTE::
Requieres tener instalado `Rust`_ y `Cargo`_. Para más información haz `clic aquí`_.
Inicio rápido
-------------
Ve `aquí`_...
Agradecimientos especiales
--------------------------
A `Timetrap`_ por existir y a `samg`_ por hacerlo. Esta herramienta es lo que estaba
buscando y a la que tomé de referencia, manteniendo compatibilidad en lo posible.
.. _Timetrap: https://github.com/samg/timetrap/
.. _samg: https://github.com/samg
.. _aquí: basic_usage.html#inicio-rapido
.. _clic aquí: https://doc.rust-lang.org/cargo/getting-started/installation.html
.. _Rust: https://rust-lang.org
.. _Cargo: https://doc.rust-lang.org/book/ch01-03-hello-cargo.html
.. _AUR: https://aur.archlinux.org/packages/tiempo-git/

View File

@ -10,8 +10,6 @@ if "%SPHINXBUILD%" == "" (
set SOURCEDIR=source
set BUILDDIR=build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
@ -25,6 +23,8 @@ if errorlevel 9009 (
exit /b 1
)
if "%1" == "" goto help
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end

4
docs/requirements.txt Normal file
View File

@ -0,0 +1,4 @@
sphinx
sphinx-autobuild
tomlkit
furo

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

34
docs/source/conf.py Normal file
View File

@ -0,0 +1,34 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
from pathlib import Path
from tomlkit import parse
# Make the Cargo.toml file the only source for 'version'
_cargo_toml = Path(__file__).resolve().parent.parent.parent / 'Cargo.toml'
project = 'Tiempo'
copyright = '2022, Categulario'
author = 'Categulario, Perrotuerto'
release = str(parse(open(_cargo_toml).read())['package']['version'])
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = []
templates_path = ['_templates']
exclude_patterns = []
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = 'furo'
html_static_path = ['_static']
html_logo = "_static/logo.png"

1117
docs/source/index.rst Normal file

File diff suppressed because it is too large Load Diff

45
scripts/build.sh Executable file
View File

@ -0,0 +1,45 @@
#!/bin/bash
# Fail if any of the commands fail
set -e
# Build the docs
cd docs/
make man
gzip -f build/man/tiempo.1
cd ..
# build the binary
rustup component add clippy
cargo clippy --all-targets --all-features -- -D warnings
cargo test
cargo build --locked --release
# move binary
install -Dm755 target/release/t build/bin/t
# move documentation
install -Dm644 CHANGELOG.md build/share/doc/tiempo/CHANGELOG.md
install -Dm644 README.md build/share/doc/tiempo/README.md
install -Dm644 LICENSE build/share/doc/tiempo/LICENSE
# move man page
install -Dm644 docs/build/man/tiempo.1.gz build/share/man/man1/tiempo.1.gz
# move completions
install -Dm644 completions/bash/t build/share/bash-completion/completions/t
install -Dm644 completions/fish/t.fish build/share/fish/vendor_completions.d/t.fish
install -Dm644 completions/zsh/_t build/share/zsh/site-functions/_t
# move install scripts
install -Dm755 scripts/install.sh build/install.sh
install -Dm755 scripts/uninstall.sh build/uninstall.sh
# compress the tar file
tar -cvzf tiempo-${CI_COMMIT_TAG}-x86_64.tar.gz build/
# makes the debian archive
./scripts/debpackage.sh
# computes the sums
sha256sum tiempo-${CI_COMMIT_TAG}-x86_64.tar.gz > tiempo-${CI_COMMIT_TAG}-x86_64.tar.gz.sum
sha256sum debian-package/tiempo_${CI_COMMIT_TAG}_amd64.deb > tiempo_${CI_COMMIT_TAG}_amd64.deb.sum
mkdir -p artifacts
mv tiempo-${CI_COMMIT_TAG}-x86_64.tar.gz artifacts/
mv debian-package/tiempo_${CI_COMMIT_TAG}_amd64.deb artifacts/
mv tiempo-${CI_COMMIT_TAG}-x86_64.tar.gz.sum artifacts/
mv tiempo_${CI_COMMIT_TAG}_amd64.deb.sum artifacts/

View File

@ -20,7 +20,6 @@ DPKG_DIR="${DPKG_STAGING}/dpkg"
PROJECT_MANTAINER="Abraham Toriz Cruz"
PROJECT_HOMEPAGE="https://gitlab.com/categulario/tiempo-rs"
PROJECT_NAME=tiempo
PROJECT_VERSION=${CI_COMMIT_TAG:1}
PROJECT_BINARY=t
PROJECT_DESCRIPTION="A command line time tracking application"
@ -28,9 +27,9 @@ mkdir -p "${DPKG_DIR}"
DPKG_BASENAME=${PROJECT_NAME}
DPKG_CONFLICTS=
DPKG_VERSION=${PROJECT_VERSION}
DPKG_VERSION=${CI_COMMIT_TAG:1}
DPKG_ARCH=amd64
DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb"
DPKG_NAME="${DPKG_BASENAME}_${CI_COMMIT_TAG}_${DPKG_ARCH}.deb"
DPKG_DEPENDS=
DPKG_SECTION=utils
@ -39,8 +38,11 @@ install -Dm755 "target/release/$PROJECT_BINARY" "${DPKG_DIR}/usr/bin/$PROJECT_BI
# README and LICENSE
install -Dm644 "README.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/README.md"
install -Dm644 "LICENSE" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE"
install -Dm644 "CHANGELOG.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/changelog"
gzip -n --best "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/changelog"
install -Dm644 "CHANGELOG.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/CHANGELOG.md"
install -Dm644 "docs/build/man/$PROJECT_NAME.1.gz" "${DPKG_DIR}/usr/share/man/man1/$PROJECT_NAME.1.gz"
install -Dm644 "completions/bash/t" "${DPKG_DIR}/usr/share/bash-completion/completions/t"
install -Dm644 "completions/fish/t.fish" "${DPKG_DIR}/usr/share/fish/vendor_completions.d/t.fish"
install -Dm644 "completions/zsh/_t" "${DPKG_DIR}/usr/share/zsh/site-functions/_t"
cat > "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright" <<EOF
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/

13
scripts/install.sh Normal file
View File

@ -0,0 +1,13 @@
#!/bin/bash
__dir=$(dirname $(realpath $0))
prefix=${1:-/usr}
install -Dm755 $__dir/bin/t $prefix/bin/t
install -Dm644 $__dir/share/fish/vendor_completions.d/t.fish $prefix/share/fish/vendor_completions.d/t.fish
install -Dm644 $__dir/share/doc/tiempo/README.md $prefix/share/doc/tiempo/README.md
install -Dm644 $__dir/share/doc/tiempo/CHANGELOG.md $prefix/share/doc/tiempo/CHANGELOG.md
install -Dm644 $__dir/share/doc/tiempo/LICENSE $prefix/share/doc/tiempo/LICENSE
install -Dm644 $__dir/share/bash-completion/completions/t $prefix/share/bash-completion/completions/t
install -Dm644 $__dir/share/zsh/site-functions/_t $prefix/share/zsh/site-functions/_t
install -Dm644 $__dir/share/man/man1/tiempo.1.gz $prefix/share/man/man1/tiempo.1.gz

14
scripts/podman-build-aur-bin.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
if [[ -z $1 ]]; then
echo "please pass a tag as first argument. Something like v1.6.0"
exit 1
fi
podman run -it --rm \
--workdir /app \
--volume ./:/app \
--env CI_COMMIT_TAG=$1 \
--env CI_PROJECT_ID=27545092 \
--uidmap 1000:0:1 --uidmap 0:1:1000 \
categulario/makepkg ./scripts/release-aur-bin.sh

13
scripts/podman-build-aur-git.sh Executable file
View File

@ -0,0 +1,13 @@
#!/bin/bash
if [[ -z $1 ]]; then
echo "please pass a tag as first argument. Something like v1.6.0"
exit 1
fi
podman run -it --rm \
--workdir /app \
--volume ./:/app \
--env CI_COMMIT_TAG=$1 \
--uidmap 1000:0:1 --uidmap 0:1:1000 \
categulario/makepkg ./scripts/release-aur-git.sh

13
scripts/podman-build.sh Normal file
View File

@ -0,0 +1,13 @@
#!/bin/bash
if [[ -z $1 ]]; then
echo "please pass a tag as first argument. Something like v1.6.0"
exit 1
fi
podman run -it --rm \
--workdir /app \
--volume ./:/app \
--volume tiempo_cargo_index:/usr/local/cargo/registry \
--env CI_COMMIT_TAG=$1 \
tiempo-build-env ./scripts/build.sh

View File

@ -1,19 +1,15 @@
#!/bin/bash
set -e
# some useful variables
VERSION=${CI_COMMIT_TAG:1}
PROJECT_NAME=tiempo
PROJECT_BINARY=t
ARCHIVENAME=$PROJECT_NAME-$VERSION-x86_64.tar.gz
# clone the repo
git clone $BIN_REPO_URL $PROJECT_NAME-bin
# enter it
cd $PROJECT_NAME-bin
ARCHIVENAME=$PROJECT_NAME-$CI_COMMIT_TAG-x86_64.tar.gz
# get the sum from the artifacts
SUM=( `cat ../$ARCHIVENAME.sum` )
SUM=( `cat artifacts/$ARCHIVENAME.sum` )
# Generate the PKGBUILD
echo "# Maintainer: Abraham Toriz <categulario at gmail dot com>
@ -28,20 +24,21 @@ depends=()
optdepends=('sqlite: for manually editing the database')
provides=('$PROJECT_NAME')
conflicts=('$PROJECT_NAME')
source=(\"https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/generic/v$VERSION/v$VERSION/$PROJECT_NAME-\${pkgver}-x86_64.tar.gz\")
source=(\"https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/generic/$CI_COMMIT_TAG/$CI_COMMIT_TAG/$PROJECT_NAME-v\${pkgver}-x86_64.tar.gz\")
sha256sums=('$SUM')
package() {
cd \"\$srcdir/build\"
install -Dm755 $PROJECT_BINARY \"\$pkgdir\"/usr/bin/$PROJECT_BINARY
install -Dm755 bin/$PROJECT_BINARY \"\$pkgdir\"/usr/bin/$PROJECT_BINARY
install -Dm644 README.md \"\$pkgdir\"/usr/share/doc/$PROJECT_NAME/README.md
install -Dm644 LICENSE \"\$pkgdir\"/usr/share/doc/$PROJECT_NAME/LICENSE
install -Dm644 CHANGELOG.md \"\$pkgdir\"/usr/share/doc/$PROJECT_NAME/CHANGELOG.md
install -Dm644 share/doc/$PROJECT_NAME/README.md \"\$pkgdir\"/usr/share/doc/$PROJECT_NAME/README.md
install -Dm644 share/doc/$PROJECT_NAME/LICENSE \"\$pkgdir\"/usr/share/doc/$PROJECT_NAME/LICENSE
install -Dm644 share/doc/$PROJECT_NAME/CHANGELOG.md \"\$pkgdir\"/usr/share/doc/$PROJECT_NAME/CHANGELOG.md
install -Dm644 share/man/man1/$PROJECT_NAME.1.gz \"\$pkgdir\"/usr/share/man/man1/$PROJECT_NAME.1.gz
install -Dm644 share/bash-completion/completions/t \"\$pkgdir\"/usr/share/bash-completion/completions/t
install -Dm644 share/fish/vendor_completions.d/t.fish \"\$pkgdir\"/usr/share/fish/vendor_completions.d/t.fish
install -Dm644 share/zsh/site-functions/_t \"\$pkgdir\"/usr/share/zsh/site-functions/_t
}
" | tee PKGBUILD > /dev/null
makepkg --printsrcinfo > .SRCINFO
git add .
git commit -m "Release version $VERSION"
git push

View File

@ -5,12 +5,6 @@ VERSION=${CI_COMMIT_TAG:1}
PROJECT_NAME=tiempo
PROJECT_BINARY=t
# clone the repo
git clone $GIT_REPO_URL $PROJECT_NAME-git
# enter it
cd $PROJECT_NAME-git
echo "# Maintainer: Abraham Toriz <categulario at gmail dot com>
pkgname=$PROJECT_NAME-git
pkgver=$VERSION
@ -21,7 +15,7 @@ url='https://gitlab.com/categulario/tiempo-rs'
license=('GPL3')
depends=()
optdepends=('sqlite: for manually editing the database')
makedepends=('cargo' 'git')
makedepends=('cargo' 'git' 'python-sphinx' 'python-tomlkit' 'gzip' 'make')
provides=('$PROJECT_NAME')
conflicts=('$PROJECT_NAME')
source=('$PROJECT_NAME-git::git+https://gitlab.com/categulario/tiempo-rs')
@ -35,6 +29,9 @@ pkgver() {
build() {
cd \"\$pkgname\"
cargo build --release --locked
cd docs
make man
gzip build/man/$PROJECT_NAME.1
}
package() {
@ -44,9 +41,11 @@ package() {
install -Dm644 README.md \"\$pkgdir\"/usr/share/doc/$PROJECT_NAME/README.md
install -Dm644 LICENSE \"\$pkgdir\"/usr/share/doc/$PROJECT_NAME/LICENSE
install -Dm644 CHANGELOG.md \"\$pkgdir\"/usr/share/doc/$PROJECT_NAME/CHANGELOG.md
install -Dm644 docs/build/man/$PROJECT_NAME.1.gz \"\$pkgdir\"/usr/share/man/man1/$PROJECT_NAME.1.gz
install -Dm644 completions/bash/t \"\$pkgdir\"/usr/share/bash-completion/completions/t
install -Dm644 completions/fish/t.fish \"\$pkgdir\"/usr/share/fish/vendor_completions.d/t.fish
install -Dm644 completions/zsh/_t \"\$pkgdir\"/usr/share/zsh/site-functions/_t
}" | tee PKGBUILD > /dev/null
makepkg --printsrcinfo > .SRCINFO
git add .
git commit -m "Release version $VERSION"
git push

12
scripts/uninstall.sh Normal file
View File

@ -0,0 +1,12 @@
#!/bin/bash
rm -f /usr/bin/t
prefix=${1:-/usr}
rm -f $prefix/bin/t
rm -rf $prefix/share/doc/tiempo/
rm -f $prefix/share/fish/vendor_completions.d/t.fish
rm -f $prefix/share/bash-completion/completions/t
rm -f $prefix/share/zsh/site-functions/_t
rm -f $prefix/share/man/man1/tiempo.1.gz

80
src/bin/t.rs Normal file
View File

@ -0,0 +1,80 @@
use std::convert::TryInto;
use std::process::exit;
use std::io;
use clap::ArgMatches;
use chrono::Utc;
use regex::Regex;
use lazy_static::lazy_static;
use tiempo::error;
use tiempo::database::SqliteDatabase;
use tiempo::env::Env;
use tiempo::config::Config;
use tiempo::commands::{
Command, Facts, r#in::InCommand, display::DisplayCommand,
sheet::SheetCommand, today::TodayCommand, yesterday::YesterdayCommand,
week::WeekCommand, month::MonthCommand, list::ListCommand, out::OutCommand,
resume::ResumeCommand, backend::BackendCommand, kill::KillCommand,
now::NowCommand, edit::EditCommand, archive::ArchiveCommand,
configure::ConfigureCommand,
};
use tiempo::io::Streams;
use tiempo::cli::make_cli;
lazy_static! {
// https://regex101.com/r/V9zYQu/1/
pub static ref NUMBER_RE: Regex = Regex::new(r"^\d+$").unwrap();
}
fn error_trap(matches: ArgMatches) -> error::Result<()> {
let env = Env::read();
let facts = Facts {
config: Config::read(env.timetrap_config_file.as_deref())?,
env,
now: Utc::now(),
};
if let Some(_matches) = matches.subcommand_matches("backend") {
return BackendCommand::handle(&facts.config);
}
let mut streams = Streams {
db: SqliteDatabase::from_path_or_create(&facts.config.database_file)?,
r#in: io::BufReader::new(io::stdin()),
out: io::stdout(),
err: io::stderr(),
};
match matches.subcommand() {
Some(("in", matches)) => InCommand::handle(matches.try_into()?, &mut streams, &facts),
Some(("out", matches)) => OutCommand::handle(matches.try_into()?, &mut streams, &facts),
Some(("resume", matches)) => ResumeCommand::handle(matches.try_into()?, &mut streams, &facts),
Some(("display", matches)) => DisplayCommand::handle(matches.try_into()?, &mut streams, &facts),
Some(("today", matches)) => TodayCommand::handle(matches.try_into()?, &mut streams, &facts),
Some(("yesterday", matches)) => YesterdayCommand::handle(matches.try_into()?, &mut streams, &facts),
Some(("week", matches)) => WeekCommand::handle(matches.try_into()?, &mut streams, &facts),
Some(("month", matches)) => MonthCommand::handle(matches.try_into()?, &mut streams, &facts),
Some(("sheet", matches)) => SheetCommand::handle(matches.try_into()?, &mut streams, &facts),
Some(("list", matches)) => ListCommand::handle(matches.try_into()?, &mut streams, &facts),
Some(("kill", matches)) => KillCommand::handle(matches.try_into()?, &mut streams, &facts),
Some(("now", matches)) => NowCommand::handle(matches.try_into()?, &mut streams, &facts),
Some(("edit", matches)) => EditCommand::handle(matches.try_into()?, &mut streams, &facts),
Some(("archive", matches)) => ArchiveCommand::handle(matches.try_into()?, &mut streams, &facts),
Some(("configure", matches)) => ConfigureCommand::handle(matches.try_into()?, &mut streams, &facts),
Some((cmd, _)) => Err(error::Error::UnimplementedCommand(cmd.into())),
None => Err(error::Error::MissingSubcommand),
}
}
fn main() {
let matches = make_cli().get_matches();
if let Err(e) = error_trap(matches) {
eprintln!("{}", e);
exit(1);
}
}

View File

@ -1,112 +1,36 @@
use std::convert::TryInto;
use std::process::exit;
use std::io;
use clap::{
App, Arg, SubCommand, AppSettings, ArgMatches, crate_version, crate_authors,
crate_description, crate_name,
Command, Arg, SubCommand, command, value_parser,
};
use chrono::Utc;
use regex::Regex;
use lazy_static::lazy_static;
use tiempo::error;
use tiempo::database::SqliteDatabase;
use tiempo::env::Env;
use tiempo::config::Config;
use tiempo::commands::{
Command, Facts, r#in::InCommand, display::DisplayCommand,
sheet::SheetCommand, today::TodayCommand, yesterday::YesterdayCommand,
week::WeekCommand, month::MonthCommand, list::ListCommand, out::OutCommand,
resume::ResumeCommand, backend::BackendCommand, kill::KillCommand,
now::NowCommand, edit::EditCommand, archive::ArchiveCommand,
configure::ConfigureCommand,
};
use tiempo::io::Streams;
lazy_static! {
// https://regex101.com/r/V9zYQu/1/
pub static ref NUMBER_RE: Regex = Regex::new(r"^\d+$").unwrap();
}
fn is_number(v: String) -> Result<(), String>{
if NUMBER_RE.is_match(&v) {
Ok(())
} else {
Err(format!("'{}' is not a valid number", v))
}
}
fn error_trap(matches: ArgMatches) -> error::Result<()> {
let env = Env::read();
let facts = Facts {
config: Config::read(env.timetrap_config_file.as_deref())?,
env,
now: Utc::now(),
};
if let Some(_matches) = matches.subcommand_matches("backend") {
return BackendCommand::handle(&facts.config);
}
let mut streams = Streams {
db: SqliteDatabase::from_path_or_create(&facts.config.database_file)?,
r#in: io::BufReader::new(io::stdin()),
out: io::stdout(),
err: io::stderr(),
};
match matches.subcommand() {
("in", Some(matches)) => InCommand::handle(matches.try_into()?, &mut streams, &facts),
("out", Some(matches)) => OutCommand::handle(matches.try_into()?, &mut streams, &facts),
("resume", Some(matches)) => ResumeCommand::handle(matches.try_into()?, &mut streams, &facts),
("display", Some(matches)) => DisplayCommand::handle(matches.try_into()?, &mut streams, &facts),
("today", Some(matches)) => TodayCommand::handle(matches.try_into()?, &mut streams, &facts),
("yesterday", Some(matches)) => YesterdayCommand::handle(matches.try_into()?, &mut streams, &facts),
("week", Some(matches)) => WeekCommand::handle(matches.try_into()?, &mut streams, &facts),
("month", Some(matches)) => MonthCommand::handle(matches.try_into()?, &mut streams, &facts),
("sheet", Some(matches)) => SheetCommand::handle(matches.try_into()?, &mut streams, &facts),
("list", Some(matches)) => ListCommand::handle(matches.try_into()?, &mut streams, &facts),
("kill", Some(matches)) => KillCommand::handle(matches.try_into()?, &mut streams, &facts),
("now", Some(matches)) => NowCommand::handle(matches.try_into()?, &mut streams, &facts),
("edit", Some(matches)) => EditCommand::handle(matches.try_into()?, &mut streams, &facts),
("archive", Some(matches)) => ArchiveCommand::handle(matches.try_into()?, &mut streams, &facts),
("configure", Some(matches)) => ConfigureCommand::handle(matches.try_into()?, &mut streams, &facts),
(cmd, _) => Err(error::Error::UnimplementedCommand(cmd.into())),
}
}
fn main() {
pub fn make_cli() -> Command<'static> {
// Let's first declare some args that repeat here and there
let start_arg = Arg::with_name("start")
.long("start").short("s")
.long("start").short('s')
.takes_value(true).value_name("TIME")
.help("Include entries that start on this date or later");
let end_arg = Arg::with_name("end")
.long("end").short("e")
.long("end").short('e')
.takes_value(true).value_name("TIME")
.help("Include entries that start on this date or earlier");
let ids_arg = Arg::with_name("ids")
.short("v").long("ids")
.short('v').long("ids")
.help("Print database ids (for use with edit)");
let grep_arg = Arg::with_name("grep")
.long("grep").short("g")
.long("grep").short('g')
.takes_value(true).value_name("REGEXP")
.help("Include entries where the note matches this regexp.");
.help("Only include entries whose note matches this regular expression");
let format_arg = Arg::with_name("format")
.short("f").long("format")
.short('f').long("format")
.takes_value(true).value_name("FORMAT")
.help(
"The output format. Valid built-in formats are chart, text, ical, \
csv, json and ids. Documentation on defining custom formats can be \
found at https://gitlab.com/categulario/tiempo"
found at https://tiempo.categulario.xyz or the man page included \
with the installation."
);
let sheet_arg = Arg::with_name("sheet")
@ -124,22 +48,18 @@ fn main() {
let id_arg = Arg::with_name("id")
.long("id")
.takes_value(true).value_name("ID")
.validator(is_number);
.value_parser(value_parser!(u64));
let interactive_arg = Arg::with_name("interactive")
.short("i")
.short('i')
.long("interactive")
.takes_value(false)
.conflicts_with("id")
.help("Choose an entry of the (unique) last N interactively");
// Now declar this app's cli
let matches = App::new("Tiempo")
.name(crate_name!())
.setting(AppSettings::SubcommandRequired)
.version(crate_version!())
.author(crate_authors!())
.about(crate_description!())
let cli = command!()
.subcommand_required(true)
.subcommand(SubCommand::with_name("archive")
.visible_alias("a")
@ -149,11 +69,11 @@ fn main() {
.arg(grep_arg.clone())
.arg(sheet_arg.clone().help("Archive entries from this sheet instead of the current one"))
.arg(Arg::with_name("fake")
.short("f").long("fake")
.short('f').long("fake")
.help("Don't actually archive the entries, just display them")
)
.arg(Arg::with_name("time")
.short("t").long("time")
.short('t').long("time")
.takes_value(true).value_name("HOURS")
.help("Time in hours to archive. Archived time will be equal or less than this.")
)
@ -171,7 +91,7 @@ fn main() {
.long("round-in-seconds")
.takes_value(true)
.value_name("SECONDS")
.validator(is_number)
.value_parser(value_parser!(u64))
.help("The duration of time to use for rounding with the -r flag. Default: 900 (15 m)"))
.arg(Arg::with_name("database_file")
.long("database-file")
@ -215,13 +135,13 @@ fn main() {
.long("week-start")
.takes_value(true)
.value_name("DAY")
.possible_values(&["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"])
.possible_values(["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"])
.help("The day of the week to use as the start of the week for t week. Default: monday"))
.arg(Arg::with_name("interactive_entries")
.long("interactive-entries")
.takes_value(true)
.value_name("N")
.validator(is_number)
.value_parser(value_parser!(u64))
.help("How many unique previous notes to show when selecting interactively"))
)
@ -276,10 +196,10 @@ fn main() {
.arg(grep_arg.clone())
.arg(sheet_arg.clone())
.arg(Arg::with_name("month")
.long("month").short("m")
.long("month").short('m')
.takes_value(true).value_name("TIME")
.aliases(&["s", "start"])
.possible_values(&[
.possible_values([
"this", "current", "last", "jan", "january", "feb",
"february", "mar", "march", "apr", "april", "may", "jun",
"june", "jul", "july", "aug", "august", "sep", "september",
@ -327,8 +247,11 @@ fn main() {
.visible_alias("l")
.about("List existing sheets")
.arg(Arg::with_name("all")
.short("a").long("all")
.short('a').long("all")
.help("List archive sheets also"))
.arg(Arg::with_name("flat")
.short('f').long("flat")
.help("show only the sheet names"))
)
.subcommand(SubCommand::with_name("kill")
@ -338,12 +261,12 @@ fn main() {
.arg(Arg::with_name("sheet")
.takes_value(true).value_name("SHEET")
.conflicts_with_all(&["id", "last"])
.required_unless_one(&["id", "last"])
.required_unless_one(["id", "last"])
.help(
"Delete an entire sheet by its name"
))
.arg(Arg::with_name("last")
.short("l").long("last")
.short('l').long("last")
.takes_value(false)
.help("Delete the last entry of the current sheet"))
)
@ -361,12 +284,12 @@ fn main() {
.arg(end_arg.clone().help("Set this as the end time"))
.arg(
Arg::with_name("append")
.long("append").short("z")
.long("append").short('z')
.help("Append to the current note instead of replacing it. The delimiter between appended notes is configurable (see configure)")
)
.arg(
Arg::with_name("move")
.short("m").long("move")
.short('m').long("move")
.takes_value(true)
.value_name("SHEET")
.help("Move entry to another sheet")
@ -377,12 +300,7 @@ fn main() {
.value_name("NOTE")
.help("The note text. It will replace the previous one unless --append is given")
)
)
);
.get_matches();
if let Err(e) = error_trap(matches) {
eprintln!("{}", e);
exit(1);
}
cli
}

View File

@ -64,7 +64,7 @@ impl Default for Facts {
}
pub trait Command<'a> {
type Args: TryFrom<&'a ArgMatches<'a>>;
type Args: TryFrom<&'a ArgMatches>;
fn handle<D: Database, I: BufRead, O: Write, E: Write>(args: Self::Args, streams: &mut Streams<D, I, O, E>, facts: &Facts) -> Result<()>;
}

View File

@ -26,7 +26,7 @@ pub struct Args {
sheet: Option<String>,
}
impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
impl<'a> TryFrom<&'a ArgMatches> for Args {
type Error = Error;
fn try_from(matches: &'a ArgMatches) -> Result<Self> {
@ -274,22 +274,22 @@ mod tests {
fn entries_are_split_properly() {
let mut old_entry = Entry {
id: 1,
start: Utc.ymd(2022, 7, 29).and_hms(10, 0, 0),
end: Some(Utc.ymd(2022, 7, 29).and_hms(11, 0, 0)),
start: Utc.with_ymd_and_hms(2022, 7, 29, 10, 0, 0).unwrap(),
end: Some(Utc.with_ymd_and_hms(2022, 7, 29, 11, 0, 0).unwrap()),
note: Some("an entry".to_string()),
sheet: "foo".to_string(),
};
assert_eq!(split_entry(&mut old_entry, Duration::minutes(25)), (
Utc.ymd(2022, 7, 29).and_hms(10, 25, 0),
Some(Utc.ymd(2022, 7, 29).and_hms(11, 0, 0)),
Utc.with_ymd_and_hms(2022, 7, 29, 10, 25, 0).unwrap(),
Some(Utc.with_ymd_and_hms(2022, 7, 29, 11, 0, 0).unwrap()),
Some("an entry".to_string()),
"foo".to_string(),
));
assert_eq!(old_entry, Entry {
id: 1,
start: Utc.ymd(2022, 7, 29).and_hms(10, 0, 0),
end: Some(Utc.ymd(2022, 7, 29).and_hms(10, 25, 0)),
start: Utc.with_ymd_and_hms(2022, 7, 29, 10, 0, 0).unwrap(),
end: Some(Utc.with_ymd_and_hms(2022, 7, 29, 10, 25, 0).unwrap()),
note: Some("an entry".to_string()),
sheet: "foo".to_string(),
});
@ -394,7 +394,7 @@ Proceed? [y/N] ");
};
let mut streams = Streams::fake(b"y\n");
let facts = Facts::new();
let time_a = Utc.ymd(2022, 8, 1).and_hms(10, 0, 0);
let time_a = Utc.with_ymd_and_hms(2022, 8, 1, 10, 0, 0).unwrap();
let time_b = time_a + Duration::minutes(90);
let time_d = time_a + Duration::hours(3);

View File

@ -53,14 +53,14 @@ impl Args {
fn yes_no_none(matches: &ArgMatches, opt: &str) -> Option<bool> {
if matches.is_present(opt) {
Some(true)
} else if matches.is_present(&format!("no_{}", opt)) {
} else if matches.is_present(format!("no_{}", opt)) {
Some(false)
} else {
None
}
}
impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
impl<'a> TryFrom<&'a ArgMatches> for Args {
type Error = Error;
fn try_from(matches: &'a ArgMatches) -> Result<Self> {

View File

@ -98,7 +98,7 @@ pub struct Args {
sheet: Option<Sheet>,
}
impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
impl<'a> TryFrom<&'a ArgMatches> for Args {
type Error = Error;
fn try_from(matches: &'a ArgMatches) -> Result<Args> {
@ -178,14 +178,14 @@ mod tests {
fn filter_by_start() {
let args = Args {
format: Some(Formatter::Csv),
start: Some(Utc.ymd(2021, 6, 30).and_hms(10, 5, 0)),
start: Some(Utc.with_ymd_and_hms(2021, 6, 30, 10, 5, 0).unwrap()),
..Default::default()
};
let mut streams = Streams::fake(b"");
let facts = Facts::new();
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 0, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 10, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
DisplayCommand::handle(args, &mut streams, &facts).unwrap();
@ -204,8 +204,8 @@ mod tests {
let mut streams = Streams::fake(b"");
let facts = Facts::new();
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("adios".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 0, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 10, 0).unwrap(), None, Some("adios".into()), "default").unwrap();
entries_for_display(None, None, None, &mut streams, Formatter::Csv, true, Some("io".parse().unwrap()), &facts).unwrap();
@ -229,9 +229,9 @@ mod tests {
let facts = Facts::new();
std::env::set_var("TZ", "CST+6");
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), Some(Utc.ymd(2021, 6, 30).and_hms(11, 0, 0)), None, "sheet1").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(11, 0, 0), Some(Utc.ymd(2021, 6, 30).and_hms(12, 0, 0)), None, "sheet2").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(12, 0, 0), Some(Utc.ymd(2021, 6, 30).and_hms(13, 0, 0)), None, "sheet1").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2021, 6, 30, 11, 0, 0).unwrap()), None, "sheet1").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 11, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2021, 6, 30, 12, 0, 0).unwrap()), None, "sheet2").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 12, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2021, 6, 30, 13, 0, 0).unwrap()), None, "sheet1").unwrap();
DisplayCommand::handle(args, &mut streams, &facts).unwrap();
@ -264,9 +264,9 @@ Timesheet: sheet2
let facts = Facts::new();
std::env::set_var("TZ", "CST+6");
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), Some(Utc.ymd(2021, 6, 30).and_hms(11, 0, 0)), None, "sheet1").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(11, 0, 0), Some(Utc.ymd(2021, 6, 30).and_hms(12, 0, 0)), None, "_sheet2").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(12, 0, 0), Some(Utc.ymd(2021, 6, 30).and_hms(13, 0, 0)), None, "sheet1").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2021, 6, 30, 11, 0, 0).unwrap()), None, "sheet1").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 11, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2021, 6, 30, 12, 0, 0).unwrap()), None, "_sheet2").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 12, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2021, 6, 30, 13, 0, 0).unwrap()), None, "sheet1").unwrap();
DisplayCommand::handle(args, &mut streams, &facts).unwrap();
@ -296,8 +296,8 @@ Timesheet: sheet1
let args = Args {
format: Some(Formatter::Csv),
start: Some(Utc.ymd(2021, 6, 29).and_hms(12, 0, 0)),
end: Some(Utc.ymd(2021, 6, 29).and_hms(13, 0, 0)),
start: Some(Utc.with_ymd_and_hms(2021, 6, 29, 12, 0, 0).unwrap()),
end: Some(Utc.with_ymd_and_hms(2021, 6, 29, 13, 0, 0).unwrap()),
..Default::default()
};
let mut streams = Streams::fake(b"").with_db(
@ -332,8 +332,8 @@ Timesheet: sheet1
..Default::default()
});
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 0, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 10, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
DisplayCommand::handle(args, &mut streams, &facts).unwrap();
@ -357,8 +357,8 @@ Timesheet: sheet1
..Default::default()
});
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 0, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 10, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
DisplayCommand::handle(args, &mut streams, &facts).unwrap();

View File

@ -32,7 +32,7 @@ impl Args {
}
}
impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
impl<'a> TryFrom<&'a ArgMatches> for Args {
type Error = Error;
fn try_from(matches: &'a ArgMatches) -> Result<Self> {
@ -142,7 +142,7 @@ mod tests {
note: Some("new note".into()),
..Default::default()
};
let now = Utc.ymd(2021, 8, 3).and_hms(20, 29, 0);
let now = Utc.with_ymd_and_hms(2021, 8, 3, 20, 29, 0).unwrap();
let an_hour_ago = now - Duration::hours(1);
let facts = Facts::new().with_now(now);
@ -175,7 +175,7 @@ mod tests {
note: Some("new note".into()),
..Default::default()
};
let now = Utc.ymd(2021, 8, 3).and_hms(20, 29, 0);
let now = Utc.with_ymd_and_hms(2021, 8, 3, 20, 29, 0).unwrap();
let an_hour_ago = now - Duration::hours(1);
let facts = Facts::new().with_now(now);
@ -198,7 +198,7 @@ mod tests {
fn edit_start() {
std::env::set_var("TZ", "CST+6");
let mut streams = Streams::fake(b"");
let now = Utc.ymd(2021, 8, 3).and_hms(20, 29, 0);
let now = Utc.with_ymd_and_hms(2021, 8, 3, 20, 29, 0).unwrap();
let args = Args {
start: Some(now - Duration::minutes(30)),
..Default::default()
@ -224,7 +224,7 @@ mod tests {
fn edit_end() {
std::env::set_var("TZ", "CST+6");
let mut streams = Streams::fake(b"");
let now = Utc.ymd(2021, 8, 3).and_hms(20, 29, 0);
let now = Utc.with_ymd_and_hms(2021, 8, 3, 20, 29, 0).unwrap();
let args = Args {
end: Some(now - Duration::minutes(30)),
..Default::default()
@ -255,7 +255,7 @@ mod tests {
append: true,
..Default::default()
};
let now = Utc.ymd(2021, 8, 3).and_hms(20, 29, 0);
let now = Utc.with_ymd_and_hms(2021, 8, 3, 20, 29, 0).unwrap();
let an_hour_ago = now - Duration::hours(1);
let facts = Facts::new().with_now(now);
@ -277,7 +277,7 @@ mod tests {
fn edit_move() {
std::env::set_var("TZ", "CST+6");
let mut streams = Streams::fake(b"");
let now = Utc.ymd(2021, 8, 3).and_hms(20, 29, 0);
let now = Utc.with_ymd_and_hms(2021, 8, 3, 20, 29, 0).unwrap();
let args = Args {
r#move: Some("new sheet".to_owned()),
..Default::default()
@ -309,7 +309,7 @@ mod tests {
append: true,
..Default::default()
};
let now = Utc.ymd(2021, 8, 3).and_hms(20, 29, 0);
let now = Utc.with_ymd_and_hms(2021, 8, 3, 20, 29, 0).unwrap();
let an_hour_ago = now - Duration::hours(1);
let facts = Facts::new().with_now(now).with_config(Config {
append_notes_delimiter: ";".to_owned(),
@ -346,8 +346,8 @@ mod tests {
let mut streams = Streams::fake(b"").with_db(
SqliteDatabase::from_path(&database_file).unwrap()
);
let now = Utc.ymd(2021, 8, 3).and_hms(20, 29, 0);
let new_end = Utc.ymd(2021, 6, 29).and_hms(14, 26, 52);
let now = Utc.with_ymd_and_hms(2021, 8, 3, 20, 29, 0).unwrap();
let new_end = Utc.with_ymd_and_hms(2021, 6, 29, 14, 26, 52).unwrap();
let args = Args {
end: Some(new_end),
..Default::default()

View File

@ -18,7 +18,7 @@ pub struct Args {
pub note: Option<String>,
}
impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
impl<'a> TryFrom<&'a ArgMatches> for Args {
type Error = Error;
fn try_from(matches: &'a ArgMatches) -> Result<Self> {

View File

@ -17,7 +17,7 @@ pub enum Args {
Last,
}
impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
impl<'a> TryFrom<&'a ArgMatches> for Args {
type Error = Error;
fn try_from(args: &'a ArgMatches) -> Result<Args> {

View File

@ -19,14 +19,16 @@ use super::{Command, Facts};
#[derive(Default)]
pub struct Args {
all: bool,
flat: bool,
}
impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
impl<'a> TryFrom<&'a ArgMatches> for Args {
type Error = Error;
fn try_from(matches: &ArgMatches) -> Result<Args> {
Ok(Args {
all: matches.is_present("all"),
flat: matches.is_present("flat"),
})
}
}
@ -43,15 +45,13 @@ impl<'a> Command<'a> for ListCommand {
O: Write,
E: Write,
{
let today = facts.now.with_timezone(&Local).date().and_hms(0, 0, 0).with_timezone(&Utc);
let today = facts.now.with_timezone(&Local).date_naive().and_hms_opt(0, 0, 0).unwrap().and_local_timezone(Utc).unwrap();
let entries = if args.all {
streams.db.entries_full(None, None)?
} else {
streams.db.entries_all_visible(None, None)?
};
let (mut entries, needs_warning) = entries_or_warning(entries, &streams.db)?;
let current = streams.db.current_sheet()?;
let last = streams.db.last_sheet()?;
@ -62,88 +62,99 @@ impl<'a> Command<'a> for ListCommand {
entries.sort_unstable_by_key(|e| e.sheet.clone());
let mut total_running = Duration::seconds(0);
let mut total_today = Duration::seconds(0);
let mut total = Duration::seconds(0);
if args.flat {
let sheets: Vec<_> = entries
.into_iter()
.map(|e| e.sheet)
.unique()
.collect();
let sheets: Vec<_> = entries
.into_iter()
.group_by(|e| e.sheet.clone())
.into_iter()
.map(|(key, group)| {
let entries: Vec<_> = group.into_iter().collect();
let s_running = facts.now - entries.iter().find(|e| e.end.is_none()).map(|e| e.start).unwrap_or(facts.now);
let s_today = entries.iter().filter(|e| e.start > today).fold(Duration::seconds(0), |acc, e| {
acc + (e.end.unwrap_or(facts.now) - e.start)
});
let s_total = entries.into_iter().fold(Duration::seconds(0), |acc, e| {
acc + (e.end.unwrap_or(facts.now) - e.start)
});
streams.out.write_all(sheets.join("\n").as_bytes())?;
streams.out.write_all(b"\n")?;
} else {
let mut total_running = Duration::seconds(0);
let mut total_today = Duration::seconds(0);
let mut total = Duration::seconds(0);
total_running = total_running + s_running;
total_today = total_today + s_today;
total = total + s_total;
let sheets: Vec<_> = entries
.into_iter()
.group_by(|e| e.sheet.clone())
.into_iter()
.map(|(key, group)| {
let entries: Vec<_> = group.into_iter().collect();
let s_running = facts.now - entries.iter().find(|e| e.end.is_none()).map(|e| e.start).unwrap_or(facts.now);
let s_today = entries.iter().filter(|e| e.start > today).fold(Duration::seconds(0), |acc, e| {
acc + (e.end.unwrap_or(facts.now) - e.start)
});
let s_total = entries.into_iter().fold(Duration::seconds(0), |acc, e| {
acc + (e.end.unwrap_or(facts.now) - e.start)
});
(
if current == key {
"*"
} else if last.as_ref() == Some(&key) {
"-"
} else {
""
},
total_running = total_running + s_running;
total_today = total_today + s_today;
total = total + s_total;
key,
(
if current == key {
"*"
} else if last.as_ref() == Some(&key) {
"-"
} else {
""
},
format_duration(s_running),
key,
format_duration(s_today),
format_duration(s_running),
format_duration(s_total),
)
})
.collect();
format_duration(s_today),
let mut tabs = Tabulate::with_columns(vec![
// indicator of current or prev sheet
Col::new().min_width(1).and_alignment(Right),
format_duration(s_total),
)
})
.collect();
// sheet name
Col::new().min_width(9).and_alignment(Left),
let mut tabs = Tabulate::with_columns(vec![
// indicator of current or prev sheet
Col::new().min_width(1).and_alignment(Right),
// running time
Col::new().min_width(9).and_alignment(Right)
.color_if(Style::new().dimmed(), |s| s == "0:00:00")
.color_if(Style::new().bold(), |s| s != "0:00:00"),
// sheet name
Col::new().min_width(9).and_alignment(Left),
// today
Col::new().min_width(9).and_alignment(Right)
.color_if(Style::new().dimmed(), |s| s == "0:00:00")
.color_if(Style::new().bold(), |s| s != "0:00:00"),
// running time
Col::new().min_width(9).and_alignment(Right)
.color_if(Style::new().dimmed(), |s| s == "0:00:00")
.color_if(Style::new().bold(), |s| s != "0:00:00"),
// accumulated
Col::new().min_width(12).and_alignment(Right),
]);
// today
Col::new().min_width(9).and_alignment(Right)
.color_if(Style::new().dimmed(), |s| s == "0:00:00")
.color_if(Style::new().bold(), |s| s != "0:00:00"),
tabs.feed(vec!["", "Timesheet", "Running", "Today", "Total Time"]);
tabs.separator(' ');
// accumulated
Col::new().min_width(12).and_alignment(Right),
]);
for sheet in sheets {
tabs.feed(vec![sheet.0.to_string(), sheet.1, sheet.2, sheet.3, sheet.4]);
tabs.feed(vec!["", "Timesheet", "Running", "Today", "Total Time"]);
tabs.separator(' ');
for sheet in sheets {
tabs.feed(vec![sheet.0.to_string(), sheet.1, sheet.2, sheet.3, sheet.4]);
}
tabs.separator('-');
tabs.feed(vec![
"".to_string(),
"".to_string(),
format_duration(total_running),
format_duration(total_today),
format_duration(total),
]);
streams.out.write_all(tabs.print(facts.env.stdout_is_tty).as_bytes())?;
}
tabs.separator('-');
tabs.feed(vec![
"".to_string(),
"".to_string(),
format_duration(total_running),
format_duration(total_today),
format_duration(total),
]);
streams.out.write_all(tabs.print(facts.env.stdout_is_tty).as_bytes())?;
warn_if_needed(&mut streams.err, needs_warning, &facts.env)?;
Ok(())
@ -153,7 +164,7 @@ impl<'a> Command<'a> for ListCommand {
#[cfg(test)]
mod tests {
use chrono::{Utc, TimeZone};
use pretty_assertions::assert_eq;
use pretty_assertions::assert_str_eq;
use crate::database::{SqliteDatabase, Database};
@ -169,18 +180,18 @@ mod tests {
streams.db.set_current_sheet("sheet2").unwrap();
streams.db.set_last_sheet("sheet4").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(1, 0, 0)), None, "_archived").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(10,13, 55)), None, "sheet1").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(7, 39, 18)), None, "sheet3").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(12, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(13, 52, 45)), None, "sheet3").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(12, 0, 0), None, None, "sheet4").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2021, 1, 1, 1, 0, 0).unwrap()), None, "_archived").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2021, 1, 1, 10,13, 55).unwrap()), None, "sheet1").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2021, 1, 1, 7, 39, 18).unwrap()), None, "sheet3").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 1, 1, 12, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2021, 1, 1, 13, 52, 45).unwrap()), None, "sheet3").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 1, 1, 12, 0, 0).unwrap(), None, None, "sheet4").unwrap();
let now = Utc.ymd(2021, 1, 1).and_hms(13, 52, 45);
let now = Utc.with_ymd_and_hms(2021, 1, 1, 13, 52, 45).unwrap();
let facts = Facts::new().with_now(now);
ListCommand::handle(args, &mut streams, &facts).unwrap();
assert_eq!(&String::from_utf8_lossy(&streams.out), " Timesheet Running Today Total Time
assert_str_eq!(&String::from_utf8_lossy(&streams.out), " Timesheet Running Today Total Time
sheet1 0:00:00 0:00:00 10:13:55
* sheet2 0:00:00 0:00:00 0:00:00
@ -195,11 +206,12 @@ mod tests {
let args = Args {
all: true,
..Default::default()
};
ListCommand::handle(args, &mut streams, &facts).unwrap();
assert_eq!(&String::from_utf8_lossy(&streams.out), " Timesheet Running Today Total Time
assert_str_eq!(&String::from_utf8_lossy(&streams.out), " Timesheet Running Today Total Time
_archived 0:00:00 0:00:00 1:00:00
sheet1 0:00:00 0:00:00 10:13:55
@ -218,21 +230,59 @@ mod tests {
SqliteDatabase::from_path("assets/test_list_old_database.db").unwrap()
);
let now = Local.ymd(2021, 7, 16).and_hms(11, 30, 45);
let now = Local.with_ymd_and_hms(2021, 7, 16, 11, 30, 45).unwrap();
let facts = Facts::new().with_now(now.with_timezone(&Utc));
ListCommand::handle(args, &mut streams, &facts).unwrap();
assert_eq!(&String::from_utf8_lossy(&streams.out), " Timesheet Running Today Total Time
assert_str_eq!(&String::from_utf8_lossy(&streams.out), " Timesheet Running Today Total Time
* default 0:10:24 0:10:26 0:10:26
--------------------------------------------
0:10:24 0:10:26 0:10:26
");
assert_eq!(
assert_str_eq!(
String::from_utf8_lossy(&streams.err),
"[WARNING] You are using the old timetrap format, it is advised that you update your database using t migrate. To supress this warning set TIEMPO_SUPRESS_TIMETRAP_WARNING=1\n"
);
}
#[test]
fn flat_display() {
std::env::set_var("TZ", "CST+6");
let mut streams = Streams::fake(b"");
streams.db.set_current_sheet("sheet2").unwrap();
streams.db.set_last_sheet("sheet4").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2021, 1, 1, 1, 0, 0).unwrap()), None, "_archived").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2021, 1, 1, 10,13, 55).unwrap()), None, "sheet1").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2021, 1, 1, 7, 39, 18).unwrap()), None, "sheet3").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 1, 1, 12, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2021, 1, 1, 13, 52, 45).unwrap()), None, "sheet3").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 1, 1, 12, 0, 0).unwrap(), None, None, "sheet4").unwrap();
let now = Utc.with_ymd_and_hms(2021, 1, 1, 13, 52, 45).unwrap();
let facts = Facts::new().with_now(now);
let args = Args {
flat: true,
..Default::default()
};
ListCommand::handle(args, &mut streams, &facts).unwrap();
assert_str_eq!(&String::from_utf8_lossy(&streams.out), "sheet1\nsheet2\nsheet3\nsheet4\n");
let facts = Facts::new().with_now(now);
let args = Args {
flat: true,
all: true,
};
streams.out.clear();
ListCommand::handle(args, &mut streams, &facts).unwrap();
assert_str_eq!(&String::from_utf8_lossy(&streams.out), "_archived\nsheet1\nsheet2\nsheet3\nsheet4\n");
}
}

View File

@ -16,31 +16,27 @@ use super::{Command, Facts, display::{Sheet, entries_for_display}};
/// Given a local datetime, returns the time when the month it belongs started
fn beginning_of_month(time: DateTime<Local>) -> DateTime<Utc> {
time.date().with_day(1).unwrap().and_hms(0, 0, 0).with_timezone(&Utc)
time.date_naive().with_day(1).unwrap().and_hms_opt(0, 0, 0).unwrap().and_local_timezone(Utc).unwrap()
}
/// Given a datetime compute the time where the previous_month started in UTC
fn beginning_of_previous_month(time: DateTime<Local>) -> DateTime<Utc> {
match time.month() {
1 => {
Local.ymd(time.year()-1, 12, 1).and_hms(0, 0, 0).with_timezone(&Utc)
Local.with_ymd_and_hms(time.year()-1, 12, 1, 0, 0, 0).unwrap().with_timezone(&Utc)
}
n => Local.ymd(time.year(), n-1, 1).and_hms(0, 0, 0).with_timezone(&Utc)
n => Local.with_ymd_and_hms(time.year(), n-1, 1, 0, 0, 0).unwrap().with_timezone(&Utc)
}
}
#[derive(Default)]
enum MonthSpec {
Last,
#[default]
This,
Month(u32),
}
impl Default for MonthSpec {
fn default() -> MonthSpec {
MonthSpec::This
}
}
impl FromStr for MonthSpec {
type Err = Error;
@ -74,7 +70,7 @@ pub struct Args {
sheet: Option<Sheet>,
}
impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
impl<'a> TryFrom<&'a ArgMatches> for Args {
type Error = Error;
fn try_from(matches: &'a ArgMatches) -> Result<Args> {
@ -110,21 +106,21 @@ impl<'a> Command<'a> for MonthCommand {
if month < now.month() {
// the specified month is in the current year
(
Local.ymd(now.year(), month, 1).and_hms(0, 0, 0).with_timezone(&Utc),
Local.with_ymd_and_hms(now.year(), month, 1, 0, 0, 0).unwrap().with_timezone(&Utc),
if month < 12 {
Local.ymd(now.year(), month+1, 1).and_hms(0, 0, 0).with_timezone(&Utc)
Local.with_ymd_and_hms(now.year(), month+1, 1, 0, 0, 0).unwrap().with_timezone(&Utc)
} else {
Local.ymd(now.year()+1, 1, 1).and_hms(0, 0, 0).with_timezone(&Utc)
Local.with_ymd_and_hms(now.year()+1, 1, 1, 0, 0, 0).unwrap().with_timezone(&Utc)
}
)
} else {
// use previous year
(
Local.ymd(now.year() - 1, month, 1).and_hms(0, 0, 0).with_timezone(&Utc),
Local.with_ymd_and_hms(now.year() - 1, month, 1, 0, 0, 0).unwrap().with_timezone(&Utc),
if month < 12 {
Local.ymd(now.year() - 1, month + 1, 1).and_hms(0, 0, 0).with_timezone(&Utc)
Local.with_ymd_and_hms(now.year() - 1, month + 1, 1, 0, 0, 0).unwrap().with_timezone(&Utc)
} else {
Local.ymd(now.year(), 1, 1).and_hms(0, 0, 0).with_timezone(&Utc)
Local.with_ymd_and_hms(now.year(), 1, 1, 0, 0, 0).unwrap().with_timezone(&Utc)
}
)
}
@ -156,14 +152,14 @@ mod tests {
let args = Default::default();
let mut streams = Streams::fake(b"");
let now = Utc.ymd(2021, 6, 30).and_hms(11, 0, 0);
let now = Utc.with_ymd_and_hms(2021, 6, 30, 11, 0, 0).unwrap();
let facts = Facts::new().with_config(Config {
default_formatter: Formatter::Ids,
..Default::default()
}).with_now(now);
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 0, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 10, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
MonthCommand::handle(args, &mut streams, &facts).unwrap();
@ -177,7 +173,7 @@ mod tests {
let args = Default::default();
let mut streams = Streams::fake(b"");
let now = Utc.ymd(2021, 6, 30).and_hms(11, 0, 0);
let now = Utc.with_ymd_and_hms(2021, 6, 30, 11, 0, 0).unwrap();
let facts = Facts::new().with_config(Config {
commands: CommandsSettings {
month: BaseCommandSettings {
@ -188,8 +184,8 @@ mod tests {
..Default::default()
}).with_now(now);
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 0, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 10, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
MonthCommand::handle(args, &mut streams, &facts).unwrap();

View File

@ -16,7 +16,7 @@ use super::{Command, Facts};
pub struct Args {
}
impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
impl<'a> TryFrom<&'a ArgMatches> for Args {
type Error = Error;
fn try_from(_matches: &'a ArgMatches) -> Result<Args> {
@ -40,42 +40,46 @@ impl<'a> Command<'a> for NowCommand {
let (entries, needs_warning) = entries_or_warning(entries, &streams.db)?;
let current = streams.db.current_sheet()?;
let last = streams.db.last_sheet()?;
if entries.is_empty() {
streams.out.write_all(b"No running entries\n")?;
} else {
let current = streams.db.current_sheet()?;
let last = streams.db.last_sheet()?;
let mut tabs = Tabulate::with_columns(vec![
// indicator of current or prev sheet
Col::new().min_width(1).and_alignment(Right),
let mut tabs = Tabulate::with_columns(vec![
// indicator of current or prev sheet
Col::new().min_width(1).and_alignment(Right),
// sheet name
Col::new().min_width(9).and_alignment(Left),
// sheet name
Col::new().min_width(9).and_alignment(Left),
// running time
Col::new().min_width(9).and_alignment(Right),
// running time
Col::new().min_width(9).and_alignment(Right),
// activity
Col::new().min_width(0).and_alignment(Left),
]);
tabs.feed(vec!["", "Timesheet", "Running", "Activity"]);
tabs.separator(' ');
for entry in entries {
tabs.feed(vec![
if current == entry.sheet {
"*"
} else if last.as_ref() == Some(&entry.sheet) {
"-"
} else {
""
}.to_string(),
entry.sheet,
format_duration(facts.now - entry.start),
entry.note.unwrap_or_else(|| "".to_string())
// activity
Col::new().min_width(0).and_alignment(Left),
]);
}
streams.out.write_all(tabs.print(facts.env.stdout_is_tty).as_bytes())?;
tabs.feed(vec!["", "Timesheet", "Running", "Activity"]);
tabs.separator(' ');
for entry in entries {
tabs.feed(vec![
if current == entry.sheet {
"*"
} else if last.as_ref() == Some(&entry.sheet) {
"-"
} else {
""
}.to_string(),
entry.sheet,
format_duration(facts.now - entry.start),
entry.note.unwrap_or_default(),
]);
}
streams.out.write_all(tabs.print(facts.env.stdout_is_tty).as_bytes())?;
}
warn_if_needed(&mut streams.err, needs_warning, &facts.env)?;
@ -86,7 +90,7 @@ impl<'a> Command<'a> for NowCommand {
#[cfg(test)]
mod tests {
use chrono::{Utc, TimeZone, Local};
use pretty_assertions::assert_eq;
use pretty_assertions::assert_str_eq;
use crate::database::{SqliteDatabase, Database};
@ -97,21 +101,21 @@ mod tests {
std::env::set_var("TZ", "CST+6");
let mut streams = Streams::fake(b"");
let now = Utc.ymd(2021, 1, 1).and_hms(13, 52, 45);
let now = Utc.with_ymd_and_hms(2021, 1, 1, 13, 52, 45).unwrap();
let facts = Facts::new().with_now(now);
streams.db.set_current_sheet("sheet2").unwrap();
streams.db.set_last_sheet("sheet4").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(1, 0, 0)), None, "_archived").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(10,13, 55)), None, "sheet1").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(7, 39, 18)), None, "sheet3").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(12, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(13, 52, 45)), None, "sheet3").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(12, 0, 0), None, Some("some".to_string()), "sheet4").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2021, 1, 1, 1, 0, 0).unwrap()), None, "_archived").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2021, 1, 1, 10,13, 55).unwrap()), None, "sheet1").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2021, 1, 1, 7, 39, 18).unwrap()), None, "sheet3").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 1, 1, 12, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2021, 1, 1, 13, 52, 45).unwrap()), None, "sheet3").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 1, 1, 12, 0, 0).unwrap(), None, Some("some".to_string()), "sheet4").unwrap();
NowCommand::handle(Default::default(), &mut streams, &facts).unwrap();
assert_eq!(&String::from_utf8_lossy(&streams.out), " Timesheet Running Activity
assert_str_eq!(&String::from_utf8_lossy(&streams.out), " Timesheet Running Activity
- sheet4 1:52:45 some
");
@ -123,19 +127,33 @@ mod tests {
SqliteDatabase::from_path("assets/test_list_old_database.db").unwrap()
);
let now = Local.ymd(2021, 7, 16).and_hms(11, 30, 45);
let now = Local.with_ymd_and_hms(2021, 7, 16, 11, 30, 45).unwrap();
let facts = Facts::new().with_now(now.with_timezone(&Utc));
NowCommand::handle(Default::default(), &mut streams, &facts).unwrap();
assert_eq!(&String::from_utf8_lossy(&streams.out), " Timesheet Running Activity
assert_str_eq!(&String::from_utf8_lossy(&streams.out), " Timesheet Running Activity
* default 0:10:24 que
");
assert_eq!(
assert_str_eq!(
String::from_utf8_lossy(&streams.err),
"[WARNING] You are using the old timetrap format, it is advised that you update your database using t migrate. To supress this warning set TIEMPO_SUPRESS_TIMETRAP_WARNING=1\n"
);
}
#[test]
fn with_no_running_entries_display_nicer_message() {
std::env::set_var("TZ", "CST+6");
let mut streams = Streams::fake(b"");
let now = Utc.with_ymd_and_hms(2021, 1, 1, 13, 52, 45).unwrap();
let facts = Facts::new().with_now(now);
NowCommand::handle(Default::default(), &mut streams, &facts).unwrap();
assert_str_eq!(&String::from_utf8_lossy(&streams.out), "No running entries
");
}
}

View File

@ -17,7 +17,7 @@ pub struct Args {
at: Option<DateTime<Utc>>,
}
impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
impl<'a> TryFrom<&'a ArgMatches> for Args {
type Error = Error;
fn try_from(matches: &'a ArgMatches) -> Result<Args> {

View File

@ -12,25 +12,21 @@ use crate::io::Streams;
use crate::interactive::note_from_last_entries;
use super::{Command, Facts, r#in, sheet};
#[derive(Default)]
enum SelectedEntry {
Id(u64),
Interactive,
#[default]
NotSpecified,
}
impl Default for SelectedEntry {
fn default() -> Self {
SelectedEntry::NotSpecified
}
}
#[derive(Default)]
pub struct Args {
entry: SelectedEntry,
at: Option<DateTime<Utc>>,
}
impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
impl<'a> TryFrom<&'a ArgMatches> for Args {
type Error = Error;
fn try_from(matches: &'a ArgMatches) -> Result<Self> {

View File

@ -14,7 +14,7 @@ pub struct Args {
pub sheet: Option<String>,
}
impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
impl<'a> TryFrom<&'a ArgMatches> for Args {
type Error = Error;
fn try_from(matches: &'a ArgMatches) -> Result<Self> {

View File

@ -2,7 +2,7 @@ use std::convert::TryFrom;
use std::io::{BufRead, Write};
use clap::ArgMatches;
use chrono::{DateTime, Utc, Local};
use chrono::{DateTime, Utc, Local, Timelike};
use regex::Regex;
use crate::error::{Result, Error};
@ -23,7 +23,7 @@ pub struct Args {
sheet: Option<Sheet>,
}
impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
impl<'a> TryFrom<&'a ArgMatches> for Args {
type Error = Error;
fn try_from(matches: &'a ArgMatches) -> Result<Args> {
@ -49,7 +49,13 @@ impl<'a> Command<'a> for TodayCommand {
O: Write,
E: Write,
{
let start = Some(facts.now.with_timezone(&Local).date().and_hms(0, 0, 0).with_timezone(&Utc));
let start = Some(facts.now
.with_timezone(&Local)
.with_hour(0).unwrap()
.with_minute(0).unwrap()
.with_second(0).unwrap()
.with_nanosecond(0).unwrap()
.with_timezone(&Utc));
entries_for_display(
start,
@ -81,10 +87,10 @@ mod tests {
let facts = Facts::new().with_config(Config {
default_formatter: Formatter::Ids,
..Default::default()
}).with_now(Utc.ymd(2021, 6, 30).and_hms(11, 0, 0));
}).with_now(Utc.with_ymd_and_hms(2021, 6, 30, 11, 0, 0).unwrap());
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 0, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 10, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
TodayCommand::handle(args, &mut streams, &facts).unwrap();
@ -106,10 +112,10 @@ mod tests {
..Default::default()
},
..Default::default()
}).with_now(Utc.ymd(2021, 6, 30).and_hms(11, 0, 0));
}).with_now(Utc.with_ymd_and_hms(2021, 6, 30, 11, 0, 0).unwrap());
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 0, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 10, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
TodayCommand::handle(args, &mut streams, &facts).unwrap();

View File

@ -2,7 +2,7 @@ use std::convert::TryFrom;
use std::io::{BufRead, Write};
use clap::ArgMatches;
use chrono::{DateTime, Utc, Local, Duration, Weekday, Datelike};
use chrono::{DateTime, Utc, Local, Duration, Weekday, Datelike, Timelike};
use regex::Regex;
use crate::error::{Result, Error};
@ -56,7 +56,12 @@ fn prev_day(now: DateTime<Local>, week_start: WeekDay) -> DateTime<Utc> {
_ => unreachable!(),
};
begining.date().and_hms(0, 0, 0).with_timezone(&Utc)
begining
.with_hour(0).unwrap()
.with_minute(0).unwrap()
.with_second(0).unwrap()
.with_nanosecond(0).unwrap()
.with_timezone(&Utc)
}
#[derive(Default)]
@ -68,7 +73,7 @@ pub struct Args {
sheet: Option<Sheet>,
}
impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
impl<'a> TryFrom<&'a ArgMatches> for Args {
type Error = Error;
fn try_from(matches: &'a ArgMatches) -> Result<Args> {
@ -120,15 +125,15 @@ mod tests {
#[test]
fn test_prev_day() {
// starting a saturday
let now = Local.ymd(2021, 7, 10).and_hms(18, 31, 0);
let now = Local.with_ymd_and_hms(2021, 7, 10, 18, 31, 0).unwrap();
assert_eq!(prev_day(now, WeekDay::Monday), Local.ymd(2021, 7, 5).and_hms(0, 0, 0).with_timezone(&Utc));
assert_eq!(prev_day(now, WeekDay::Tuesday), Local.ymd(2021, 7, 6).and_hms(0, 0, 0).with_timezone(&Utc));
assert_eq!(prev_day(now, WeekDay::Wednesday), Local.ymd(2021, 7, 7).and_hms(0, 0, 0).with_timezone(&Utc));
assert_eq!(prev_day(now, WeekDay::Thursday), Local.ymd(2021, 7, 8).and_hms(0, 0, 0).with_timezone(&Utc));
assert_eq!(prev_day(now, WeekDay::Friday), Local.ymd(2021, 7, 9).and_hms(0, 0, 0).with_timezone(&Utc));
assert_eq!(prev_day(now, WeekDay::Saturday), Local.ymd(2021, 7, 10).and_hms(0, 0, 0).with_timezone(&Utc));
assert_eq!(prev_day(now, WeekDay::Sunday), Local.ymd(2021, 7, 4).and_hms(0, 0, 0).with_timezone(&Utc));
assert_eq!(prev_day(now, WeekDay::Monday), Local.with_ymd_and_hms(2021, 7, 5, 0, 0, 0).unwrap().with_timezone(&Utc));
assert_eq!(prev_day(now, WeekDay::Tuesday), Local.with_ymd_and_hms(2021, 7, 6, 0, 0, 0).unwrap().with_timezone(&Utc));
assert_eq!(prev_day(now, WeekDay::Wednesday), Local.with_ymd_and_hms(2021, 7, 7, 0, 0, 0).unwrap().with_timezone(&Utc));
assert_eq!(prev_day(now, WeekDay::Thursday), Local.with_ymd_and_hms(2021, 7, 8, 0, 0, 0).unwrap().with_timezone(&Utc));
assert_eq!(prev_day(now, WeekDay::Friday), Local.with_ymd_and_hms(2021, 7, 9, 0, 0, 0).unwrap().with_timezone(&Utc));
assert_eq!(prev_day(now, WeekDay::Saturday), Local.with_ymd_and_hms(2021, 7, 10, 0, 0, 0).unwrap().with_timezone(&Utc));
assert_eq!(prev_day(now, WeekDay::Sunday), Local.with_ymd_and_hms(2021, 7, 4, 0, 0, 0).unwrap().with_timezone(&Utc));
}
#[test]
@ -137,14 +142,14 @@ mod tests {
let args = Default::default();
let mut streams = Streams::fake(b"");
let now = Utc.ymd(2021, 7, 1).and_hms(10, 0, 0);
let now = Utc.with_ymd_and_hms(2021, 7, 1, 10, 0, 0).unwrap();
let facts = Facts::new().with_config(Config {
default_formatter: Formatter::Ids,
..Default::default()
}).with_now(now);
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 0, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 10, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
WeekCommand::handle(args, &mut streams, &facts).unwrap();
@ -158,7 +163,7 @@ mod tests {
let args = Default::default();
let mut streams = Streams::fake(b"");
let now = Utc.ymd(2021, 7, 1).and_hms(10, 0, 0);
let now = Utc.with_ymd_and_hms(2021, 7, 1, 10, 0, 0).unwrap();
let facts = Facts::new().with_config(Config {
commands: CommandsSettings {
week: BaseCommandSettings {
@ -169,8 +174,8 @@ mod tests {
..Default::default()
}).with_now(now);
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 0, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 10, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
WeekCommand::handle(args, &mut streams, &facts).unwrap();

View File

@ -21,7 +21,7 @@ pub struct Args {
sheet: Option<Sheet>,
}
impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
impl<'a> TryFrom<&'a ArgMatches> for Args {
type Error = Error;
fn try_from(matches: &'a ArgMatches) -> Result<Args> {
@ -46,9 +46,9 @@ impl<'a> Command<'a> for YesterdayCommand {
O: Write,
E: Write,
{
let today = facts.now.with_timezone(&Local).date();
let start = Some((today - Duration::days(1)).and_hms(0, 0, 0).with_timezone(&Utc));
let end = Some(today.and_hms(0, 0, 0).with_timezone(&Utc));
let today = facts.now.with_timezone(&Local).date_naive();
let start = Some((today - Duration::days(1)).and_hms_opt(0, 0, 0).unwrap().and_local_timezone(Utc).unwrap());
let end = Some(today.and_hms_opt(0, 0, 0).unwrap().and_local_timezone(Utc).unwrap());
entries_for_display(
start,
@ -79,20 +79,20 @@ mod tests {
..Default::default()
};
let mut streams = Streams::fake(b"");
let two_days_ago = Local::now().date() - Duration::days(2);
let yesterday = Local::now().date() - Duration::days(1);
let today = Local::now().date();
let two_days_ago = Local::now().date_naive() - Duration::days(2);
let yesterday = Local::now().date_naive() - Duration::days(1);
let today = Local::now().date_naive();
let facts = Facts::new();
streams.db.entry_insert(two_days_ago.and_hms(1, 2, 3).with_timezone(&Utc), None, None, "default").unwrap();
streams.db.entry_insert(yesterday.and_hms(1, 2, 3).with_timezone(&Utc), None, Some("This!".into()), "default").unwrap();
streams.db.entry_insert(today.and_hms(1, 2, 3).with_timezone(&Utc), None, None, "default").unwrap();
streams.db.entry_insert(two_days_ago.and_hms_opt(1, 2, 3).unwrap().and_local_timezone(Utc).unwrap(), None, None, "default").unwrap();
streams.db.entry_insert(yesterday.and_hms_opt(1, 2, 3).unwrap().and_local_timezone(Utc).unwrap(), None, Some("This!".into()), "default").unwrap();
streams.db.entry_insert(today.and_hms_opt(1, 2, 3).unwrap().and_local_timezone(Utc).unwrap(), None, None, "default").unwrap();
YesterdayCommand::handle(args, &mut streams, &facts).unwrap();
assert_eq!(&String::from_utf8_lossy(&streams.out), &format!("start,end,note,sheet
{},,This!,default
", yesterday.and_hms(1, 2, 3).with_timezone(&Utc).to_rfc3339_opts(chrono::SecondsFormat::Micros, true)));
", yesterday.and_hms_opt(1, 2, 3).unwrap().and_local_timezone(Utc).unwrap().to_rfc3339_opts(chrono::SecondsFormat::Micros, true)));
assert_eq!(
String::from_utf8_lossy(&streams.err),
@ -106,14 +106,14 @@ mod tests {
let args = Default::default();
let mut streams = Streams::fake(b"");
let now = Utc.ymd(2021, 7, 1).and_hms(10, 0, 0);
let now = Utc.with_ymd_and_hms(2021, 7, 1, 10, 0, 0).unwrap();
let facts = Facts::new().with_config(Config {
default_formatter: Formatter::Ids,
..Default::default()
}).with_now(now);
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 0, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 10, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
YesterdayCommand::handle(args, &mut streams, &facts).unwrap();
@ -127,7 +127,7 @@ mod tests {
let args = Default::default();
let mut streams = Streams::fake(b"");
let now = Utc.ymd(2021, 7, 1).and_hms(10, 0, 0);
let now = Utc.with_ymd_and_hms(2021, 7, 1, 10, 0, 0).unwrap();
let facts = Facts::new().with_config(Config {
commands: CommandsSettings {
yesterday: BaseCommandSettings {
@ -138,8 +138,8 @@ mod tests {
..Default::default()
}).with_now(now);
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 0, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
streams.db.entry_insert(Utc.with_ymd_and_hms(2021, 6, 30, 10, 10, 0).unwrap(), None, Some("hola".into()), "default").unwrap();
YesterdayCommand::handle(args, &mut streams, &facts).unwrap();

View File

@ -324,7 +324,7 @@ impl Database for SqliteDatabase {
#[cfg(test)]
mod tests {
use chrono::TimeZone;
use chrono::{TimeZone, NaiveDate};
use pretty_assertions::assert_eq;
use super::*;
@ -334,21 +334,21 @@ mod tests {
let mut db = SqliteDatabase::from_memory().unwrap();
db.init().unwrap();
db.entry_insert(Utc.ymd(2021, 7, 7).and_hms(1, 0, 0), None, None, "XXX").unwrap();
db.entry_insert(Utc.ymd(2021, 7, 7).and_hms(1, 0, 0), None, None, "OOO").unwrap();
db.entry_insert(Utc.ymd(2021, 7, 7).and_hms(2, 0, 0), None, None, "XXX").unwrap();
db.entry_insert(Utc.ymd(2021, 7, 7).and_hms(2, 0, 0), None, None, "OOO").unwrap();
db.entry_insert(Utc.ymd(2021, 7, 7).and_hms(3, 0, 0), None, None, "XXX").unwrap();
db.entry_insert(Utc.ymd(2021, 7, 7).and_hms(3, 0, 0), None, None, "OOO").unwrap();
db.entry_insert(Utc.with_ymd_and_hms(2021, 7, 7, 1, 0, 0).unwrap(), None, None, "XXX").unwrap();
db.entry_insert(Utc.with_ymd_and_hms(2021, 7, 7, 1, 0, 0).unwrap(), None, None, "OOO").unwrap();
db.entry_insert(Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(), None, None, "XXX").unwrap();
db.entry_insert(Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(), None, None, "OOO").unwrap();
db.entry_insert(Utc.with_ymd_and_hms(2021, 7, 7, 3, 0, 0).unwrap(), None, None, "XXX").unwrap();
db.entry_insert(Utc.with_ymd_and_hms(2021, 7, 7, 3, 0, 0).unwrap(), None, None, "OOO").unwrap();
let start = Utc.ymd(2021, 7, 7).and_hms(1, 30, 0);
let end = Utc.ymd(2021, 7, 7).and_hms(2, 30, 0);
let start = Utc.with_ymd_and_hms(2021, 7, 7, 1, 30, 0).unwrap();
let end = Utc.with_ymd_and_hms(2021, 7, 7, 2, 30, 0).unwrap();
// filter by start and end
assert_eq!(
db.entries_by_sheet("XXX", Some(start), Some(end)).unwrap().into_iter().map(|e| e.start).collect::<Vec<_>>(),
vec![
Utc.ymd(2021, 7, 7).and_hms(2, 0, 0),
Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(),
]
);
@ -356,8 +356,8 @@ mod tests {
assert_eq!(
db.entries_by_sheet("XXX", Some(start), None).unwrap().into_iter().map(|e| e.start).collect::<Vec<_>>(),
vec![
Utc.ymd(2021, 7, 7).and_hms(2, 0, 0),
Utc.ymd(2021, 7, 7).and_hms(3, 0, 0),
Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2021, 7, 7, 3, 0, 0).unwrap(),
]
);
@ -365,8 +365,8 @@ mod tests {
assert_eq!(
db.entries_by_sheet("XXX", None, Some(end)).unwrap().into_iter().map(|e| e.start).collect::<Vec<_>>(),
vec![
Utc.ymd(2021, 7, 7).and_hms(1, 0, 0),
Utc.ymd(2021, 7, 7).and_hms(2, 0, 0),
Utc.with_ymd_and_hms(2021, 7, 7, 1, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(),
]
);
@ -374,9 +374,9 @@ mod tests {
assert_eq!(
db.entries_by_sheet("XXX", None, None).unwrap().into_iter().map(|e| e.start).collect::<Vec<_>>(),
vec![
Utc.ymd(2021, 7, 7).and_hms(1, 0, 0),
Utc.ymd(2021, 7, 7).and_hms(2, 0, 0),
Utc.ymd(2021, 7, 7).and_hms(3, 0, 0),
Utc.with_ymd_and_hms(2021, 7, 7, 1, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2021, 7, 7, 3, 0, 0).unwrap(),
]
);
}
@ -386,21 +386,21 @@ mod tests {
let mut db = SqliteDatabase::from_memory().unwrap();
db.init().unwrap();
db.entry_insert(Utc.ymd(2021, 7, 7).and_hms(1, 0, 0), None, None, "XXX").unwrap();
db.entry_insert(Utc.ymd(2021, 7, 7).and_hms(1, 0, 0), None, None, "_OO").unwrap();
db.entry_insert(Utc.ymd(2021, 7, 7).and_hms(2, 0, 0), None, None, "XXX").unwrap();
db.entry_insert(Utc.ymd(2021, 7, 7).and_hms(2, 0, 0), None, None, "_OO").unwrap();
db.entry_insert(Utc.ymd(2021, 7, 7).and_hms(3, 0, 0), None, None, "XXX").unwrap();
db.entry_insert(Utc.ymd(2021, 7, 7).and_hms(3, 0, 0), None, None, "_OO").unwrap();
db.entry_insert(Utc.with_ymd_and_hms(2021, 7, 7, 1, 0, 0).unwrap(), None, None, "XXX").unwrap();
db.entry_insert(Utc.with_ymd_and_hms(2021, 7, 7, 1, 0, 0).unwrap(), None, None, "_OO").unwrap();
db.entry_insert(Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(), None, None, "XXX").unwrap();
db.entry_insert(Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(), None, None, "_OO").unwrap();
db.entry_insert(Utc.with_ymd_and_hms(2021, 7, 7, 3, 0, 0).unwrap(), None, None, "XXX").unwrap();
db.entry_insert(Utc.with_ymd_and_hms(2021, 7, 7, 3, 0, 0).unwrap(), None, None, "_OO").unwrap();
let start = Utc.ymd(2021, 7, 7).and_hms(1, 30, 0);
let end = Utc.ymd(2021, 7, 7).and_hms(2, 30, 0);
let start = Utc.with_ymd_and_hms(2021, 7, 7, 1, 30, 0).unwrap();
let end = Utc.with_ymd_and_hms(2021, 7, 7, 2, 30, 0).unwrap();
// filter by start and end
assert_eq!(
db.entries_all_visible(Some(start), Some(end)).unwrap().into_iter().map(|e| e.start).collect::<Vec<_>>(),
vec![
Utc.ymd(2021, 7, 7).and_hms(2, 0, 0),
Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(),
]
);
@ -408,8 +408,8 @@ mod tests {
assert_eq!(
db.entries_all_visible(Some(start), None).unwrap().into_iter().map(|e| e.start).collect::<Vec<_>>(),
vec![
Utc.ymd(2021, 7, 7).and_hms(2, 0, 0),
Utc.ymd(2021, 7, 7).and_hms(3, 0, 0),
Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2021, 7, 7, 3, 0, 0).unwrap(),
]
);
@ -417,8 +417,8 @@ mod tests {
assert_eq!(
db.entries_all_visible(None, Some(end)).unwrap().into_iter().map(|e| e.start).collect::<Vec<_>>(),
vec![
Utc.ymd(2021, 7, 7).and_hms(1, 0, 0),
Utc.ymd(2021, 7, 7).and_hms(2, 0, 0),
Utc.with_ymd_and_hms(2021, 7, 7, 1, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(),
]
);
@ -426,9 +426,9 @@ mod tests {
assert_eq!(
db.entries_all_visible(None, None).unwrap().into_iter().map(|e| e.start).collect::<Vec<_>>(),
vec![
Utc.ymd(2021, 7, 7).and_hms(1, 0, 0),
Utc.ymd(2021, 7, 7).and_hms(2, 0, 0),
Utc.ymd(2021, 7, 7).and_hms(3, 0, 0),
Utc.with_ymd_and_hms(2021, 7, 7, 1, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2021, 7, 7, 3, 0, 0).unwrap(),
]
);
}
@ -438,22 +438,22 @@ mod tests {
let mut db = SqliteDatabase::from_memory().unwrap();
db.init().unwrap();
db.entry_insert(Utc.ymd(2021, 7, 7).and_hms(1, 0, 0), None, None, "XXX").unwrap();
db.entry_insert(Utc.ymd(2021, 7, 7).and_hms(1, 0, 0), None, None, "_OO").unwrap();
db.entry_insert(Utc.ymd(2021, 7, 7).and_hms(2, 0, 0), None, None, "XXX").unwrap();
db.entry_insert(Utc.ymd(2021, 7, 7).and_hms(2, 0, 0), None, None, "_OO").unwrap();
db.entry_insert(Utc.ymd(2021, 7, 7).and_hms(3, 0, 0), None, None, "XXX").unwrap();
db.entry_insert(Utc.ymd(2021, 7, 7).and_hms(3, 0, 0), None, None, "_OO").unwrap();
db.entry_insert(Utc.with_ymd_and_hms(2021, 7, 7, 1, 0, 0).unwrap(), None, None, "XXX").unwrap();
db.entry_insert(Utc.with_ymd_and_hms(2021, 7, 7, 1, 0, 0).unwrap(), None, None, "_OO").unwrap();
db.entry_insert(Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(), None, None, "XXX").unwrap();
db.entry_insert(Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(), None, None, "_OO").unwrap();
db.entry_insert(Utc.with_ymd_and_hms(2021, 7, 7, 3, 0, 0).unwrap(), None, None, "XXX").unwrap();
db.entry_insert(Utc.with_ymd_and_hms(2021, 7, 7, 3, 0, 0).unwrap(), None, None, "_OO").unwrap();
let start = Utc.ymd(2021, 7, 7).and_hms(1, 30, 0);
let end = Utc.ymd(2021, 7, 7).and_hms(2, 30, 0);
let start = Utc.with_ymd_and_hms(2021, 7, 7, 1, 30, 0).unwrap();
let end = Utc.with_ymd_and_hms(2021, 7, 7, 2, 30, 0).unwrap();
// filter by start and end
assert_eq!(
db.entries_full(Some(start), Some(end)).unwrap().into_iter().map(|e| e.start).collect::<Vec<_>>(),
vec![
Utc.ymd(2021, 7, 7).and_hms(2, 0, 0),
Utc.ymd(2021, 7, 7).and_hms(2, 0, 0),
Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(),
]
);
@ -461,10 +461,10 @@ mod tests {
assert_eq!(
db.entries_full(Some(start), None).unwrap().into_iter().map(|e| e.start).collect::<Vec<_>>(),
vec![
Utc.ymd(2021, 7, 7).and_hms(2, 0, 0),
Utc.ymd(2021, 7, 7).and_hms(3, 0, 0),
Utc.ymd(2021, 7, 7).and_hms(2, 0, 0),
Utc.ymd(2021, 7, 7).and_hms(3, 0, 0),
Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2021, 7, 7, 3, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2021, 7, 7, 3, 0, 0).unwrap(),
]
);
@ -472,10 +472,10 @@ mod tests {
assert_eq!(
db.entries_full(None, Some(end)).unwrap().into_iter().map(|e| e.start).collect::<Vec<_>>(),
vec![
Utc.ymd(2021, 7, 7).and_hms(1, 0, 0),
Utc.ymd(2021, 7, 7).and_hms(2, 0, 0),
Utc.ymd(2021, 7, 7).and_hms(1, 0, 0),
Utc.ymd(2021, 7, 7).and_hms(2, 0, 0),
Utc.with_ymd_and_hms(2021, 7, 7, 1, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2021, 7, 7, 1, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(),
]
);
@ -483,12 +483,12 @@ mod tests {
assert_eq!(
db.entries_full(None, None).unwrap().into_iter().map(|e| e.start).collect::<Vec<_>>(),
vec![
Utc.ymd(2021, 7, 7).and_hms(1, 0, 0),
Utc.ymd(2021, 7, 7).and_hms(2, 0, 0),
Utc.ymd(2021, 7, 7).and_hms(3, 0, 0),
Utc.ymd(2021, 7, 7).and_hms(1, 0, 0),
Utc.ymd(2021, 7, 7).and_hms(2, 0, 0),
Utc.ymd(2021, 7, 7).and_hms(3, 0, 0),
Utc.with_ymd_and_hms(2021, 7, 7, 1, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2021, 7, 7, 3, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2021, 7, 7, 1, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2021, 7, 7, 2, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2021, 7, 7, 3, 0, 0).unwrap(),
]
);
}
@ -503,10 +503,10 @@ mod tests {
let mut db = SqliteDatabase::from_memory().unwrap();
db.init().unwrap();
let sometime = Utc.ymd(2022, 7, 27);
let sometime = NaiveDate::from_ymd_opt(2022, 7, 27).unwrap();
db.entry_insert(sometime.and_hms(11, 0, 0), Some(sometime.and_hms(12, 0, 0)), Some("latest".into()), "foo").unwrap();
db.entry_insert(sometime.and_hms(10, 0, 0), Some(sometime.and_hms(11, 0, 0)), Some("oldest".into()), "foo").unwrap();
db.entry_insert(sometime.and_hms_opt(11, 0, 0).unwrap().and_local_timezone(Utc).unwrap(), Some(sometime.and_hms_opt(12, 0, 0).unwrap().and_local_timezone(Utc).unwrap()), Some("latest".into()), "foo").unwrap();
db.entry_insert(sometime.and_hms_opt(10, 0, 0).unwrap().and_local_timezone(Utc).unwrap(), Some(sometime.and_hms_opt(11, 0, 0).unwrap().and_local_timezone(Utc).unwrap()), Some("oldest".into()), "foo").unwrap();
// filter by start and end
assert_eq!(
@ -514,8 +514,8 @@ mod tests {
Entry {
id: 1,
note: Some("latest".into()),
start: sometime.and_hms(11, 0, 0),
end: Some(sometime.and_hms(12, 0, 0)),
start: sometime.and_hms_opt(11, 0, 0).unwrap().and_local_timezone(Utc).unwrap(),
end: Some(sometime.and_hms_opt(12, 0, 0).unwrap().and_local_timezone(Utc).unwrap()),
sheet: "foo".into(),
}
);

View File

@ -1,5 +1,5 @@
use std::process::{Command, Stdio};
use std::io::{Read, Write, Seek, SeekFrom};
use std::io::{Read, Write, Seek};
use tempfile::NamedTempFile;
@ -38,7 +38,7 @@ pub fn get_string(note_editor: Option<&str>, prev_contents: Option<String>) -> R
if let Some(contents) = prev_contents {
tmpfile.write_all(contents.as_bytes())?;
tmpfile.seek(SeekFrom::Start(0))?;
tmpfile.rewind()?;
}
c.arg(tmpfile.as_ref());

View File

@ -19,6 +19,9 @@ pub enum Error {
#[error("The subcommand '{0}' is not implemented")]
UnimplementedCommand(String),
#[error("A subcommand was not specified and no default command is set")]
MissingSubcommand,
/// Sometimes a specific variant for an error is not necessary if the error
/// can only happen in one place in the code. This is what the generic error
/// is for and nothing else.

View File

@ -15,9 +15,10 @@ pub mod ical;
pub mod custom;
pub mod chart;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Formatter {
#[default]
Text,
Csv,
Json,
@ -27,12 +28,6 @@ pub enum Formatter {
Custom(String),
}
impl Default for Formatter {
fn default() -> Formatter {
Formatter::Text
}
}
impl Formatter {
/// Prints the given entries to the specified output device.
///

View File

@ -1,9 +1,9 @@
use std::io::Write;
use std::fmt::Write as _;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use crate::tabulate::{Tabulate, Col, Align::*};
use chrono::{Local, Datelike, Date, Duration};
use chrono::{Local, Datelike, NaiveDate, Duration};
use ansi_term::{Style, Color::{Green, Red, White, Fixed}};
use crate::commands::Facts;
@ -12,12 +12,12 @@ use crate::error::Result;
use crate::config::WeekDay;
struct Dates {
current: Date<Local>,
end: Date<Local>,
current: NaiveDate,
end: NaiveDate,
}
impl Dates {
fn range(from: Date<Local>, to: Date<Local>) -> Dates {
fn range(from: NaiveDate, to: NaiveDate) -> Dates {
Dates {
current: from,
end: to,
@ -26,7 +26,7 @@ impl Dates {
}
impl Iterator for Dates {
type Item = Date<Local>;
type Item = NaiveDate;
fn next(&mut self) -> Option<Self::Item> {
if self.current > self.end {
@ -34,7 +34,7 @@ impl Iterator for Dates {
} else {
let val = self.current;
self.current = self.current + Duration::days(1);
self.current += Duration::days(1);
Some(val)
}
@ -81,9 +81,10 @@ pub fn print_formatted<W: Write>(entries: Vec<Entry>, out: &mut W, facts: &Facts
let mut entries_by_date = HashMap::new();
let mut first_date = None;
let mut last_date = None;
let mut timesheets = HashSet::new();
for entry in entries.into_iter() {
let entrys_date = entry.start.with_timezone(&Local).date();
let entrys_date = entry.start.with_timezone(&Local).date_naive();
let hours = entry.hours(facts.now);
if first_date.is_none() {
@ -97,6 +98,8 @@ pub fn print_formatted<W: Write>(entries: Vec<Entry>, out: &mut W, facts: &Facts
last_date = last_date.map(|d| d.max(entrys_date));
}
timesheets.insert(entry.sheet);
let e = entries_by_date.entry(entrys_date).or_insert(0.0);
*e += hours;
@ -175,6 +178,16 @@ pub fn print_formatted<W: Write>(entries: Vec<Entry>, out: &mut W, facts: &Facts
out.write_all(tabs.print(facts.env.stdout_is_tty).as_bytes())?;
if timesheets.len() == 1 {
out.write_all(format!("\nTimesheet: {}\n", timesheets.into_iter().next().unwrap()).as_bytes())?;
} else {
let mut timesheets: Vec<_> = timesheets.into_iter().collect();
timesheets.sort_unstable();
out.write_all(format!("\nTimesheets: {}\n", timesheets.join(", ")).as_bytes())?;
}
Ok(())
}
@ -191,10 +204,10 @@ mod tests {
fn sample_printing() {
std::env::set_var("TZ", "CST+6");
let day1 = Utc.ymd(2022, 8, 15).and_hms(12, 0, 0);
let day2 = Utc.ymd(2022, 8, 16).and_hms(12, 0, 0);
let day3 = Utc.ymd(2022, 8, 17).and_hms(12, 0, 0);
let day4 = Utc.ymd(2022, 8, 18).and_hms(12, 0, 0);
let day1 = Utc.with_ymd_and_hms(2022, 8, 15, 12, 0, 0).unwrap();
let day2 = Utc.with_ymd_and_hms(2022, 8, 16, 12, 0, 0).unwrap();
let day3 = Utc.with_ymd_and_hms(2022, 8, 17, 12, 0, 0).unwrap();
let day4 = Utc.with_ymd_and_hms(2022, 8, 18, 12, 0, 0).unwrap();
let entries = vec![
Entry::new_sample(1, day1, Some(day1 + Duration::hours(5))),
@ -226,6 +239,8 @@ Aug 15 Mon \u{1b}[42m \u{1b}[0m\u{1b}[48;5;10m \u{1b}[0m 5.0
18 Thu \u{1b}[42m \u{1b}[0m\u{1b}[47m \u{1b}[0m 2.0
Week \u{1b}[31m14.5\u{1b}[0m/20.0
Timesheet: default
");
}
@ -234,8 +249,8 @@ Aug 15 Mon \u{1b}[42m \u{1b}[0m\u{1b}[48;5;10m \u{1b}[0m 5.0
fn partitioned_week() {
std::env::set_var("TZ", "CST+6");
let day1 = Utc.ymd(2022, 8, 28).and_hms(12, 0, 0);
let day2 = Utc.ymd(2022, 8, 29).and_hms(12, 0, 0);
let day1 = Utc.with_ymd_and_hms(2022, 8, 28, 12, 0, 0).unwrap();
let day2 = Utc.with_ymd_and_hms(2022, 8, 29, 12, 0, 0).unwrap();
let entries = vec![
Entry::new_sample(1, day1, Some(day1 + Duration::hours(5))),
@ -255,6 +270,8 @@ Aug 28 Sun \u{1b}[48;5;10m \u{1b}[0m 5.0
Aug 29 Mon \u{1b}[48;5;10m \u{1b}[0m 3.0
Week 3.0
Timesheet: default
");
}
@ -273,8 +290,8 @@ Aug 29 Mon \u{1b}[48;5;10m \u{1b}[0m 3.0
fn days_without_hours_appear() {
std::env::set_var("TZ", "CST+6");
let day1 = Utc.ymd(2022, 8, 15).and_hms(12, 0, 0);
let day3 = Utc.ymd(2022, 8, 17).and_hms(12, 0, 0);
let day1 = Utc.with_ymd_and_hms(2022, 8, 15, 12, 0, 0).unwrap();
let day3 = Utc.with_ymd_and_hms(2022, 8, 17, 12, 0, 0).unwrap();
let entries = vec![
Entry::new_sample(1, day1, Some(day1 + Duration::hours(5))),
@ -292,6 +309,8 @@ Aug 15 Mon \u{1b}[48;5;10m \u{1b}[0m 5.0
17 Wed \u{1b}[48;5;10m \u{1b}[0m 4.0
Week 9.0
Timesheet: default
");
}
@ -299,10 +318,10 @@ Aug 15 Mon \u{1b}[48;5;10m \u{1b}[0m 5.0
fn display_without_goals_set() {
std::env::set_var("TZ", "CST+6");
let day1 = Utc.ymd(2022, 8, 15).and_hms(12, 0, 0);
let day2 = Utc.ymd(2022, 8, 16).and_hms(12, 0, 0);
let day3 = Utc.ymd(2022, 8, 17).and_hms(12, 0, 0);
let day4 = Utc.ymd(2022, 8, 18).and_hms(12, 0, 0);
let day1 = Utc.with_ymd_and_hms(2022, 8, 15, 12, 0, 0).unwrap();
let day2 = Utc.with_ymd_and_hms(2022, 8, 16, 12, 0, 0).unwrap();
let day3 = Utc.with_ymd_and_hms(2022, 8, 17, 12, 0, 0).unwrap();
let day4 = Utc.with_ymd_and_hms(2022, 8, 18, 12, 0, 0).unwrap();
let entries = vec![
Entry::new_sample(1, day1, Some(day1 + Duration::hours(5))),
@ -323,6 +342,41 @@ Aug 15 Mon \u{1b}[48;5;10m \u{1b}[0m 5.0
18 Thu \u{1b}[48;5;10m \u{1b}[0m 2.0
Week 14.5
Timesheet: default
");
}
#[test]
fn multiple_timesheets_to_display() {
std::env::set_var("TZ", "CST+6");
let day1 = Utc.with_ymd_and_hms(2022, 8, 15, 12, 0, 0).unwrap();
let day2 = Utc.with_ymd_and_hms(2022, 8, 16, 12, 0, 0).unwrap();
let day3 = Utc.with_ymd_and_hms(2022, 8, 17, 12, 0, 0).unwrap();
let day4 = Utc.with_ymd_and_hms(2022, 8, 18, 12, 0, 0).unwrap();
let entries = vec![
Entry::new_sample(1, day1, Some(day1 + Duration::hours(5))).with_sheet("var"),
Entry::new_sample(2, day2, Some(day2 + Duration::minutes(60 * 3 + 30))).with_sheet("var"),
Entry::new_sample(3, day3, Some(day3 + Duration::hours(4))).with_sheet("foo"),
Entry::new_sample(4, day4, Some(day4 + Duration::hours(2))).with_sheet("foo"),
];
let mut out = Vec::new();
let facts = Facts::new();
print_formatted(entries, &mut out, &facts).unwrap();
assert_str_eq!(String::from_utf8_lossy(&out), " Date Day Chart Hours
Aug 15 Mon \u{1b}[48;5;10m \u{1b}[0m 5.0
16 Tue \u{1b}[48;5;10m \u{1b}[0m 3.5
17 Wed \u{1b}[48;5;10m \u{1b}[0m 4.0
18 Thu \u{1b}[48;5;10m \u{1b}[0m 2.0
Week 14.5
Timesheets: foo, var
");
}
}

View File

@ -10,9 +10,9 @@ pub fn print_formatted<W: Write>(entries: Vec<Entry>, out: &mut W, ids: bool) ->
let mut wtr = Writer::from_writer(out);
if ids {
wtr.write_record(&["id", "start", "end", "note", "sheet"])?;
wtr.write_record(["id", "start", "end", "note", "sheet"])?;
} else {
wtr.write_record(&["start", "end", "note", "sheet"])?;
wtr.write_record(["start", "end", "note", "sheet"])?;
}
for entry in entries {
@ -46,8 +46,8 @@ mod tests {
#[test]
fn test_print_formatted() {
let entries = vec![
Entry::new_sample(1, Utc.ymd(2021, 6, 30).and_hms(18, 12, 34), Some(Utc.ymd(2021, 6, 30).and_hms(19, 0, 0))),
Entry::new_sample(2, Utc.ymd(2021, 6, 30).and_hms(18, 12, 34), None),
Entry::new_sample(1, Utc.with_ymd_and_hms(2021, 6, 30, 18, 12, 34).unwrap(), Some(Utc.with_ymd_and_hms(2021, 6, 30, 19, 0, 0).unwrap())),
Entry::new_sample(2, Utc.with_ymd_and_hms(2021, 6, 30, 18, 12, 34).unwrap(), None),
];
let mut out = Vec::new();
@ -62,8 +62,8 @@ mod tests {
#[test]
fn test_print_formatted_ids() {
let entries = vec![
Entry::new_sample(1, Utc.ymd(2021, 6, 30).and_hms(18, 12, 34), Some(Utc.ymd(2021, 6, 30).and_hms(19, 0, 0))),
Entry::new_sample(2, Utc.ymd(2021, 6, 30).and_hms(18, 12, 34), None),
Entry::new_sample(1, Utc.with_ymd_and_hms(2021, 6, 30, 18, 12, 34).unwrap(), Some(Utc.with_ymd_and_hms(2021, 6, 30, 19, 0, 0).unwrap())),
Entry::new_sample(2, Utc.with_ymd_and_hms(2021, 6, 30, 18, 12, 34).unwrap(), None),
];
let mut out = Vec::new();

View File

@ -81,7 +81,7 @@ pub fn print_formatted<W: Write>(entries: Vec<Entry>, out: &mut W, facts: &Facts
]);
}
let entries_by_date = group.group_by(|e| e.start.with_timezone(&Local).date());
let entries_by_date = group.group_by(|e| e.start.with_timezone(&Local).date_naive());
let mut total = Duration::seconds(0);
for (date, entries) in entries_by_date.into_iter() {
@ -158,13 +158,13 @@ mod tests {
std::env::set_var("TZ", "CST+6");
let mut output = Vec::new();
let entries = vec![
Entry::new_sample(1, Utc.ymd(2008, 10, 3).and_hms(12, 0, 0), Some(Utc.ymd(2008, 10, 3).and_hms(14, 0, 0))),
Entry::new_sample(2, Utc.ymd(2008, 10, 3).and_hms(16, 0, 0), Some(Utc.ymd(2008, 10, 3).and_hms(18, 0, 0))),
Entry::new_sample(3, Utc.ymd(2008, 10, 5).and_hms(16, 0, 0), Some(Utc.ymd(2008, 10, 5).and_hms(18, 0, 0))),
Entry::new_sample(4, Utc.ymd(2008, 10, 5).and_hms(18, 0, 0), None),
Entry::new_sample(1, Utc.with_ymd_and_hms(2008, 10, 3, 12, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2008, 10, 3, 14, 0, 0).unwrap())),
Entry::new_sample(2, Utc.with_ymd_and_hms(2008, 10, 3, 16, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2008, 10, 3, 18, 0, 0).unwrap())),
Entry::new_sample(3, Utc.with_ymd_and_hms(2008, 10, 5, 16, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2008, 10, 5, 18, 0, 0).unwrap())),
Entry::new_sample(4, Utc.with_ymd_and_hms(2008, 10, 5, 18, 0, 0).unwrap(), None),
];
let now = Utc.ymd(2008, 10, 5).and_hms(20, 0, 0);
let now = Utc.with_ymd_and_hms(2008, 10, 5, 20, 0, 0).unwrap();
let facts = Facts::new().with_now(now);
print_formatted(entries, &mut output, &facts, false).unwrap();
@ -187,10 +187,13 @@ mod tests {
std::env::set_var("TZ", "CST+6");
let mut output = Vec::new();
let entries = vec![
Entry::new_sample(1, Utc.ymd(2008, 10, 3).and_hms_milli(12, 0, 0, 432), Some(Utc.ymd(2008, 10, 3).and_hms_milli(14, 0, 0, 312))),
Entry::new_sample(
1,
Utc.with_ymd_and_hms(2008, 10, 3, 12, 0, 0).unwrap().with_nanosecond(432_000_000).unwrap(),
Some(Utc.with_ymd_and_hms(2008, 10, 3, 14, 0, 0).unwrap().with_nanosecond(312_000_000).unwrap())),
];
let now = Utc.ymd(2008, 10, 5).and_hms(20, 0, 0);
let now = Utc.with_ymd_and_hms(2008, 10, 5, 20, 0, 0).unwrap();
let facts = Facts::new().with_now(now);
print_formatted(entries, &mut output, &facts, false).unwrap();
@ -209,11 +212,11 @@ mod tests {
std::env::set_var("TZ", "CST+6");
let mut output = Vec::new();
let entries = vec![
Entry::new_sample(1, Utc.ymd(2008, 10, 1).and_hms(12, 0, 0), Some(Utc.ymd(2008, 10, 3).and_hms(14, 0, 0))),
Entry::new_sample(2, Utc.ymd(2008, 10, 3).and_hms(12, 0, 0), Some(Utc.ymd(2008, 10, 3).and_hms(14, 0, 0))),
Entry::new_sample(1, Utc.with_ymd_and_hms(2008, 10, 1, 12, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2008, 10, 3, 14, 0, 0).unwrap())),
Entry::new_sample(2, Utc.with_ymd_and_hms(2008, 10, 3, 12, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2008, 10, 3, 14, 0, 0).unwrap())),
];
let now = Utc.ymd(2008, 10, 5).and_hms(20, 0, 0);
let now = Utc.with_ymd_and_hms(2008, 10, 5, 20, 0, 0).unwrap();
let facts = Facts::new().with_now(now);
print_formatted(entries, &mut output, &facts, false).unwrap();
@ -234,13 +237,13 @@ mod tests {
std::env::set_var("TZ", "CST+6");
let mut output = Vec::new();
let entries = vec![
Entry::new_sample(1, Utc.ymd(2008, 10, 3).and_hms(12, 0, 0), Some(Utc.ymd(2008, 10, 3).and_hms(14, 0, 0))),
Entry::new_sample(2, Utc.ymd(2008, 10, 3).and_hms(16, 0, 0), Some(Utc.ymd(2008, 10, 3).and_hms(18, 0, 0))),
Entry::new_sample(3, Utc.ymd(2008, 10, 5).and_hms(16, 0, 0), Some(Utc.ymd(2008, 10, 5).and_hms(18, 0, 0))),
Entry::new_sample(4, Utc.ymd(2008, 10, 5).and_hms(18, 0, 0), None),
Entry::new_sample(1, Utc.with_ymd_and_hms(2008, 10, 3, 12, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2008, 10, 3, 14, 0, 0).unwrap())),
Entry::new_sample(2, Utc.with_ymd_and_hms(2008, 10, 3, 16, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2008, 10, 3, 18, 0, 0).unwrap())),
Entry::new_sample(3, Utc.with_ymd_and_hms(2008, 10, 5, 16, 0, 0).unwrap(), Some(Utc.with_ymd_and_hms(2008, 10, 5, 18, 0, 0).unwrap())),
Entry::new_sample(4, Utc.with_ymd_and_hms(2008, 10, 5, 18, 0, 0).unwrap(), None),
];
let now = Utc.ymd(2008, 10, 5).and_hms(20, 0, 0);
let now = Utc.with_ymd_and_hms(2008, 10, 5, 20, 0, 0).unwrap();
let facts = Facts::new().with_now(now);
print_formatted(entries, &mut output, &facts, true).unwrap();
@ -266,13 +269,13 @@ mod tests {
Entry {
id: 60000,
sheet: "default".into(),
start: Utc.ymd(2008, 10, 5).and_hms(16, 0, 0),
end: Some(Utc.ymd(2008, 10, 5).and_hms(18, 0, 0)),
start: Utc.with_ymd_and_hms(2008, 10, 5, 16, 0, 0).unwrap(),
end: Some(Utc.with_ymd_and_hms(2008, 10, 5, 18, 0, 0).unwrap()),
note: Some(LONG_NOTE.into()),
},
];
let now = Utc.ymd(2008, 10, 5).and_hms(20, 0, 0);
let now = Utc.with_ymd_and_hms(2008, 10, 5, 20, 0, 0).unwrap();
let facts = Facts::new().with_now(now);
print_formatted(entries, &mut output, &facts, true).unwrap();
@ -301,13 +304,13 @@ mod tests {
Entry {
id: 1,
sheet: "default".into(),
start: Utc.ymd(2008, 10, 5).and_hms(16, 0, 0),
end: Some(Utc.ymd(2008, 10, 5).and_hms(18, 0, 0)),
start: Utc.with_ymd_and_hms(2008, 10, 5, 16, 0, 0).unwrap(),
end: Some(Utc.with_ymd_and_hms(2008, 10, 5, 18, 0, 0).unwrap()),
note: Some("first line\nand a second line".into()),
},
];
let now = Utc.ymd(2008, 10, 5).and_hms(20, 0, 0);
let now = Utc.with_ymd_and_hms(2008, 10, 5, 20, 0, 0).unwrap();
let facts = Facts::new().with_now(now);
print_formatted(entries, &mut output, &facts, false).unwrap();
@ -330,13 +333,13 @@ mod tests {
Entry {
id: 1,
sheet: "default".into(),
start: Utc.ymd(2008, 10, 5).and_hms(16, 0, 0),
end: Some(Utc.ymd(2008, 10, 5).and_hms(18, 0, 0)),
start: Utc.with_ymd_and_hms(2008, 10, 5, 16, 0, 0).unwrap(),
end: Some(Utc.with_ymd_and_hms(2008, 10, 5, 18, 0, 0).unwrap()),
note: Some("quiúbole".into()),
},
];
let now = Utc.ymd(2008, 10, 5).and_hms(20, 0, 0);
let now = Utc.with_ymd_and_hms(2008, 10, 5, 20, 0, 0).unwrap();
let facts = Facts::new().with_now(now);
print_formatted(entries, &mut output, &facts, false).unwrap();
@ -359,20 +362,20 @@ mod tests {
Entry {
id: 1,
sheet: "sheet1".to_string(),
start: Utc.ymd(2008, 10, 5).and_hms(16, 0, 0),
end: Some(Utc.ymd(2008, 10, 5).and_hms(18, 0, 0)),
start: Utc.with_ymd_and_hms(2008, 10, 5, 16, 0, 0).unwrap(),
end: Some(Utc.with_ymd_and_hms(2008, 10, 5, 18, 0, 0).unwrap()),
note: Some("quiúbole".to_string()),
},
Entry {
id: 2,
sheet: "sheet2".to_string(),
start: Utc.ymd(2008, 10, 5).and_hms(16, 0, 0),
end: Some(Utc.ymd(2008, 10, 5).and_hms(18, 0, 0)),
start: Utc.with_ymd_and_hms(2008, 10, 5, 16, 0, 0).unwrap(),
end: Some(Utc.with_ymd_and_hms(2008, 10, 5, 18, 0, 0).unwrap()),
note: Some("quiúbole".to_string()),
},
];
let now = Utc.ymd(2008, 10, 5).and_hms(20, 0, 0);
let now = Utc.with_ymd_and_hms(2008, 10, 5, 20, 0, 0).unwrap();
let facts = Facts::new().with_now(now);
print_formatted(entries, &mut output, &facts, false).unwrap();

View File

@ -15,3 +15,4 @@ pub mod old;
pub mod interactive;
pub mod env;
pub mod io;
pub mod cli;

View File

@ -28,6 +28,13 @@ impl Entry {
}
}
pub fn with_sheet(self, sheet: &str) -> Entry {
Entry {
sheet: sheet.into(),
..self
}
}
pub fn timespan(&self) -> Option<Duration> {
self.end.map(|e| e - self.start)
}

View File

@ -14,10 +14,8 @@ fn date_from_parts<T: TimeZone>(
timezone: T, input: &str, year: i32, month: u32, day: u32, hour: u32,
minute: u32, second: u32
) -> Result<DateTime<Utc>> {
let try_date = timezone.ymd_opt(
year, month, day
).and_hms_opt(
hour, minute, second
let try_date = timezone.with_ymd_and_hms(
year, month, day, hour, minute, second
);
match try_date {
@ -36,9 +34,9 @@ fn offset_from_parts(east: bool, hours: i32, minutes: i32) -> FixedOffset {
second += minutes * 60;
if east {
FixedOffset::east(second)
FixedOffset::east_opt(second).unwrap()
} else {
FixedOffset::west(second)
FixedOffset::west_opt(second).unwrap()
}
}
@ -94,9 +92,9 @@ pub fn parse_time(input: &str) -> Result<DateTime<Utc>> {
// first try to parse as a full datetime with optional timezone
if let Some(caps) = DATETIME_REGEX.captures(input) {
let year: i32 = (&caps["year"]).parse().unwrap();
let month: u32 = (&caps["month"]).parse().unwrap();
let day: u32 = (&caps["day"]).parse().unwrap();
let year: i32 = caps["year"].parse().unwrap();
let month: u32 = caps["month"].parse().unwrap();
let day: u32 = caps["day"].parse().unwrap();
let hour: u32 = caps.name("hour").map(|t| t.as_str().parse().unwrap()).unwrap_or(0);
let minute: u32 = caps.name("minute").map(|t| t.as_str().parse().unwrap()).unwrap_or(0);
let second: u32 = caps.name("second").map(|t| t.as_str().parse().unwrap()).unwrap_or(0);
@ -107,8 +105,8 @@ pub fn parse_time(input: &str) -> Result<DateTime<Utc>> {
Utc, input, year, month, day, hour, minute, second,
)
} else {
let hours: i32 = (&caps["ohour"]).parse().unwrap();
let minutes: i32 = (&caps["omin"]).parse().unwrap();
let hours: i32 = caps["ohour"].parse().unwrap();
let minutes: i32 = caps["omin"].parse().unwrap();
let fo = offset_from_parts(&caps["sign"] == "+", hours, minutes);
date_from_parts(
@ -124,7 +122,7 @@ pub fn parse_time(input: &str) -> Result<DateTime<Utc>> {
}
if let Some(caps) = HOUR_REGEX.captures(input) {
let hour: u32 = (&caps["hour"]).parse().unwrap();
let hour: u32 = caps["hour"].parse().unwrap();
let minute: u32 = caps.name("minute").map(|t| t.as_str().parse().unwrap()).unwrap_or(0);
let second: u32 = caps.name("second").map(|t| t.as_str().parse().unwrap()).unwrap_or(0);
@ -136,8 +134,8 @@ pub fn parse_time(input: &str) -> Result<DateTime<Utc>> {
Utc, input, year, month, day, hour, minute, second,
)
} else {
let hours: i32 = (&caps["ohour"]).parse().unwrap();
let minutes: i32 = (&caps["omin"]).parse().unwrap();
let hours: i32 = caps["ohour"].parse().unwrap();
let minutes: i32 = caps["omin"].parse().unwrap();
let fo = offset_from_parts(&caps["sign"] == "+", hours, minutes);
let (year, month, day) = date_parts(fo.from_utc_datetime(&Utc::now().naive_utc()));
@ -163,54 +161,54 @@ pub fn parse_hours(input: &str) -> Result<u16> {
#[cfg(test)]
mod tests {
use chrono::{TimeZone, Duration};
use chrono::{TimeZone, Duration, Timelike};
use super::*;
#[test]
fn parse_datetime_string() {
assert_eq!(parse_time("2021-05-21 11:36").unwrap(), Local.ymd(2021, 5, 21).and_hms(11, 36, 0));
assert_eq!(parse_time("2021-05-21 11:36:12").unwrap(), Local.ymd(2021, 5, 21).and_hms(11, 36, 12));
assert_eq!(parse_time("2021-05-21 11:36").unwrap(), Local.with_ymd_and_hms(2021, 5, 21, 11, 36, 0).unwrap());
assert_eq!(parse_time("2021-05-21 11:36:12").unwrap(), Local.with_ymd_and_hms(2021, 5, 21, 11, 36, 12).unwrap());
assert_eq!(parse_time("2021-05-21T11:36").unwrap(), Local.ymd(2021, 5, 21).and_hms(11, 36, 0));
assert_eq!(parse_time("2021-05-21T11:36:12").unwrap(), Local.ymd(2021, 5, 21).and_hms(11, 36, 12));
assert_eq!(parse_time("2021-05-21T11:36").unwrap(), Local.with_ymd_and_hms(2021, 5, 21, 11, 36, 0).unwrap());
assert_eq!(parse_time("2021-05-21T11:36:12").unwrap(), Local.with_ymd_and_hms(2021, 5, 21, 11, 36, 12).unwrap());
}
#[test]
fn parse_date() {
assert_eq!(parse_time("2021-05-21").unwrap(), Local.ymd(2021, 5, 21).and_hms(0, 0, 0));
assert_eq!(parse_time("2021-05-21").unwrap(), Local.with_ymd_and_hms(2021, 5, 21, 0, 0, 0).unwrap());
}
#[test]
fn parse_hour() {
let localdate = Local::now().date();
let localdate = Local::now().with_hour(0).unwrap().with_minute(0).unwrap().with_second(0).unwrap().with_nanosecond(0).unwrap();
assert_eq!(parse_time("11:36").unwrap(), localdate.and_hms(11, 36, 0));
assert_eq!(parse_time("11:36:35").unwrap(), localdate.and_hms(11, 36, 35));
assert_eq!(parse_time("11:36").unwrap(), localdate.with_hour(11).unwrap().with_minute(36).unwrap().with_timezone(&Utc));
assert_eq!(parse_time("11:36:35").unwrap(), localdate.with_hour(11).unwrap().with_minute(36).unwrap().with_second(35).unwrap().with_timezone(&Utc));
}
#[test]
fn parse_hour_with_timezone() {
let hours: i32 = 3600;
let todayutc = Utc::now().date();
let todayutc = Utc::now().date_naive();
assert_eq!(parse_time("11:36Z").unwrap(), todayutc.and_hms(11, 36, 0));
assert_eq!(parse_time("11:36:35z").unwrap(), todayutc.and_hms(11, 36, 35));
assert_eq!(parse_time("11:36Z").unwrap(), todayutc.and_hms_opt(11, 36, 0).unwrap().and_local_timezone(Utc).unwrap());
assert_eq!(parse_time("11:36:35z").unwrap(), todayutc.and_hms_opt(11, 36, 35).unwrap().and_local_timezone(Utc).unwrap());
let offset = FixedOffset::west(5 * hours);
let todayoffset = offset.from_utc_datetime(&Utc::now().naive_utc()).date();
assert_eq!(parse_time("11:36-5:00").unwrap(), todayoffset.and_hms(11, 36, 0));
let offset = FixedOffset::west_opt(5 * hours).unwrap();
let today_at_offset = offset.from_utc_datetime(&Utc::now().naive_utc()).with_hour(0).unwrap().with_minute(0).unwrap().with_second(0).unwrap().with_nanosecond(0).unwrap();
assert_eq!(parse_time("11:36-5:00").unwrap(), today_at_offset.with_hour(11).unwrap().with_minute(36).unwrap().with_timezone(&Utc));
let offset = FixedOffset::east(5 * hours);
let todayoffset = offset.from_utc_datetime(&Utc::now().naive_utc()).date();
assert_eq!(parse_time("11:36:35+5:00").unwrap(), todayoffset.and_hms(11, 36, 35));
let offset = FixedOffset::east_opt(5 * hours).unwrap();
let today_at_offset = offset.from_utc_datetime(&Utc::now().naive_utc()).with_hour(0).unwrap().with_minute(0).unwrap().with_second(0).unwrap().with_nanosecond(0).unwrap();
assert_eq!(parse_time("11:36:35+5:00").unwrap(), today_at_offset.with_hour(11).unwrap().with_minute(36).unwrap().with_second(35).unwrap().with_timezone(&Utc));
}
#[test]
fn parse_with_specified_timezone() {
assert_eq!(parse_time("2021-05-21T11:36:12Z").unwrap(), Utc.ymd(2021, 5, 21).and_hms(11, 36, 12));
assert_eq!(parse_time("2021-05-21T11:36:12-3:00").unwrap(), Utc.ymd(2021, 5, 21).and_hms(14, 36, 12));
assert_eq!(parse_time("2021-05-21T11:36:12+3:00").unwrap(), Utc.ymd(2021, 5, 21).and_hms(8, 36, 12));
assert_eq!(parse_time("2021-05-21T11:36:12Z").unwrap(), Utc.with_ymd_and_hms(2021, 5, 21, 11, 36, 12).unwrap());
assert_eq!(parse_time("2021-05-21T11:36:12-3:00").unwrap(), Utc.with_ymd_and_hms(2021, 5, 21, 14, 36, 12).unwrap());
assert_eq!(parse_time("2021-05-21T11:36:12+3:00").unwrap(), Utc.with_ymd_and_hms(2021, 5, 21, 8, 36, 12).unwrap());
}
fn time_diff(t1: DateTime<Utc>, t2: DateTime<Local>) {