Showing preview only (913K chars total). Download the full file or copy to clipboard to get everything.
Repository: lucassabreu/clockify-cli
Branch: main
Commit: fd1bfddec02c
Files: 216
Total size: 852.5 KB
Directory structure:
gitextract_9hng928q/
├── .deepsource.toml
├── .github/
│ └── workflows/
│ ├── golangci-lint.yml
│ ├── release.yml
│ └── test-unit.yaml
├── .gitignore
├── .gitmodules
├── .goreleaser.yml
├── .mockery.yaml
├── .nvimrc
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── api/
│ ├── client.go
│ ├── client_test.go
│ ├── dto/
│ │ ├── dto.go
│ │ └── request.go
│ ├── httpClient.go
│ ├── logger.go
│ ├── project_test.go
│ ├── tag_test.go
│ ├── task_test.go
│ └── timeentry_test.go
├── cmd/
│ ├── clockify-cli/
│ │ └── main.go
│ ├── gendocs/
│ │ └── main.go
│ └── release/
│ └── main.go
├── docs/
│ └── project-layout.md
├── go.mod
├── go.sum
├── internal/
│ ├── consoletest/
│ │ └── test.go
│ ├── mocks/
│ │ ├── gen.go
│ │ ├── mock_Client.go
│ │ ├── mock_Config.go
│ │ ├── mock_Factory.go
│ │ └── simple_config.go
│ └── testhlp/
│ └── helper.go
├── netlify.toml
├── pkg/
│ ├── cmd/
│ │ ├── client/
│ │ │ ├── add/
│ │ │ │ ├── add.go
│ │ │ │ └── add_test.go
│ │ │ ├── client.go
│ │ │ ├── list/
│ │ │ │ ├── list.go
│ │ │ │ └── list_test.go
│ │ │ └── util/
│ │ │ └── util.go
│ │ ├── completion/
│ │ │ └── completion.go
│ │ ├── config/
│ │ │ ├── config.go
│ │ │ ├── get/
│ │ │ │ ├── get.go
│ │ │ │ └── get_test.go
│ │ │ ├── init/
│ │ │ │ ├── init.go
│ │ │ │ └── init_test.go
│ │ │ ├── list/
│ │ │ │ ├── list.go
│ │ │ │ └── list_test.go
│ │ │ ├── set/
│ │ │ │ ├── set.go
│ │ │ │ └── set_test.go
│ │ │ └── util/
│ │ │ └── util.go
│ │ ├── project/
│ │ │ ├── add/
│ │ │ │ ├── add.go
│ │ │ │ └── add_test.go
│ │ │ ├── edit/
│ │ │ │ ├── edit.go
│ │ │ │ └── edit_test.go
│ │ │ ├── get/
│ │ │ │ ├── get.go
│ │ │ │ └── get_test.go
│ │ │ ├── list/
│ │ │ │ ├── list.go
│ │ │ │ └── list_test.go
│ │ │ ├── project.go
│ │ │ └── util/
│ │ │ └── util.go
│ │ ├── root.go
│ │ ├── tag/
│ │ │ └── tag.go
│ │ ├── task/
│ │ │ ├── add/
│ │ │ │ ├── add.go
│ │ │ │ └── add_test.go
│ │ │ ├── delete/
│ │ │ │ ├── delete.go
│ │ │ │ └── delete_test.go
│ │ │ ├── done/
│ │ │ │ ├── done.go
│ │ │ │ └── done_test.go
│ │ │ ├── edit/
│ │ │ │ ├── edit.go
│ │ │ │ └── edit_test.go
│ │ │ ├── list/
│ │ │ │ ├── list.go
│ │ │ │ └── list_test.go
│ │ │ ├── quick-add/
│ │ │ │ ├── quick-add.go
│ │ │ │ └── quick-add_test.go
│ │ │ ├── task.go
│ │ │ └── util/
│ │ │ ├── read-flags.go
│ │ │ └── report.go
│ │ ├── time-entry/
│ │ │ ├── clone/
│ │ │ │ └── clone.go
│ │ │ ├── delete/
│ │ │ │ └── delete.go
│ │ │ ├── edit/
│ │ │ │ ├── edit.go
│ │ │ │ └── edit_test.go
│ │ │ ├── edit-multipple/
│ │ │ │ └── edit-multiple.go
│ │ │ ├── in/
│ │ │ │ ├── in.go
│ │ │ │ └── in_test.go
│ │ │ ├── invoiced/
│ │ │ │ └── invoiced.go
│ │ │ ├── manual/
│ │ │ │ └── manual.go
│ │ │ ├── out/
│ │ │ │ └── out.go
│ │ │ ├── report/
│ │ │ │ ├── last-day/
│ │ │ │ │ └── last-day.go
│ │ │ │ ├── last-month/
│ │ │ │ │ └── last-month.go
│ │ │ │ ├── last-week/
│ │ │ │ │ └── last-week.go
│ │ │ │ ├── last-week-day/
│ │ │ │ │ └── last-week-day.go
│ │ │ │ ├── report.go
│ │ │ │ ├── this-month/
│ │ │ │ │ └── this-month.go
│ │ │ │ ├── this-week/
│ │ │ │ │ └── this-week.go
│ │ │ │ ├── today/
│ │ │ │ │ ├── today.go
│ │ │ │ │ └── today_test.go
│ │ │ │ ├── util/
│ │ │ │ │ ├── report.go
│ │ │ │ │ ├── report_flag_test.go
│ │ │ │ │ └── reportwithrange_test.go
│ │ │ │ └── yesterday/
│ │ │ │ └── yesterday.go
│ │ │ ├── show/
│ │ │ │ └── show.go
│ │ │ ├── split/
│ │ │ │ ├── split.go
│ │ │ │ └── split_test.go
│ │ │ ├── timeentry.go
│ │ │ └── util/
│ │ │ ├── create.go
│ │ │ ├── description-completer.go
│ │ │ ├── fill-with-flags.go
│ │ │ ├── flags.go
│ │ │ ├── help.go
│ │ │ ├── interactive.go
│ │ │ ├── interactive_test.go
│ │ │ ├── name-for-id.go
│ │ │ ├── out-in-progress.go
│ │ │ ├── report.go
│ │ │ ├── util.go
│ │ │ ├── util_test.go
│ │ │ ├── validate-closing.go
│ │ │ └── validate.go
│ │ ├── user/
│ │ │ ├── me/
│ │ │ │ ├── me.go
│ │ │ │ └── me_test.go
│ │ │ ├── user.go
│ │ │ ├── user_test.go
│ │ │ └── util/
│ │ │ └── util.go
│ │ ├── version/
│ │ │ ├── version.go
│ │ │ └── version_test.go
│ │ └── workspace/
│ │ ├── workspace.go
│ │ └── workspace_test.go
│ ├── cmdcompl/
│ │ ├── flags.go
│ │ └── valid-args.go
│ ├── cmdcomplutil/
│ │ ├── client.go
│ │ ├── factory.go
│ │ ├── project.go
│ │ ├── project_test.go
│ │ ├── tag.go
│ │ ├── task.go
│ │ ├── user.go
│ │ └── workspace.go
│ ├── cmdutil/
│ │ ├── args.go
│ │ ├── args_test.go
│ │ ├── config.go
│ │ ├── errors.go
│ │ ├── factory.go
│ │ ├── flags.go
│ │ ├── flags_test.go
│ │ ├── project.go
│ │ └── version.go
│ ├── output/
│ │ ├── client/
│ │ │ ├── csv.go
│ │ │ ├── default.go
│ │ │ ├── json.go
│ │ │ ├── quiet.go
│ │ │ └── template.go
│ │ ├── project/
│ │ │ ├── csv.go
│ │ │ ├── default.go
│ │ │ ├── json.go
│ │ │ ├── quiet.go
│ │ │ └── template.go
│ │ ├── tag/
│ │ │ ├── default.go
│ │ │ ├── quiet.go
│ │ │ └── template.go
│ │ ├── task/
│ │ │ ├── csv.go
│ │ │ ├── default.go
│ │ │ ├── json.go
│ │ │ ├── quiet.go
│ │ │ └── template.go
│ │ ├── time-entry/
│ │ │ ├── csv.go
│ │ │ ├── default.go
│ │ │ ├── default_test.go
│ │ │ ├── duration.go
│ │ │ ├── duration_test.go
│ │ │ ├── json.go
│ │ │ ├── markdown.go
│ │ │ ├── markdown.gotmpl.md
│ │ │ ├── markdown_test.go
│ │ │ ├── quiet.go
│ │ │ └── template.go
│ │ ├── user/
│ │ │ ├── default.go
│ │ │ ├── json.go
│ │ │ ├── quiet.go
│ │ │ └── template.go
│ │ ├── util/
│ │ │ ├── color.go
│ │ │ └── template.go
│ │ └── workspace/
│ │ ├── default.go
│ │ ├── quiet.go
│ │ └── template.go
│ ├── search/
│ │ ├── client.go
│ │ ├── errors.go
│ │ ├── find.go
│ │ ├── find_test.go
│ │ ├── project.go
│ │ ├── tag.go
│ │ ├── task.go
│ │ └── user.go
│ ├── timeentryhlp/
│ │ └── timeentry.go
│ ├── timehlp/
│ │ ├── range.go
│ │ ├── relative.go
│ │ ├── time.go
│ │ ├── time_test.go
│ │ └── util.go
│ └── ui/
│ ├── color.go
│ └── ui.go
├── scripts/
│ └── site-build
├── site/
│ ├── .gitignore
│ ├── archetypes/
│ │ └── default.md
│ ├── config.toml
│ ├── content/
│ │ ├── _index.md
│ │ └── usage/
│ │ └── _index.md
│ ├── layouts/
│ │ └── partials/
│ │ ├── logo.html
│ │ └── menu-footer.html
│ └── static/
│ └── css/
│ └── custom.css
└── strhlp/
├── strhlp.go
└── strhlp_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .deepsource.toml
================================================
version = 1
[[analyzers]]
name = "go"
enabled = true
[analyzers.meta]
import_root = "github.com/lucassabreu/clockify-cli"
dependencies_vendored = false
[[analyzers]]
name = "test-coverage"
enabled = true
================================================
FILE: .github/workflows/golangci-lint.yml
================================================
name: golangci-lint
on:
push:
tags:
- v*
branches:
- main
pull_request:
permissions:
contents: read
# Optional: allow read access to pull request. Use with `only-new-issues` option.
# pull-requests: read
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: 1.24
- name: golangci-lint
uses: golangci/golangci-lint-action@v9
with:
version: latest
================================================
FILE: .github/workflows/release.yml
================================================
name: goreleaser
on:
pull_request:
push:
tags:
- "*"
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v6
- name: go-setup
uses: actions/setup-go@v6
with:
go-version: 1.24
- name: install snapcraft
uses: samuelmeuli/action-snapcraft@v3
- name: install nix
uses: cachix/install-nix-action@v31
- name: goreleaser-setup
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: latest
install-only: true
- if: startsWith(github.ref, 'refs/tags/')
name: release a new version
run: |
make release "tag=${GITHUB_REF#refs/tags/}"
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN_GORELEASER }}
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
- if: startsWith(github.ref, 'refs/tags/') == false
name: test releasing a snapshot version
run: make release SNAPSHOT=1 tag=Unreleased
- if: startsWith(github.ref, 'refs/tags/')
name: trigger Netlify deploy with new release
run: |
curl -vs -X POST "https://api.netlify.com/build_hooks/${NETLIFY_HOOK}" \
--data-urlencode "trigger_title=triggered+by github actions (tag: ${GITHUB_REF#refs/tags/})" \
--data-urlencode "trigger_branch=main"
env:
NETLIFY_HOOK: ${{ secrets.NETLIFY_HOOK }}
================================================
FILE: .github/workflows/test-unit.yaml
================================================
name: Unit Tests
on:
pull_request:
push:
tags:
- v*
branches:
- main
jobs:
tests:
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: 1.24
- name: Get dependencies
run: |
go mod download
go install gotest.tools/gotestsum@latest
- name: Generate coverage report
run: |
gotestsum --format dots -- \
-coverprofile=coverage.txt \
-covermode=atomic \
./...
- name: Upload coverage report
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.txt
flags: unittests
- name: Report test coverage to DeepSource
uses: deepsourcelabs/test-coverage-action@master
with:
key: go
coverage-file: ./coverage.txt
dsn: ${{ secrets.DEEPSOURCE_DSN }}
================================================
FILE: .gitignore
================================================
build/
dist/
snap.login
/clockify-cli
site/content/commands/
site/public/
site/content/license
================================================
FILE: .gitmodules
================================================
[submodule "site/themes/hugo-theme-relearn"]
path = site/themes/hugo-theme-relearn
url = https://github.com/McShelby/hugo-theme-relearn.git
================================================
FILE: .goreleaser.yml
================================================
version: 2
builds:
- env:
- CGO_ENABLED=0
goos:
- windows
- linux
- darwin
hooks:
pre:
- go mod download
main: ./cmd/clockify-cli
archives:
- id: default
name_template: >-
{{- .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end -}}
format_overrides:
- goos: windows
formats: [zip]
files:
- LICENSE
checksum:
name_template: "checksums.txt"
snapshot:
version_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
snapcrafts:
- name: clockify-cli
summary: Helps to interact with Clockfy's API
description: Helps to interact with Clockfy's API
grade: stable
publish: true
confinement: strict
apps:
clockify-cli:
plugs: ["network"]
homebrew_casks:
- name: clockify-cli
repository:
owner: lucassabreu
name: homebrew-tap
homepage: https://github.com/lucassabreu/clockify-cli
description: Helps to interact with Clockfy's API
nix:
- name: clockify-cli
goamd64: v1
# The project name and current git tag are used in the format string.
#
# Templates: allowed.
commit_msg_template: "{{ .ProjectName }}: {{ .Tag }}"
# Your app's homepage.
#
# Templates: allowed.
# Default: inferred from global metadata.
homepage: "https://clockify-cli.netlify.app/"
# Your app's description.
#
# Templates: allowed.
# Default: inferred from global metadata.
description: "A simple cli to manage your time entries on Clockify from terminal"
license: "asl20"
# Repository to push the generated files to.
repository:
# Repository owner.
#
# Templates: allowed.
owner: lucassabreu
# Repository name.
#
# Templates: allowed.
name: nur-packages
# Optionally a branch can be provided.
#
# Default: default repository branch.
# Templates: allowed.
branch: main
================================================
FILE: .mockery.yaml
================================================
dir: internal/mocks
template: testify
template-data:
unroll-variadic: true
packages:
github.com/lucassabreu/clockify-cli/internal/mocks:
interfaces:
Client:
configs:
- filename: "mock_Client.go"
Config:
configs:
- filename: "mock_Config.go"
Factory:
configs:
- filename: "mock_Factory.go"
================================================
FILE: .nvimrc
================================================
set spell
set spelllang=en
set textwidth=79
set colorcolumn=80
let g:goyo_width = 103
autocmd FileType markdown setlocal ts=2 sts=2 sw=2 expandtab textwidth=99 colorcolumn=100
autocmd FileType markdown setlocal nofoldenable
autocmd BufRead,BufNewFile *.md setlocal spell wrap
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [v0.63.0] - 2026-03-26
### Added
- when not informed in the command new time entries will read the `billable` flag from the Task or Project set
### Fixed
- updated github workflows to use the newest versions of the actions
- removed unused code
- fixed assorted lint errors
### Thanks
Thank you to [@reva](https://github.com/reva) for the improvements on
[#292](https://github.com/lucassabreu/clockify-cli/pull/292).
## [v0.62.0] - 2026-03-20
### Added
- prompt for API URL on `config init` command to allow configuring different Clockify datacenters
- new script cmd/release/main.go to help with new releases
### Fixed
- changelog was not showing on the site
## [v0.61.1] - 2026-02-21
### Fixed
- when initializing the config the folder might not exist yet
## [v0.61.0] - 2026-02-21
### Added
- support to config file to be at `$HOME/.config` also, instead of just `$HOME`.
### Changed
- hugo theme to be compatible with newer versions
## [v0.60.0] - 2026-02-11
### Added
- support to set which api to use with the client, this became necessary because of the EU datacenters
### Thanks
Thank you to [@mbosc](https://github.com/mbosc) for the improvements on
[#285](https://github.com/lucassabreu/clockify-cli/pull/285).
## [v0.59.0] - 2026-01-20
### Changed
- change url for API token creation
### Thanks
Thank you to [@davidsneighbour](https://github.com/davidsneighbour) for the implementing
the improvements on [#283](https://github.com/lucassabreu/clockify-cli/pull/283).
## [v0.58.0] - 2025-11-24
### Added
- reporting custom fields for the time entries
### Thanks
Thank you to [@calebtrepowski](https://github.com/calebtrepowski) for the implementing
the improvements on [#282](https://github.com/lucassabreu/clockify-cli/pull/282).
## [v0.57.0] - 2025-10-14
### Added
- documentation about nix packages
### Thanks
Thank you to [@Sekky61](https://github.com/Sekky61) for the information
on Issue [#280](https://github.com/lucassabreu/clockify-cli/pull/280).
## [v0.56.2] - 2025-09-30
### Fixed
- NUR repository name was wrong
## [v0.56.1] - 2025-09-30
### Fixed
- license is required
## [v0.56.0] - 2025-09-30
### Added
- support for nix packages
## [v0.55.2] - 2025-07-28
### Fixed
- update README section about installing using homebrew
## [v0.55.1] - 2025-07-28
### Fixed
- migration from homebrew Formula to Casks needed fixing
## [v0.55.0] - 2025-06-26
### Added
- support to limit how many time entries should be listed on the `report` commands, and choose which page to
show
## [v0.54.2] - 2025-06-25
### Fixed
- deepsource suggestions
- `last` alias on `show`, `clone`, `edit` and `edit-multiple` would select future time entries if they
existed, now only time entries started before now will be considered
## [v0.54.1] - 2025-06-20
### Fixed
- when config "show-client" was on, printing time entries without projects were breaking the cli
- goreleaser config deprecations
- installing snapcraft from apt does not work anymore
### Thanks
Thank you to [@melluh](https://github.com/melluh) for fixing the bug
on PR [#275](https://github.com/lucassabreu/clockify-cli/pull/275).
## [v0.54.0] - 2024-06-15
### Changed
- markdown output now tries to resemble the time entry calendar dialog
## [v0.53.1] - 2024-06-14
### Fixed
- was printing the language before the duration as float
## [v0.53.0] - 2024-06-14
### Added
- new config `lang` to allow setting the number format to be used when printing
- support to using client's name or id for autocompletion on bash
- new config `timezone` to allow setting which timezone to use when reporting the time of a time entry
## [v0.52.0] - 2024-06-02
### Added
- new command `split` to allow break a time entry into others with break points
## [v0.51.1] - 2024-05-30
### Fixed
- when using `show-client` without `show-task` column headers became unaligned
## [v0.51.0] - 2024-05-29
### Added
- new config `show-client` that sets the reports/output of time entries to show its client, if exists
## [v0.50.1] - 2024-05-25
### Fixed
- snapcraft requires explicit confinement
## [v0.50.0] - 2024-05-25
### Added
- more unit tests
### Changed
- using throttle/ticket providing system to limit requests per second to the clockify's api to prevent the
error message: `Too Many Requests (code: 429)`
- upgrade go version to 1.19
## [v0.49.0] - 2024-03-29
### Added
- report subcommands now allowing passing multiple projects to search/filter
- report subcommands now will search all the time entries of a client with the flag `--client` without using
`--project`
## [v0.48.2] - 2024-02-22
### Fixed
- using name for id options with `[` in the name makes the cli panic
## [v0.48.1] - 2024-02-16
### Fixed
- match how strings are compared when using `allow-name-for-id` and filtering on interactive mode.
## [v0.48.0] - 2024-02-16
### Added
- new config `search-project-with-client` to set whether or not the cli should lookup projects using the
client's name too
## [v0.47.0] - 2024-02-09
### Added
- new flag `--client` to filter projects by client when managing time entries
### Changed
- `mockey` update and its configuration has changed
- github actions steps updated to node20
## [v0.46.0] - 2023-12-06
### Added
- support for the formats `HMM` and `HHMM` for time input
### Fixed
- update github actions workflows to use newer actions
### Thanks
Thank you to [@aVolpe](https://github.com/aVolpe) for implementing new time formats as input
on PR [#251](https://github.com/lucassabreu/clockify-cli/pull/251).
## [v0.45.0] - 2023-08-05
### Added
- new function `since` to be used on the time entry format to help working with time
- new function `until` to be used on the time entry format to help working with time
## [v0.44.2] - 2023-04-04
### Fixed
- when searching for task names with special characters ("-" for example), this was fixed for the filter
## [v0.44.1] - 2023-03-06
### Fixed
- time entries were created as billable without user input.
- bump golang.org/x/text from 0.3.7 to 0.3.8 ([#244](https://github.com/lucassabreu/clockify-cli/pull/244))
- `go get` is not a supported option to install the cli
([#245](https://github.com/lucassabreu/clockify-cli/pull/245))
### Added
- test coverage for interactive mode components and `in` command.
### Thanks
Thank you to [@diegoquintanav](https://github.com/diegoquintanav) for reporting and fixing the documentation
on PR [#245](https://github.com/lucassabreu/clockify-cli/pull/245).
## [v0.44.0] - 2022-12-18
### Added
- new flag and config `interactive-page-size` to set how many entries should be shown on select prompts.
- new command `task quick-add` to easily create multiple tasks on a project.
## [v0.43.0] - 2022-12-13
### Added
- support to `last` alias when deleting a time entry.
### Thanks
Thank you to [@jjnilton](https://github.com/jjnilton) for fixing the issue
[#229](https://github.com/lucassabreu/clockify-cli/issues/229) on PR
[#238](https://github.com/lucassabreu/clockify-cli/pull/238).
## [v0.42.2] - 2022-12-06
### Fixed
- `duration` and `estimate` of a task can be `null`, and when the `estimate` where null the cli was failing
with: "duration null is invalid"
## [v0.42.1] - 2022-12-01
### Fixed
- when the `duration` of a project is `null`, a error "duration null is invalid" was blocking the use of the
cli
## [v0.42.0] - 2022-11-09
### Added
- test help function `runClient` to mock calls to Clockfy's API
- added the following methods on `api.Client` (with test coverage)
+ UpdateProjectUserBillableRate
+ UpdateProjectUserCostRate
+ UpdateProjectEstimate
+ UpdateProjectMemberships
+ UpdateProjectTemplate
+ DeleteProject
- added flag `hydrated` at `project list` to get "enriched" projects with their custom fields, tasks and
memberships in one call, these can be accessed using the `format` or `json` output formats.
- new command `project get` to show a project using its ID or name.
### Fixed
- when running `edit` for a time entry that had a task to change its project, the command was failing if no
task set, because it tried to find the task from the older project in the new one.
## [v0.41.0] - 2022-08-31
### Added
- new parameter called `allow-archived-tags` to allow selection of archived tags.
- new flag `tag` on `report` commands, this will filter the time entries with all the tags informed.
## [v0.40.0] - 2022-08-09
### Added
- test coverage for `task` commands
- new method `UpdateProject` on `api.Client` to update a project
- new command `project edit` to allow batch editing multiple projects
### Fixed
- using `\t` and `\n` on format output will behaviour as expected
## [v0.39.0] - 2022-07-31
### Added
- flags `--billable` and `--not-billable` to report commands to filter time entries that are
billable or not respectively
## [v0.38.4] - 2022-07-27
### Fixed
- fixing pagination for time entry listing
## [v0.38.3] - 2022-07-26
### Fixed
- `config init` tests were broken
- `report today` was using local timezone, which created the wrong range of time for the api
## [v0.38.2] - 2022-07-26
### Added
- tests for pkg/cmd/config
- created a helper pkg for interactive console testing
- tests for pkg/cmd/version
- add codecov to pull requests
### Changed
- `api.Client` changed into a interface to easy testing
- user reports now show the user timezone
- flag `debug` dropped in favor of `log-level` to allow a finer control of the output for reporting
bugs.
### Fixed
- `Client.WorkspaceUsers` was not paginating over the results, this created bugs on `config init`
and `user list`
- `make dist` was building all system to the same file
### Thanks
Thank you to [@mhogerheijde](https://github.com/mhogerheijde) for fixing the issue
[#204](https://github.com/lucassabreu/clockify-cli/issues/204).
## [v0.38.1] - 2022-07-05
### Added
- link to LICENSE added to README.md
- link to CHANGELOG added to README.md
### Changed
- function `bool2str` substituted with a map to appease deepsource.io
- change task prompt to not list tasks that are inactive.
### Fixed
- `in` command on interactive mode was exiting when the user tried to start a timer on a project
were they don't have direct access to (only by their group). This is a bug on the API, but a
fix was done to not block the users.
## [v0.38.0] - 2022-07-01
### Added
- badge with amount of downloads from github releases
- most of the commands have better descriptions explaining flag usages and command examples.
- document describing the [project layout][] and where to add or find files.
- document with [how to contribute][contribute] to the project
- site preview on branches going to `main`.
- added `golang-lint` as a Github Action on every PR.
- functions `json`, `yaml` and `pad` add to all golang template formatters.
### Changed
- site specific files moved from `docs/` to `site/` to free docs folder for actual documentation.
- moved `cmd/*` and `internal/output/*` into the new locations as stated on [project layout][]
- new `cmdutil.Factory` interface to work as a "service locator" so sharing some states, behaviours
and "services" can be easier.
- project dependencies were updated.
- memory and performance improvements
- `config --init` changed to `config init` to better organized the commands logic.
- site home page changed to better explain how to setup the project and to direct to new documents.
### Fixed
- `report` commands could fail to list time entries closer to midnight because of timezone
differences.
## [v0.37.0] - 2022-05-17
### Added
- build windows binaries
## [v0.36.2] - 2022-05-13
### Fixed
- `edit-multiple` was not updating time entries without interactive mode.
## [v0.36.1] - 2022-05-10
### Fixed
- `clone` command was using the start time of the copied time entry to close the current one
instead of the start time of new one being created.
## [v0.36.0] - 2022-05-09
### Added
- support for relative time for time parameters, can be +1:40, or +1h40m
### Fixed
- negative duration was printed broken, now it show as a valid negative duration
### Changed
- new error types from required fields and invalid entity ids.
- all errors have a minimal context to help on support.
- `manageEntry` refactored to not have as many control flags
- reduce copy of objects on loops
## [v0.35.1] - 2022-05-04
### Fixed
- fake "not found" errors will have more context for the message
### Changed
- all `api/client.go` will have stack traces.
## [v0.35.0] - 2022-05-03
### Added
- `task edit` command allows changing a existing task and changing its status.
- `task delete` command allows removing a existing task.
- `task done` command is a helper for `task edit --done`.
### Changed
- `task add` command now accepts `assignees` and `estimate` for task creation
- `end` argument of `report` command accepts the alias `yesterday` for previous date.
## [v0.34.0] - 2022-04-27
### Changed
- `end` argument of `report` command accepts the aliases `now` and `today` for current date.
## [v0.33.1] - 2022-04-25
### Fixed
- enabling `show-task` config were hiding the description column for table report format.
## [v0.33.0] - 2022-04-21
### Added
- flag to filter projects on `report` command.
## [v0.32.2] - 2022-02-25
### Fixed
- examples on `README` were out of date with current commands and outputs.
- short description of `show` and `report` were too long.
## [v0.32.1] - 2022-02-25
### Removed
- `log` subcommand removed (deprecated since [v0.28.0])
- `log in-progress` subcommand removed (deprecated since [v0.29.0])
### Changed
- `report` subcommand allows calls using the alias `log`
## [v0.32.0] - 2022-02-14
### Added
- new options `--random-color` when creating a project, to auto-generate a color for the project.
### Thanks
Thank you to [@NoF0rte](https://github.com/NoF0rte) for these improvements to the CLI.
## [v0.31.0] - 2022-02-08
### Added
- new commands `task add` and `task list` to manage tasks on projects
- new commands `client add` and `client list` to manage clients
- new command `project add` create projects
### Changed
- command `project list` has new parameter `clients` to filter only the projects related to the
clients informed.
### Thanks
Thank you to [@NoF0rte](https://github.com/NoF0rte) for these improvements to the CLI.
## [v0.30.1] - 2022-01-17
### Fixed
- `manual` subcommand was allowing creation of open time entries.
## [v0.30.0] - 2022-01-15
### Changed
- if creation of incomplete time entries is not allowed, the commands will verify if the project is
active or not.
- when closing a running time entry before creating a new one, the client will validate it before
asking information on interactive mode.
### Fixed
- archived projects were being shown as options to select in interactive mode, now only active are
shown.
## [v0.29.0] - 2022-01-12
### Changed
- `show` subcommand has its parameter as optional, and shows current time entry by default when the
parameter is omitted.
- `report` subcommand has its parameters as optional, and use `today` as value when none is set.
- `log in-progress` subcommand is deprecated in favor of `show`/`show current`
## [v0.28.0] - 2022-01-10
### Changed
- `log` subcommand deprecated in favor of `report`
- default `report` now allows to set only one date of the range, in this situation it will treat
start and end date as being the same.
### Added
- new `report today` to show only the time entries from today, with `report` options
- new `report yesterday` to show only the time entries from yesterday, with `report` options
### Fixed
- golang commands were wrong
- there was a output on report subcommands breaking the format
- changelog release links were wrong
## [v0.27.1] - 2021-12-31
### Fixed
- `report last-month` was failing to create a valid range time because it was not truncated to 0
hours.
## [v0.27.0] - 2021-12-31
### Changed
- `formatTimeEntry` renamed into `printTimeEntry`, and simplified to just call `printTimeEntries`
with a list containing the time entry informed.
- go version on `go.mod` updated to 1.17
### Added
- all subcommands that can print more than one time entry will print the total duration for that
listing, this can be disabled with the `config` subcommand.
- `report` subcommands now have a `description` flag to filter time entries that contains text on
its description.
- all subcommands that output time entries now have two new formats: `duration-formatted` and
`duration-float`, that do sum all durations of the time entries and print only the sum, formatted
as time or as "floaty-hour", respectively
### Fixed
- `report` subcommands which required pagination on the requests to the api were not doing so, the
time entry list shown by this command was incomplete.
## [v0.26.1] - 2021-12-07
### Changed
- hide "interrupted" error from the output
### Fixed
- removed `println` in the code breaking the component.
- prevent error when no time has been chosen as output for `AskForDateTime`
## [v0.26.0] - 2021-11-02
### Added
- add description suggestion using the recent time entries.
### Changed
- some code and style fixes detected by [deepsource](https://deepsource.io/)
- refactored date-time flags into its own function.
- refactored ask for date-time helper function to not have control flags.
## [v0.25.0] - 2021-10-08
### Fixed
- `report` subcommands were showing only the time not the date when the time entry was created.
- `--quiet` help was wrong, it said "print as json", but it prints only the id.
### Added
- project color is used to "render" project name on the terminal, if the output is being piped or
redirected then colors will be ignored to prevent problems and miss-interpretation of the output.
- `show` subcommand prints details about time entries without having to list of the time entries of
a given date.
- `edit`, `edit-multiple`, `show`, `clone` support "^n" expression to select a time entry to act
on, "^0" is the same as "current", "^1" is the same as "last", "^2" chooses the time entry before
the last one, etc.
- new `md` (markdown) format to print time entries
## [v0.24.1] - 2021-09-20
### Fixed
- `out` subcommand was not setting the user to look on ending the time entry.
- listing subcommands didn't show "hydrated" information about time entries,
`GetUsersHydratedTimeEntries` was not telling the api to return hydrated data.
### Added
- all client method calls now validated for required fields, this makes easier to see bugs and
prevent errors to creping up into releases.
## [v0.24.0] - 2021-09-18
### Added
- new commands `mask-invoiced` and `mark-not-invoiced` created to allow users to set this
information using the cli.
### Changed
- creation/update/out of time entries is made using the current api, instead of the old one
- listing of workspaces and users is made using the current api, instead of the old one
- all specific calls for the api for listing time entries were refactored to use a main function to
request then, the client methods still exist and maintain the same inputs/outputs, but are calling
the same function instead of reimplementing the call every time
- getting of a project now uses the current api
- debug messages of requests now show a "name" on it to help identify what where the intention of
the call
### Removed
- client method for recent time entries was not listed as a valid api, so its is now removed.
## [v0.23.1] - 2021-09-17
### Fixed
- `last` and `current` aliases were failing to find and select the right time entry, it is a problem
with the old api for getting "recent time entries", fixed by [@zerodahero](https://github.com/zerodahero)
## [v0.23.0] - 2021-09-16
### Added
- client uses current api to retrieve all tasks of a project
- interactive mode support to select tasks
- name or id support for tasks
- terminal auto-complete support for `task` flag
- new config `show-task` that sets the reports/output of time entries to show its task, if exists
### Fixed
- package `golang.org/x/crypto/ssh/terminal` was deprecated, substituted by `golang.org/x/term`
### Removed
- output formatters for `dto.TimeEntryImpl` were not being used.
## [v0.22.0] - 2021-09-05
### Changed
- use new go version (1.17)
- custom `changed` function is the same as using `Flags.Changed`, changed to use just the later
- use `hydrated` parameter on "get time entry" endpoint instead of getting details individually
- change in progress time entry using the current api
- using "Hydrated" instead of "Full" to be consistent will the api
### Fixed
- remove default message for 404 errors from the api
- `edit-multiple` without interactive mode were not working with the `allow-name-for-id` flag.
## [v0.21.0] - 2021-08-16
### Fixed
- deploy to Netlify was not being triggered after release build, making the html documentation always wrong.
- using terminal size of stdout file descriptor, this may fix problems on windows to print reports.
- special characters will be ignored when looking for a project or tag with similar name.
### Added
- `--interactive` flag now describes how to disable it (suggestion from [#115](https://github.com/lucassabreu/clockify-cli/issues/115))
- example to create a time entry using only flags no README.
- keep the same options to print/output on all commands that show time entries.
- support for names for id for tags
### Changed
- improved output examples to better resemble real output.
- updated go dependencies
- `reports` package renamed to `internal/output`, to prevent usage from other packages and solve ambiguity
with `report` command and `report api` (to come)
- flag `allow-project-name` now will be called `allow-name-for-id` to account for other entities that would
benefit from using their names instead of their ids
### Removed
- features about integration with github:issues, azure dev and trello will not be implemented, at least not
in a foreseeable future.
## [v0.20.0] - 2021-08-10
### Changed
- `manual` and `in` commands now support the use of `--project`, `--description`, `--when` and `--when-to-close`
flags besides existing positional arguments (now optional even without interactive mode).
### Added
- shorthand names for flags `when`, `when-to-close`, `description`, `project` and `tag`
## [v0.19.5] - 2021-08-03
### Fixed
- select UI component can fail to return a valid option if the default value were not in the list, to prevent
that if the default value is empty or not in the list, no default value will be set.
## [v0.19.4] - 2021-07-21
### Fixed
- `edit` command were resetting the start time to "now" if the user didn't set the `--when` flag.
- `when` and `when-to-close` flags on `edit` help had the wrong description.
## [v0.19.3] - 2021-07-20
### Fixed
- `clone` should create a open time entry by default.
### Changed
- `delete` command accepts multiple ids instead of just one.
## [v0.19.2] - 2021-07-20
### Fixed
- `in` and `clone` commands were starting at 0001-01-01 because the default value of the flag was not being read.
## [v0.19.1] - 2021-07-19
### Fixed
- `README` now contains updated help output.
- `edit-multiple` help should be capitalized.
## [v0.19.0] - 2021-07-19
### Added
- subcommand `edit-multiple` allows the user to edit all properties (except for the time interval) of multiple time entries
simultaneously. when not in interactive mode the user can choose exactly which properties to change and to keep.
### Changed
- flags used for creation and edition of time entries are now centralized into three functions `addFlagsForTimeEntryCreation`
to add flags used to create time entries, `addFlagsForTimeEntryEdit` for flags used on edition, and
`fillTimeEntryWithFlags` to replicated the flag values into the time entry.
### Deprecated
- flag `end-at` on edit subcommand will be removed in favor of `when-to-close` to be consistent with other subcommands.
- flag `tags` on many subcommands will be removed in favor of `tag` to imply that its one by flag.
## [v0.18.1] - 2021-07-12
### Fixed
- when the input for start time is cancelled (ctrl+c), clockify-cli was blocking the user by looping
on the field until a valid date-time string was used, or the process were killed.
### Changed
- library `github.com/AlecAivazis/survey` updated to the latest version.
- `README` updated to show new configurations.
## [v0.18.0] - 2021-07-08
### Added
- commands `in`, `clone` and `manual` will show a new "None" option on the projects list on the
interactive mode if the workspace allows time entries without projects.
- config `allow-incomplete` allows the user to set if they want to create "incomplete time entries"
or to validated then before creation. Flag `--allow-incomplete` and environment variable
`CLOCKIFY_ALLOW_INCOMPLETE` can be used for the same purpose. by default time entries will be
validated.
### Changed
- commands `in` and `clone` when creating an "open" time entry will not validate if the workspace
requires a project or not, allowing the creation of open incomplete/invalid time entries, similar
to the browser application.
- `newEntry` function changed to `manageEntry` and will allow a callback to deal with the filled and
validated time entry instead of always creating a new one, that way same code that were duplicated
between it and the `edit` command can be united.
### Fixed
- `no-closing` configuration was removed, because was not used anymore.
## [v0.17.2] - 2021-06-17
### Fixed
- goreleaser needs a GitHub token with more permissions to create the homebrew Formulae.
## [v0.17.1] - 2021-06-16
### Changed
- changing travis ci for gihub actions, seens easier to use and one less login to handle
## [v0.17.0] - 2021-06-16
### Added
- command `report last-day`, this command will list time entries from the last day the user worked.
- command `report last-week-day`, this command will look for the last day were the user should
have worked (based on the new config `workweek-days`) and list the time entries for that day.
- config `workweek-days` for the user to set which days of the week they work. it can be set
interactively.
## [v0.16.1] - 2021-06-16
### Fixed
- interactive selection of project would panic if the list were empty (filtering can empty the list)
and pressing enter. now will return as "no project selected".
### Changed
- `workspaces` command is now named `workspace`, `workspaces` still supported
- `workspace` default print format now shows the workspace marked as "default"
## [v0.16.0] - 2021-05-14
### Added
- `project list` can print the projects as JSON and CSV.
- `project list` command default print format shows the client name and id
## [v0.15.1] - 2020-09-30
### Fixed
- if the workspace has more the one page of projects, in interactive mode, only the first page was
being shown. now fixed to run over all pages to fill the list.
### Added
- "Getting Started" section on README.md to help new users to setup theirs environment.
## [v0.15.0] - 2020-09-12
### Added
- support for command line completion on `fish`, `bash` and `zsh` for subcommands and flag name's
- command line completion for arguments and flags for Tags, Projects, Workspaces and Users.
- alias `remove` to command `delete`
### Changed
- using the API `v1` version to get tags available to a workspace.
- `api.Client.Workspaces` renamed to `api.Client.GetWorkspaces` to follow pattern used on other
functions.
- command `config`, `config set` and `config init` combined to be only one command `config`
- improvements on help of many commands to show usable values.
- `github.com/spf13/cobra` updated to latest possible current version to use completion improvements
not yet released
- "interactive mode" functions moved to a separate package.
## [v0.14.1] - 2020-09-09
### Fixed
- the project select on interactive mode was not respecting the "default" project when cloning
or informed through flags/parameters
## [v0.14.0] - 2020-09-08
### Changed
- ask for "interactive mode" and "auto-closing" global configurations on `config init` command.
## [v0.13.0] - 2020-09-08
### Added
- select and multi-select interactive now support "glob like" expressions to filter a option
### Changed
- client name of a project is shown on interactive mode to help identify the project.
### Fixed
- select and multi-select options now support "non-english" characters like "á" by converting then to a ASCII equivalent character.
## [v0.12.2] - 2020-09-04
### Fixed
- flag `--token` help was not showing the right env var name.
## [v0.12.1] - 2020-08-22
### Added
- "How to install" section on README to help new users to understand which options are available.
### Fixed
- improving the "homebrew tap" to allow installation using: `brew install lucassabreu/tap/clockify-cli`
## [v0.12.0] - 2020-08-31
### Added
- support to homebrew for macOs users.
## [v0.11.0] - 2020-08-22
### Added
- new `delete` command to remove a existing time entry from a workspace.
- `edit` command support to interactive mode.
### Fixed
- when cloning a time entry, using interactive mode, the tags selected were not being respected.
- `edit` command was removing all data from time-entry if the flag to fill the field was not being set.
## [v0.10.1] - 2020-08-10
### Fixed
- `in` and `manual` command were showing a error "Project '' informed was not found", even
when no project id/name is informed, this is now fixed.
## [v0.10.0] - 2020-08-07
### Added
- `clone` command now allow to change the project and description on the
time entry creation, interactive mode already had this possibility
- new flag `archived` on `project list` to list archived projects
- a new global config `allow-project-name` that, when enabled, allow the user to the project
name (or parts of it) to be used where the project id is asked.
- common function to get all pages on a paginated request, to not reimplement it, and guarantee
all entities are being used/returned.
### Fixed
- `clone` sub-command was not asking to confirm the tags when the original time entry already
had some.
- `clone` command now will respect flags `--tags` and `--when-to-close`.
- "billable" attribute was not being cloned
- keep the current CHANGELOG when extracting the last tag
- some grammatic errors ("applyied" => applied)
- remove mentions to GitHub or Trello token, until integration is implemented
## [v0.9.0] - 2020-07-20
### Added
- new sub-command `version` to allow a quick way to know which version is installed
- sub-command `report` now supports `this-week` and `last-week` as time range aliases
listing respectively all entries which start this week, and all entries that happened
on previous week.
### Changed
- all relevant errors now have stack trace with then, which will be printed when the
flag `--debug` is used.
- error reporting now centralized, removing the need for a helper function in each
sub-command
- `report`command default output (table) with show in which day the times entries were made.
## [v0.8.1] - 2020-07-09
### Fixed
- `clone` sub-command was not working because the `no-closing` viper config was being
connected with a non-existing `--no-closing` flag in the `in` sub-command, that does
not exist anymore.
## [v0.8.0] - 2020-07-08
### Added
- created a new sub-command `manual` that will allow to create "completed" time entries
in a more easy way.
- created a new flag `--when-to-close` on `in` and `clone` to set close time for the
time entry being started (if wanted).
### Changed
- `clone` sub-command allows the flag `--no-closing` and will have the same flags as
`in` to set start and end time (if wanted)
- `in` sub-command will always stops time entries that are open in the moment of the
sub-command call.
- some helps and messages were improved to better describe what the command does
### Removed
- flags `--trello-token` and `--github-token` were removed because they are not
currently used and may give false impressions about the cli
### Fixed
- some code for the in and clone sub-commands were duplicated, now they are in `newEntry`
function that they both used.
## [v0.7.2] - 2020-06-21
### Fixed
- using JSON to notify Netlify, to prevent "malformed url errors"
## [v0.7.1] - 2020-06-21
### Fixed
- snapcraft build/release problems after Travis config update
## [v0.7.0] - 2020-06-21
### Added
- build every pull request as a snapshot to check if it is not failing
- command to auto-generated hugo formatted markdown files from the commands
- implemented a site to better help people to understand what the CLI does,
without having to download it (live on: https://clockify-cli.netlify.app/)
### Changed
- improved headers on the CHANGELOG to better represent the hierarchies
- moved `in clone` to be just `clone`
### Fixed
- missing release links for the title on the CHANGELOG
- filling the brackets on the LICENSE file
## [v0.6.1] - 2020-06-16
### Added
- `config` command can print the "global" parameters in `json` or `yaml`
- `config` now accepts a argument, which is the name of the parameter,
when informed only this parameter will be printed
## [v0.6.0] - 2020-06-16
### Added
- some badges, who does not like they?
### Fixed
- help was showing `CLOCKIFY_WROKSPACE` as env var for workspace, the right name is
`CLOCKIFY_WORKSPACE`
- fixed some `golint` warnings
### Changed
- go mod dependencies updated
- `snapcraft` package only requires network
### Removed
- Removed `GetCurrentUser` in favor of `GetMe` to be closer to the APIs format
## [v0.5.0] - 2020-06-15
### Changed
- `in`, `log` and `report` now don't require you to inform a "user-id", if none is set,
than will get the user id from the token used to access the api
### Added
- `me` command returns information about the user who owns the token used to access
the clockify's api
## [v0.4.0] - 2020-06-01
### Added
- table format will show time entry tags
### Changed
- when adding fake entries with `--fill-missing-dates`, will set end time as equal
to start time, so the duration will be 0 seconds
## [v0.3.2] - 2020-05-22
### Changed
- printing duration as "h:mm:ss" instead of the Go's default format,
because is more user and sheet applications friendly.
## [v0.3.1] - 2020-04-01
### Fixed
- fixed `--no-closing` being ignored
- interactive flow of `clone` was keeping previous time interval
## [v0.3.0] - 2020-04-01
### Fixed
- minor grammar bug fixes
### Changed
- improvements to the code moving interactive logic of the "in" command into `cmd/common.go`
- "in clone" is now interactive and will ask the user to confirm the time entry data before
creating it.
## [v0.2.2] - 2020-03-18
### Fixed
- the endpoint `workspaces/<workspace-id>/tags/<tag-id>` does not exist anymore, instead the
`api.Client` will get all tags of the workspace (`api.Client.GetTags`) and filter the response
to find the tag by its id.
## [v0.2.1] - 2020-03-02
### Fixed
- `clockify-cli report` parameter `--fill-missing-dates`, was not working
## [v0.2.0] - 2020-03-02
### Added
- `clockify-cli report --fill-missing-dates` when this parameters is set, if there
are dates from the range informed, will be created "stub" entries to better show
that are missing entries.
## [v0.1.7] - 2020-02-03
### Added
- `api.Client` now supports getting one specific time entry from a workspace,
without the need to paginate through all time entries to find it (`GetTimeEntry`
function).
### Fixed
- `clockify-cli report` was not getting all pages from the period, implemented
support for pagination and to get "all pages" at once into `Client.Log` and
`Client.LogRange`
### Changed
- updated README, so it shows the `--help` output as it is now
## [v0.1.6] - 2020-02-03
### Fixed
- fixed bug after Clockify's API changed, where `user` and `project` are not
automatically provided by the "time-entries" endpoint, unless sending
an extra parameter `hydrated=true`, and `user` is not provided anymore, so
now we find it using the user id from the function filter
## [v0.1.5] - 2020-01-08
### Fixed
- fixed bug on the `log` commands, where the previous api url is not available
anymore, now using `v1/workspace/{workspace}/user/{user}/times-entries`
- spelling of some words fixed and improving some aspects of the code
### Changed
- `go.mod` updated
### Added
- seamless support for query parameters using the interface `QueryAppender`
- support for retrieving the current user of the token (`v1/user`) in the API client.
- `.nvimrc` added to provide spell check
## [v0.1.4] - 2019-08-05
### Added
- Permissions to `snap` installation, so configuration file can be used
## [v0.1.3] - 2019-08-02
### Changed
- Set `publish` to `true` so it will be sent to `snapcraft`
## [v0.1.2] - 2019-08-02
### Added
- Add release to snapcraft by the name `clockify-cli`
- Add command `clockify-cli report` implemented to generate bigger exports. CSV, JSON,
`gofmt` and table formats allowed in this command.
## [v0.1.1] - 2019-06-10
### Changed
- The list returned by the `log` command will the sorted starting from the oldest
time entry.
## [v0.1.0] - 2019-04-08
### Added
- Add `goreleaser` to manage binary and releases of the command
- `clockify-cli in` asks user about new entry information when `interactive` is
enabled
- Command `clockify-cli config init` allows to start a fresh setup, creating a
configuration file
- Command `clockify-cli config set` updates/creates one configuration key into the
configuration file
- `clockify-cli in` commands now allow more flexible time format inputs, can be:
hh:mm, hh:mm:ss, yyyy-mm-dd hh:mm or yyyy-mm-dd hh:mm:ss
- Command `clockify-cli out` implemented, it will close any pending time entry,
and show the last entry info when closing it with success
- Command `clockify-cli in clone` implemented, to allow creation of new time
entries based on existing ones, it also close pending ones, if any
- Command `clockify-cli project list` was implemented, it allows to list the
projects of a workspace, format the return to table, json, and just id. Helps
with script automation
- Using https://github.com/spf13/viper to link environment variables and configuration
files with the global flags. User can set variables `CLOCKIFY_TOKEN`,
`CLOCKIFY_WORKSPACE` and `CLOCKIFY_USER_ID` instead of using the command flags
- Command `clockify-cli tags` created, to list workspace tags
- Command `clockify-cli in` implemented, to allow creation of new time entries,
it also close pending ones, if any
- Command `clockify-cli edit <id>` implemented, to allow updates on time entries,
including the in-progress one using the id: "current
- `--debug` option to allow better understanding of the requests
- Command `clockify-cli log in-progress` implemented, with options to format the
output, and in the TimeEntry format, instead of TimeEntryImpl
- Command `clockify-cli log` implemented, with options to format the output,
will require the user for now
- Package `dto` created to hold all payload objects
- Package `api.Client` to call Clockfy's API
- Command `clockify-cli workspaces` created, with options to format the output
- Command `clockify-cli workspaces users` created, with options to format the
output to allow retrieving the user's ID
## [v0.0.1] - 2019-03-03
### Added
- This CHANGELOG file to hopefully serve as an evolving example of a
standardized open source project CHANGELOG.
- README now show which features are expected, and that nothings is done yet
- Golang CLI using [cobra](https://github.com/spf13/cobra)
- Makefile to help setup actions
[Unreleased]: https://github.com/lucassabreu/clockify-cli/compare/v0.63.0...HEAD
[v0.63.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.63.0
[v0.62.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.62.0
[v0.61.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.61.1
[v0.61.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.61.0
[v0.60.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.60.0
[v0.59.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.59.0
[v0.58.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.58.0
[v0.57.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.57.0
[v0.56.2]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.56.2
[v0.56.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.56.1
[v0.56.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.56.0
[v0.55.2]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.55.2
[v0.55.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.55.1
[v0.55.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.55.0
[v0.54.2]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.54.2
[v0.54.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.54.1
[v0.54.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.54.0
[v0.53.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.53.1
[v0.53.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.53.0
[v0.52.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.52.0
[v0.51.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.51.1
[v0.51.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.51.0
[v0.50.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.50.1
[v0.50.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.50.0
[v0.49.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.49.0
[v0.48.2]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.48.2
[v0.48.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.48.1
[v0.48.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.48.0
[v0.47.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.47.0
[v0.46.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.46.0
[v0.45.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.45.0
[v0.44.2]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.44.2
[v0.44.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.44.1
[v0.44.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.44.0
[v0.43.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.43.0
[v0.42.2]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.42.2
[v0.42.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.42.1
[v0.42.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.42.0
[v0.41.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.41.0
[v0.40.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.40.0
[v0.39.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.39.0
[v0.38.4]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.38.4
[v0.38.3]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.38.3
[v0.38.2]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.38.2
[v0.38.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.38.1
[v0.38.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.38.0
[v0.37.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.37.0
[v0.36.2]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.36.2
[v0.36.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.36.1
[v0.36.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.36.0
[v0.35.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.35.1
[v0.35.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.35.0
[v0.34.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.34.0
[v0.33.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.33.1
[v0.33.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.33.0
[v0.32.2]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.32.2
[v0.32.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.32.1
[v0.32.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.32.0
[v0.31.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.31.0
[v0.30.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.30.1
[v0.30.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.30.0
[v0.29.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.29.0
[v0.28.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.28.0
[v0.27.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.27.1
[v0.27.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.27.0
[v0.26.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.26.1
[v0.26.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.26.0
[v0.25.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.25.0
[v0.24.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.24.1
[v0.24.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.24.0
[v0.23.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.23.1
[v0.23.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.23.0
[v0.22.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.22.0
[v0.21.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.21.0
[v0.20.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.20.0
[v0.19.5]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.19.5
[v0.19.4]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.19.4
[v0.19.3]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.19.3
[v0.19.2]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.19.2
[v0.19.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.19.1
[v0.19.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.19.0
[v0.18.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.18.1
[v0.18.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.18.0
[v0.17.2]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.17.2
[v0.17.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.17.1
[v0.17.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.17.0
[v0.16.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.16.1
[v0.16.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.16.0
[v0.15.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.15.1
[v0.15.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.15.0
[v0.14.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.14.1
[v0.14.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.14.0
[v0.13.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.13.0
[v0.12.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.12.1
[v0.12.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.12.0
[v0.11.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.11.0
[v0.10.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.10.1
[v0.10.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.10.0
[v0.9.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.9.0
[v0.8.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.8.1
[v0.8.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.8.0
[v0.7.2]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.7.2
[v0.7.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.7.1
[v0.7.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.7.0
[v0.6.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.6.1
[v0.6.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.6.0
[v0.5.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.5.0
[v0.4.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.4.0
[v0.3.2]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.3.2
[v0.3.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.3.1
[v0.3.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.3.0
[v0.2.2]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.2.2
[v0.2.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.2.1
[v0.2.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.2.0
[v0.1.7]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.1.7
[v0.1.6]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.1.6
[v0.1.5]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.1.5
[v0.1.4]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.1.4
[v0.1.3]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.1.3
[v0.1.2]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.1.2
[v0.1.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.1.1
[v0.1.0]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.1.0
[v0.0.1]: https://github.com/lucassabreu/clockify-cli/releases/tag/v0.0.1
[project layout]: https://github.com/lucassabreu/clockify-cli/blob/main/docs/project-layout.md
[contribute]: https://github.com/lucassabreu/clockify-cli/blob/feat/factory/CONTRIBUTING.md
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
Thank you for the interest in Contributing to Clockify CLI.
We accept pull requests for bug fixes and features (preferably that were discussed on an issue
before). Also opening issues with feature requests and reporting bugs are very important
contributions.
Please do:
- Check in the [issues][issues] if the [bug][bugs] or [feature request][enhancement] has not been submitted.
- Open an issue if things aren't working as expected.
- Open an issue to propose new features or improvements on existing ones.
- Open a pull request to fix a [bug][bugs].
- Open a pull request for any open issue labelled [`type: enhancement`][enhancement].
Please avoid:
- Opening pull requests for issues marked as `blocked`.
All enhancement and bug issues are marked with a `level` label, it may help you know the
size/complexity of it.
## Building the project
Prerequisites:
- Go 1.19+
Run `make deps-install` to install the packages used by the project.
Run `make deps-upgrade` if you need to upgrade all of them, run `go help get` to see how to update
individual ones.
To build your changes into a binary run `make dist`, all three versions (Windows, Mac and Linux)
will be created under the `dist/` folder.
You can also just run `go run cmd/clockify-cli/main.go` to execute the source directly.
See the [project layout documentation][project layout] to know where to find and create specific
components.
## Submitting a pull request
Contributions to this project are [released][legal] to the public under the
[project's open source license][license]. By participating in this project you agree to abide by
its terms.
We generate manual pages from source on every release. You do not need to submit pull requests for
documentation specifically; manual pages for commands will automatically get updated after your
pull requests gets accepted.
### With [`gh`][gh]
1. Clone this repository
2. Make and commit your changes.
3. Submit a pull request: `gh pr create --web`
4. In its body link which issue it is related, if there is one
### Without `gh`
1. [Fork the repository][fork]
2. Make and commit your changes
3. [Open a pull request][open-pr]
4. In its body link which issue it is related, if there is one
## Resources
- [How to Contribute to Open Source][]
- [Using Pull Requests][]
- [GitHub Help][]
## Credits
This document is based on the [CONTRIBUTING.md from github/cli/cli][credit].
[fork]: https://github.com/lucassabreu/clockify-cli/fork
[open-pr]: https://github.com/lucassabreu/clockify-cli/compare
[credit]: https://github.com/cli/cli/blob/trunk/.github/CONTRIBUTING.md
[issues]: https://github.com/lucassabreu/clockify-cli/issues
[bugs]: https://github.com/lucassabreu/clockify-cli/issues?q=is%3Aopen+is%3Aissue+label%3A%22type%3A+bug%22
[enhancement]: https://github.com/lucassabreu/clockify-cli/issues?q=is%3Aissue+is%3Aopen+label%3A%22type%3A+enhancement%22
[project layout]: ./docs/project-layout.md
[gh]: https://github.com/cli/cli
[legal]: https://docs.github.com/en/free-pro-team@latest/github/site-policy/github-terms-of-service#6-contributions-under-repository-license
[license]: ./LICENSE
[How to Contribute to Open Source]: https://opensource.guide/how-to-contribute/
[Using Pull Requests]: https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/about-pull-requests
[GitHub Help]: https://docs.github.com/
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2020 Lucas dos Santos Abreu <lucas.s.abreu@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: Makefile
================================================
export GO111MODULE=on
MAIN_PKG=./cmd/clockify-cli
# Absolutely awesome: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
help: ## show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'
clean: ## clean all buildable files
rm -rf dist
deps-install: ## install golang dependencies
go mod download
deps-upgrade: ## upgrade go dependencies
go get -u -v $(MAIN_PKG)
go mod tidy
build: clean dist
dist: deps-install dist/darwin dist/linux dist/windows ## build all cli versions (default)
dist-internal:
mkdir -p dist/$(goos)
GOOS=$(goos) GOARCH=$(goarch) go build -o dist/$(goos)/clockify-cli $(MAIN_PKG)
dist/darwin:
make dist-internal goos=darwin goarch=amd64
dist/linux:
make dist-internal goos=linux goarch=amd64
dist/windows:
make dist-internal goos=windows goarch=amd64
go-install: deps-install ## install dev version
go install $(MAIN_PKG)
go-generate: deps-install ## recreates generate files
go install github.com/vektra/mockery/v3@v3.4.0
mockery
test-install: deps-install go-generate
go install gotest.tools/gotestsum@latest
test: test-install ## runs all tests
gotestsum --format dots-v2
test_coverprofile=coverage.txt
test_covermode=atomic
test-coverage: test-install ## runs all tests and output coverage
gotestsum --format dots-v2 -- \
-coverprofile=$(coverprofile) \
-covermode=$(covermode) \
./...
test-watch: test-install ## runs all tests and watch changes
gotestsum --format testname --watch -- -failfast
goreleaser-test: tag=Unreleased
goreleaser-test: release
ifeq ($(tag),Unreleased)
SNAPSHOT=1
endif
tag=
release: ## releases a tagged version
sed "/^## \[$(tag)/, /^## \[/!d" CHANGELOG.md | tail -n +2 | head -n -2 > /tmp/rn.md
curl -sL https://git.io/goreleaser | bash /dev/stdin --release-notes /tmp/rn.md \
--clean $(if $(SNAPSHOT),--snapshot --skip=publish,)
ifneq ($(SNAPSHOT),1)
curl -X POST -d '{"trigger_branch":"$(tag)","trigger_title":"Releasing $(tag)"}' https://api.netlify.com/build_hooks/5eef4f99028bddbb4093e4c6 -v
endif
site/themes/hugo-theme-relearn/.git:
git submodule update --init
site-build: site/themes/hugo-theme-relearn/.git ## generates command documents and builds the site
./scripts/site-build
site-serve: site-build ## builds the site, and serves it locally
cd site && hugo serve
create-release-minor: ## create a new minor release
go run cmd/release/main.go minor
create-release-patch: ## create a new patch release
go run cmd/release/main.go patch
================================================
FILE: README.md
================================================

============
A simple cli to manage your time entries and projects on Clockify from terminal
[](https://github.com/lucassabreu/clockify-cli/releases/latest)
[](https://github.com/lucassabreu/clockify-cli/releases)
[](https://snapcraft.io/clockify-cli)
[](.github/workflows/release.yml)
[](https://goreportcard.com/report/github.com/lucassabreu/clockify-cli)
[](https://app.netlify.com/sites/clockify-cli/deploys)
[](https://deepsource.io/gh/lucassabreu/clockify-cli/?ref=repository-badge)
Documentation
-------------
See the [project site](https://clockify-cli.netlify.app/) for the how to setup and use this CLI.
See more information about the sub-commands at: https://clockify-cli.netlify.app/en/commands/clockify-cli/
Contributing
------------
On how to help improve the tool, suggest new features or report bugs, please take a look at the
[CONTRIBUTING.md](CONTRIBUTING.md).
Features
--------
* [x] List time entries from a day
* [x] List in progress entry
* [x] Report time entries using a date range
* [x] Inform date range as parameters
* [x] "auto filter" for last month
* [x] "auto filter" for this month
* [x] Start a new time entry
* [x] Cloning last time entry
* [x] Ask input interactively
* [x] Stop the last entry
* [x] List workspace projects
* [x] List Clockify Workspaces
* [x] List Clockify Workspaces Users
* [x] List Clockify Tags
* [x] Edit time entry
* [x] Configuration management
* [x] Initialize configuration
* [x] Update individual configuration
* [x] Show current configuration
How to install [](https://github.com/goreleaser)
--------------
#### Using [`homebrew`](https://brew.sh/):
```sh
brew install --cask lucassabreu/tap/clockify-cli
```
#### Using [`snapcraft`](https://snapcraft.io/clockify-cli)
```sh
sudo snap install clockify-cli
```
#### Using `go install`
```sh
go install github.com/lucassabreu/clockify-cli/cmd/clockify-cli@latest
```
The installed application for a default `go` installation should be located on your [$GOBIN path][go-envs]. You can add `$GOBIN` to your `$PATH`, or move it to a directory listed on your `$PATH` (e.g.: `/usr/local/bin`).
[go-envs]: https://pkg.go.dev/cmd/go#hdr-Environment_variables
#### Using `nix`/[NUR](http://github.com/nix-community/NUR)
Add this input, overlay and package into your flake:
```nix
# ...
inputs = {
nur = {
url = "github:nix-community/NUR";
inputs.nixpkgs.follows = "nixpkgs";
};
};
# ...
pkgs = import nixpkgs {
inherit system;
overlays = [ nur.overlays.default ];
};
# ...
environment.systemPackages = with pkgs; [
nur.repos.lucassabreu.clockify-cli
];
```
#### By Hand
Go to the [releases page](https://github.com/lucassabreu/clockify-cli/releases) and download the pre-compiled
binary that fits your system.
Changelog
---------
[Changelog](./CHANGELOG.md)
License
-------
[Apache License](LICENSE)
================================================
FILE: api/client.go
================================================
package api
import (
"context"
"encoding/hex"
"fmt"
"net/http"
"net/url"
"regexp"
"strings"
"time"
"github.com/lucassabreu/clockify-cli/api/dto"
"github.com/lucassabreu/clockify-cli/strhlp"
"github.com/pkg/errors"
)
// Client will help to access Clockify API
type Client interface {
// SetDebugLogger when set will output the responses of requests to the
// logger
SetDebugLogger(logger Logger) Client
// SetInfoLogger when set will output which requests and params are used to
// the logger
SetInfoLogger(logger Logger) Client
GetWorkspace(GetWorkspace) (dto.Workspace, error)
GetWorkspaces(GetWorkspaces) ([]dto.Workspace, error)
GetMe() (dto.User, error)
GetUser(GetUser) (dto.User, error)
WorkspaceUsers(WorkspaceUsersParam) ([]dto.User, error)
AddClient(AddClientParam) (dto.Client, error)
GetClients(GetClientsParam) ([]dto.Client, error)
// GetProjects get all project of a workspace
GetProjects(GetProjectsParam) ([]dto.Project, error)
// GetProject get a single Project, if exists
GetProject(GetProjectParam) (*dto.Project, error)
// AddProject creates a new project
AddProject(AddProjectParam) (dto.Project, error)
// UpdateProject changes basic information about the project
UpdateProject(UpdateProjectParam) (dto.Project, error)
// UpdateProjectUserCostRate will update the hourly rate of a user on a
// project
UpdateProjectUserBillableRate(UpdateProjectUserRateParam) (
dto.Project, error)
// UpdateProjectUserCostRate will update the cost of a user on a project
UpdateProjectUserCostRate(UpdateProjectUserRateParam) (
dto.Project, error)
// UpdateProjectEstimate change how the estime of a project is measured
UpdateProjectEstimate(UpdateProjectEstimateParam) (dto.Project, error)
// UpdateProjectMemberships changes who has access to add time entries to
// the project
UpdateProjectMemberships(UpdateProjectMembershipsParam) (dto.Project, error)
// UpdateProjectTemplate changes if a project is a template or not
UpdateProjectTemplate(UpdateProjectTemplateParam) (dto.Project, error)
// DeleteProject removes a project forever
DeleteProject(DeleteProjectParam) (dto.Project, error)
AddTask(AddTaskParam) (dto.Task, error)
DeleteTask(DeleteTaskParam) (dto.Task, error)
GetTask(GetTaskParam) (dto.Task, error)
GetTasks(GetTasksParam) ([]dto.Task, error)
UpdateTask(UpdateTaskParam) (dto.Task, error)
GetTag(GetTagParam) (*dto.Tag, error)
GetTags(GetTagsParam) ([]dto.Tag, error)
ChangeInvoiced(ChangeInvoicedParam) error
CreateTimeEntry(CreateTimeEntryParam) (dto.TimeEntryImpl, error)
DeleteTimeEntry(DeleteTimeEntryParam) error
GetHydratedTimeEntry(GetTimeEntryParam) (*dto.TimeEntry, error)
GetHydratedTimeEntryInProgress(GetTimeEntryInProgressParam) (*dto.TimeEntry, error)
GetTimeEntry(GetTimeEntryParam) (*dto.TimeEntryImpl, error)
GetTimeEntryInProgress(GetTimeEntryInProgressParam) (*dto.TimeEntryImpl, error)
GetUserTimeEntries(GetUserTimeEntriesParam) ([]dto.TimeEntryImpl, error)
GetUsersHydratedTimeEntries(GetUserTimeEntriesParam) ([]dto.TimeEntry, error)
Log(LogParam) ([]dto.TimeEntry, error)
LogRange(LogRangeParam) ([]dto.TimeEntry, error)
UpdateTimeEntry(UpdateTimeEntryParam) (dto.TimeEntryImpl, error)
Out(OutParam) error
}
type client struct {
baseURL *url.URL
http.Client
debugLogger Logger
infoLogger Logger
requestTickets chan struct{}
}
// BASE_URL is the Clockify API base URL
const BASE_URL = "https://api.clockify.me/api"
// REQUEST_RATE_LIMIT maximum number of requests per second
const REQUEST_RATE_LIMIT = 50
// ErrorMissingAPIKey returned if X-Api-Key is missing
var ErrorMissingAPIKey = errors.New("api Key must be informed")
// ErrorMissingAPIURL returned if base url is missing
var ErrorMissingAPIURL = errors.New("api URL must be informed")
func NewClientFromUrlAndKey(
apiKey,
urlString string,
) (Client, error) {
if apiKey == "" {
return nil, errors.WithStack(ErrorMissingAPIKey)
}
if urlString == "" {
return nil, errors.WithStack(ErrorMissingAPIURL)
}
u, err := url.Parse(urlString)
if err != nil {
return nil, errors.WithStack(err)
}
return &client{
baseURL: u,
Client: http.Client{
Transport: transport{
apiKey: apiKey,
next: http.DefaultTransport,
},
},
requestTickets: startRequestTick(REQUEST_RATE_LIMIT),
}, nil
}
// NewClient create a new Client, based on: https://clockify.github.io/clockify_api_docs/
func NewClient(apiKey string) (Client, error) {
return NewClientFromUrlAndKey(
apiKey,
BASE_URL,
)
}
func startRequestTick(limit int) chan struct{} {
ch := make(chan struct{}, limit)
running := true
release := func() {
i := len(ch)
for i < limit {
if !running {
return
}
i = i + 1
ch <- struct{}{}
}
}
go func() {
release()
for {
select {
case <-time.After(time.Second):
go release()
case <-context.Background().Done():
running = false
defer close(ch)
return
}
}
}()
return ch
}
// GetWorkspaces will be used to filter the workspaces
type GetWorkspaces struct {
Name string
}
// Workspaces list all the user's workspaces
func (c *client) GetWorkspaces(f GetWorkspaces) ([]dto.Workspace, error) {
var w []dto.Workspace
r, err := c.NewRequest("GET", "v1/workspaces", nil)
if err != nil {
return w, err
}
_, err = c.Do(r, &w, "GetWorkspaces")
if err != nil {
return w, errors.Wrap(err, "get workspaces")
}
if f.Name == "" {
return w, nil
}
var ws []dto.Workspace
n := strhlp.Normalize(strings.TrimSpace(f.Name))
for i := 0; i < len(w); i++ {
if strings.Contains(strhlp.Normalize(w[i].Name), n) {
ws = append(ws, w[i])
}
}
return ws, nil
}
type field string
const (
workspaceField = field("workspace")
userIDField = field("user id")
userOrGroupIDField = field("user or group")
projectField = field("project id")
timeEntryIDField = field("time entry id")
nameField = field("name")
taskIDField = field("task id")
estimateMethodField = field("estimate method")
estimateTypeField = field("estimate type")
resetOptionField = field("reset option")
)
// RequiredFieldError indicates that a field should be filled, but was not
type RequiredFieldError struct {
Field string
}
func (e RequiredFieldError) Error() string {
return e.Field + " is required"
}
func required(values map[field]string) error {
for f := range values {
if values[f] == "" {
return RequiredFieldError{Field: string(f)}
}
}
return nil
}
var regexId = regexp.MustCompile("^[a-fA-F0-9]{24}$")
// IsValidID checks if a string looks like a valid ID
func IsValidID(id string) bool {
return regexId.MatchString(id)
}
// InvalidIDError indicates that a field should be a valid ID, but it is not
type InvalidIDError struct {
Field string
ID string
}
func (e InvalidIDError) Error() string {
return e.Field + " (\"" + e.ID + "\") is not valid ID"
}
func checkIDs(ids map[field]string) error {
for field, id := range ids {
if !IsValidID(id) {
return InvalidIDError{Field: string(field), ID: id}
}
}
return nil
}
func checkWorkspace(workspace string) error {
ids := map[field]string{workspaceField: workspace}
if err := required(ids); err != nil {
return err
}
return checkIDs(ids)
}
func wrapError(err *error, message string, args ...interface{}) {
if err == nil {
return
}
*err = errors.Wrapf(*err, message, args...)
}
type EntityNotFound struct {
EntityName string
ID string
}
func (e EntityNotFound) Error() string {
return e.EntityName + " with id " + e.ID + " was not found"
}
func (e EntityNotFound) Unwrap() error {
return dto.Error{Code: 404, Message: e.Error()}
}
type GetWorkspace struct {
ID string
}
func (c *client) GetWorkspace(p GetWorkspace) (dto.Workspace, error) {
var err error
defer wrapError(&err, "get workspace %s", p.ID)
if err = checkWorkspace(p.ID); err != nil {
return dto.Workspace{}, errors.WithStack(err)
}
ws, err := c.GetWorkspaces(GetWorkspaces{})
if err != nil {
return dto.Workspace{}, err
}
for i := 0; i < len(ws); i++ {
if ws[i].ID == p.ID {
return ws[i], nil
}
}
err = EntityNotFound{
EntityName: "workspace",
ID: p.ID,
}
return dto.Workspace{}, err
}
// WorkspaceUsersParam params to query workspace users
type WorkspaceUsersParam struct {
Workspace string
Email string
PaginationParam
}
// WorkspaceUsers all users in a Workspace
func (c *client) WorkspaceUsers(p WorkspaceUsersParam) (users []dto.User, err error) {
defer wrapError(&err, "get users")
if err := checkWorkspace(p.Workspace); err != nil {
return users, err
}
users, err = paginate[dto.User](
c,
"GET",
fmt.Sprintf("v1/workspaces/%s/users", p.Workspace),
p.PaginationParam,
dto.WorkspaceUsersRequest{
Email: p.Email,
},
"WorkspaceUsers",
)
return
}
// PaginationParam parameters about pagination
type PaginationParam struct {
AllPages bool
Page int
PageSize int
}
// AllPages sets the query to retrieve all pages
func AllPages() PaginationParam {
return PaginationParam{AllPages: true}
}
// LogParam params to query entries
type LogParam struct {
Workspace string
UserID string
Date time.Time
PaginationParam
}
// Log list time entries from a date
func (c *client) Log(p LogParam) ([]dto.TimeEntry, error) {
c.infof("Log - Date Param: %s", p.Date)
d := p.Date.Round(time.Hour)
d = d.Add(time.Hour * time.Duration(d.Hour()) * -1)
return c.LogRange(LogRangeParam{
Workspace: p.Workspace,
UserID: p.UserID,
FirstDate: d,
LastDate: d.Add(time.Hour * 24),
PaginationParam: p.PaginationParam,
})
}
// LogRangeParam params to query entries
type LogRangeParam struct {
Workspace string
UserID string
FirstDate time.Time
LastDate time.Time
Description string
ProjectID string
TagIDs []string
PaginationParam
}
// LogRange list time entries by date range
func (c *client) LogRange(p LogRangeParam) ([]dto.TimeEntry, error) {
c.infof("LogRange - First Date Param: %s | Last Date Param: %s", p.FirstDate, p.LastDate)
return c.GetUsersHydratedTimeEntries(GetUserTimeEntriesParam{
Workspace: p.Workspace,
UserID: p.UserID,
Start: &p.FirstDate,
End: &p.LastDate,
Description: p.Description,
ProjectID: p.ProjectID,
TagIDs: p.TagIDs,
PaginationParam: p.PaginationParam,
})
}
type GetUserTimeEntriesParam struct {
Workspace string
UserID string
OnlyInProgress *bool
Start *time.Time
End *time.Time
Description string
ProjectID string
TagIDs []string
PaginationParam
}
// GetUserTimeEntries will list the time entries of a user on a workspace, can be paginated
func (c *client) GetUserTimeEntries(p GetUserTimeEntriesParam) ([]dto.TimeEntryImpl, error) {
return getUserTimeEntriesImpl[dto.TimeEntryImpl](c, p, false)
}
// GetUsersHydratedTimeEntries will list hydrated time entries of a user on a workspace, can be paginated
func (c *client) GetUsersHydratedTimeEntries(p GetUserTimeEntriesParam) ([]dto.TimeEntry, error) {
timeEntries, err := getUserTimeEntriesImpl[dto.TimeEntry](c, p, true)
if err != nil {
return timeEntries, err
}
user, err := c.GetUser(GetUser{p.Workspace, p.UserID})
if err != nil {
return timeEntries, err
}
for i := 0; i < len(timeEntries); i++ {
timeEntries[i].User = &user
}
return timeEntries, err
}
func getUserTimeEntriesImpl[K dto.TimeEntry | dto.TimeEntryImpl](
c *client,
p GetUserTimeEntriesParam,
hydrated bool,
) (tes []K, err error) {
defer wrapError(&err, "get time entries from user \"%s\"", p.UserID)
ids := map[field]string{
workspaceField: p.Workspace,
userIDField: p.UserID,
}
if err := required(ids); err != nil {
return tes, err
}
if err := checkIDs(ids); err != nil {
return tes, err
}
inProgressFilter := "nil"
if p.OnlyInProgress != nil {
if *p.OnlyInProgress {
inProgressFilter = "true"
} else {
inProgressFilter = "false"
}
}
c.infof(
"GetUserTimeEntries - Workspace: %s | User: %s | In Progress: %s "+
"| Description: %s | Project: %s",
p.Workspace,
p.UserID,
inProgressFilter,
p.Description,
p.ProjectID,
)
r := dto.UserTimeEntriesRequest{
OnlyInProgress: p.OnlyInProgress,
Hydrated: &hydrated,
Description: p.Description,
Project: p.ProjectID,
TagIDs: p.TagIDs,
}
if p.Start != nil {
r.Start = &dto.DateTime{Time: *p.Start}
}
if p.End != nil {
r.End = &dto.DateTime{Time: *p.End}
}
tes, err = paginate[K](
c,
"GET",
fmt.Sprintf(
"v1/workspaces/%s/user/%s/time-entries",
p.Workspace,
p.UserID,
),
p.PaginationParam,
r,
"GetUserTimeEntries",
)
return
}
func paginate[K any](
c *client,
method, uri string,
p PaginationParam,
request dto.PaginatedRequest,
name string,
) ([]K, error) {
page := p.Page
if p.AllPages {
page = 1
}
if p.PageSize == 0 {
p.PageSize = 50
}
var ls []K
stop := false
for !stop {
r, err := c.NewRequest(
method,
uri,
request.WithPagination(page, p.PageSize),
)
if err != nil {
return ls, err
}
var response []K
_, err = c.Do(r, &response, name)
if err != nil {
return ls, err
}
count := len(response)
if count > 0 {
ls = append(ls, response...)
}
stop = count < p.PageSize || !p.AllPages
page++
}
return ls, nil
}
// GetTimeEntryInProgressParam params to query entries
type GetTimeEntryInProgressParam struct {
Workspace string
UserID string
}
// GetTimeEntryInProgress show time entry in progress (if any)
func (c *client) GetTimeEntryInProgress(p GetTimeEntryInProgressParam) (timeEntryImpl *dto.TimeEntryImpl, err error) {
b := true
ts, err := c.GetUserTimeEntries(GetUserTimeEntriesParam{
Workspace: p.Workspace,
UserID: p.UserID,
OnlyInProgress: &b,
PaginationParam: PaginationParam{PageSize: 1},
})
if err != nil {
return
}
if err == nil && len(ts) > 0 {
timeEntryImpl = &ts[0]
}
return
}
// GetHydratedTimeEntryInProgress show hydrated time entry in progress (if any)
func (c *client) GetHydratedTimeEntryInProgress(p GetTimeEntryInProgressParam) (timeEntry *dto.TimeEntry, err error) {
b := true
ts, err := c.GetUsersHydratedTimeEntries(GetUserTimeEntriesParam{
Workspace: p.Workspace,
UserID: p.UserID,
OnlyInProgress: &b,
})
if err == nil && len(ts) > 0 {
timeEntry = &ts[0]
}
return
}
// GetTimeEntryParam params to get a Time Entry
type GetTimeEntryParam struct {
Workspace string
TimeEntryID string
ConsiderDurationFormat bool
}
// GetTimeEntry will retrieve a Time Entry using its Workspace and ID
func (c *client) GetTimeEntry(p GetTimeEntryParam) (timeEntry *dto.TimeEntryImpl, err error) {
defer wrapError(&err, "get time entry \"%s\"", p.TimeEntryID)
ids := map[field]string{
workspaceField: p.Workspace,
timeEntryIDField: p.TimeEntryID,
}
if err = required(ids); err != nil {
return nil, err
}
if err = checkIDs(ids); err != nil {
return nil, err
}
r, err := c.NewRequest(
"GET",
fmt.Sprintf(
"v1/workspaces/%s/time-entries/%s",
p.Workspace,
p.TimeEntryID,
),
dto.GetTimeEntryRequest{
ConsiderDurationFormat: &p.ConsiderDurationFormat,
},
)
if err != nil {
return timeEntry, err
}
_, err = c.Do(r, &timeEntry, "GetTimeEntry")
return timeEntry, err
}
func (c *client) GetHydratedTimeEntry(p GetTimeEntryParam) (timeEntry *dto.TimeEntry, err error) {
defer wrapError(&err, "get hydrated time entry \"%s\"", p.TimeEntryID)
ids := map[field]string{
workspaceField: p.Workspace,
timeEntryIDField: p.TimeEntryID,
}
if err = required(ids); err != nil {
return nil, err
}
if err = checkIDs(ids); err != nil {
return nil, err
}
b := true
r, err := c.NewRequest(
"GET",
fmt.Sprintf(
"v1/workspaces/%s/time-entries/%s",
p.Workspace,
p.TimeEntryID,
),
dto.GetTimeEntryRequest{
ConsiderDurationFormat: &p.ConsiderDurationFormat,
Hydrated: &b,
},
)
if err != nil {
return timeEntry, err
}
_, err = c.Do(r, &timeEntry, "GetHydratedTimeEntry")
return timeEntry, err
}
// GetTagParam params to find a tag
type GetTagParam struct {
Workspace string
TagID string
}
// GetTag get a single tag, if it exists
func (c *client) GetTag(p GetTagParam) (*dto.Tag, error) {
tags, err := c.GetTags(GetTagsParam{
Workspace: p.Workspace,
})
if err != nil {
return nil, err
}
for i := 0; i < len(tags); i++ {
if tags[i].ID == p.TagID {
return &tags[i], nil
}
}
return nil, errors.Errorf(
"tag %s not found on workspace %s", p.TagID, p.Workspace)
}
// GetProjectParam params to get a Project
type GetProjectParam struct {
Workspace string
ProjectID string
Hydrate bool
}
// GetProject get a single Project, if exists
func (c *client) GetProject(p GetProjectParam) (pr *dto.Project, err error) {
defer wrapError(&err, "get project \"%s\"", p.ProjectID)
ids := map[field]string{
workspaceField: p.Workspace,
projectField: p.ProjectID,
}
if err = required(ids); err != nil {
return pr, err
}
if err = checkIDs(ids); err != nil {
return pr, err
}
r, err := c.NewRequest(
"GET",
fmt.Sprintf(
"v1/workspaces/%s/projects/%s",
p.Workspace,
p.ProjectID,
),
dto.GetProjectRequest{Hydrated: p.Hydrate},
)
if err != nil {
return pr, err
}
_, err = c.Do(r, &pr, "GetProject")
if p.Hydrate && pr != nil {
pr.Hydrated = true
}
return pr, err
}
// GetUser params to get a user
type GetUser struct {
Workspace string
UserID string
}
// GetUser filters the wanted user from the workspace users
func (c *client) GetUser(p GetUser) (dto.User, error) {
var err error
defer wrapError(&err, "get user \"%s\"", p.UserID)
ids := map[field]string{
workspaceField: p.Workspace,
userIDField: p.UserID,
}
if err = required(ids); err != nil {
return dto.User{}, err
}
if err = checkIDs(ids); err != nil {
return dto.User{}, err
}
us, err := c.WorkspaceUsers(WorkspaceUsersParam{
Workspace: p.Workspace,
PaginationParam: AllPages(),
})
if err != nil {
return dto.User{}, errors.Wrapf(err, "get user %s", p.UserID)
}
for i := 0; i < len(us); i++ {
if us[i].ID == p.UserID {
return us[i], nil
}
}
err = EntityNotFound{
EntityName: "user",
ID: p.UserID,
}
return dto.User{}, err
}
// GetMe get details about the user who created the token
func (c *client) GetMe() (dto.User, error) {
r, err := c.NewRequest("GET", "v1/user", nil)
if err != nil {
return dto.User{}, err
}
var user dto.User
_, err = c.Do(r, &user, "GetMe")
return user, err
}
// GetTasksParam param to find tasks of a project
type GetTasksParam struct {
Workspace string
ProjectID string
Active bool
Name string
PaginationParam
}
// GetTasks get tasks of a project
func (c *client) GetTasks(p GetTasksParam) (ps []dto.Task, err error) {
defer wrapError(&err, "get tasks from project \"%s\"", p.ProjectID)
ids := map[field]string{
workspaceField: p.Workspace,
projectField: p.ProjectID,
}
if err = required(ids); err != nil {
return ps, err
}
if err = checkIDs(ids); err != nil {
return ps, err
}
ps, err = paginate[dto.Task](
c,
"GET",
fmt.Sprintf(
"v1/workspaces/%s/projects/%s/tasks",
p.Workspace,
p.ProjectID,
),
p.PaginationParam,
dto.GetTasksRequest{
Name: p.Name,
Active: p.Active,
},
"GetTasks",
)
return ps, err
}
// GetTaskParam param to get a task on a project
type GetTaskParam struct {
Workspace string
ProjectID string
TaskID string
}
// GetTasks get tasks of a project
func (c *client) GetTask(p GetTaskParam) (t dto.Task, err error) {
defer wrapError(&err, "get task \"%s\"", p.TaskID)
ids := map[field]string{
workspaceField: p.Workspace,
projectField: p.ProjectID,
taskIDField: p.TaskID,
}
if err = required(ids); err != nil {
return t, err
}
if err = checkIDs(ids); err != nil {
return t, err
}
r, err := c.NewRequest(
"GET",
fmt.Sprintf(
"v1/workspaces/%s/projects/%s/tasks/%s",
p.Workspace,
p.ProjectID,
p.TaskID,
),
nil,
)
if err != nil {
return t, err
}
_, err = c.Do(r, &t, "GetTask")
return t, err
}
type TaskStatus string
const (
TaskStatusDefault = ""
TaskStatusDone = "DONE"
TaskStatusActive = "ACTIVE"
)
// AddTaskParam param to add tasks to a project
type AddTaskParam struct {
Workspace string
ProjectID string
Name string
AssigneeIDs *[]string
Estimate *time.Duration
Status TaskStatus
Billable *bool
}
func (c *client) AddTask(p AddTaskParam) (task dto.Task, err error) {
defer wrapError(&err, "add task to project \"%s\"", p.ProjectID)
if err = required(map[field]string{
nameField: p.Name,
workspaceField: p.Workspace,
projectField: p.ProjectID,
}); err != nil {
return task, err
}
if err = checkIDs(map[field]string{
workspaceField: p.Workspace,
projectField: p.ProjectID,
}); err != nil {
return task, err
}
r := dto.AddTaskRequest{
Name: p.Name,
AssigneeIDs: p.AssigneeIDs,
Billable: p.Billable,
}
if p.Status != TaskStatus("") {
s := string(p.Status)
r.Status = &s
}
if p.Estimate != nil {
e := dto.Duration{Duration: *p.Estimate}
r.Estimate = &e
}
req, err := c.NewRequest(
"POST",
fmt.Sprintf(
"v1/workspaces/%s/projects/%s/tasks",
p.Workspace,
p.ProjectID,
),
r,
)
if err != nil {
return task, err
}
_, err = c.Do(req, &task, "AddTask")
return task, err
}
// UpdateTaskParam param to update tasks to a project
type UpdateTaskParam struct {
Workspace string
ProjectID string
TaskID string
Name string
AssigneeIDs *[]string
Estimate *time.Duration
Status TaskStatus
Billable *bool
}
func (c *client) UpdateTask(p UpdateTaskParam) (task dto.Task, err error) {
defer wrapError(&err, "update task \"%s\"", p.TaskID)
if err = required(map[field]string{
nameField: p.Name,
taskIDField: p.TaskID,
workspaceField: p.Workspace,
projectField: p.ProjectID,
}); err != nil {
return task, err
}
if err = checkIDs(map[field]string{
taskIDField: p.TaskID,
workspaceField: p.Workspace,
projectField: p.ProjectID,
}); err != nil {
return task, err
}
r := dto.UpdateTaskRequest{
Name: p.Name,
AssigneeIDs: p.AssigneeIDs,
Billable: p.Billable,
}
if p.Status != TaskStatus("") {
s := string(p.Status)
r.Status = &s
}
if p.Estimate != nil {
e := dto.Duration{Duration: *p.Estimate}
r.Estimate = &e
}
req, err := c.NewRequest(
"PUT",
fmt.Sprintf(
"v1/workspaces/%s/projects/%s/tasks/%s",
p.Workspace,
p.ProjectID,
p.TaskID,
),
r,
)
if err != nil {
return task, err
}
_, err = c.Do(req, &task, "UpdateTask")
return task, err
}
// DeleteTaskParam param to update tasks to a project
type DeleteTaskParam struct {
Workspace string
ProjectID string
TaskID string
}
func (c *client) DeleteTask(p DeleteTaskParam) (task dto.Task, err error) {
defer wrapError(&err, "delete task \"%s\"", p.TaskID)
ids := map[field]string{
taskIDField: p.TaskID,
workspaceField: p.Workspace,
projectField: p.ProjectID,
}
if err = required(ids); err != nil {
return task, err
}
if err = checkIDs(ids); err != nil {
return task, err
}
req, err := c.NewRequest(
"DELETE",
fmt.Sprintf(
"v1/workspaces/%s/projects/%s/tasks/%s",
p.Workspace,
p.ProjectID,
p.TaskID,
),
nil,
)
if err != nil {
return task, err
}
_, err = c.Do(req, &task, "DeleteTask")
return task, err
}
// CreateTimeEntryParam params to create a new time entry
type CreateTimeEntryParam struct {
Workspace string
Start time.Time
End *time.Time
Billable *bool
Description string
ProjectID string
TaskID string
TagIDs []string
}
// CreateTimeEntry create a new time entry
func (c *client) CreateTimeEntry(p CreateTimeEntryParam) (
t dto.TimeEntryImpl, err error) {
defer wrapError(&err, "create time entry")
if err = checkWorkspace(p.Workspace); err != nil {
return t, err
}
var end *dto.DateTime
if p.End != nil {
end = &dto.DateTime{Time: *p.End}
}
r, err := c.NewRequest(
"POST",
fmt.Sprintf(
"v1/workspaces/%s/time-entries",
p.Workspace,
),
dto.CreateTimeEntryRequest{
Start: dto.DateTime{Time: p.Start},
End: end,
Billable: p.Billable,
Description: p.Description,
ProjectID: p.ProjectID,
TaskID: p.TaskID,
TagIDs: p.TagIDs,
},
)
if err != nil {
return t, err
}
_, err = c.Do(r, &t, "CreateTimeEntry")
return t, err
}
// GetTagsParam params to get all tags of a workspace
type GetTagsParam struct {
Workspace string
Name string
Archived *bool
PaginationParam
}
// GetTags get all tags of a workspace
func (c *client) GetTags(p GetTagsParam) (ps []dto.Tag, err error) {
defer wrapError(&err, "get tags")
if err = checkWorkspace(p.Workspace); err != nil {
return ps, err
}
ps, err = paginate[dto.Tag](
c,
"GET",
fmt.Sprintf(
"v1/workspaces/%s/tags",
p.Workspace,
),
p.PaginationParam,
dto.GetTagsRequest{
Name: p.Name,
Archived: p.Archived,
},
"GetTags",
)
return ps, err
}
// GetClientsParam params to get all clients of a workspace
type GetClientsParam struct {
Workspace string
Name string
Archived *bool
PaginationParam
}
// GetClients gets all clients of a workspace
func (c *client) GetClients(p GetClientsParam) (
clients []dto.Client, err error) {
defer wrapError(&err, "get clients")
if err = checkWorkspace(p.Workspace); err != nil {
return clients, err
}
clients, err = paginate[dto.Client](
c,
"GET",
fmt.Sprintf(
"v1/workspaces/%s/clients",
p.Workspace,
),
p.PaginationParam,
dto.GetClientsRequest{
Name: p.Name,
Archived: p.Archived,
},
"GetClients",
)
return
}
type AddClientParam struct {
Workspace string
Name string
}
// AddClient adds a new client to a workspace
func (c *client) AddClient(p AddClientParam) (client dto.Client, err error) {
defer wrapError(&err, "add client")
if err = required(map[field]string{
nameField: p.Name,
workspaceField: p.Workspace,
}); err != nil {
return client, err
}
if err = checkIDs(map[field]string{
workspaceField: p.Workspace,
}); err != nil {
return client, err
}
req, err := c.NewRequest(
"POST",
fmt.Sprintf(
"v1/workspaces/%s/clients",
p.Workspace,
),
dto.AddClientRequest{
Name: p.Name,
},
)
if err != nil {
return client, err
}
_, err = c.Do(req, &client, "AddClient")
return client, err
}
// GetProjectsParam params to get all project of a workspace
type GetProjectsParam struct {
Workspace string
Name string
Clients []string
Archived *bool
Hydrate bool
PaginationParam
}
// GetProjects get all project of a workspace
func (c *client) GetProjects(p GetProjectsParam) (ps []dto.Project, err error) {
defer wrapError(&err, "get projects")
if err = checkWorkspace(p.Workspace); err != nil {
return ps, err
}
ps, err = paginate[dto.Project](
c,
"GET",
fmt.Sprintf(
"v1/workspaces/%s/projects",
p.Workspace,
),
p.PaginationParam,
dto.GetProjectsRequest{
Name: p.Name,
Archived: p.Archived,
Clients: p.Clients,
Hydrated: p.Hydrate,
},
"GetProjects",
)
if p.Hydrate {
for i := range ps {
ps[i].Hydrated = true
}
}
return ps, err
}
type AddProjectParam struct {
Workspace string
Name string
ClientId string
Color string
Note string
Billable bool
Public bool
}
func parseColor(c string) (string, error) {
if !strings.HasPrefix(c, "#") {
c = "#" + c
}
if len(c) != 4 && len(c) != 7 {
return c, errors.New("color must have 3 (#000) or 6 (#ffffff) numbers")
}
if len(c) == 4 {
c = string([]byte{'#', c[1], c[1], c[2], c[2], c[3], c[3]})
}
if _, err := hex.DecodeString(c[1:]); err != nil {
return c, errors.Wrap(err, "color \""+c+"\" is not a hex string")
}
return c, nil
}
// AddProject adds a new project to a workspace
func (c *client) AddProject(p AddProjectParam) (
project dto.Project, err error) {
defer wrapError(&err, "add project")
if err = required(map[field]string{
nameField: p.Name,
workspaceField: p.Workspace,
}); err != nil {
return project, err
}
if err = checkIDs(map[field]string{
workspaceField: p.Workspace,
}); err != nil {
return project, err
}
if p.Color != "" {
p.Color, err = parseColor(p.Color)
if err != nil {
return
}
}
req, err := c.NewRequest(
"POST",
fmt.Sprintf(
"v1/workspaces/%s/projects",
p.Workspace,
),
dto.AddProjectRequest{
Name: p.Name,
ClientId: p.ClientId,
IsPublic: p.Public,
Color: p.Color,
Note: p.Note,
Billable: p.Billable,
Public: p.Public,
},
)
if err != nil {
return project, err
}
_, err = c.Do(req, &project, "AddProject")
return project, err
}
// UpdateProjectParam sets the properties to change on a project
// Workspace and ID are required
type UpdateProjectParam struct {
Workspace string
ProjectID string
Name string
ClientId *string
Color string
Note *string
Billable *bool
Public *bool
Archived *bool
}
// UpdateProject will change properties of a Project, leave the property as nil
// or "empty" to not change it
func (c *client) UpdateProject(p UpdateProjectParam) (
project dto.Project, err error) {
defer wrapError(&err, "update project")
if err = required(map[field]string{
projectField: p.ProjectID,
workspaceField: p.Workspace,
}); err != nil {
return project, err
}
if err = checkIDs(map[field]string{
projectField: p.ProjectID,
workspaceField: p.Workspace,
}); err != nil {
return project, err
}
if p.Color != "" {
p.Color, err = parseColor(p.Color)
if err != nil {
return
}
}
var name, color *string
if p.Name != "" {
name = &p.Name
}
if p.Color != "" {
color = &p.Color
}
req, err := c.NewRequest(
"PUT",
"v1/workspaces/"+p.Workspace+"/projects/"+p.ProjectID,
dto.UpdateProjectRequest{
Name: name,
ClientId: p.ClientId,
IsPublic: p.Public,
Color: color,
Note: p.Note,
Billable: p.Billable,
Archived: p.Archived,
},
)
if err != nil {
return project, err
}
_, err = c.Do(req, &project, "UpdateProject")
return project, err
}
// UpdateMembership represents the membership of a User or User Group to a
// project
type UpdateMembership struct {
UserOrGroupID string
HourlyRateAmount int64
}
// UpdateProjectMembershipsParam will change which users and groups have
// access to the project
type UpdateProjectMembershipsParam struct {
Workspace string
ProjectID string
Memberships []UpdateMembership
}
// UpdateProjectMemberships changes who has access to add time entries to
// the project
func (c *client) UpdateProjectMemberships(p UpdateProjectMembershipsParam) (
pr dto.Project, err error) {
defer wrapError(&err, "update project memberships")
if err = required(map[field]string{
projectField: p.ProjectID,
workspaceField: p.Workspace,
}); err != nil {
return
}
if err = checkIDs(map[field]string{
projectField: p.ProjectID,
workspaceField: p.Workspace,
}); err != nil {
return
}
members := make([]dto.UpdateProjectMembership, len(p.Memberships))
for i := range p.Memberships {
id := map[field]string{
userOrGroupIDField: p.Memberships[i].UserOrGroupID}
if err = required(id); err != nil {
return
}
if err = checkIDs(id); err != nil {
return
}
members[i].UserID = p.Memberships[i].UserOrGroupID
members[i].HourlyRate.Amount = p.Memberships[i].HourlyRateAmount
}
req, err := c.NewRequest(
"PATCH",
"v1/workspaces/"+p.Workspace+"/projects/"+p.ProjectID+"/memberships",
dto.UpdateProjectMembershipsRequest{
Memberships: members,
},
)
if err != nil {
return pr, err
}
_, err = c.Do(req, &pr, "UpdateProjectMemberships")
return pr, err
}
// UpdateProjectTemplateParam sets which project will be updated,and if it will
// became a template or not
type UpdateProjectTemplateParam struct {
Workspace string
ProjectID string
Template bool
}
// UpdateProjectTemplate changes if a project is a template or not
func (c *client) UpdateProjectTemplate(p UpdateProjectTemplateParam) (
pr dto.Project, err error) {
defer wrapError(&err, "update project template")
if err = required(map[field]string{
projectField: p.ProjectID,
workspaceField: p.Workspace,
}); err != nil {
return
}
if err = checkIDs(map[field]string{
projectField: p.ProjectID,
workspaceField: p.Workspace,
}); err != nil {
return
}
req, err := c.NewRequest(
"PATCH",
"v1/workspaces/"+p.Workspace+"/projects/"+p.ProjectID+"/template",
dto.UpdateProjectTemplateRequest{
IsTemplate: p.Template,
},
)
if err != nil {
return pr, err
}
_, err = c.Do(req, &pr, "UpdateProjectTemplate")
return pr, err
}
// UpdateProjectUserRateParam sets the parameters to update the billable/cost
// rate, if Since is not nil, then all time entries after that time will be
// updated to new rate
type UpdateProjectUserRateParam struct {
Workspace string
ProjectID string
UserID string
Amount uint
Since *time.Time
}
func (c *client) UpdateProjectUserBillableRate(
p UpdateProjectUserRateParam) (project dto.Project, err error) {
defer wrapError(&err, "update project user billable rate")
if err = required(map[field]string{
projectField: p.ProjectID,
workspaceField: p.Workspace,
userIDField: p.UserID,
}); err != nil {
return
}
if err = checkIDs(map[field]string{
projectField: p.ProjectID,
workspaceField: p.Workspace,
userIDField: p.UserID,
}); err != nil {
return
}
var since *dto.DateTime
if p.Since != nil {
since = &dto.DateTime{Time: *p.Since}
}
req, err := c.NewRequest(
"PUT",
"v1/workspaces/"+p.Workspace+"/projects/"+p.ProjectID+
"/users/"+p.UserID+"/hourly-rate",
dto.UpdateProjectUserRateRequest{
Amount: p.Amount,
Since: since,
},
)
if err != nil {
return project, err
}
_, err = c.Do(req, &project, "UpdateProjectUserBillableRate")
return project, err
}
func (c *client) UpdateProjectUserCostRate(
p UpdateProjectUserRateParam) (project dto.Project, err error) {
defer wrapError(&err, "update project user cost rate")
if err = required(map[field]string{
projectField: p.ProjectID,
workspaceField: p.Workspace,
userIDField: p.UserID,
}); err != nil {
return
}
if err = checkIDs(map[field]string{
projectField: p.ProjectID,
workspaceField: p.Workspace,
userIDField: p.UserID,
}); err != nil {
return
}
var since *dto.DateTime
if p.Since != nil {
since = &dto.DateTime{Time: *p.Since}
}
req, err := c.NewRequest(
"PUT",
"v1/workspaces/"+p.Workspace+"/projects/"+p.ProjectID+
"/users/"+p.UserID+"/cost-rate",
dto.UpdateProjectUserRateRequest{
Amount: p.Amount,
Since: since,
},
)
if err != nil {
return project, err
}
_, err = c.Do(req, &project, "UpdateProjectUserCostRate")
return project, err
}
// EstimateMethod are methods to estimate projects (none, budget and time)
type EstimateMethod string
const (
// EstimateMethodNone dont estimate the project
EstimateMethodNone = EstimateMethod("none")
// EstimateMethodTime estimate by time
EstimateMethodTime = EstimateMethod("time")
// EstimateMethodBudget estimate by budget
EstimateMethodBudget = EstimateMethod("budget")
)
// EstimateType sets if the estimate is for the role project or per task
type EstimateType string
const (
EstimateTypeProject = EstimateType("project")
EstimateTypeTask = EstimateType("task")
)
func (t EstimateType) toRequestType() *dto.EstimateType {
switch t {
case EstimateTypeTask:
v := dto.EstimateTypeAuto
return &v
case EstimateTypeProject:
v := dto.EstimateTypeManual
return &v
default:
return nil
}
}
// EstimateResetOption defines the period in which the estimates reset
type EstimateResetOption string
const (
EstimateResetOptionDefault = EstimateType("")
EstimateResetOptionMonthly = EstimateResetOption("monthly")
)
func (t EstimateResetOption) toRequestType() *dto.EstimateResetOption {
switch t {
case EstimateResetOptionMonthly:
v := dto.EstimateResetOptionMonthly
return &v
default:
return nil
}
}
// UpdateProjectEstimateParam holds parameters to change project estimate
type UpdateProjectEstimateParam struct {
Workspace string
ProjectID string
Method EstimateMethod
Type EstimateType
ResetOption EstimateResetOption
Estimate int64
}
// UpdateProjectEstimate change how the estime of a project is measured
func (c *client) UpdateProjectEstimate(p UpdateProjectEstimateParam) (
r dto.Project, err error) {
defer wrapError(&err, "update project estimate")
if err = required(map[field]string{
projectField: p.ProjectID,
workspaceField: p.Workspace,
estimateMethodField: string(p.Method),
}); err != nil {
return
}
if err = checkIDs(map[field]string{
projectField: p.ProjectID,
workspaceField: p.Workspace,
}); err != nil {
return
}
if err = shouldBeOneOf(estimateMethodField, string(p.Method), []string{
string(EstimateMethodNone),
string(EstimateMethodTime),
string(EstimateMethodBudget),
}); err != nil {
return
}
if p.Method != EstimateMethodNone {
if err = shouldBeOneOf(estimateTypeField, string(p.Type), []string{
string(EstimateTypeProject),
string(EstimateTypeTask),
}); err != nil {
return
}
if err = shouldBeOneOf(resetOptionField, string(p.ResetOption),
[]string{
string(EstimateResetOptionDefault),
string(EstimateResetOptionMonthly),
}); err != nil {
return
}
if p.Type != EstimateTypeProject {
p.Estimate = 0
} else if p.Estimate <= 0 {
err = errors.New(
"estimate should be greater than zero for type project")
return
}
}
b := dto.UpdateProjectEstimateRequest{}
if p.Method != EstimateMethodNone {
be := dto.BaseEstimateRequest{
Active: true,
Type: p.Type.toRequestType(),
ResetOptions: p.ResetOption.toRequestType(),
}
switch p.Method {
case EstimateMethodBudget:
b.BudgetEstimate.BaseEstimateRequest = be
if p.Estimate > 0 {
e := uint64(p.Estimate)
b.BudgetEstimate.Estimate = &e
}
case EstimateMethodTime:
b.TimeEstimate.BaseEstimateRequest = be
if p.Estimate > 0 {
b.TimeEstimate.Estimate = &dto.Duration{
Duration: time.Duration(p.Estimate)}
}
}
}
req, err := c.NewRequest(
"PATCH",
"v1/workspaces/"+p.Workspace+"/projects/"+p.ProjectID+"/estimate",
b,
)
if err != nil {
return
}
_, err = c.Do(req, &r, "UpdateProjectEstimate")
return
}
// DeleteProjectParam identifies which project to delete
type DeleteProjectParam struct {
Workspace string
ProjectID string
}
// DeleteProject removes a project forever
func (c *client) DeleteProject(p DeleteProjectParam) (
pr dto.Project, err error) {
defer wrapError(&err, "delete project")
ids := map[field]string{
workspaceField: p.Workspace,
projectField: p.ProjectID,
}
if err = required(ids); err != nil {
return pr, err
}
if err = checkIDs(ids); err != nil {
return pr, err
}
r, err := c.NewRequest(
"DELETE",
"v1/workspaces/"+p.Workspace+"/projects/"+p.ProjectID,
nil,
)
if err != nil {
return pr, err
}
_, err = c.Do(r, &pr, "DeleteProject")
return pr, err
}
// InvalidOptionError indicates that the parameter has a limited set of valid
// values, and the one used is not one of them (see Options for the valid ones)
type InvalidOptionError struct {
Field string
Options []string
}
func (i *InvalidOptionError) Error() string {
return "valid options for " + i.Field + " are " + strhlp.ListForHumans(i.Options)
}
func shouldBeOneOf(f field, s string, o []string) error {
if strhlp.InSlice(s, o) {
return nil
}
return &InvalidOptionError{
Field: string(f),
Options: o,
}
}
// OutParam params to end the current time entry
type OutParam struct {
Workspace string
UserID string
End time.Time
}
// Out create a new time entry
func (c *client) Out(p OutParam) (err error) {
defer wrapError(&err, "end running time entry")
ids := map[field]string{
workspaceField: p.Workspace,
userIDField: p.UserID,
}
if err = required(ids); err != nil {
return err
}
if err = checkIDs(ids); err != nil {
return err
}
r, err := c.NewRequest(
"PATCH",
fmt.Sprintf(
"v1/workspaces/%s/user/%s/time-entries",
p.Workspace,
p.UserID,
),
dto.OutTimeEntryRequest{
End: dto.DateTime{Time: p.End},
},
)
if err != nil {
return err
}
_, err = c.Do(r, nil, "Out")
return err
}
// UpdateTimeEntryParam params to update a new time entry
type UpdateTimeEntryParam struct {
Workspace string
TimeEntryID string
Start time.Time
End *time.Time
Billable bool
Description string
ProjectID string
TaskID string
TagIDs []string
}
// UpdateTimeEntry update a time entry
func (c *client) UpdateTimeEntry(p UpdateTimeEntryParam) (
t dto.TimeEntryImpl, err error) {
defer wrapError(&err, "update time entry \"%s\"", p.TimeEntryID)
ids := map[field]string{
workspaceField: p.Workspace,
timeEntryIDField: p.TimeEntryID,
}
if err = required(ids); err != nil {
return t, err
}
if err = checkIDs(ids); err != nil {
return t, err
}
var end *dto.DateTime
if p.End != nil {
end = &dto.DateTime{Time: *p.End}
}
r, err := c.NewRequest(
"PUT",
fmt.Sprintf(
"v1/workspaces/%s/time-entries/%s",
p.Workspace,
p.TimeEntryID,
),
dto.UpdateTimeEntryRequest{
Start: dto.DateTime{Time: p.Start},
End: end,
Billable: p.Billable,
Description: p.Description,
ProjectID: p.ProjectID,
TaskID: p.TaskID,
TagIDs: p.TagIDs,
},
)
if err != nil {
return t, err
}
_, err = c.Do(r, &t, "UpdateTimeEntry")
return t, err
}
// DeleteTimeEntryParam params to update a new time entry
type DeleteTimeEntryParam struct {
Workspace string
TimeEntryID string
}
// DeleteTimeEntry deletes a time entry
func (c *client) DeleteTimeEntry(p DeleteTimeEntryParam) (err error) {
defer wrapError(&err, "delete time entry \"%s\"", p.TimeEntryID)
ids := map[field]string{
workspaceField: p.Workspace,
timeEntryIDField: p.TimeEntryID,
}
if err = required(ids); err != nil {
return err
}
if err = checkIDs(ids); err != nil {
return err
}
r, err := c.NewRequest(
"DELETE",
fmt.Sprintf(
"v1/workspaces/%s/time-entries/%s",
p.Workspace,
p.TimeEntryID,
),
nil,
)
if err != nil {
return err
}
_, err = c.Do(r, nil, "DeleteTimeEntry")
return err
}
type ChangeInvoicedParam struct {
Workspace string
TimeEntryIDs []string
Invoiced bool
}
// ChangeInvoiced changes time entries to invoiced or not
func (c *client) ChangeInvoiced(p ChangeInvoicedParam) error {
r, err := c.NewRequest(
"PATCH",
fmt.Sprintf(
"v1/workspaces/%s/time-entries/invoiced",
p.Workspace,
),
dto.ChangeTimeEntriesInvoicedRequest{
TimeEntryIDs: p.TimeEntryIDs,
Invoiced: p.Invoiced,
},
)
if err != nil {
return err
}
_, err = c.Do(r, nil, "ChangeInvoiced")
return err
}
================================================
FILE: api/client_test.go
================================================
package api_test
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/lucassabreu/clockify-cli/api"
"github.com/stretchr/testify/assert"
)
var exampleID = "62f2af744a912b05acc7c79e"
type testCase interface {
getName() string
getParam() interface{}
getResult() interface{}
getErr() string
hasHttpCalls() bool
getHttpCallFor(uri string) httpCall
getPendingHttpCalls() []httpCall
}
type httpCall interface {
getRequestMethod() string
getRequestUrl() string
getRequestBody() string
getResponseStatus() int
getResponseBody() string
}
func runClient(t *testing.T, tt testCase,
fn func(api.Client, interface{}) (interface{}, error)) {
t.Run(tt.getName(), func(t *testing.T) {
httpCalled := false
t.Cleanup(func() {
if !tt.hasHttpCalls() {
assert.False(t, httpCalled, "should not call api")
return
}
assert.True(t, httpCalled, "should call api")
})
s := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
httpCalled = true
if !tt.hasHttpCalls() {
t.Error("should not call api")
w.WriteHeader(500)
return
}
hc := tt.getHttpCallFor(r.URL.String())
if hc == nil {
assert.FailNow(t, "should not call api "+r.URL.String())
w.WriteHeader(500)
return
}
assert.Equal(t, hc.getRequestUrl(), r.URL.String())
assert.Equal(t,
hc.getRequestMethod(), strings.ToLower(r.Method))
b, _ := io.ReadAll(r.Body)
if hc.getRequestBody() != "" {
var eMap, aMap map[string]interface{}
assert.NoError(t, json.Unmarshal(b, &aMap))
assert.NoError(t,
json.Unmarshal([]byte(hc.getRequestBody()), &eMap))
assert.Equal(t, eMap, aMap)
} else {
assert.Empty(t, string(b))
}
w.WriteHeader(hc.getResponseStatus())
rb := hc.getResponseBody()
if rb == "" {
rb = "{}"
}
_, err := w.Write([]byte(rb))
assert.NoError(t, err)
}))
defer s.Close()
c, _ := api.NewClientFromUrlAndKey(
"a-key",
s.URL,
)
r, err := fn(c, tt.getParam())
if tt.getErr() != "" {
if !assert.Error(t, err) {
return
}
assert.Regexp(t, tt.getErr(), err.Error())
return
}
if !assert.NoError(t, err) || tt.getResult() == nil {
return
}
assert.Equal(t, tt.getResult(), r)
})
}
type simpleTestCase struct {
name string
param interface{}
result interface{}
err string
requestMethod string
requestUrl string
requestBody string
responseStatus int
responseBody string
once bool
}
func (s *simpleTestCase) getRequestMethod() string {
return s.requestMethod
}
func (s *simpleTestCase) getRequestUrl() string {
return s.requestUrl
}
func (s *simpleTestCase) getRequestBody() string {
return s.requestBody
}
func (s *simpleTestCase) getResponseStatus() int {
return s.responseStatus
}
func (s *simpleTestCase) getResponseBody() string {
return s.responseBody
}
func (s *simpleTestCase) getName() string {
return s.name
}
func (s *simpleTestCase) getParam() interface{} {
return s.param
}
func (s *simpleTestCase) getResult() interface{} {
return s.result
}
func (s *simpleTestCase) getErr() string {
return s.err
}
func (s *simpleTestCase) getHttpCallFor(_ string) httpCall {
if !s.once {
s.once = true
return s
}
return nil
}
func (s *simpleTestCase) getPendingHttpCalls() []httpCall {
if s.once {
return []httpCall{}
}
return []httpCall{s}
}
func (s *simpleTestCase) hasHttpCalls() bool {
return s.requestUrl != ""
}
type multiRequestTestCase struct {
name string
param interface{}
err string
result interface{}
calls map[string]httpCall
hasCalls bool
}
func (m *multiRequestTestCase) getName() string {
return m.name
}
func (m *multiRequestTestCase) getParam() interface{} {
return m.param
}
func (m *multiRequestTestCase) getResult() interface{} {
return m.result
}
func (m *multiRequestTestCase) getErr() string {
return m.err
}
func (m *multiRequestTestCase) hasHttpCalls() bool {
return m.hasCalls
}
func (m *multiRequestTestCase) getHttpCallFor(uri string) httpCall {
if !m.hasCalls {
return nil
}
c := m.calls[uri]
delete(m.calls, uri)
return c
}
func (m *multiRequestTestCase) getPendingHttpCalls() []httpCall {
if !m.hasCalls {
return []httpCall{}
}
l := make([]httpCall, len(m.calls))
for _, c := range m.calls {
l = append(l, c)
}
return l
}
func (m *multiRequestTestCase) addHttpCall(c httpCall) *multiRequestTestCase {
if m.calls == nil {
m.calls = make(map[string]httpCall)
m.hasCalls = true
}
if _, ok := m.calls[c.getRequestUrl()]; ok {
panic("http call for " + c.getRequestUrl() + " already exists")
}
m.calls[c.getRequestUrl()] = c
return m
}
type httpRequest struct {
method string
url string
body string
status int
response string
}
func (h *httpRequest) getRequestMethod() string {
return h.method
}
func (h *httpRequest) getRequestUrl() string {
return h.url
}
func (h *httpRequest) getRequestBody() string {
return h.body
}
func (h *httpRequest) getResponseStatus() int {
return h.status
}
func (h *httpRequest) getResponseBody() string {
return h.response
}
================================================
FILE: api/dto/dto.go
================================================
package dto
import (
"fmt"
"strings"
"time"
)
// Error api errors
type Error struct {
Message string `json:"message"`
Code int `json:"code"`
}
func (e Error) Error() string {
return fmt.Sprintf("%s (code: %d)", e.Message, e.Code)
}
// Workspace DTO
type Workspace struct {
ID string `json:"id"`
Name string `json:"name"`
ImageURL string `json:"imageUrl"`
Settings WorkspaceSettings `json:"workspaceSettings"`
HourlyRate Rate `json:"hourlyRate"`
Memberships []Membership
}
// Membership DTO
type Membership struct {
HourlyRate *Rate `json:"hourlyRate"`
CostRate *Rate `json:"costRate"`
Status MembershipStatus `json:"membershipStatus"`
Type string `json:"membershipType"`
TargetID string `json:"targetId"`
UserID string `json:"userId"`
}
// MembershipStatus possible Membership Status
type MembershipStatus string
// MembershipStatusPending membership is Pending
const MembershipStatusPending = MembershipStatus("PENDING")
// MembershipStatusActive membership is Active
const MembershipStatusActive = MembershipStatus("ACTIVE")
// MembershipStatusDeclined membership is Declined
const MembershipStatusDeclined = MembershipStatus("DECLINED")
// MembershipStatusInactive membership is Inactive
const MembershipStatusInactive = MembershipStatus("INACTIVE")
// WorkspaceSettings DTO
type WorkspaceSettings struct {
AdminOnlyPages []string `json:"adminOnlyPages"`
AutomaticLock AutomaticLock `json:"automaticLock"`
CanSeeTimeSheet bool `json:"canSeeTimeSheet"`
DefaultBillableProjects bool `json:"defaultBillableProjects"`
ForceDescription bool `json:"forceDescription"`
ForceProjects bool `json:"forceProjects"`
ForceTags bool `json:"forceTags"`
ForceTasks bool `json:"forceTasks"`
LockTimeEntries time.Time `json:"lockTimeEntries"`
OnlyAdminsCreateProject bool `json:"onlyAdminsCreateProject"`
OnlyAdminsCreateTag bool `json:"onlyAdminsCreateTag"`
OnlyAdminsCreateTask bool `json:"onlyAdminsCreateTask"`
OnlyAdminsSeeAllTimeEntries bool `json:"onlyAdminsSeeAllTimeEntries"`
OnlyAdminsSeeBillableRates bool `json:"onlyAdminsSeeBillableRates"`
OnlyAdminsSeeDashboard bool `json:"onlyAdminsSeeDashboard"`
OnlyAdminsSeePublicProjectsEntries bool `json:"onlyAdminsSeePublicProjectsEntries"`
ProjectFavorites bool `json:"projectFavorites"`
ProjectGroupingLabel string `json:"projectGroupingLabel"`
ProjectPickerSpecialFilter bool `json:"projectPickerSpecialFilter"`
Round Round `json:"round"`
TimeRoundingInReports bool `json:"timeRoundingInReports"`
TrackTimeDownToSecond bool `json:"trackTimeDownToSecond"`
IsProjectPublicByDefault bool `json:"isProjectPublicByDefault"`
CanSeeTracker bool `json:"canSeeTracker"`
FeatureSubscriptionType string `json:"featureSubscriptionType"`
}
// AutomaticLock DTO
type AutomaticLock struct {
ChangeDay string `json:"changeDay"`
DayOfMonth int `json:"dayOfMonth"`
FirstDay string `json:"firstDay"`
OlderThanPeriod string `json:"olderThanPeriod"`
OlderThanValue int `json:"olderThanValue"`
Type string `json:"type"`
}
// Round DTO
type Round struct {
Minutes string `json:"minutes"`
Round string `json:"round"`
}
// Rate DTO
type Rate struct {
Amount int64 `json:"amount"`
Currency string `json:"currency,omitempty"`
}
// TimeEntry DTO
type TimeEntry struct {
ID string `json:"id"`
Billable bool `json:"billable"`
Description string `json:"description"`
HourlyRate Rate `json:"hourlyRate"`
IsLocked bool `json:"isLocked"`
Project *Project `json:"project"`
CustomFields []CustomField `json:"customFieldValues"`
ProjectID string `json:"projectId"`
Tags []Tag `json:"tags"`
Task *Task `json:"task"`
TimeInterval TimeInterval `json:"timeInterval"`
TotalBillable int64 `json:"totalBillable"`
User *User `json:"user"`
WorkspaceID string `json:"workspaceId"`
}
// NewTimeInterval will create a TimeInterval from start and end times
func NewTimeInterval(start time.Time, end *time.Time) TimeInterval {
start = start.UTC()
if end != nil {
*end = end.UTC()
}
t := TimeInterval{
Start: start.UTC(),
End: end,
}
if end == nil {
t := time.Now().UTC()
end = &t
}
t.Duration = Duration{end.Sub(t.Start)}.String()
return t
}
// TimeInterval DTO
type TimeInterval struct {
Duration string `json:"duration"`
End *time.Time `json:"end"`
Start time.Time `json:"start"`
}
// Tag DTO
type Tag struct {
ID string `json:"id"`
Name string `json:"name"`
WorkspaceID string `json:"workspaceId"`
}
func (e Tag) GetID() string { return e.ID }
func (e Tag) GetName() string { return e.Name }
func (e Tag) String() string { return e.Name + " (" + e.ID + ")" }
// TaskStatus task status
type TaskStatus string
// TaskStatusActive task is Active
const TaskStatusActive = TaskStatus("ACTIVE")
// TaskStatusDone task is Done
const TaskStatusDone = TaskStatus("DONE")
// Task DTO
type Task struct {
AssigneeIDs []string `json:"assigneeIds"`
UserGroupIDs []string `json:"userGroupIds"`
Estimate *Duration `json:"estimate"`
ID string `json:"id"`
Name string `json:"name"`
ProjectID string `json:"projectId"`
Billable bool `json:"billable"`
HourlyRate *Rate `json:"hourlyRate"`
CostRate *Rate `json:"costRate"`
Status TaskStatus `json:"status"`
Duration *Duration `json:"duration"`
Favorite bool `json:"favorite"`
}
func (e Task) GetID() string { return e.ID }
func (e Task) GetName() string { return e.Name }
// Client DTO
type Client struct {
ID string `json:"id"`
Name string `json:"name"`
WorkspaceID string `json:"workspaceId"`
Archived bool `json:"archived"`
}
func (e Client) GetID() string { return e.ID }
func (e Client) GetName() string { return e.Name }
// CustomField DTO
type CustomField struct {
CustomFieldID string `json:"customFieldId"`
TimeEntryId string `json:"timeEntryId"`
Name string `json:"name"`
Type string `json:"type"`
Value interface{} `json:"value"`
}
// ValueAsString converter for CustomFieldDTO
/*
Custom field `Value` can be either a string or an array of strings.
This function is used to get the value always as string, using the `|` symbol
as separator between each individual string.
*/
func (cf CustomField) ValueAsString() string {
switch v := cf.Value.(type) {
case string:
return v
case []interface{}:
parts := make([]string, len(v))
for i, item := range v {
parts[i] = fmt.Sprint(item)
}
return strings.Join(parts, "|")
case []string:
return strings.Join(v, "|")
case nil:
return ""
default:
return fmt.Sprint(v)
}
}
// Project DTO
type Project struct {
WorkspaceID string `json:"workspaceId"`
ID string `json:"id"`
Name string `json:"name"`
Note string `json:"note"`
Color string `json:"color"`
ClientID string `json:"clientId"`
ClientName string `json:"clientName"`
HourlyRate Rate `json:"hourlyRate"`
CostRate *Rate `json:"costRate"`
Billable bool `json:"billable"`
TimeEstimate TimeEstimate `json:"timeEstimate"`
BudgetEstimate BaseEstimate `json:"budgetEstimate"`
Duration *Duration `json:"duration"`
Archived bool `json:"archived"`
Template bool `json:"template"`
Public bool `json:"public"`
Favorite bool `json:"favorite"`
Memberships []Membership `json:"memberships"`
// Hydrated indicates if the attributes CustomFields and Tasks are filled
Hydrated bool `json:"-"`
CustomFields []CustomField `json:"customFields,omitempty"`
Tasks []Task `json:"tasks,omitempty"`
}
func (p Project) GetID() string { return p.ID }
func (p Project) GetName() string { return p.Name }
// EstimateType possible Estimate types
type EstimateType string
// EstimateTypeAuto estimate is Auto
const EstimateTypeAuto = EstimateType("AUTO")
// EstimateTypeManual estimate is Manual
const EstimateTypeManual = EstimateType("MANUAL")
// EstimateResetOption possible Estimate Reset Options
type EstimateResetOption string
// EstimateResetOptionMonthly estimate is Auto
const EstimateResetOptionMonthly = EstimateResetOption("MONTHLY")
// BaseEstimate DTO
type BaseEstimate struct {
Type EstimateType `json:"type"`
Active bool `json:"active"`
ResetOptions *EstimateResetOption `json:"resetOptions"`
}
// TimeEstimate DTO
type TimeEstimate struct {
BaseEstimate
Estimate Duration `json:"estimate"`
IncludeNonBillable bool `json:"includeNonBillable"`
}
// BudgetEstimate DTO
type BudgetEstimate struct {
BaseEstimate
Estimate uint `json:"estimate"`
}
// UserStatus possible user status
type UserStatus string
// UserStatusActive when the user is Active
const UserStatusActive = UserStatus("ACTIVE")
// UserStatusPendingEmailVerification when the user is Pending Email Verification
const UserStatusPendingEmailVerification = UserStatus("PENDING_EMAIL_VERIFICATION")
// UserStatusDeleted when the user is Deleted
const UserStatusDeleted = UserStatus("DELETED")
// User DTO
type User struct {
ID string `json:"id"`
ActiveWorkspace string `json:"activeWorkspace"`
DefaultWorkspace string `json:"defaultWorkspace"`
Email string `json:"email"`
Memberships []Membership `json:"memberships"`
Name string `json:"name"`
ProfilePicture string `json:"profilePicture"`
Settings UserSettings `json:"settings"`
Status UserStatus `json:"status"`
Roles *[]Role `json:"roles"`
}
func (e User) GetID() string { return e.ID }
func (e User) GetName() string { return e.Name }
// Role DTO
type Role struct {
Role string `json:"role"`
Entities []RoleEntity `json:"entities"`
}
type RoleEntity struct {
ID string `json:"id"`
Name string `json:"name"`
}
// WeekStart when the week starts
type WeekStart string
// WeekStartMonday when start at Monday
const WeekStartMonday = WeekStart("MONDAY")
// WeekStartTuesday when start at Tuesday
const WeekStartTuesday = WeekStart("TUESDAY")
// WeekStartWednesday when start at Wednesday
const WeekStartWednesday = WeekStart("WEDNESDAY")
// WeekStartThursday when start at Thursday
const WeekStartThursday = WeekStart("THURSDAY")
// WeekStartFriday when start at Friday
const WeekStartFriday = WeekStart("FRIDAY")
// WeekStartSaturday when start at Saturday
const WeekStartSaturday = WeekStart("SATURDAY")
// WeekStartSunday when start at Sunday
const WeekStartSunday = WeekStart("SUNDAY")
// UserSettings DTO
type UserSettings struct {
DateFormat string `json:"dateFormat"`
IsCompactViewOn bool `json:"isCompactViewOn"`
LongRunning bool `json:"longRunning"`
SendNewsletter bool `json:"sendNewsletter"`
SummaryReportSettings SummaryReportSettings `json:"summaryReportSettings"`
TimeFormat string `json:"timeFormat"`
TimeTrackingManual bool `json:"timeTrackingManual"`
TimeZone string `json:"timeZone"`
WeekStart string `json:"weekStart"`
WeeklyUpdates bool `json:"weeklyUpdates"`
}
// SummaryReportSettings DTO
type SummaryReportSettings struct {
Group string `json:"group"`
Subgroup string `json:"subgroup"`
}
// InvitedUser DTO
type InvitedUser struct {
ID string `json:"id"`
Email string `json:"email"`
Invitation Invitation `json:"invitation"`
Memberships []Membership `json:"memberships"`
}
// Invitation DTO
type Invitation struct {
Creation time.Time `json:"creation"`
InvitationCode string `json:"invitationCode"`
Membership Membership `json:"membership"`
WorkspaceID string `json:"workspaceId"`
WorkspaceName string `json:"workspaceName"`
}
// TimeEntriesList DTO
type TimeEntriesList struct {
AllEntriesCount int64 `json:"allEntriesCount"`
GotAllEntries bool `json:"gotAllEntries"`
TimeEntriesList []TimeEntryImpl `json:"timeEntriesList"`
}
// TimeEntryImpl DTO
type TimeEntryImpl struct {
Billable bool `json:"billable"`
Description string `json:"description"`
ID string `json:"id"`
IsLocked bool `json:"isLocked"`
ProjectID string `json:"projectId"`
TagIDs []string `json:"tagIds"`
TaskID string `json:"taskId"`
TimeInterval TimeInterval `json:"timeInterval"`
UserID string `json:"userId"`
WorkspaceID string `json:"workspaceId"`
}
================================================
FILE: api/dto/request.go
================================================
package dto
import (
"encoding/json"
"fmt"
"net/url"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
)
// DateTime is a time presentation for parameters
type DateTime struct {
time.Time
}
// MarshalJSON converts DateTime correctly
func (d DateTime) MarshalJSON() ([]byte, error) {
return []byte(strconv.Quote(d.String())), nil
}
func (d DateTime) String() string {
return d.Time.UTC().Format("2006-01-02T15:04:05Z")
}
// Duration is a time presentation for parameters
type Duration struct {
time.Duration
}
// MarshalJSON converts Duration correctly
func (d Duration) MarshalJSON() ([]byte, error) {
return []byte("\"" + d.String() + "\""), nil
}
// UnmarshalJSON converts a JSON value to Duration correctly
func (d *Duration) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return errors.Wrap(err, "unmarshal duration")
}
dc, err := StringToDuration(s)
if err != nil {
return err
}
*d = Duration{dc}
return err
}
func StringToDuration(s string) (time.Duration, error) {
if len(s) < 4 {
return 0, errors.Errorf("duration %s is invalid", s)
}
var u, dc time.Duration
var j, i int
for ; i < len(s); i++ {
switch s[i] {
case 'P', 'T':
j = i + 1
continue
case 'H':
u = time.Hour
case 'M':
u = time.Minute
case 'S':
u = time.Second
default:
continue
}
v, err := strconv.Atoi(s[j:i])
if err != nil {
return 0, errors.Wrap(err, "cast cast "+s[j:i]+" to int")
}
dc = dc + time.Duration(v)*u
j = i + 1
}
return dc, nil
}
func (d Duration) String() string {
s := d.Duration.String()
i := strings.LastIndex(s, ".")
if i > -1 {
s = s[0:i] + "s"
}
return "PT" + strings.ToUpper(s)
}
func (dd Duration) HumanString() string {
d := dd.Duration
p := ""
if d < 0 {
p = "-"
d = d * -1
}
return p + fmt.Sprintf("%d:%02d:%02d",
int64(d.Hours()), int64(d.Minutes())%60, int64(d.Seconds())%60)
}
type pagination struct {
page int
pageSize int
}
func newPagination(page, size int) pagination {
return pagination{
page: page,
pageSize: size,
}
}
// AppendToQuery decorates the URL with pagination parameters
func (p pagination) AppendToQuery(u *url.URL) *url.URL {
v := u.Query()
if p.page != 0 {
v.Add("page", strconv.Itoa(p.page))
}
if p.pageSize != 0 {
v.Add("page-size", strconv.Itoa(p.pageSize))
}
u.RawQuery = v.Encode()
return u
}
type PaginatedRequest interface {
WithPagination(page, size int) PaginatedRequest
}
// GetTimeEntryRequest to get a time entry
type GetTimeEntryRequest struct {
Hydrated *bool
ConsiderDurationFormat *bool
}
// AppendToQuery decorates the URL with the query string needed for this Request
func (r GetTimeEntryRequest) AppendToQuery(u *url.URL) *url.URL {
v := u.Query()
if r.Hydrated != nil && *r.Hydrated {
v.Add("hydrated", "true")
}
if r.ConsiderDurationFormat != nil && *r.ConsiderDurationFormat {
v.Add("consider-duration-format", "true")
}
u.RawQuery = v.Encode()
return u
}
// UserTimeEntriesRequest to get entries of a user
type UserTimeEntriesRequest struct {
Description string
Start *DateTime
End *DateTime
Project string
Task string
TagIDs []string
ProjectRequired *bool
TaskRequired *bool
ConsiderDurationFormat *bool
Hydrated *bool
OnlyInProgress *bool
pagination
}
// WithPagination add pagination to the UserTimeEntriesRequest
func (r UserTimeEntriesRequest) WithPagination(page, size int) PaginatedRequest {
r.pagination = newPagination(page, size)
return r
}
// AppendToQuery decorates the URL with the query string needed for this Request
func (r UserTimeEntriesRequest) AppendToQuery(u *url.URL) *url.URL {
u = r.pagination.AppendToQuery(u)
v := u.Query()
if r.Start != nil {
v.Add("start", r.Start.String())
}
if r.End != nil {
v.Add("end", r.End.String())
}
addNotNil := func(b *bool, p string) {
if b == nil {
return
}
if *b {
v.Add(p, "1")
} else {
v.Add(p, "0")
}
}
addNotNil(r.ProjectRequired, "project-required")
addNotNil(r.TaskRequired, "task-required")
addNotNil(r.ConsiderDurationFormat, "consider-duration-format")
addNotNil(r.Hydrated, "hydrated")
addNotNil(r.OnlyInProgress, "in-progress")
addNotEmpty := func(s string, p string) {
if s == "" {
return
}
v.Add(p, s)
}
addNotEmpty(r.Description, "description")
addNotEmpty(r.Project, "project")
addNotEmpty(r.Task, "task")
for _, t := range r.TagIDs {
addNotEmpty(t, "tags")
}
u.RawQuery = v.Encode()
return u
}
// OutTimeEntryRequest to end the current time entry
type OutTimeEntryRequest struct {
End DateTime `json:"end"`
}
// CreateTimeEntryRequest to create a time entry is created
type CreateTimeEntryRequest struct {
Start DateTime `json:"start,omitempty"`
End *DateTime `json:"end,omitempty"`
Billable *bool `json:"billable,omitempty"`
Description string `json:"description,omitempty"`
ProjectID string `json:"projectId,omitempty"`
TaskID string `json:"taskId,omitempty"`
TagIDs []string `json:"tagIds,omitempty"`
CustomFields []CustomFieldValue `json:"customFields,omitempty"`
}
// CustomFieldValue DTO
type CustomFieldValue struct {
CustomFieldID string `json:"customFieldId"`
Status string `json:"status"`
Name string `json:"name"`
Type string `json:"type"`
Value string `json:"value"`
}
// UpdateTimeEntryRequest to update a time entry
type UpdateTimeEntryRequest struct {
Start DateTime `json:"start,omitempty"`
End *DateTime `json:"end,omitempty"`
Billable bool `json:"billable,omitempty"`
Description string `json:"description,omitempty"`
ProjectID string `json:"projectId,omitempty"`
TaskID string `json:"taskId,omitempty"`
TagIDs []string `json:"tagIds,omitempty"`
CustomFields []CustomFieldValue `json:"customFields,omitempty"`
}
type GetClientsRequest struct {
Name string
Archived *bool
pagination
}
// WithPagination add pagination to the GetClientsRequest
func (r GetClientsRequest) WithPagination(page, size int) PaginatedRequest {
r.pagination = newPagination(page, size)
return r
}
// AppendToQuery decorates the URL with the query string needed for this Request
func (r GetClientsRequest) AppendToQuery(u *url.URL) *url.URL {
u = r.pagination.AppendToQuery(u)
v := u.Query()
if r.Name != "" {
v.Add("name", r.Name)
}
if r.Archived != nil {
v.Add("archived", boolString[*r.Archived])
}
u.RawQuery = v.Encode()
return u
}
type AddClientRequest struct {
Name string `json:"name"`
}
type GetProjectsRequest struct {
Name string
Archived *bool
Clients []string
Hydrated bool
pagination
}
// WithPagination add pagination to the GetProjectRequest
func (r GetProjectsRequest) WithPagination(page, size int) PaginatedRequest {
r.pagination = newPagination(page, size)
return r
}
var boolString = map[bool]string{
true: "true",
false: "false",
}
// AppendToQuery decorates the URL with the query string needed for this Request
func (r GetProjectsRequest) AppendToQuery(u *url.URL) *url.URL {
u = r.pagination.AppendToQuery(u)
v := u.Query()
if r.Name != "" {
v.Add("name", r.Name)
}
if r.Hydrated {
v.Add("hydrated", "true")
}
if r.Archived != nil {
v.Add("archived", boolString[*r.Archived])
}
if len(r.Clients) > 0 {
v.Add("clients", strings.Join(r.Clients, ","))
}
u.RawQuery = v.Encode()
return u
}
// GetProjectRequest query parameters to fetch a project
type GetProjectRequest struct {
Hydrated bool
}
// AppendToQuery decorates the URL with a query string
func (r GetProjectRequest) AppendToQuery(u *url.URL) *url.URL {
v := u.Query()
if r.Hydrated {
v.Add("hydrated", "true")
}
u.RawQuery = v.Encode()
return u
}
// AddProjectRequest represents the parameters to create a project
type AddProjectRequest struct {
Name string `json:"name"`
ClientId string `json:"clientId,omitempty"`
IsPublic bool `json:"isPublic"`
Color string `json:"color,omitempty"`
Note string `json:"note,omitempty"`
Billable bool `json:"billable"`
Public bool `json:"public"`
}
// UpdateProjectRequest represents the parameters to update a project
type UpdateProjectRequest struct {
Name *string `json:"name,omitempty"`
ClientId *string `json:"clientId,omitempty"`
IsPublic *bool `json:"isPublic,omitempty"`
Color *string `json:"color,omitempty"`
Note *string `json:"note,omitempty"`
Billable *bool `json:"billable,omitempty"`
Archived *bool `json:"archived,omitempty"`
}
// UpdateProjectMembershipsRequest represents a request to change which users
// and groups have access to a project
type UpdateProjectMembershipsRequest struct {
Memberships []UpdateProjectMembership `json:"memberships"`
}
// UpdateProjectMembership sets which user or group has access, and their
// hourly rate
type UpdateProjectMembership struct {
UserID string `json:"userId"`
HourlyRate Rate `json:"hourlyRate"`
}
// UpdateProjectTemplateRequest represents a request to change isTemplate flag
// of a project
type UpdateProjectTemplateRequest struct {
IsTemplate bool `json:"isTemplate"`
}
type GetTagsRequest struct {
Name string
Archived *bool
pagination
}
// WithPagination add pagination to the GetTagsRequest
func (r GetTagsRequest) WithPagination(page, size int) PaginatedRequest {
r.pagination = newPagination(page, size)
return r
}
// AppendToQuery decorates the URL with the query string needed for this Request
func (r GetTagsRequest) AppendToQuery(u *url.URL) *url.URL {
u = r.pagination.AppendToQuery(u)
v := u.Query()
if r.Name != "" {
v.Add("name", r.Name)
}
if r.Archived != nil {
v.Add("archived", boolString[*r.Archived])
}
u.RawQuery = v.Encode()
return u
}
// GetTasksRequest represents the query filters to search tasks of a project
type GetTasksRequest struct {
Name string
Active bool
pagination
}
// WithPagination add pagination to the GetTasksRequest
func (r GetTasksRequest) WithPagination(page, size int) PaginatedRequest {
r.pagination = newPagination(page, size)
return r
}
// AppendToQuery decorates the URL with the query string needed for this Request
func (r GetTasksRequest) AppendToQuery(u *url.URL) *url.URL {
u = r.pagination.AppendToQuery(u)
v := u.Query()
if r.Name != "" {
v.Add("name", r.Name)
}
if r.Active {
v.Add("is-active", "true")
}
u.RawQuery = v.Encode()
return u
}
type AddTaskRequest struct {
Name string `json:"name"`
AssigneeIDs *[]string `json:"assigneeIds,omitempty"`
Billable *bool `json:"billable,omitempty"`
Estimate *Duration `json:"estimate,omitempty"`
Status *string `json:"status,omitempty"`
}
type UpdateTaskRequest struct {
Name string `json:"name"`
AssigneeIDs *[]string `json:"assigneeIds,omitempty"`
Billable *bool `json:"billable,omitempty"`
Estimate *Duration `json:"estimate,omitempty"`
Status *string `json:"status,omitempty"`
}
type ChangeTimeEntriesInvoicedRequest struct {
TimeEntryIDs []string `json:"timeEntryIds"`
Invoiced bool `json:"invoiced"`
}
type WorkspaceUsersRequest struct {
Email string
pagination
}
// WithPagination add pagination to the WorkspaceUsersRequest
func (r WorkspaceUsersRequest) WithPagination(page, size int) PaginatedRequest {
r.pagination = newPagination(page, size)
return r
}
// AppendToQuery decorates the URL with the query string needed for this Request
func (r WorkspaceUsersRequest) AppendToQuery(u *url.URL) *url.URL {
u = r.pagination.AppendToQuery(u)
v := u.Query()
if r.Email != "" {
v.Add("email", r.Email)
}
u.RawQuery = v.Encode()
return u
}
// UpdateProjectUserRateRequest represents a request to change a user
// billable rate on a project
type UpdateProjectUserRateRequest struct {
Amount uint `json:"amount"`
Since *DateTime `json:"since,omitempty"`
}
// BaseEstimateRequest is basic information to estime a project
type BaseEstimateRequest struct {
Type *EstimateType `json:"type,omitempty"`
Active bool `json:"active"`
ResetOptions *EstimateResetOption `json:"resetOption,omitempty"`
}
// TimeEstimateRequest set parameters for time estimate on a project
type TimeEstimateRequest struct {
BaseEstimateRequest
Estimate *Duration `json:"estimate,omitempty"`
}
// BudgetEstimateRequest set parameters for time estimate on a project
type BudgetEstimateRequest struct {
BaseEstimateRequest
Estimate *uint64 `json:"estimate,omitempty"`
}
// UpdateProjectEstimateRequest represents a request to set a estimate of a
// project
type UpdateProjectEstimateRequest struct {
TimeEstimate TimeEstimateRequest `json:"timeEstimate"`
BudgetEstimate BudgetEstimateRequest `json:"budgetEstimate"`
}
================================================
FILE: api/httpClient.go
================================================
package api
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/url"
"github.com/lucassabreu/clockify-cli/api/dto"
"github.com/pkg/errors"
)
// QueryAppender an interface to identify if the parameters should be sent through the query or body
type QueryAppender interface {
AppendToQuery(*url.URL) *url.URL
}
// ErrorNotFound Not Found
var ErrorNotFound = dto.Error{Message: "Nothing was found", Code: 404}
// ErrorForbidden Forbidden
var ErrorForbidden = dto.Error{Message: "Forbidden", Code: 403}
// ErrorTooManyRequests Too Many Requests
var ErrorTooManyRequests = dto.Error{Message: "Too Many Requests", Code: 429}
type transport struct {
apiKey string
next http.RoundTripper
}
func (t transport) RoundTrip(r *http.Request) (*http.Response, error) {
r.Header.Set("X-Api-Key", t.apiKey)
return t.next.RoundTrip(r)
}
// NewRequest to be used in Client
func (c *client) NewRequest(method, uri string, body interface{}) (*http.Request, error) {
u, err := c.baseURL.Parse(c.baseURL.Path + "/" + uri)
if err != nil {
return nil, err
}
if qa, ok := body.(QueryAppender); ok {
u = qa.AppendToQuery(u)
}
if method == "GET" {
body = nil
}
var buf io.ReadWriter
if body != nil {
buf = new(bytes.Buffer)
err := json.NewEncoder(buf).Encode(body)
if err != nil {
return nil, err
}
c.infof("request body: %s", buf.(*bytes.Buffer))
}
req, err := http.NewRequest(method, u.String(), buf)
if err != nil {
return nil, err
}
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
req.Header.Set("Accept", "application/json")
return req, nil
}
// Do executes a http.Request inside the Clockify's Client
func (c *client) Do(
req *http.Request, v interface{}, name string) (r *http.Response, err error) {
<-c.requestTickets
r, err = c.Client.Do(req)
if err != nil {
return r, err
}
defer func() {
if e := r.Body.Close(); e != nil {
err = e
}
}()
buf := new(bytes.Buffer)
_, err = io.Copy(buf, r.Body)
if err != nil {
return nil, errors.WithStack(err)
}
if c.debugLogger != nil {
c.debugf("name: %s, method: %s, url: %s, status: %d, response: \"%s\"",
name, req.Method, req.URL.String(), r.StatusCode, buf)
} else {
c.infof("name: %s, method: %s, url: %s, status: %d",
name, req.Method, req.URL.String(), r.StatusCode)
}
decoder := json.NewDecoder(buf)
if r.StatusCode < 200 || r.StatusCode > 300 {
var apiErr dto.Error
err = decoder.Decode(&apiErr)
if err != nil && err != io.EOF {
return r, errors.WithStack(err)
}
if r.StatusCode == 404 && apiErr.Message == "" {
apiErr = ErrorNotFound
}
if r.StatusCode == 403 && apiErr.Message == "" {
apiErr = ErrorForbidden
}
if r.StatusCode == 429 && apiErr.Message == "" {
apiErr = ErrorTooManyRequests
}
if apiErr.Message == "" {
apiErr.Message = "No response"
}
return r, errors.WithStack(apiErr)
}
if v == nil {
return r, nil
}
if buf.Len() == 0 {
return r, nil
}
return r, errors.WithStack(decoder.Decode(v))
}
================================================
FILE: api/logger.go
================================================
package api
// Logger for the Client
type Logger interface {
Print(v ...interface{})
Printf(format string, v ...interface{})
Println(v ...interface{})
}
// SetDebugLogger debug logger
func (c *client) SetDebugLogger(logger Logger) Client {
c.debugLogger = logger
return c
}
func (c *client) debugf(format string, v ...interface{}) {
if c.debugLogger == nil {
return
}
c.debugLogger.Printf(format, v...)
}
// SetInfoLogger info logger
func (c *client) SetInfoLogger(logger Logger) Client {
c.infoLogger = logger
return c
}
func (c *client) infof(format string, v ...interface{}) {
if c.infoLogger == nil {
return
}
c.infoLogger.Printf(format, v...)
}
================================================
FILE: api/project_test.go
================================================
package api_test
import (
"testing"
"time"
"github.com/lucassabreu/clockify-cli/api"
"github.com/lucassabreu/clockify-cli/api/dto"
)
func TestUpdateProjectMemberships(t *testing.T) {
exampleID2 := "62f2af744a912b05acc7c792"
errPrefix := `update project memberships: `
uri := "/v1/workspaces/" + exampleID +
"/projects/" + exampleID +
"/memberships"
tts := []testCase{
&simpleTestCase{
name: "requires workspace",
param: api.UpdateProjectMembershipsParam{ProjectID: "p1"},
err: errPrefix + "workspace is required",
},
&simpleTestCase{
name: "requires project",
param: api.UpdateProjectMembershipsParam{Workspace: "w"},
err: errPrefix + "project id is required",
},
&simpleTestCase{
name: "valid workspace",
param: api.UpdateProjectMembershipsParam{
Workspace: "w",
ProjectID: exampleID,
},
err: errPrefix + "workspace .* is not valid ID",
},
&simpleTestCase{
name: "valid project",
param: api.UpdateProjectMembershipsParam{
Workspace: exampleID,
ProjectID: "p1",
},
err: errPrefix + "project .* is not valid ID",
},
&simpleTestCase{
name: "valid user or groups",
param: api.UpdateProjectMembershipsParam{
Workspace: exampleID,
ProjectID: exampleID,
Memberships: []api.UpdateMembership{{
UserOrGroupID: "ug",
}},
},
err: errPrefix + "user or group .* is not valid ID",
},
&simpleTestCase{
name: "required user or groups",
param: api.UpdateProjectMembershipsParam{
Workspace: exampleID,
ProjectID: exampleID,
Memberships: []api.UpdateMembership{
{UserOrGroupID: ""},
},
},
err: errPrefix + `user or group is required`,
},
&simpleTestCase{
name: "valid user or groups (second one)",
param: api.UpdateProjectMembershipsParam{
Workspace: exampleID,
ProjectID: exampleID,
Memberships: []api.UpdateMembership{
{UserOrGroupID: exampleID},
{UserOrGroupID: "ug"},
},
},
err: errPrefix + `user or group \("ug"\) is not valid ID`,
},
&simpleTestCase{
name: "simplest update",
param: api.UpdateProjectMembershipsParam{
Workspace: exampleID,
ProjectID: exampleID,
},
result: dto.Project{ID: "p1", Name: "project 1"},
requestMethod: "patch",
requestUrl: uri,
requestBody: `{"memberships":[]}`,
responseStatus: 200,
responseBody: `{"id":"p1", "name": "project 1"}`,
},
&simpleTestCase{
name: "update with members",
param: api.UpdateProjectMembershipsParam{
Workspace: exampleID,
ProjectID: exampleID,
Memberships: []api.UpdateMembership{{
UserOrGroupID: exampleID,
}},
},
result: dto.Project{ID: "p1", Name: "project 1"},
requestMethod: "patch",
requestUrl: uri,
requestBody: `{"memberships":[{
"userId":"` + exampleID + `", "hourlyRate":{"amount":0}
}]}`,
responseStatus: 200,
responseBody: `{"id":"p1", "name": "project 1"}`,
},
&simpleTestCase{
name: "update with many members",
param: api.UpdateProjectMembershipsParam{
Workspace: exampleID,
ProjectID: exampleID,
Memberships: []api.UpdateMembership{
{UserOrGroupID: exampleID},
{UserOrGroupID: exampleID2, HourlyRateAmount: 10},
},
},
result: dto.Project{ID: "p1", Name: "project 1"},
requestMethod: "patch",
requestUrl: uri,
requestBody: `{"memberships":[
{"userId":"` + exampleID + `", "hourlyRate":{"amount":0}},
{"userId":"` + exampleID2 + `", "hourlyRate":{"amount":10}}
]}`,
responseStatus: 200,
responseBody: `{"id":"p1", "name": "project 1"}`,
},
&simpleTestCase{
name: "error response",
param: api.UpdateProjectMembershipsParam{
Workspace: exampleID,
ProjectID: exampleID,
},
requestMethod: "patch",
requestUrl: uri,
requestBody: `{"memberships":[]}`,
responseStatus: 400,
responseBody: `{"code": 10, "message":"error"}`,
err: errPrefix + `error`,
},
}
for _, tt := range tts {
runClient(t, tt,
func(c api.Client, p interface{}) (interface{}, error) {
return c.UpdateProjectMemberships(
p.(api.UpdateProjectMembershipsParam))
})
}
}
func TestDeleteProject(t *testing.T) {
errPrefix := `delete project: `
uri := "/v1/workspaces/" + exampleID + "/projects/" + exampleID
tts := []testCase{
&simpleTestCase{
name: "requires workspace",
param: api.DeleteProjectParam{ProjectID: "p1"},
err: errPrefix + "workspace is required",
},
&simpleTestCase{
name: "requires project",
param: api.DeleteProjectParam{Workspace: "w"},
err: errPrefix + "project id is required",
},
&simpleTestCase{
name: "valid workspace",
param: api.DeleteProjectParam{
Workspace: "w",
ProjectID: exampleID,
},
err: errPrefix + "workspace .* is not valid ID",
},
&simpleTestCase{
name: "valid project",
param: api.DeleteProjectParam{
Workspace: exampleID,
ProjectID: "p1",
},
err: errPrefix + "project .* is not valid ID",
},
&simpleTestCase{
name: "delete",
param: api.DeleteProjectParam{
Workspace: exampleID,
ProjectID: exampleID,
},
result: dto.Project{ID: "p1", Name: "project 1"},
requestMethod: "delete",
requestUrl: uri,
responseStatus: 200,
responseBody: `{"id":"p1", "name": "project 1"}`,
},
&simpleTestCase{
name: "error response",
param: api.DeleteProjectParam{
Workspace: exampleID,
ProjectID: exampleID,
},
requestMethod: "delete",
requestUrl: uri,
responseStatus: 400,
responseBody: `{"code": 10, "message":"error"}`,
err: errPrefix + `error`,
},
}
for _, tt := range tts {
runClient(t, tt,
func(c api.Client, p interface{}) (interface{}, error) {
return c.DeleteProject(p.(api.DeleteProjectParam))
})
}
}
func TestGetProject(t *testing.T) {
errPrefix := `get project "\w*": `
uri := "/v1/workspaces/" + exampleID + "/projects/" + exampleID
tts := []testCase{
&simpleTestCase{
name: "requires workspace",
param: api.GetProjectParam{ProjectID: "p1"},
err: errPrefix + "workspace is required",
},
&simpleTestCase{
name: "requires project",
param: api.GetProjectParam{Workspace: "w"},
err: errPrefix + "project id is required",
},
&simpleTestCase{
name: "valid workspace",
param: api.GetProjectParam{
Workspace: "w",
ProjectID: exampleID,
},
err: errPrefix + "workspace .* is not valid ID",
},
&simpleTestCase{
name: "valid project",
param: api.GetProjectParam{
Workspace: exampleID,
ProjectID: "p1",
},
err: errPrefix + "project .* is not valid ID",
},
&simpleTestCase{
name: "simple",
param: api.GetProjectParam{
Workspace: exampleID,
ProjectID: exampleID,
},
result: &dto.Project{ID: "p1", Name: "project 1"},
requestMethod: "get",
requestUrl: uri,
responseStatus: 200,
responseBody: `{"id":"p1", "name": "project 1"}`,
},
&simpleTestCase{
name: "hydrated",
param: api.GetProjectParam{
Workspace: exampleID,
ProjectID: exampleID,
Hydrate: true,
},
result: &dto.Project{ID: "p1", Name: "project 1", Hydrated: true},
requestMethod: "get",
requestUrl: uri + "?hydrated=true",
responseStatus: 200,
responseBody: `{"id":"p1", "name": "project 1"}`,
},
&simpleTestCase{
name: "error response",
param: api.GetProjectParam{
Workspace: exampleID,
ProjectID: exampleID,
},
requestMethod: "get",
requestUrl: uri,
responseStatus: 400,
responseBody: `{"code": 10, "message":"error"}`,
err: errPrefix + `error`,
},
&simpleTestCase{
name: "not found",
param: api.GetProjectParam{
Workspace: exampleID,
ProjectID: exampleID,
},
requestMethod: "get",
requestUrl: uri,
responseStatus: 404,
responseBody: `{"code": 0, "message":"not found"}`,
err: errPrefix + `not found`,
},
}
for _, tt := range tts {
runClient(t, tt,
func(c api.Client, p interface{}) (interface{}, error) {
return c.GetProject(p.(api.GetProjectParam))
})
}
}
func TestGetProjects(t *testing.T) {
errPrefix := "get projects: "
uri := "/v1/workspaces/" + exampleID + "/projects"
var l []dto.Project
tts := []testCase{
&simpleTestCase{
name: "requires workspace",
param: api.GetProjectsParam{},
err: errPrefix + "workspace is required",
},
&simpleTestCase{
name: "valid workspace",
param: api.GetProjectsParam{Workspace: "w"},
err: errPrefix + "workspace .* is not valid ID",
},
(&multiRequestTestCase{
name: "get all pages, but find none",
param: api.GetProjectsParam{
Workspace: exampleID,
PaginationParam: api.AllPages(),
},
result: l,
}).
addHttpCall(&httpRequest{
method: "get",
url: uri + "?page=1&page-size=50",
status: 200,
response: "[]",
}),
(&multiRequestTestCase{
name: "get all pages, find five",
param: api.GetProjectsParam{
Workspace: exampleID,
PaginationParam: api.PaginationParam{
PageSize: 2,
AllPages: true,
},
},
result: []dto.Project{
{ID: "p1"},
{ID: "p2"},
{ID: "p3"},
{ID: "p4"},
{ID: "p5"},
},
}).
addHttpCall(&httpRequest{
method: "get",
url: uri + "?page=1&page-size=2",
status: 200,
response: `[{"id":"p1"},{"id":"p2"}]`,
}).
addHttpCall(&httpRequest{
method: "get",
url: uri + "?page=2&page-size=2",
status: 200,
response: `[{"id":"p3"},{"id":"p4"}]`,
}).
addHttpCall(&httpRequest{
method: "get",
url: uri + "?page=3&page-size=2",
status: 200,
response: `[{"id":"p5"}]`,
}),
(&multiRequestTestCase{
name: "get all pages, hydrated",
param: api.GetProjectsParam{
Workspace: exampleID,
Hydrate: true,
PaginationParam: api.PaginationParam{
PageSize: 1,
AllPages: true,
},
},
result: []dto.Project{
{ID: "p1", Hydrated: true},
{ID: "p2", Hydrated: true},
},
}).
addHttpCall(&httpRequest{
method: "get",
url: uri + "?hydrated=true&page=1&page-size=1",
status: 200,
response: `[{"id":"p1"}]`,
}).
addHttpCall(&httpRequest{
method: "get",
url: uri + "?hydrated=true&page=2&page-size=1",
status: 200,
response: `[{"id":"p2"}]`,
}).
addHttpCall(&httpRequest{
method: "get",
url: uri + "?hydrated=true&page=3&page-size=1",
status: 200,
response: `[]`,
}),
&simpleTestCase{
name: "all parameters",
param: api.GetProjectsParam{
Workspace: exampleID,
Hydrate: true,
Name: "project",
Clients: []string{"c1", "c2"},
PaginationParam: api.AllPages(),
},
result: []dto.Project{{
ID: "p1", Name: "project 1", Hydrated: true}},
requestMethod: "get",
requestUrl: uri +
"?clients=c1%2Cc2&hydrated=true&name=project&" +
"page=1&page-size=50",
responseStatus: 200,
responseBody: `[{"id":"p1", "name": "project 1"}]`,
},
&simpleTestCase{
name: "error response",
param: api.GetProjectsParam{
Workspace: exampleID,
PaginationParam: api.PaginationParam{Page: 2},
},
requestMethod: "get",
requestUrl: uri + "?page=2&page-size=50",
responseStatus: 400,
responseBody: `{"code": 10, "message":"error"}`,
err: `get projects: error \(code: 10\)`,
},
&simpleTestCase{
name: "missing duration",
param: api.GetProjectsParam{
Workspace: exampleID,
PaginationParam: api.AllPages(),
},
result: []dto.Project{
{
ID: "wod",
Name: "without duration",
Archived: true,
Duration: nil,
},
{
ID: "wd",
Name: "with duration",
Archived: true,
Duration: &dto.Duration{
Duration: 561*time.Hour +
13*time.Minute + 28*time.Second,
},
},
},
requestMethod: "get",
requestUrl: uri + "?page=1&page-size=50",
responseStatus: 200,
responseBody: `[{
"id":"wod",
"name":"without duration",
"archived":true,
"duration":null,
"note":""
}, {
"id":"wd",
"name":"with duration",
"archived":true,
"duration":"PT561H13M28S",
"note":""
}]`,
},
}
for _, tt := range tts {
runClient(t, tt,
func(c api.Client, p interface{}) (interface{}, error) {
return c.GetProjects(p.(api.GetProjectsParam))
})
}
}
func TestUpdateProjectTemplate(t *testing.T) {
errPrefix := "update project template: "
tts := []simpleTestCase{
{
name: "workspace require",
param: api.UpdateProjectTemplateParam{
ProjectID: exampleID,
},
err: errPrefix + "workspace is required",
},
{
name: "project require",
param: api.UpdateProjectTemplateParam{
Workspace: exampleID,
},
err: errPrefix + "project id is required",
},
{
name: "valid workspace",
param: api.UpdateProjectTemplateParam{
ProjectID: exampleID,
Workspace: "w",
},
err: errPrefix + "workspace .* is not valid ID",
},
{
name: "valid project",
param: api.UpdateProjectTemplateParam{
ProjectID: "p",
Workspace: exampleID,
},
err: errPrefix + "project .* is not valid ID",
},
{
name: "into template",
param: api.UpdateProjectTemplateParam{
ProjectID: exampleID,
Workspace: exampleID,
Template: true,
},
result: dto.Project{ID: exampleID},
requestMethod: "patch",
requestUrl: "/v1/workspaces/" + exampleID +
"/projects/" + exampleID + "/template",
requestBody: `{"isTemplate":true}`,
responseStatus: 200,
responseBody: `{"id":"` + exampleID + `"}`,
},
{
name: "not a template",
param: api.UpdateProjectTemplateParam{
ProjectID: exampleID,
Workspace: exampleID,
Template: false,
},
result: dto.Project{ID: exampleID},
requestMethod: "patch",
requestUrl: "/v1/workspaces/" + exampleID +
"/projects/" + exampleID + "/template",
requestBody: `{"isTemplate":false}`,
responseStatus: 200,
responseBody: `{"id":"` + exampleID + `"}`,
},
{
name: "error",
param: api.UpdateProjectTemplateParam{
ProjectID: exampleID,
Workspace: exampleID,
Template: false,
},
err: errPrefix + "failed .code: 90.",
requestMethod: "patch",
requestUrl: "/v1/workspaces/" + exampleID +
"/projects/" + exampleID + "/template",
requestBody: `{"isTemplate":false}`,
responseStatus: 400,
responseBody: `{"message":"failed", "code": 90}`,
},
}
for i := range tts {
runClient(t, &tts[i],
func(c api.Client, p interface{}) (interface{}, error) {
return c.UpdateProjectTemplate(
p.(api.UpdateProjectTemplateParam))
})
}
}
func TestUpdateProjectEstimate(t *testing.T) {
errPrefix := "update project estimate: "
tts := []simpleTestCase{
{
name: "workspace require",
param: api.UpdateProjectEstimateParam{
ProjectID: exampleID,
Method: api.EstimateMethodNone,
},
err: errPrefix + "workspace is required",
},
{
name: "project require",
param: api.UpdateProjectEstimateParam{
Workspace: exampleID,
Method: api.EstimateMethodNone,
},
err: errPrefix + "project id is required",
},
{
name: "estimate method required",
param: api.UpdateProjectEstimateParam{
Workspace: exampleID,
ProjectID: exampleID,
},
err: errPrefix + "estimate method is required",
},
{
name: "valid workspace",
param: api.UpdateProjectEstimateParam{
ProjectID: exampleID,
Workspace: "w",
Method: api.EstimateMethodNone,
},
err: errPrefix + "workspace .* is not valid ID",
},
{
name: "valid project",
param: api.UpdateProjectEstimateParam{
ProjectID: "p",
Workspace: exampleID,
Method: api.EstimateMethodNone,
},
err: errPrefix + "project .* is not valid ID",
},
{
name: "valid method",
param: api.UpdateProjectEstimateParam{
ProjectID: exampleID,
Workspace: exampleID,
Method: "m",
},
err: errPrefix + "valid options for estimate method are",
},
{
name: "type should be set for budget",
param: api.UpdateProjectEstimateParam{
ProjectID: exampleID,
Workspace: exampleID,
Method: api.EstimateMethodBudget,
Type: "t",
},
err: errPrefix + "valid options for estimate type are",
},
{
name: "valid reset option",
param: api.UpdateProjectEstimateParam{
ProjectID: exampleID,
Workspace: exampleID,
Method: api.EstimateMethodBudget,
Type: api.EstimateTypeTask,
ResetOption: "daily",
},
err: errPrefix + "valid options for reset option are",
},
{
name: "type should be set for time",
param: api.UpdateProjectEstimateParam{
ProjectID: exampleID,
Workspace: exampleID,
Method: api.EstimateMethodTime,
},
err: errPrefix + "valid options for estimate type are",
},
{
name: "estimate should be set for budget method & type project",
param: api.UpdateProjectEstimateParam{
ProjectID: exampleID,
Workspace: exampleID,
Method: api.EstimateMethodBudget,
Type: api.EstimateTypeProject,
},
err: errPrefix +
"estimate should be greater than zero for type project",
},
{
name: "estimate should be set for time method & type project",
param: api.UpdateProjectEstimateParam{
ProjectID: exampleID,
Workspace: exampleID,
Method: api.EstimateMethodTime,
Type: api.EstimateTypeProject,
},
err: errPrefix +
"estimate should be greater than zero for type project",
},
{
name: "estimate should be positive for time method & type project",
param: api.UpdateProjectEstimateParam{
ProjectID: exampleID,
Workspace: exampleID,
Method: api.EstimateMethodTime,
Type: api.EstimateTypeProject,
Estimate: -1,
},
err: errPrefix +
"estimate should be greater than zero for type project",
},
{
name: "estimate should be positive for time method & type project",
param: api.UpdateProjectEstimateParam{
ProjectID: exampleID,
Workspace: exampleID,
Method: api.EstimateMethodTime,
Type: api.EstimateTypeProject,
Estimate: -1,
},
err: errPrefix +
"estimate should be greater than zero for type project",
},
{
name: "set estimate with budget for project",
param: api.UpdateProjectEstimateParam{
ProjectID: exampleID,
Workspace: exampleID,
Method: api.EstimateMethodBudget,
Type: api.EstimateTypeProject,
Estimate: 1000,
},
requestMethod: "patch",
requestUrl: "/v1/workspaces/" + exampleID +
"/projects/" + exampleID + "/estimate",
requestBody: `{
"timeEstimate": {"active": false},
"budgetEstimate": {
"active": true,
"estimate": 1000,
"type": "MANUAL"
}
}`,
responseStatus: 200,
},
{
name: "set estimate with time for project",
param: api.UpdateProjectEstimateParam{
ProjectID: exampleID,
Workspace: exampleID,
Method: api.EstimateMethodTime,
Type: api.EstimateTypeProject,
Estimate: int64(time.Minute)*90 + int64(time.Second)*15,
},
requestMethod: "patch",
requestUrl: "/v1/workspaces/" + exampleID +
"/projects/" + exampleID + "/estimate",
requestBody: `{
"budgetEstimate": {"active": false},
"timeEstimate": {
"active": true,
"estimate": "PT1H30M15S",
"type": "MANUAL"
}
}`,
responseStatus: 200,
},
{
name: "set estimate to none for project",
param: api.UpdateProjectEstimateParam{
ProjectID: exampleID,
Workspace: exampleID,
Method: api.EstimateMethodNone,
},
requestMethod: "patch",
requestUrl: "/v1/workspaces/" + exampleID +
"/projects/" + exampleID + "/estimate",
requestBody: `{
"budgetEstimate": {"active": false},
"timeEstimate": {"active": false}
}`,
responseStatus: 200,
},
{
name: "set estimate with budget for tasks",
param: api.UpdateProjectEstimateParam{
ProjectID: exampleID,
Workspace: exampleID,
Method: api.EstimateMethodBudget,
Type: api.EstimateTypeTask,
Estimate: 1000,
},
requestMethod: "patch",
requestUrl: "/v1/workspaces/" + exampleID +
"/projects/" + exampleID + "/estimate",
requestBody: `{
"timeEstimate": {"active": false},
"budgetEstimate": {
"active": true,
"type": "AUTO"
}
}`,
responseStatus: 200,
},
{
name: "set estimate with time for task",
param: api.UpdateProjectEstimateParam{
ProjectID: exampleID,
Workspace: exampleID,
Method: api.EstimateMethodTime,
Type: api.EstimateTypeTask,
Estimate: int64(time.Minute)*90 + int64(time.Second)*15,
},
requestMethod: "patch",
requestUrl: "/v1/workspaces/" + exampleID +
"/projects/" + exampleID + "/estimate",
requestBody: `{
"budgetEstimate": {"active": false},
"timeEstimate": {
"active": true,
"type": "AUTO"
}
}`,
responseStatus: 200,
},
{
name: "set estimate with time for task, and monthly reset",
param: api.UpdateProjectEstimateParam{
ProjectID: exampleID,
Workspace: exampleID,
Method: api.EstimateMethodTime,
Type: api.EstimateTypeTask,
ResetOption: api.EstimateResetOptionMonthly,
},
requestMethod: "patch",
requestUrl: "/v1/workspaces/" + exampleID +
"/projects/" + exampleID + "/estimate",
requestBody: `{
"budgetEstimate": {"active": false},
"timeEstimate": {
"active": true,
"type": "AUTO",
"resetOption": "MONTHLY"
}
}`,
responseStatus: 200,
},
}
for i := range tts {
runClient(t, &tts[i],
func(c api.Client, p interface{}) (interface{}, error) {
return c.UpdateProjectEstimate(
p.(api.UpdateProjectEstimateParam))
})
}
}
func TestUpdateProjectUserCostRate(t *testing.T) {
testUpdateProjectUserRate(t,
"update project user cost rate: ",
"cost-rate",
func(c api.Client, p interface{}) (interface{}, error) {
return c.UpdateProjectUserCostRate(
p.(api.UpdateProjectUserRateParam))
})
}
func TestUpdateProjectUserBillableRate(t *testing.T) {
testUpdateProjectUserRate(t,
"update project user billable rate: ",
"hourly-rate",
func(c api.Client, p interface{}) (interface{}, error) {
return c.UpdateProjectUserBillableRate(
p.(api.UpdateProjectUserRateParam))
})
}
func testUpdateProjectUserRate(t *testing.T,
errPrefix, uriSufix string,
fn func(api.Client, interface{}) (interface{}, error)) {
since, _ := time.Parse("2006-01-02", "2022-02-02")
tts := []simpleTestCase{
{
name: "project is required",
param: api.UpdateProjectUserRateParam{
Workspace: "w",
UserID: "u",
},
err: errPrefix + "project id is required",
},
{
name: "workspace is required",
param: api.UpdateProjectUserRateParam{
ProjectID: "p-1",
UserID: "u",
},
err: errPrefix + "workspace is required",
},
{
name: "user is required",
param: api.UpdateProjectUserRateParam{
ProjectID: "p-1",
Workspace: "w",
},
err: errPrefix + "user id is required",
},
{
name: "project should be a ID",
param: api.UpdateProjectUserRateParam{
ProjectID: "p-1",
Workspace: exampleID,
UserID: exampleID,
},
err: errPrefix + "project id (.*) is not valid",
},
{
name: "user should be a ID",
param: api.UpdateProjectUserRateParam{
ProjectID: exampleID,
Workspace: exampleID,
UserID: "u-1",
},
err: errPrefix + "user id (.*) is not valid",
},
{
name: "workspace should be a ID",
param: api.UpdateProjectUserRateParam{
ProjectID: exampleID,
Workspace: "w",
UserID: exampleID,
},
err: errPrefix + "workspace (.*) is not valid",
},
{
name: "only amount",
param: api.UpdateProjectUserRateParam{
ProjectID: exampleID,
Workspace: exampleID,
UserID: exampleID,
Amount: 10,
},
requestMethod: "put",
requestUrl: "/v1/workspaces/" + exampleID +
"/projects/" + exampleID +
"/users/" + exampleID + "/" + uriSufix,
requestBody: `{"amount":10}`,
responseStatus: 200,
},
{
name: "amount and since",
param: api.UpdateProjectUserRateParam{
ProjectID: exampleID,
Workspace: exampleID,
UserID: exampleID,
Amount: 10,
Since: &since,
},
requestMethod: "put",
requestUrl: "/v1/workspaces/" + exampleID +
"/projects/" + exampleID +
"/users/" + exampleID + "/" + uriSufix,
requestBody: `{"amount":10,"since":"2022-02-02T00:00:00Z"}`,
err: errPrefix + "custom error.*code: 42",
responseStatus: 400,
responseBody: `{"message":"custom error","code":42}`,
},
{
name: "fail",
param: api.UpdateProjectUserRateParam{
ProjectID: exampleID,
Workspace: exampleID,
UserID: exampleID,
Amount: 10,
Since: &since,
},
requestMethod: "put",
requestUrl: "/v1/workspaces/" + exampleID +
"/projects/" + exampleID +
"/users/" + exampleID + "/" + uriSufix,
requestBody: `{"amount":10,"since":"2022-02-02T00:00:00Z"}`,
err: errPrefix + "custom error.*code: 42",
responseStatus: 400,
responseBody: `{"message":"custom error","code":42}`,
},
}
for i := range tts {
runClient(t, &tts[i], fn)
}
}
func TestUpdateProject(t *testing.T) {
bt := true
bf := false
n := "special"
empty := ""
tts := []simpleTestCase{
{
name: "project is required",
param: api.UpdateProjectParam{Workspace: "w"},
err: "update project: project id is required",
},
{
name: "workspace is required",
param: api.UpdateProjectParam{ProjectID: "p-1"},
err: "update project: workspace is required",
},
{
name: "project should be a ID",
param: api.UpdateProjectParam{
ProjectID: "p-1",
Workspace: exampleID,
},
err: "update project: project id (.*) is not valid",
},
{
name: "workspace should be a ID",
param: api.UpdateProjectParam{
ProjectID: exampleID,
Workspace: "w",
},
err: "update project: workspace (.*) is not valid",
},
{
name: "color is not hex",
param: api.UpdateProjectParam{
ProjectID: exampleID,
Workspace: exampleID,
Color: "#zzz",
},
err: "update project: color .* is not a hex string",
},
{
name: "color must have 3 or 6 numbers (4)",
param: api.UpdateProjectParam{
ProjectID: exampleID,
Workspace: exampleID,
Color: "#0000",
},
err: "update project: color must have 3.*or 6.*numbers",
},
{
name: "color must have 3 or 6 numbers (2)",
param: api.UpdateProjectParam{
ProjectID: exampleID,
Workspace: exampleID,
Color: "#00",
},
err: "update project: color must have 3.*or 6.*numbers",
},
{
name: "empty update",
param: api.UpdateProjectParam{
ProjectID: exampleID,
Workspace: exampleID,
},
requestMethod: "put",
requestUrl: "/v1/workspaces/" + exampleID +
"/projects/" + exampleID,
requestBody: "{}",
responseStatus: 200,
},
{
name: "full update",
param: api.UpdateProjectParam{
ProjectID: exampleID,
Workspace: exampleID,
Name: "a new name",
Public: &bt,
Archived: &bf,
Note: &n,
ClientId: &exampleID,
Color: "012345",
Billable: &bt,
},
requestMethod: "put",
requestUrl: "/v1/workspaces/" + exampleID +
"/projects/" + exampleID,
requestBody: `{
"archived":false,
"isPublic":true,
"billable":true,
"clientId":"` + exampleID + `",
"note": "special",
"color": "#012345",
"name":"a new name"
}`,
responseStatus: 200,
},
{
name: "expand color and remove client",
param: api.UpdateProjectParam{
ProjectID: exampleID,
Workspace: exampleID,
ClientId: &empty,
Color: "#0f0",
},
requestMethod: "put",
requestUrl: "/v1/workspaces/" + exampleID +
"/projects/" + exampleID,
requestBody: `{
"clientId":"",
"color": "#00ff00"
}`,
responseStatus: 200,
},
{
name: "report 404",
param: api.UpdateProjectParam{
ProjectID: exampleID,
Workspace: exampleID,
},
err: "update project: Nothing was found .*404",
requestMethod: "put",
requestUrl: "/v1/workspaces/" + exampleID +
"/projects/" + exampleID,
requestBody: `{}`,
responseStatus: 404,
},
{
name: "report 403",
param: api.UpdateProjectParam{
ProjectID: exampleID,
Workspace: exampleID,
},
err: "update project: Forbidden.*403",
requestMethod: "put",
requestUrl: "/v1/workspaces/" + exampleID +
"/projects/" + exampleID,
requestBody: `{}`,
responseStatus: 403,
},
{
name: "report no response",
param: api.UpdateProjectParam{
ProjectID: exampleID,
Workspace: exampleID,
},
err: "update project: No response",
requestMethod: "put",
requestUrl: "/v1/workspaces/" + exampleID +
"/projects/" + exampleID,
requestBody: `{}`,
responseStatus: 400,
responseBody: `{}`,
},
{
name: "report error",
param: api.UpdateProjectParam{
ProjectID: exampleID,
Workspace: exampleID,
},
err: "update project: custom error.*code: 42",
requestMethod: "put",
requestUrl: "/v1/workspaces/" + exampleID +
"/projects/" + exampleID,
requestBody: `{}`,
responseStatus: 400,
responseBody: `{"message":"custom error","code":42}`,
},
}
for i := range tts {
runClient(t, &tts[i], func(
c api.Client, p interface{}) (interface{}, error) {
return c.UpdateProject(p.(api.UpdateProjectParam))
})
}
}
================================================
FILE: api/tag_test.go
================================================
package api_test
import (
"testing"
"github.com/lucassabreu/clockify-cli/api"
"github.com/lucassabreu/clockify-cli/api/dto"
)
func TestGetTags(t *testing.T) {
errPrefix := `get tags.*: `
uri := "/v1/workspaces/" + exampleID +
"/tags"
var l []dto.Tag
tts := []testCase{
&simpleTestCase{
name: "requires workspace",
param: api.GetTagsParam{},
err: errPrefix + "workspace is required",
},
&simpleTestCase{
name: "valid workspace",
param: api.GetTagsParam{Workspace: "w"},
err: errPrefix + "workspace .* is not valid ID",
},
(&multiRequestTestCase{
name: "get all pages, but find none",
param: api.GetTagsParam{
Workspace: exampleID,
PaginationParam: api.AllPages(),
},
result: l,
}).
addHttpCall(&httpRequest{
method: "get",
url: uri + "?page=1&page-size=50",
status: 200,
response: "[]",
}),
(&multiRequestTestCase{
name: "get all pages, find five",
param: api.GetTagsParam{
Workspace: exampleID,
PaginationParam: api.PaginationParam{
PageSize: 2,
AllPages: true,
},
},
result: []dto.Tag{
{ID: "p1"},
{ID: "p2"},
{ID: "p3"},
{ID: "p4"},
{ID: "p5"},
},
}).
addHttpCall(&httpRequest{
method: "get",
url: uri + "?page=1&page-size=2",
status: 200,
response: `[{"id":"p1"},{"id":"p2"}]`,
}).
addHttpCall(&httpRequest{
method: "get",
url: uri + "?page=2&page-size=2",
status: 200,
response: `[{"id":"p3"},{"id":"p4"}]`,
}).
addHttpCall(&httpRequest{
method: "get",
url: uri + "?page=3&page-size=2",
status: 200,
response: `[{"id":"p5"}]`,
}),
&simpleTestCase{
name: "all parameters",
param: api.GetTagsParam{
Workspace: exampleID,
Name: "tag",
PaginationParam: api.AllPages(),
},
result: []dto.Tag{{ID: "p1", Name: "tag 1"}},
requestMethod: "get",
requestUrl: uri +
"?name=tag&page=1&page-size=50",
responseStatus: 200,
responseBody: `[{"id":"p1", "name": "tag 1"}]`,
},
&simpleTestCase{
name: "error response",
param: api.GetTagsParam{
Workspace: exampleID,
PaginationParam: api.PaginationParam{Page: 2},
},
requestMethod: "get",
requestUrl: uri + "?page=2&page-size=50",
responseStatus: 400,
responseBody: `{"code": 10, "message":"error"}`,
err: errPrefix + `error \(code: 10\)`,
},
}
for _, tt := range tts {
runClient(t, tt,
func(c api.Client, p interface{}) (interface{}, error) {
return c.GetTags(
p.(api.GetTagsParam))
})
}
}
================================================
FILE: api/task_test.go
================================================
package api_test
import (
"testing"
"time"
"github.com/lucassabreu/clockify-cli/api"
"github.com/lucassabreu/clockify-cli/api/dto"
)
func TestGetTasks(t *testing.T) {
errPrefix := `get tasks from project .*: `
uri := "/v1/workspaces/" + exampleID +
"/projects/" + exampleID +
"/tasks"
var l []dto.Task
tts := []testCase{
&simpleTestCase{
name: "requires workspace",
param: api.GetTasksParam{ProjectID: exampleID},
err: errPrefix + "workspace is required",
},
&simpleTestCase{
name: "valid workspace",
param: api.GetTasksParam{Workspace: "w", ProjectID: exampleID},
err: errPrefix + "workspace .* is not valid ID",
},
&simpleTestCase{
name: "requires project id",
param: api.GetTasksParam{Workspace: exampleID},
err: errPrefix + "project id is required",
},
&simpleTestCase{
name: "valid project id",
param: api.GetTasksParam{ProjectID: "w", Workspace: exampleID},
err: errPrefix + "project id .* is not valid ID",
},
(&multiRequestTestCase{
name: "get all pages, but find none",
param: api.GetTasksParam{
Workspace: exampleID,
ProjectID: exampleID,
PaginationParam: api.AllPages(),
},
result: l,
}).
addHttpCall(&httpRequest{
method: "get",
url: uri + "?page=1&page-size=50",
status: 200,
response: "[]",
}),
(&multiRequestTestCase{
name: "get all pages, find five",
param: api.GetTasksParam{
Workspace: exampleID,
ProjectID: exampleID,
PaginationParam: api.PaginationParam{
PageSize: 2,
AllPages: true,
},
},
result: []dto.Task{
{ID: "p1"},
{ID: "p2"},
{ID: "p3"},
{ID: "p4"},
{ID: "p5"},
},
}).
addHttpCall(&httpRequest{
method: "get",
url: uri + "?page=1&page-size=2",
status: 200,
response: `[{"id":"p1"},{"id":"p2"}]`,
}).
addHttpCall(&httpRequest{
method: "get",
url: uri + "?page=2&page-size=2",
status: 200,
response: `[{"id":"p3"},{"id":"p4"}]`,
}).
addHttpCall(&httpRequest{
method: "get",
url: uri + "?page=3&page-size=2",
status: 200,
response: `[{"id":"p5"}]`,
}),
&simpleTestCase{
name: "all parameters",
param: api.GetTasksParam{
Workspace: exampleID,
ProjectID: exampleID,
Name: "project",
PaginationParam: api.AllPages(),
},
result: []dto.Task{{ID: "p1", Name: "project 1"}},
requestMethod: "get",
requestUrl: uri +
"?name=project&page=1&page-size=50",
responseStatus: 200,
responseBody: `[{"id":"p1", "name": "project 1"}]`,
},
&simpleTestCase{
name: "error response",
param: api.GetTasksParam{
Workspace: exampleID,
ProjectID: exampleID,
PaginationParam: api.PaginationParam{Page: 2},
},
requestMethod: "get",
requestUrl: uri + "?page=2&page-size=50",
responseStatus: 400,
responseBody: `{"code": 10, "message":"error"}`,
err: `get tasks from project .*: error \(code: 10\)`,
},
&simpleTestCase{
name: "missing estimate",
param: api.GetTasksParam{
Workspace: exampleID,
ProjectID: exampleID,
PaginationParam: api.AllPages(),
},
result: []dto.Task{
{
ID: "wod",
Name: "without durations",
ProjectID: "p",
Duration: nil,
Estimate: nil,
Status: api.TaskStatusDone,
UserGroupIDs: []string{},
AssigneeIDs: []string{},
Billable: true,
},
{
ID: "wd",
Name: "with durations",
ProjectID: "p",
Duration: &dto.Duration{
Duration: 120 * time.Hour,
},
Estimate: &dto.Duration{
Duration: 120 * time.Hour,
},
Status: api.TaskStatusActive,
UserGroupIDs: []string{},
AssigneeIDs: []string{},
Billable: true,
},
},
requestMethod: "get",
requestUrl: uri + "?page=1&page-size=50",
responseStatus: 200,
responseBody: `[{
"id": "wod",
"name": "without durations",
"projectId": "p",
"assigneeIds": [],
"assigneeId": null,
"userGroupIds": [],
"estimate": null,
"status": "DONE",
"duration": null,
"billable": true,
"hourlyRate": null,
"costRate": null
},{
"id": "wd",
"name": "with durations",
"projectId": "p",
"assigneeIds": [],
"assigneeId": null,
"userGroupIds": [],
"estimate": "P120H",
"status": "ACTIVE",
"duration": "P120H",
"billable": true,
"hourlyRate": null,
"costRate": null
}]`,
},
}
for _, tt := range tts {
runClient(t, tt,
func(c api.Client, p interface{}) (interface{}, error) {
return c.GetTasks(
p.(api.GetTasksParam))
})
}
}
================================================
FILE: api/timeentry_test.go
================================================
package api_test
import (
"testing"
"github.com/lucassabreu/clockify-cli/api"
"github.com/lucassabreu/clockify-cli/api/dto"
. "github.com/lucassabreu/clockify-cli/internal/testhlp"
"github.com/lucassabreu/clockify-cli/pkg/timehlp"
)
func TestCreateTimeEntry(t *testing.T) {
uri := "/v1/workspaces/" + exampleID + "/time-entries"
end := MustParseTime(timehlp.SimplerTimeFormat, "2022-11-07 11:00")
bTrue := true
bFalse := false
tts := []testCase{
&simpleTestCase{
name: "workspace is required",
param: api.CreateTimeEntryParam{},
err: "workspace is required",
},
&simpleTestCase{
name: "workspace is valid",
param: api.CreateTimeEntryParam{
Workspace: "w",
},
err: "workspace .* is not valid ID",
},
&simpleTestCase{
name: "with just start time",
param: api.CreateTimeEntryParam{
Workspace: exampleID,
Start: MustParseTime(timehlp.SimplerTimeFormat,
"2022-11-07 10:00"),
},
requestMethod: "post",
requestUrl: uri,
requestBody: `{"start":"2022-11-07T10:00:00Z"}`,
responseStatus: 200,
responseBody: `{"id": "1"}`,
result: dto.TimeEntryImpl{ID: "1"},
},
&simpleTestCase{
name: "with all options (billable)",
param: api.CreateTimeEntryParam{
Workspace: exampleID,
Start: MustParseTime(timehlp.SimplerTimeFormat,
"2022-11-07 10:00"),
End: &end,
Billable: &bTrue,
Description: "new entry",
ProjectID: "p",
TaskID: "t",
TagIDs: []string{"tag1", "tag2"},
},
requestMethod: "post",
requestUrl: uri,
requestBody: `{
"start":"2022-11-07T10:00:00Z",
"end":"2022-11-07T11:00:00Z",
"billable": true,
"description": "new entry",
"projectId": "p",
"taskId": "t",
"tagIds": ["tag1","tag2"]
}`,
responseStatus: 200,
responseBody: `{"id": "1"}`,
result: dto.TimeEntryImpl{ID: "1"},
},
&simpleTestCase{
name: "not billable",
param: api.CreateTimeEntryParam{
Workspace: exampleID,
Start: MustParseTime(timehlp.SimplerTimeFormat,
"2022-11-07 10:00"),
Billable: &bFalse,
Description: "new entry",
ProjectID: "p",
},
requestMethod: "post",
requestUrl: uri,
requestBody: `{
"start":"2022-11-07T10:00:00Z",
"billable": false,
"description": "new entry",
"projectId": "p"
}`,
responseStatus: 200,
responseBody: `{"id": "1"}`,
result: dto.TimeEntryImpl{ID: "1"},
},
&simpleTestCase{
name: "error response",
param: api.CreateTimeEntryParam{
Workspace: exampleID,
Start: MustParseTime(timehlp.SimplerTimeFormat,
"2022-11-07 10:00"),
},
requestMethod: "post",
requestUrl: uri,
requestBody: `{"start":"2022-11-07T10:00:00Z"}`,
responseStatus: 400,
responseBody: `{"code": 10, "message":"error"}`,
err: `error`,
},
}
for _, tt := range tts {
runClient(t, tt,
func(c api.Client, p interface{}) (interface{}, error) {
return c.CreateTimeEntry(
p.(api.CreateTimeEntryParam))
})
}
}
================================================
FILE: cmd/clockify-cli/main.go
================================================
package main
import (
"errors"
"fmt"
"os"
"path"
"strings"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/lucassabreu/clockify-cli/pkg/cmd"
"github.com/lucassabreu/clockify-cli/pkg/cmdutil"
"github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
var (
version = "dev"
commit = "none"
date = "unknown"
)
const (
exitOK = 0
exitError = 1
exitCancel = 2
)
func main() {
exitCode := execute()
os.Exit(exitCode)
}
func execute() int {
f := cmdutil.NewFactory(cmdutil.Version{
Tag: version,
Commit: commit,
Date: date,
})
rootCmd := cmd.NewCmdRoot(f)
rootCmd.SetFlagErrorFunc(func(_ *cobra.Command, err error) error {
return cmdutil.FlagErrorWrap(err)
})
cmd := rootCmd
err := bindViper(rootCmd)
if err == nil {
cmd, err = rootCmd.ExecuteC()
}
if err == nil {
gitextract_9hng928q/
├── .deepsource.toml
├── .github/
│ └── workflows/
│ ├── golangci-lint.yml
│ ├── release.yml
│ └── test-unit.yaml
├── .gitignore
├── .gitmodules
├── .goreleaser.yml
├── .mockery.yaml
├── .nvimrc
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── api/
│ ├── client.go
│ ├── client_test.go
│ ├── dto/
│ │ ├── dto.go
│ │ └── request.go
│ ├── httpClient.go
│ ├── logger.go
│ ├── project_test.go
│ ├── tag_test.go
│ ├── task_test.go
│ └── timeentry_test.go
├── cmd/
│ ├── clockify-cli/
│ │ └── main.go
│ ├── gendocs/
│ │ └── main.go
│ └── release/
│ └── main.go
├── docs/
│ └── project-layout.md
├── go.mod
├── go.sum
├── internal/
│ ├── consoletest/
│ │ └── test.go
│ ├── mocks/
│ │ ├── gen.go
│ │ ├── mock_Client.go
│ │ ├── mock_Config.go
│ │ ├── mock_Factory.go
│ │ └── simple_config.go
│ └── testhlp/
│ └── helper.go
├── netlify.toml
├── pkg/
│ ├── cmd/
│ │ ├── client/
│ │ │ ├── add/
│ │ │ │ ├── add.go
│ │ │ │ └── add_test.go
│ │ │ ├── client.go
│ │ │ ├── list/
│ │ │ │ ├── list.go
│ │ │ │ └── list_test.go
│ │ │ └── util/
│ │ │ └── util.go
│ │ ├── completion/
│ │ │ └── completion.go
│ │ ├── config/
│ │ │ ├── config.go
│ │ │ ├── get/
│ │ │ │ ├── get.go
│ │ │ │ └── get_test.go
│ │ │ ├── init/
│ │ │ │ ├── init.go
│ │ │ │ └── init_test.go
│ │ │ ├── list/
│ │ │ │ ├── list.go
│ │ │ │ └── list_test.go
│ │ │ ├── set/
│ │ │ │ ├── set.go
│ │ │ │ └── set_test.go
│ │ │ └── util/
│ │ │ └── util.go
│ │ ├── project/
│ │ │ ├── add/
│ │ │ │ ├── add.go
│ │ │ │ └── add_test.go
│ │ │ ├── edit/
│ │ │ │ ├── edit.go
│ │ │ │ └── edit_test.go
│ │ │ ├── get/
│ │ │ │ ├── get.go
│ │ │ │ └── get_test.go
│ │ │ ├── list/
│ │ │ │ ├── list.go
│ │ │ │ └── list_test.go
│ │ │ ├── project.go
│ │ │ └── util/
│ │ │ └── util.go
│ │ ├── root.go
│ │ ├── tag/
│ │ │ └── tag.go
│ │ ├── task/
│ │ │ ├── add/
│ │ │ │ ├── add.go
│ │ │ │ └── add_test.go
│ │ │ ├── delete/
│ │ │ │ ├── delete.go
│ │ │ │ └── delete_test.go
│ │ │ ├── done/
│ │ │ │ ├── done.go
│ │ │ │ └── done_test.go
│ │ │ ├── edit/
│ │ │ │ ├── edit.go
│ │ │ │ └── edit_test.go
│ │ │ ├── list/
│ │ │ │ ├── list.go
│ │ │ │ └── list_test.go
│ │ │ ├── quick-add/
│ │ │ │ ├── quick-add.go
│ │ │ │ └── quick-add_test.go
│ │ │ ├── task.go
│ │ │ └── util/
│ │ │ ├── read-flags.go
│ │ │ └── report.go
│ │ ├── time-entry/
│ │ │ ├── clone/
│ │ │ │ └── clone.go
│ │ │ ├── delete/
│ │ │ │ └── delete.go
│ │ │ ├── edit/
│ │ │ │ ├── edit.go
│ │ │ │ └── edit_test.go
│ │ │ ├── edit-multipple/
│ │ │ │ └── edit-multiple.go
│ │ │ ├── in/
│ │ │ │ ├── in.go
│ │ │ │ └── in_test.go
│ │ │ ├── invoiced/
│ │ │ │ └── invoiced.go
│ │ │ ├── manual/
│ │ │ │ └── manual.go
│ │ │ ├── out/
│ │ │ │ └── out.go
│ │ │ ├── report/
│ │ │ │ ├── last-day/
│ │ │ │ │ └── last-day.go
│ │ │ │ ├── last-month/
│ │ │ │ │ └── last-month.go
│ │ │ │ ├── last-week/
│ │ │ │ │ └── last-week.go
│ │ │ │ ├── last-week-day/
│ │ │ │ │ └── last-week-day.go
│ │ │ │ ├── report.go
│ │ │ │ ├── this-month/
│ │ │ │ │ └── this-month.go
│ │ │ │ ├── this-week/
│ │ │ │ │ └── this-week.go
│ │ │ │ ├── today/
│ │ │ │ │ ├── today.go
│ │ │ │ │ └── today_test.go
│ │ │ │ ├── util/
│ │ │ │ │ ├── report.go
│ │ │ │ │ ├── report_flag_test.go
│ │ │ │ │ └── reportwithrange_test.go
│ │ │ │ └── yesterday/
│ │ │ │ └── yesterday.go
│ │ │ ├── show/
│ │ │ │ └── show.go
│ │ │ ├── split/
│ │ │ │ ├── split.go
│ │ │ │ └── split_test.go
│ │ │ ├── timeentry.go
│ │ │ └── util/
│ │ │ ├── create.go
│ │ │ ├── description-completer.go
│ │ │ ├── fill-with-flags.go
│ │ │ ├── flags.go
│ │ │ ├── help.go
│ │ │ ├── interactive.go
│ │ │ ├── interactive_test.go
│ │ │ ├── name-for-id.go
│ │ │ ├── out-in-progress.go
│ │ │ ├── report.go
│ │ │ ├── util.go
│ │ │ ├── util_test.go
│ │ │ ├── validate-closing.go
│ │ │ └── validate.go
│ │ ├── user/
│ │ │ ├── me/
│ │ │ │ ├── me.go
│ │ │ │ └── me_test.go
│ │ │ ├── user.go
│ │ │ ├── user_test.go
│ │ │ └── util/
│ │ │ └── util.go
│ │ ├── version/
│ │ │ ├── version.go
│ │ │ └── version_test.go
│ │ └── workspace/
│ │ ├── workspace.go
│ │ └── workspace_test.go
│ ├── cmdcompl/
│ │ ├── flags.go
│ │ └── valid-args.go
│ ├── cmdcomplutil/
│ │ ├── client.go
│ │ ├── factory.go
│ │ ├── project.go
│ │ ├── project_test.go
│ │ ├── tag.go
│ │ ├── task.go
│ │ ├── user.go
│ │ └── workspace.go
│ ├── cmdutil/
│ │ ├── args.go
│ │ ├── args_test.go
│ │ ├── config.go
│ │ ├── errors.go
│ │ ├── factory.go
│ │ ├── flags.go
│ │ ├── flags_test.go
│ │ ├── project.go
│ │ └── version.go
│ ├── output/
│ │ ├── client/
│ │ │ ├── csv.go
│ │ │ ├── default.go
│ │ │ ├── json.go
│ │ │ ├── quiet.go
│ │ │ └── template.go
│ │ ├── project/
│ │ │ ├── csv.go
│ │ │ ├── default.go
│ │ │ ├── json.go
│ │ │ ├── quiet.go
│ │ │ └── template.go
│ │ ├── tag/
│ │ │ ├── default.go
│ │ │ ├── quiet.go
│ │ │ └── template.go
│ │ ├── task/
│ │ │ ├── csv.go
│ │ │ ├── default.go
│ │ │ ├── json.go
│ │ │ ├── quiet.go
│ │ │ └── template.go
│ │ ├── time-entry/
│ │ │ ├── csv.go
│ │ │ ├── default.go
│ │ │ ├── default_test.go
│ │ │ ├── duration.go
│ │ │ ├── duration_test.go
│ │ │ ├── json.go
│ │ │ ├── markdown.go
│ │ │ ├── markdown.gotmpl.md
│ │ │ ├── markdown_test.go
│ │ │ ├── quiet.go
│ │ │ └── template.go
│ │ ├── user/
│ │ │ ├── default.go
│ │ │ ├── json.go
│ │ │ ├── quiet.go
│ │ │ └── template.go
│ │ ├── util/
│ │ │ ├── color.go
│ │ │ └── template.go
│ │ └── workspace/
│ │ ├── default.go
│ │ ├── quiet.go
│ │ └── template.go
│ ├── search/
│ │ ├── client.go
│ │ ├── errors.go
│ │ ├── find.go
│ │ ├── find_test.go
│ │ ├── project.go
│ │ ├── tag.go
│ │ ├── task.go
│ │ └── user.go
│ ├── timeentryhlp/
│ │ └── timeentry.go
│ ├── timehlp/
│ │ ├── range.go
│ │ ├── relative.go
│ │ ├── time.go
│ │ ├── time_test.go
│ │ └── util.go
│ └── ui/
│ ├── color.go
│ └── ui.go
├── scripts/
│ └── site-build
├── site/
│ ├── .gitignore
│ ├── archetypes/
│ │ └── default.md
│ ├── config.toml
│ ├── content/
│ │ ├── _index.md
│ │ └── usage/
│ │ └── _index.md
│ ├── layouts/
│ │ └── partials/
│ │ ├── logo.html
│ │ └── menu-footer.html
│ └── static/
│ └── css/
│ └── custom.css
└── strhlp/
├── strhlp.go
└── strhlp_test.go
SYMBOL INDEX (1259 symbols across 188 files)
FILE: api/client.go
type Client (line 19) | type Client interface
type client (line 86) | type client struct
method GetWorkspaces (line 182) | func (c *client) GetWorkspaces(f GetWorkspaces) ([]dto.Workspace, erro...
method GetWorkspace (line 306) | func (c *client) GetWorkspace(p GetWorkspace) (dto.Workspace, error) {
method WorkspaceUsers (line 342) | func (c *client) WorkspaceUsers(p WorkspaceUsersParam) (users []dto.Us...
method Log (line 384) | func (c *client) Log(p LogParam) ([]dto.TimeEntry, error) {
method LogRange (line 412) | func (c *client) LogRange(p LogRangeParam) ([]dto.TimeEntry, error) {
method GetUserTimeEntries (line 441) | func (c *client) GetUserTimeEntries(p GetUserTimeEntriesParam) ([]dto....
method GetUsersHydratedTimeEntries (line 447) | func (c *client) GetUsersHydratedTimeEntries(p GetUserTimeEntriesParam...
method GetTimeEntryInProgress (line 589) | func (c *client) GetTimeEntryInProgress(p GetTimeEntryInProgressParam)...
method GetHydratedTimeEntryInProgress (line 609) | func (c *client) GetHydratedTimeEntryInProgress(p GetTimeEntryInProgre...
method GetTimeEntry (line 630) | func (c *client) GetTimeEntry(p GetTimeEntryParam) (timeEntry *dto.Tim...
method GetHydratedTimeEntry (line 666) | func (c *client) GetHydratedTimeEntry(p GetTimeEntryParam) (timeEntry ...
method GetTag (line 711) | func (c *client) GetTag(p GetTagParam) (*dto.Tag, error) {
method GetProject (line 738) | func (c *client) GetProject(p GetProjectParam) (pr *dto.Project, err e...
method GetUser (line 783) | func (c *client) GetUser(p GetUser) (dto.User, error) {
method GetMe (line 822) | func (c *client) GetMe() (dto.User, error) {
method GetTasks (line 845) | func (c *client) GetTasks(p GetTasksParam) (ps []dto.Task, err error) {
method GetTask (line 887) | func (c *client) GetTask(p GetTaskParam) (t dto.Task, err error) {
method AddTask (line 942) | func (c *client) AddTask(p AddTaskParam) (task dto.Task, err error) {
method UpdateTask (line 1006) | func (c *client) UpdateTask(p UpdateTaskParam) (task dto.Task, err err...
method DeleteTask (line 1068) | func (c *client) DeleteTask(p DeleteTaskParam) (task dto.Task, err err...
method CreateTimeEntry (line 1117) | func (c *client) CreateTimeEntry(p CreateTimeEntryParam) (
method GetTags (line 1165) | func (c *client) GetTags(p GetTagsParam) (ps []dto.Tag, err error) {
method GetClients (line 1198) | func (c *client) GetClients(p GetClientsParam) (
method AddClient (line 1231) | func (c *client) AddClient(p AddClientParam) (client dto.Client, err e...
method GetProjects (line 1278) | func (c *client) GetProjects(p GetProjectsParam) (ps []dto.Project, er...
method AddProject (line 1342) | func (c *client) AddProject(p AddProjectParam) (
method UpdateProject (line 1407) | func (c *client) UpdateProject(p UpdateProjectParam) (
method UpdateProjectMemberships (line 1479) | func (c *client) UpdateProjectMemberships(p UpdateProjectMembershipsPa...
method UpdateProjectTemplate (line 1538) | func (c *client) UpdateProjectTemplate(p UpdateProjectTemplateParam) (
method UpdateProjectUserBillableRate (line 1583) | func (c *client) UpdateProjectUserBillableRate(
method UpdateProjectUserCostRate (line 1626) | func (c *client) UpdateProjectUserCostRate(
method UpdateProjectEstimate (line 1731) | func (c *client) UpdateProjectEstimate(p UpdateProjectEstimateParam) (
method DeleteProject (line 1829) | func (c *client) DeleteProject(p DeleteProjectParam) (
method Out (line 1891) | func (c *client) Out(p OutParam) (err error) {
method UpdateTimeEntry (line 1941) | func (c *client) UpdateTimeEntry(p UpdateTimeEntryParam) (
method DeleteTimeEntry (line 1996) | func (c *client) DeleteTimeEntry(p DeleteTimeEntryParam) (err error) {
method ChangeInvoiced (line 2037) | func (c *client) ChangeInvoiced(p ChangeInvoicedParam) error {
constant BASE_URL (line 95) | BASE_URL = "https://api.clockify.me/api"
constant REQUEST_RATE_LIMIT (line 98) | REQUEST_RATE_LIMIT = 50
function NewClientFromUrlAndKey (line 106) | func NewClientFromUrlAndKey(
function NewClient (line 136) | func NewClient(apiKey string) (Client, error) {
function startRequestTick (line 143) | func startRequestTick(limit int) chan struct{} {
type GetWorkspaces (line 177) | type GetWorkspaces struct
type field (line 212) | type field
constant workspaceField (line 215) | workspaceField = field("workspace")
constant userIDField (line 216) | userIDField = field("user id")
constant userOrGroupIDField (line 217) | userOrGroupIDField = field("user or group")
constant projectField (line 218) | projectField = field("project id")
constant timeEntryIDField (line 219) | timeEntryIDField = field("time entry id")
constant nameField (line 220) | nameField = field("name")
constant taskIDField (line 221) | taskIDField = field("task id")
constant estimateMethodField (line 222) | estimateMethodField = field("estimate method")
constant estimateTypeField (line 223) | estimateTypeField = field("estimate type")
constant resetOptionField (line 224) | resetOptionField = field("reset option")
type RequiredFieldError (line 228) | type RequiredFieldError struct
method Error (line 232) | func (e RequiredFieldError) Error() string {
function required (line 236) | func required(values map[field]string) error {
function IsValidID (line 249) | func IsValidID(id string) bool {
type InvalidIDError (line 254) | type InvalidIDError struct
method Error (line 259) | func (e InvalidIDError) Error() string {
function checkIDs (line 263) | func checkIDs(ids map[field]string) error {
function checkWorkspace (line 273) | func checkWorkspace(workspace string) error {
function wrapError (line 282) | func wrapError(err *error, message string, args ...interface{}) {
type EntityNotFound (line 289) | type EntityNotFound struct
method Error (line 294) | func (e EntityNotFound) Error() string {
method Unwrap (line 298) | func (e EntityNotFound) Unwrap() error {
type GetWorkspace (line 302) | type GetWorkspace struct
type WorkspaceUsersParam (line 334) | type WorkspaceUsersParam struct
type PaginationParam (line 364) | type PaginationParam struct
function AllPages (line 371) | func AllPages() PaginationParam {
type LogParam (line 376) | type LogParam struct
type LogRangeParam (line 400) | type LogRangeParam struct
type GetUserTimeEntriesParam (line 427) | type GetUserTimeEntriesParam struct
function getUserTimeEntriesImpl (line 466) | func getUserTimeEntriesImpl[K dto.TimeEntry | dto.TimeEntryImpl](
function paginate (line 537) | func paginate[K any](
type GetTimeEntryInProgressParam (line 583) | type GetTimeEntryInProgressParam struct
type GetTimeEntryParam (line 623) | type GetTimeEntryParam struct
type GetTagParam (line 705) | type GetTagParam struct
type GetProjectParam (line 731) | type GetProjectParam struct
type GetUser (line 777) | type GetUser struct
type GetTasksParam (line 835) | type GetTasksParam struct
type GetTaskParam (line 880) | type GetTaskParam struct
type TaskStatus (line 923) | type TaskStatus
constant TaskStatusDefault (line 926) | TaskStatusDefault = ""
constant TaskStatusDone (line 927) | TaskStatusDone = "DONE"
constant TaskStatusActive (line 928) | TaskStatusActive = "ACTIVE"
type AddTaskParam (line 932) | type AddTaskParam struct
type UpdateTaskParam (line 995) | type UpdateTaskParam struct
type DeleteTaskParam (line 1062) | type DeleteTaskParam struct
type CreateTimeEntryParam (line 1105) | type CreateTimeEntryParam struct
type GetTagsParam (line 1156) | type GetTagsParam struct
type GetClientsParam (line 1189) | type GetClientsParam struct
type AddClientParam (line 1225) | type AddClientParam struct
type GetProjectsParam (line 1267) | type GetProjectsParam struct
type AddProjectParam (line 1311) | type AddProjectParam struct
function parseColor (line 1321) | func parseColor(c string) (string, error) {
type UpdateProjectParam (line 1393) | type UpdateProjectParam struct
type UpdateMembership (line 1464) | type UpdateMembership struct
type UpdateProjectMembershipsParam (line 1471) | type UpdateProjectMembershipsParam struct
type UpdateProjectTemplateParam (line 1531) | type UpdateProjectTemplateParam struct
type UpdateProjectUserRateParam (line 1575) | type UpdateProjectUserRateParam struct
type EstimateMethod (line 1670) | type EstimateMethod
constant EstimateMethodNone (line 1674) | EstimateMethodNone = EstimateMethod("none")
constant EstimateMethodTime (line 1676) | EstimateMethodTime = EstimateMethod("time")
constant EstimateMethodBudget (line 1678) | EstimateMethodBudget = EstimateMethod("budget")
type EstimateType (line 1682) | type EstimateType
method toRequestType (line 1689) | func (t EstimateType) toRequestType() *dto.EstimateType {
constant EstimateTypeProject (line 1685) | EstimateTypeProject = EstimateType("project")
constant EstimateTypeTask (line 1686) | EstimateTypeTask = EstimateType("task")
type EstimateResetOption (line 1703) | type EstimateResetOption
method toRequestType (line 1710) | func (t EstimateResetOption) toRequestType() *dto.EstimateResetOption {
constant EstimateResetOptionDefault (line 1706) | EstimateResetOptionDefault = EstimateType("")
constant EstimateResetOptionMonthly (line 1707) | EstimateResetOptionMonthly = EstimateResetOption("monthly")
type UpdateProjectEstimateParam (line 1721) | type UpdateProjectEstimateParam struct
type DeleteProjectParam (line 1823) | type DeleteProjectParam struct
type InvalidOptionError (line 1863) | type InvalidOptionError struct
method Error (line 1868) | func (i *InvalidOptionError) Error() string {
function shouldBeOneOf (line 1872) | func shouldBeOneOf(f field, s string, o []string) error {
type OutParam (line 1884) | type OutParam struct
type UpdateTimeEntryParam (line 1928) | type UpdateTimeEntryParam struct
type DeleteTimeEntryParam (line 1990) | type DeleteTimeEntryParam struct
type ChangeInvoicedParam (line 2030) | type ChangeInvoicedParam struct
FILE: api/client_test.go
type testCase (line 17) | type testCase interface
type httpCall (line 28) | type httpCall interface
function runClient (line 36) | func runClient(t *testing.T, tt testCase,
type simpleTestCase (line 111) | type simpleTestCase struct
method getRequestMethod (line 127) | func (s *simpleTestCase) getRequestMethod() string {
method getRequestUrl (line 131) | func (s *simpleTestCase) getRequestUrl() string {
method getRequestBody (line 135) | func (s *simpleTestCase) getRequestBody() string {
method getResponseStatus (line 139) | func (s *simpleTestCase) getResponseStatus() int {
method getResponseBody (line 143) | func (s *simpleTestCase) getResponseBody() string {
method getName (line 147) | func (s *simpleTestCase) getName() string {
method getParam (line 151) | func (s *simpleTestCase) getParam() interface{} {
method getResult (line 155) | func (s *simpleTestCase) getResult() interface{} {
method getErr (line 159) | func (s *simpleTestCase) getErr() string {
method getHttpCallFor (line 163) | func (s *simpleTestCase) getHttpCallFor(_ string) httpCall {
method getPendingHttpCalls (line 171) | func (s *simpleTestCase) getPendingHttpCalls() []httpCall {
method hasHttpCalls (line 179) | func (s *simpleTestCase) hasHttpCalls() bool {
type multiRequestTestCase (line 183) | type multiRequestTestCase struct
method getName (line 194) | func (m *multiRequestTestCase) getName() string {
method getParam (line 198) | func (m *multiRequestTestCase) getParam() interface{} {
method getResult (line 202) | func (m *multiRequestTestCase) getResult() interface{} {
method getErr (line 206) | func (m *multiRequestTestCase) getErr() string {
method hasHttpCalls (line 210) | func (m *multiRequestTestCase) hasHttpCalls() bool {
method getHttpCallFor (line 214) | func (m *multiRequestTestCase) getHttpCallFor(uri string) httpCall {
method getPendingHttpCalls (line 223) | func (m *multiRequestTestCase) getPendingHttpCalls() []httpCall {
method addHttpCall (line 234) | func (m *multiRequestTestCase) addHttpCall(c httpCall) *multiRequestTe...
type httpRequest (line 247) | type httpRequest struct
method getRequestMethod (line 256) | func (h *httpRequest) getRequestMethod() string {
method getRequestUrl (line 260) | func (h *httpRequest) getRequestUrl() string {
method getRequestBody (line 264) | func (h *httpRequest) getRequestBody() string {
method getResponseStatus (line 268) | func (h *httpRequest) getResponseStatus() int {
method getResponseBody (line 272) | func (h *httpRequest) getResponseBody() string {
FILE: api/dto/dto.go
type Error (line 10) | type Error struct
method Error (line 15) | func (e Error) Error() string {
type Workspace (line 20) | type Workspace struct
type Membership (line 30) | type Membership struct
type MembershipStatus (line 40) | type MembershipStatus
constant MembershipStatusPending (line 43) | MembershipStatusPending = MembershipStatus("PENDING")
constant MembershipStatusActive (line 46) | MembershipStatusActive = MembershipStatus("ACTIVE")
constant MembershipStatusDeclined (line 49) | MembershipStatusDeclined = MembershipStatus("DECLINED")
constant MembershipStatusInactive (line 52) | MembershipStatusInactive = MembershipStatus("INACTIVE")
type WorkspaceSettings (line 55) | type WorkspaceSettings struct
type AutomaticLock (line 84) | type AutomaticLock struct
type Round (line 94) | type Round struct
type Rate (line 100) | type Rate struct
type TimeEntry (line 106) | type TimeEntry struct
function NewTimeInterval (line 124) | func NewTimeInterval(start time.Time, end *time.Time) TimeInterval {
type TimeInterval (line 146) | type TimeInterval struct
type Tag (line 153) | type Tag struct
method GetID (line 159) | func (e Tag) GetID() string { return e.ID }
method GetName (line 160) | func (e Tag) GetName() string { return e.Name }
method String (line 161) | func (e Tag) String() string { return e.Name + " (" + e.ID + ")" }
type TaskStatus (line 164) | type TaskStatus
constant TaskStatusActive (line 167) | TaskStatusActive = TaskStatus("ACTIVE")
constant TaskStatusDone (line 170) | TaskStatusDone = TaskStatus("DONE")
type Task (line 173) | type Task struct
method GetID (line 188) | func (e Task) GetID() string { return e.ID }
method GetName (line 189) | func (e Task) GetName() string { return e.Name }
type Client (line 192) | type Client struct
method GetID (line 199) | func (e Client) GetID() string { return e.ID }
method GetName (line 200) | func (e Client) GetName() string { return e.Name }
type CustomField (line 203) | type CustomField struct
method ValueAsString (line 217) | func (cf CustomField) ValueAsString() string {
type Project (line 237) | type Project struct
method GetID (line 269) | func (p Project) GetID() string { return p.ID }
method GetName (line 270) | func (p Project) GetName() string { return p.Name }
type EstimateType (line 273) | type EstimateType
constant EstimateTypeAuto (line 276) | EstimateTypeAuto = EstimateType("AUTO")
constant EstimateTypeManual (line 279) | EstimateTypeManual = EstimateType("MANUAL")
type EstimateResetOption (line 282) | type EstimateResetOption
constant EstimateResetOptionMonthly (line 285) | EstimateResetOptionMonthly = EstimateResetOption("MONTHLY")
type BaseEstimate (line 288) | type BaseEstimate struct
type TimeEstimate (line 295) | type TimeEstimate struct
type BudgetEstimate (line 302) | type BudgetEstimate struct
type UserStatus (line 308) | type UserStatus
constant UserStatusActive (line 311) | UserStatusActive = UserStatus("ACTIVE")
constant UserStatusPendingEmailVerification (line 314) | UserStatusPendingEmailVerification = UserStatus("PENDING_EMAIL_VERIFICAT...
constant UserStatusDeleted (line 317) | UserStatusDeleted = UserStatus("DELETED")
type User (line 320) | type User struct
method GetID (line 333) | func (e User) GetID() string { return e.ID }
method GetName (line 334) | func (e User) GetName() string { return e.Name }
type Role (line 337) | type Role struct
type RoleEntity (line 342) | type RoleEntity struct
type WeekStart (line 348) | type WeekStart
constant WeekStartMonday (line 351) | WeekStartMonday = WeekStart("MONDAY")
constant WeekStartTuesday (line 354) | WeekStartTuesday = WeekStart("TUESDAY")
constant WeekStartWednesday (line 357) | WeekStartWednesday = WeekStart("WEDNESDAY")
constant WeekStartThursday (line 360) | WeekStartThursday = WeekStart("THURSDAY")
constant WeekStartFriday (line 363) | WeekStartFriday = WeekStart("FRIDAY")
constant WeekStartSaturday (line 366) | WeekStartSaturday = WeekStart("SATURDAY")
constant WeekStartSunday (line 369) | WeekStartSunday = WeekStart("SUNDAY")
type UserSettings (line 372) | type UserSettings struct
type SummaryReportSettings (line 386) | type SummaryReportSettings struct
type InvitedUser (line 392) | type InvitedUser struct
type Invitation (line 400) | type Invitation struct
type TimeEntriesList (line 409) | type TimeEntriesList struct
type TimeEntryImpl (line 416) | type TimeEntryImpl struct
FILE: api/dto/request.go
type DateTime (line 15) | type DateTime struct
method MarshalJSON (line 20) | func (d DateTime) MarshalJSON() ([]byte, error) {
method String (line 24) | func (d DateTime) String() string {
type Duration (line 29) | type Duration struct
method MarshalJSON (line 34) | func (d Duration) MarshalJSON() ([]byte, error) {
method UnmarshalJSON (line 39) | func (d *Duration) UnmarshalJSON(b []byte) error {
method String (line 87) | func (d Duration) String() string {
method HumanString (line 97) | func (dd Duration) HumanString() string {
function StringToDuration (line 54) | func StringToDuration(s string) (time.Duration, error) {
type pagination (line 109) | type pagination struct
method AppendToQuery (line 122) | func (p pagination) AppendToQuery(u *url.URL) *url.URL {
function newPagination (line 114) | func newPagination(page, size int) pagination {
type PaginatedRequest (line 137) | type PaginatedRequest interface
type GetTimeEntryRequest (line 142) | type GetTimeEntryRequest struct
method AppendToQuery (line 148) | func (r GetTimeEntryRequest) AppendToQuery(u *url.URL) *url.URL {
type UserTimeEntriesRequest (line 163) | type UserTimeEntriesRequest struct
method WithPagination (line 181) | func (r UserTimeEntriesRequest) WithPagination(page, size int) Paginat...
method AppendToQuery (line 187) | func (r UserTimeEntriesRequest) AppendToQuery(u *url.URL) *url.URL {
type OutTimeEntryRequest (line 240) | type OutTimeEntryRequest struct
type CreateTimeEntryRequest (line 245) | type CreateTimeEntryRequest struct
type CustomFieldValue (line 257) | type CustomFieldValue struct
type UpdateTimeEntryRequest (line 266) | type UpdateTimeEntryRequest struct
type GetClientsRequest (line 277) | type GetClientsRequest struct
method WithPagination (line 285) | func (r GetClientsRequest) WithPagination(page, size int) PaginatedReq...
method AppendToQuery (line 291) | func (r GetClientsRequest) AppendToQuery(u *url.URL) *url.URL {
type AddClientRequest (line 309) | type AddClientRequest struct
type GetProjectsRequest (line 313) | type GetProjectsRequest struct
method WithPagination (line 323) | func (r GetProjectsRequest) WithPagination(page, size int) PaginatedRe...
method AppendToQuery (line 334) | func (r GetProjectsRequest) AppendToQuery(u *url.URL) *url.URL {
type GetProjectRequest (line 361) | type GetProjectRequest struct
method AppendToQuery (line 366) | func (r GetProjectRequest) AppendToQuery(u *url.URL) *url.URL {
type AddProjectRequest (line 379) | type AddProjectRequest struct
type UpdateProjectRequest (line 390) | type UpdateProjectRequest struct
type UpdateProjectMembershipsRequest (line 402) | type UpdateProjectMembershipsRequest struct
type UpdateProjectMembership (line 408) | type UpdateProjectMembership struct
type UpdateProjectTemplateRequest (line 415) | type UpdateProjectTemplateRequest struct
type GetTagsRequest (line 419) | type GetTagsRequest struct
method WithPagination (line 427) | func (r GetTagsRequest) WithPagination(page, size int) PaginatedRequest {
method AppendToQuery (line 433) | func (r GetTagsRequest) AppendToQuery(u *url.URL) *url.URL {
type GetTasksRequest (line 451) | type GetTasksRequest struct
method WithPagination (line 459) | func (r GetTasksRequest) WithPagination(page, size int) PaginatedReque...
method AppendToQuery (line 465) | func (r GetTasksRequest) AppendToQuery(u *url.URL) *url.URL {
type AddTaskRequest (line 482) | type AddTaskRequest struct
type UpdateTaskRequest (line 490) | type UpdateTaskRequest struct
type ChangeTimeEntriesInvoicedRequest (line 498) | type ChangeTimeEntriesInvoicedRequest struct
type WorkspaceUsersRequest (line 503) | type WorkspaceUsersRequest struct
method WithPagination (line 509) | func (r WorkspaceUsersRequest) WithPagination(page, size int) Paginate...
method AppendToQuery (line 515) | func (r WorkspaceUsersRequest) AppendToQuery(u *url.URL) *url.URL {
type UpdateProjectUserRateRequest (line 531) | type UpdateProjectUserRateRequest struct
type BaseEstimateRequest (line 537) | type BaseEstimateRequest struct
type TimeEstimateRequest (line 544) | type TimeEstimateRequest struct
type BudgetEstimateRequest (line 550) | type BudgetEstimateRequest struct
type UpdateProjectEstimateRequest (line 557) | type UpdateProjectEstimateRequest struct
FILE: api/httpClient.go
type QueryAppender (line 15) | type QueryAppender interface
type transport (line 28) | type transport struct
method RoundTrip (line 33) | func (t transport) RoundTrip(r *http.Request) (*http.Response, error) {
method NewRequest (line 40) | func (c *client) NewRequest(method, uri string, body interface{}) (*http...
method Do (line 78) | func (c *client) Do(
FILE: api/logger.go
type Logger (line 4) | type Logger interface
method SetDebugLogger (line 11) | func (c *client) SetDebugLogger(logger Logger) Client {
method debugf (line 16) | func (c *client) debugf(format string, v ...interface{}) {
method SetInfoLogger (line 25) | func (c *client) SetInfoLogger(logger Logger) Client {
method infof (line 30) | func (c *client) infof(format string, v ...interface{}) {
FILE: api/project_test.go
function TestUpdateProjectMemberships (line 11) | func TestUpdateProjectMemberships(t *testing.T) {
function TestDeleteProject (line 166) | func TestDeleteProject(t *testing.T) {
function TestGetProject (line 237) | func TestGetProject(t *testing.T) {
function TestGetProjects (line 339) | func TestGetProjects(t *testing.T) {
function TestUpdateProjectTemplate (line 529) | func TestUpdateProjectTemplate(t *testing.T) {
function TestUpdateProjectEstimate (line 631) | func TestUpdateProjectEstimate(t *testing.T) {
function TestUpdateProjectUserCostRate (line 921) | func TestUpdateProjectUserCostRate(t *testing.T) {
function TestUpdateProjectUserBillableRate (line 931) | func TestUpdateProjectUserBillableRate(t *testing.T) {
function testUpdateProjectUserRate (line 941) | func testUpdateProjectUserRate(t *testing.T,
function TestUpdateProject (line 1061) | func TestUpdateProject(t *testing.T) {
FILE: api/tag_test.go
function TestGetTags (line 10) | func TestGetTags(t *testing.T) {
FILE: api/task_test.go
function TestGetTasks (line 11) | func TestGetTasks(t *testing.T) {
FILE: api/timeentry_test.go
function TestCreateTimeEntry (line 12) | func TestCreateTimeEntry(t *testing.T) {
FILE: cmd/clockify-cli/main.go
constant exitOK (line 26) | exitOK = 0
constant exitError (line 27) | exitError = 1
constant exitCancel (line 28) | exitCancel = 2
function main (line 31) | func main() {
function execute (line 36) | func execute() int {
function bindViper (line 81) | func bindViper(rootCmd *cobra.Command) error {
FILE: cmd/gendocs/main.go
constant gendocFrontmatterTemplate (line 16) | gendocFrontmatterTemplate = `---
function main (line 25) | func main() {
function execute (line 32) | func execute() error {
FILE: cmd/release/main.go
type Version (line 14) | type Version struct
method String (line 20) | func (v Version) String() string {
function bumpVersion (line 24) | func bumpVersion(v Version, bumpType string) Version {
function findLatestVersion (line 37) | func findLatestVersion(content string) (Version, error) {
function findUnreleasedSection (line 50) | func findUnreleasedSection(content string) (string, int, int) {
function extractUnreleasedContent (line 85) | func extractUnreleasedContent(unreleasedSection string) string {
function updateUnreleasedLink (line 103) | func updateUnreleasedLink(content string, newVersion string) string {
function runGitCommand (line 115) | func runGitCommand(args ...string) error {
function main (line 123) | func main() {
FILE: internal/consoletest/test.go
type ExpectConsole (line 16) | type ExpectConsole interface
type console (line 23) | type console struct
method ExpectEOF (line 28) | func (c *console) ExpectEOF() {
method ExpectString (line 34) | func (c *console) ExpectString(s string) {
method Send (line 40) | func (c *console) Send(s string) {
method SendLine (line 46) | func (c *console) SendLine(s string) {
type FileWriter (line 53) | type FileWriter interface
type FileReader (line 59) | type FileReader interface
function RunTestConsole (line 68) | func RunTestConsole(
FILE: internal/mocks/gen.go
type Factory (line 8) | type Factory interface
type Config (line 12) | type Config interface
type Client (line 16) | type Client interface
FILE: internal/mocks/mock_Client.go
function NewMockClient (line 15) | func NewMockClient(t interface {
type MockClient (line 28) | type MockClient struct
method EXPECT (line 36) | func (_m *MockClient) EXPECT() *MockClient_Expecter {
method AddClient (line 41) | func (_mock *MockClient) AddClient(addClientParam api.AddClientParam) ...
method AddProject (line 101) | func (_mock *MockClient) AddProject(addProjectParam api.AddProjectPara...
method AddTask (line 161) | func (_mock *MockClient) AddTask(addTaskParam api.AddTaskParam) (dto.T...
method ChangeInvoiced (line 221) | func (_mock *MockClient) ChangeInvoiced(changeInvoicedParam api.Change...
method CreateTimeEntry (line 272) | func (_mock *MockClient) CreateTimeEntry(createTimeEntryParam api.Crea...
method DeleteProject (line 332) | func (_mock *MockClient) DeleteProject(deleteProjectParam api.DeletePr...
method DeleteTask (line 392) | func (_mock *MockClient) DeleteTask(deleteTaskParam api.DeleteTaskPara...
method DeleteTimeEntry (line 452) | func (_mock *MockClient) DeleteTimeEntry(deleteTimeEntryParam api.Dele...
method GetClients (line 503) | func (_mock *MockClient) GetClients(getClientsParam api.GetClientsPara...
method GetHydratedTimeEntry (line 565) | func (_mock *MockClient) GetHydratedTimeEntry(getTimeEntryParam api.Ge...
method GetHydratedTimeEntryInProgress (line 627) | func (_mock *MockClient) GetHydratedTimeEntryInProgress(getTimeEntryIn...
method GetMe (line 689) | func (_mock *MockClient) GetMe() (dto.User, error) {
method GetProject (line 742) | func (_mock *MockClient) GetProject(getProjectParam api.GetProjectPara...
method GetProjects (line 804) | func (_mock *MockClient) GetProjects(getProjectsParam api.GetProjectsP...
method GetTag (line 866) | func (_mock *MockClient) GetTag(getTagParam api.GetTagParam) (*dto.Tag...
method GetTags (line 928) | func (_mock *MockClient) GetTags(getTagsParam api.GetTagsParam) ([]dto...
method GetTask (line 990) | func (_mock *MockClient) GetTask(getTaskParam api.GetTaskParam) (dto.T...
method GetTasks (line 1050) | func (_mock *MockClient) GetTasks(getTasksParam api.GetTasksParam) ([]...
method GetTimeEntry (line 1112) | func (_mock *MockClient) GetTimeEntry(getTimeEntryParam api.GetTimeEnt...
method GetTimeEntryInProgress (line 1174) | func (_mock *MockClient) GetTimeEntryInProgress(getTimeEntryInProgress...
method GetUser (line 1236) | func (_mock *MockClient) GetUser(getUser api.GetUser) (dto.User, error) {
method GetUserTimeEntries (line 1296) | func (_mock *MockClient) GetUserTimeEntries(getUserTimeEntriesParam ap...
method GetUsersHydratedTimeEntries (line 1358) | func (_mock *MockClient) GetUsersHydratedTimeEntries(getUserTimeEntrie...
method GetWorkspace (line 1420) | func (_mock *MockClient) GetWorkspace(getWorkspace api.GetWorkspace) (...
method GetWorkspaces (line 1480) | func (_mock *MockClient) GetWorkspaces(getWorkspaces api.GetWorkspaces...
method Log (line 1542) | func (_mock *MockClient) Log(logParam api.LogParam) ([]dto.TimeEntry, ...
method LogRange (line 1604) | func (_mock *MockClient) LogRange(logRangeParam api.LogRangeParam) ([]...
method Out (line 1666) | func (_mock *MockClient) Out(outParam api.OutParam) error {
method SetDebugLogger (line 1717) | func (_mock *MockClient) SetDebugLogger(logger api.Logger) api.Client {
method SetInfoLogger (line 1770) | func (_mock *MockClient) SetInfoLogger(logger api.Logger) api.Client {
method UpdateProject (line 1823) | func (_mock *MockClient) UpdateProject(updateProjectParam api.UpdatePr...
method UpdateProjectEstimate (line 1883) | func (_mock *MockClient) UpdateProjectEstimate(updateProjectEstimatePa...
method UpdateProjectMemberships (line 1943) | func (_mock *MockClient) UpdateProjectMemberships(updateProjectMembers...
method UpdateProjectTemplate (line 2003) | func (_mock *MockClient) UpdateProjectTemplate(updateProjectTemplatePa...
method UpdateProjectUserBillableRate (line 2063) | func (_mock *MockClient) UpdateProjectUserBillableRate(updateProjectUs...
method UpdateProjectUserCostRate (line 2123) | func (_mock *MockClient) UpdateProjectUserCostRate(updateProjectUserRa...
method UpdateTask (line 2183) | func (_mock *MockClient) UpdateTask(updateTaskParam api.UpdateTaskPara...
method UpdateTimeEntry (line 2243) | func (_mock *MockClient) UpdateTimeEntry(updateTimeEntryParam api.Upda...
method WorkspaceUsers (line 2303) | func (_mock *MockClient) WorkspaceUsers(workspaceUsersParam api.Worksp...
type MockClient_Expecter (line 32) | type MockClient_Expecter struct
method AddClient (line 73) | func (_e *MockClient_Expecter) AddClient(addClientParam interface{}) *...
method AddProject (line 133) | func (_e *MockClient_Expecter) AddProject(addProjectParam interface{})...
method AddTask (line 193) | func (_e *MockClient_Expecter) AddTask(addTaskParam interface{}) *Mock...
method ChangeInvoiced (line 244) | func (_e *MockClient_Expecter) ChangeInvoiced(changeInvoicedParam inte...
method CreateTimeEntry (line 304) | func (_e *MockClient_Expecter) CreateTimeEntry(createTimeEntryParam in...
method DeleteProject (line 364) | func (_e *MockClient_Expecter) DeleteProject(deleteProjectParam interf...
method DeleteTask (line 424) | func (_e *MockClient_Expecter) DeleteTask(deleteTaskParam interface{})...
method DeleteTimeEntry (line 475) | func (_e *MockClient_Expecter) DeleteTimeEntry(deleteTimeEntryParam in...
method GetClients (line 537) | func (_e *MockClient_Expecter) GetClients(getClientsParam interface{})...
method GetHydratedTimeEntry (line 599) | func (_e *MockClient_Expecter) GetHydratedTimeEntry(getTimeEntryParam ...
method GetHydratedTimeEntryInProgress (line 661) | func (_e *MockClient_Expecter) GetHydratedTimeEntryInProgress(getTimeE...
method GetMe (line 720) | func (_e *MockClient_Expecter) GetMe() *MockClient_GetMe_Call {
method GetProject (line 776) | func (_e *MockClient_Expecter) GetProject(getProjectParam interface{})...
method GetProjects (line 838) | func (_e *MockClient_Expecter) GetProjects(getProjectsParam interface{...
method GetTag (line 900) | func (_e *MockClient_Expecter) GetTag(getTagParam interface{}) *MockCl...
method GetTags (line 962) | func (_e *MockClient_Expecter) GetTags(getTagsParam interface{}) *Mock...
method GetTask (line 1022) | func (_e *MockClient_Expecter) GetTask(getTaskParam interface{}) *Mock...
method GetTasks (line 1084) | func (_e *MockClient_Expecter) GetTasks(getTasksParam interface{}) *Mo...
method GetTimeEntry (line 1146) | func (_e *MockClient_Expecter) GetTimeEntry(getTimeEntryParam interfac...
method GetTimeEntryInProgress (line 1208) | func (_e *MockClient_Expecter) GetTimeEntryInProgress(getTimeEntryInPr...
method GetUser (line 1268) | func (_e *MockClient_Expecter) GetUser(getUser interface{}) *MockClien...
method GetUserTimeEntries (line 1330) | func (_e *MockClient_Expecter) GetUserTimeEntries(getUserTimeEntriesPa...
method GetUsersHydratedTimeEntries (line 1392) | func (_e *MockClient_Expecter) GetUsersHydratedTimeEntries(getUserTime...
method GetWorkspace (line 1452) | func (_e *MockClient_Expecter) GetWorkspace(getWorkspace interface{}) ...
method GetWorkspaces (line 1514) | func (_e *MockClient_Expecter) GetWorkspaces(getWorkspaces interface{}...
method Log (line 1576) | func (_e *MockClient_Expecter) Log(logParam interface{}) *MockClient_L...
method LogRange (line 1638) | func (_e *MockClient_Expecter) LogRange(logRangeParam interface{}) *Mo...
method Out (line 1689) | func (_e *MockClient_Expecter) Out(outParam interface{}) *MockClient_O...
method SetDebugLogger (line 1742) | func (_e *MockClient_Expecter) SetDebugLogger(logger interface{}) *Moc...
method SetInfoLogger (line 1795) | func (_e *MockClient_Expecter) SetInfoLogger(logger interface{}) *Mock...
method UpdateProject (line 1855) | func (_e *MockClient_Expecter) UpdateProject(updateProjectParam interf...
method UpdateProjectEstimate (line 1915) | func (_e *MockClient_Expecter) UpdateProjectEstimate(updateProjectEsti...
method UpdateProjectMemberships (line 1975) | func (_e *MockClient_Expecter) UpdateProjectMemberships(updateProjectM...
method UpdateProjectTemplate (line 2035) | func (_e *MockClient_Expecter) UpdateProjectTemplate(updateProjectTemp...
method UpdateProjectUserBillableRate (line 2095) | func (_e *MockClient_Expecter) UpdateProjectUserBillableRate(updatePro...
method UpdateProjectUserCostRate (line 2155) | func (_e *MockClient_Expecter) UpdateProjectUserCostRate(updateProject...
method UpdateTask (line 2215) | func (_e *MockClient_Expecter) UpdateTask(updateTaskParam interface{})...
method UpdateTimeEntry (line 2275) | func (_e *MockClient_Expecter) UpdateTimeEntry(updateTimeEntryParam in...
method WorkspaceUsers (line 2337) | func (_e *MockClient_Expecter) WorkspaceUsers(workspaceUsersParam inte...
type MockClient_AddClient_Call (line 67) | type MockClient_AddClient_Call struct
method Run (line 77) | func (_c *MockClient_AddClient_Call) Run(run func(addClientParam api.A...
method Return (line 90) | func (_c *MockClient_AddClient_Call) Return(client dto.Client, err err...
method RunAndReturn (line 95) | func (_c *MockClient_AddClient_Call) RunAndReturn(run func(addClientPa...
type MockClient_AddProject_Call (line 127) | type MockClient_AddProject_Call struct
method Run (line 137) | func (_c *MockClient_AddProject_Call) Run(run func(addProjectParam api...
method Return (line 150) | func (_c *MockClient_AddProject_Call) Return(project dto.Project, err ...
method RunAndReturn (line 155) | func (_c *MockClient_AddProject_Call) RunAndReturn(run func(addProject...
type MockClient_AddTask_Call (line 187) | type MockClient_AddTask_Call struct
method Run (line 197) | func (_c *MockClient_AddTask_Call) Run(run func(addTaskParam api.AddTa...
method Return (line 210) | func (_c *MockClient_AddTask_Call) Return(task dto.Task, err error) *M...
method RunAndReturn (line 215) | func (_c *MockClient_AddTask_Call) RunAndReturn(run func(addTaskParam ...
type MockClient_ChangeInvoiced_Call (line 238) | type MockClient_ChangeInvoiced_Call struct
method Run (line 248) | func (_c *MockClient_ChangeInvoiced_Call) Run(run func(changeInvoicedP...
method Return (line 261) | func (_c *MockClient_ChangeInvoiced_Call) Return(err error) *MockClien...
method RunAndReturn (line 266) | func (_c *MockClient_ChangeInvoiced_Call) RunAndReturn(run func(change...
type MockClient_CreateTimeEntry_Call (line 298) | type MockClient_CreateTimeEntry_Call struct
method Run (line 308) | func (_c *MockClient_CreateTimeEntry_Call) Run(run func(createTimeEntr...
method Return (line 321) | func (_c *MockClient_CreateTimeEntry_Call) Return(timeEntryImpl dto.Ti...
method RunAndReturn (line 326) | func (_c *MockClient_CreateTimeEntry_Call) RunAndReturn(run func(creat...
type MockClient_DeleteProject_Call (line 358) | type MockClient_DeleteProject_Call struct
method Run (line 368) | func (_c *MockClient_DeleteProject_Call) Run(run func(deleteProjectPar...
method Return (line 381) | func (_c *MockClient_DeleteProject_Call) Return(project dto.Project, e...
method RunAndReturn (line 386) | func (_c *MockClient_DeleteProject_Call) RunAndReturn(run func(deleteP...
type MockClient_DeleteTask_Call (line 418) | type MockClient_DeleteTask_Call struct
method Run (line 428) | func (_c *MockClient_DeleteTask_Call) Run(run func(deleteTaskParam api...
method Return (line 441) | func (_c *MockClient_DeleteTask_Call) Return(task dto.Task, err error)...
method RunAndReturn (line 446) | func (_c *MockClient_DeleteTask_Call) RunAndReturn(run func(deleteTask...
type MockClient_DeleteTimeEntry_Call (line 469) | type MockClient_DeleteTimeEntry_Call struct
method Run (line 479) | func (_c *MockClient_DeleteTimeEntry_Call) Run(run func(deleteTimeEntr...
method Return (line 492) | func (_c *MockClient_DeleteTimeEntry_Call) Return(err error) *MockClie...
method RunAndReturn (line 497) | func (_c *MockClient_DeleteTimeEntry_Call) RunAndReturn(run func(delet...
type MockClient_GetClients_Call (line 531) | type MockClient_GetClients_Call struct
method Run (line 541) | func (_c *MockClient_GetClients_Call) Run(run func(getClientsParam api...
method Return (line 554) | func (_c *MockClient_GetClients_Call) Return(clients []dto.Client, err...
method RunAndReturn (line 559) | func (_c *MockClient_GetClients_Call) RunAndReturn(run func(getClients...
type MockClient_GetHydratedTimeEntry_Call (line 593) | type MockClient_GetHydratedTimeEntry_Call struct
method Run (line 603) | func (_c *MockClient_GetHydratedTimeEntry_Call) Run(run func(getTimeEn...
method Return (line 616) | func (_c *MockClient_GetHydratedTimeEntry_Call) Return(timeEntry *dto....
method RunAndReturn (line 621) | func (_c *MockClient_GetHydratedTimeEntry_Call) RunAndReturn(run func(...
type MockClient_GetHydratedTimeEntryInProgress_Call (line 655) | type MockClient_GetHydratedTimeEntryInProgress_Call struct
method Run (line 665) | func (_c *MockClient_GetHydratedTimeEntryInProgress_Call) Run(run func...
method Return (line 678) | func (_c *MockClient_GetHydratedTimeEntryInProgress_Call) Return(timeE...
method RunAndReturn (line 683) | func (_c *MockClient_GetHydratedTimeEntryInProgress_Call) RunAndReturn...
type MockClient_GetMe_Call (line 715) | type MockClient_GetMe_Call struct
method Run (line 724) | func (_c *MockClient_GetMe_Call) Run(run func()) *MockClient_GetMe_Call {
method Return (line 731) | func (_c *MockClient_GetMe_Call) Return(user dto.User, err error) *Moc...
method RunAndReturn (line 736) | func (_c *MockClient_GetMe_Call) RunAndReturn(run func() (dto.User, er...
type MockClient_GetProject_Call (line 770) | type MockClient_GetProject_Call struct
method Run (line 780) | func (_c *MockClient_GetProject_Call) Run(run func(getProjectParam api...
method Return (line 793) | func (_c *MockClient_GetProject_Call) Return(project *dto.Project, err...
method RunAndReturn (line 798) | func (_c *MockClient_GetProject_Call) RunAndReturn(run func(getProject...
type MockClient_GetProjects_Call (line 832) | type MockClient_GetProjects_Call struct
method Run (line 842) | func (_c *MockClient_GetProjects_Call) Run(run func(getProjectsParam a...
method Return (line 855) | func (_c *MockClient_GetProjects_Call) Return(projects []dto.Project, ...
method RunAndReturn (line 860) | func (_c *MockClient_GetProjects_Call) RunAndReturn(run func(getProjec...
type MockClient_GetTag_Call (line 894) | type MockClient_GetTag_Call struct
method Run (line 904) | func (_c *MockClient_GetTag_Call) Run(run func(getTagParam api.GetTagP...
method Return (line 917) | func (_c *MockClient_GetTag_Call) Return(tag *dto.Tag, err error) *Moc...
method RunAndReturn (line 922) | func (_c *MockClient_GetTag_Call) RunAndReturn(run func(getTagParam ap...
type MockClient_GetTags_Call (line 956) | type MockClient_GetTags_Call struct
method Run (line 966) | func (_c *MockClient_GetTags_Call) Run(run func(getTagsParam api.GetTa...
method Return (line 979) | func (_c *MockClient_GetTags_Call) Return(tags []dto.Tag, err error) *...
method RunAndReturn (line 984) | func (_c *MockClient_GetTags_Call) RunAndReturn(run func(getTagsParam ...
type MockClient_GetTask_Call (line 1016) | type MockClient_GetTask_Call struct
method Run (line 1026) | func (_c *MockClient_GetTask_Call) Run(run func(getTaskParam api.GetTa...
method Return (line 1039) | func (_c *MockClient_GetTask_Call) Return(task dto.Task, err error) *M...
method RunAndReturn (line 1044) | func (_c *MockClient_GetTask_Call) RunAndReturn(run func(getTaskParam ...
type MockClient_GetTasks_Call (line 1078) | type MockClient_GetTasks_Call struct
method Run (line 1088) | func (_c *MockClient_GetTasks_Call) Run(run func(getTasksParam api.Get...
method Return (line 1101) | func (_c *MockClient_GetTasks_Call) Return(tasks []dto.Task, err error...
method RunAndReturn (line 1106) | func (_c *MockClient_GetTasks_Call) RunAndReturn(run func(getTasksPara...
type MockClient_GetTimeEntry_Call (line 1140) | type MockClient_GetTimeEntry_Call struct
method Run (line 1150) | func (_c *MockClient_GetTimeEntry_Call) Run(run func(getTimeEntryParam...
method Return (line 1163) | func (_c *MockClient_GetTimeEntry_Call) Return(timeEntryImpl *dto.Time...
method RunAndReturn (line 1168) | func (_c *MockClient_GetTimeEntry_Call) RunAndReturn(run func(getTimeE...
type MockClient_GetTimeEntryInProgress_Call (line 1202) | type MockClient_GetTimeEntryInProgress_Call struct
method Run (line 1212) | func (_c *MockClient_GetTimeEntryInProgress_Call) Run(run func(getTime...
method Return (line 1225) | func (_c *MockClient_GetTimeEntryInProgress_Call) Return(timeEntryImpl...
method RunAndReturn (line 1230) | func (_c *MockClient_GetTimeEntryInProgress_Call) RunAndReturn(run fun...
type MockClient_GetUser_Call (line 1262) | type MockClient_GetUser_Call struct
method Run (line 1272) | func (_c *MockClient_GetUser_Call) Run(run func(getUser api.GetUser)) ...
method Return (line 1285) | func (_c *MockClient_GetUser_Call) Return(user dto.User, err error) *M...
method RunAndReturn (line 1290) | func (_c *MockClient_GetUser_Call) RunAndReturn(run func(getUser api.G...
type MockClient_GetUserTimeEntries_Call (line 1324) | type MockClient_GetUserTimeEntries_Call struct
method Run (line 1334) | func (_c *MockClient_GetUserTimeEntries_Call) Run(run func(getUserTime...
method Return (line 1347) | func (_c *MockClient_GetUserTimeEntries_Call) Return(timeEntryImpls []...
method RunAndReturn (line 1352) | func (_c *MockClient_GetUserTimeEntries_Call) RunAndReturn(run func(ge...
type MockClient_GetUsersHydratedTimeEntries_Call (line 1386) | type MockClient_GetUsersHydratedTimeEntries_Call struct
method Run (line 1396) | func (_c *MockClient_GetUsersHydratedTimeEntries_Call) Run(run func(ge...
method Return (line 1409) | func (_c *MockClient_GetUsersHydratedTimeEntries_Call) Return(timeEntr...
method RunAndReturn (line 1414) | func (_c *MockClient_GetUsersHydratedTimeEntries_Call) RunAndReturn(ru...
type MockClient_GetWorkspace_Call (line 1446) | type MockClient_GetWorkspace_Call struct
method Run (line 1456) | func (_c *MockClient_GetWorkspace_Call) Run(run func(getWorkspace api....
method Return (line 1469) | func (_c *MockClient_GetWorkspace_Call) Return(workspace dto.Workspace...
method RunAndReturn (line 1474) | func (_c *MockClient_GetWorkspace_Call) RunAndReturn(run func(getWorks...
type MockClient_GetWorkspaces_Call (line 1508) | type MockClient_GetWorkspaces_Call struct
method Run (line 1518) | func (_c *MockClient_GetWorkspaces_Call) Run(run func(getWorkspaces ap...
method Return (line 1531) | func (_c *MockClient_GetWorkspaces_Call) Return(workspaces []dto.Works...
method RunAndReturn (line 1536) | func (_c *MockClient_GetWorkspaces_Call) RunAndReturn(run func(getWork...
type MockClient_Log_Call (line 1570) | type MockClient_Log_Call struct
method Run (line 1580) | func (_c *MockClient_Log_Call) Run(run func(logParam api.LogParam)) *M...
method Return (line 1593) | func (_c *MockClient_Log_Call) Return(timeEntrys []dto.TimeEntry, err ...
method RunAndReturn (line 1598) | func (_c *MockClient_Log_Call) RunAndReturn(run func(logParam api.LogP...
type MockClient_LogRange_Call (line 1632) | type MockClient_LogRange_Call struct
method Run (line 1642) | func (_c *MockClient_LogRange_Call) Run(run func(logRangeParam api.Log...
method Return (line 1655) | func (_c *MockClient_LogRange_Call) Return(timeEntrys []dto.TimeEntry,...
method RunAndReturn (line 1660) | func (_c *MockClient_LogRange_Call) RunAndReturn(run func(logRangePara...
type MockClient_Out_Call (line 1683) | type MockClient_Out_Call struct
method Run (line 1693) | func (_c *MockClient_Out_Call) Run(run func(outParam api.OutParam)) *M...
method Return (line 1706) | func (_c *MockClient_Out_Call) Return(err error) *MockClient_Out_Call {
method RunAndReturn (line 1711) | func (_c *MockClient_Out_Call) RunAndReturn(run func(outParam api.OutP...
type MockClient_SetDebugLogger_Call (line 1736) | type MockClient_SetDebugLogger_Call struct
method Run (line 1746) | func (_c *MockClient_SetDebugLogger_Call) Run(run func(logger api.Logg...
method Return (line 1759) | func (_c *MockClient_SetDebugLogger_Call) Return(client api.Client) *M...
method RunAndReturn (line 1764) | func (_c *MockClient_SetDebugLogger_Call) RunAndReturn(run func(logger...
type MockClient_SetInfoLogger_Call (line 1789) | type MockClient_SetInfoLogger_Call struct
method Run (line 1799) | func (_c *MockClient_SetInfoLogger_Call) Run(run func(logger api.Logge...
method Return (line 1812) | func (_c *MockClient_SetInfoLogger_Call) Return(client api.Client) *Mo...
method RunAndReturn (line 1817) | func (_c *MockClient_SetInfoLogger_Call) RunAndReturn(run func(logger ...
type MockClient_UpdateProject_Call (line 1849) | type MockClient_UpdateProject_Call struct
method Run (line 1859) | func (_c *MockClient_UpdateProject_Call) Run(run func(updateProjectPar...
method Return (line 1872) | func (_c *MockClient_UpdateProject_Call) Return(project dto.Project, e...
method RunAndReturn (line 1877) | func (_c *MockClient_UpdateProject_Call) RunAndReturn(run func(updateP...
type MockClient_UpdateProjectEstimate_Call (line 1909) | type MockClient_UpdateProjectEstimate_Call struct
method Run (line 1919) | func (_c *MockClient_UpdateProjectEstimate_Call) Run(run func(updatePr...
method Return (line 1932) | func (_c *MockClient_UpdateProjectEstimate_Call) Return(project dto.Pr...
method RunAndReturn (line 1937) | func (_c *MockClient_UpdateProjectEstimate_Call) RunAndReturn(run func...
type MockClient_UpdateProjectMemberships_Call (line 1969) | type MockClient_UpdateProjectMemberships_Call struct
method Run (line 1979) | func (_c *MockClient_UpdateProjectMemberships_Call) Run(run func(updat...
method Return (line 1992) | func (_c *MockClient_UpdateProjectMemberships_Call) Return(project dto...
method RunAndReturn (line 1997) | func (_c *MockClient_UpdateProjectMemberships_Call) RunAndReturn(run f...
type MockClient_UpdateProjectTemplate_Call (line 2029) | type MockClient_UpdateProjectTemplate_Call struct
method Run (line 2039) | func (_c *MockClient_UpdateProjectTemplate_Call) Run(run func(updatePr...
method Return (line 2052) | func (_c *MockClient_UpdateProjectTemplate_Call) Return(project dto.Pr...
method RunAndReturn (line 2057) | func (_c *MockClient_UpdateProjectTemplate_Call) RunAndReturn(run func...
type MockClient_UpdateProjectUserBillableRate_Call (line 2089) | type MockClient_UpdateProjectUserBillableRate_Call struct
method Run (line 2099) | func (_c *MockClient_UpdateProjectUserBillableRate_Call) Run(run func(...
method Return (line 2112) | func (_c *MockClient_UpdateProjectUserBillableRate_Call) Return(projec...
method RunAndReturn (line 2117) | func (_c *MockClient_UpdateProjectUserBillableRate_Call) RunAndReturn(...
type MockClient_UpdateProjectUserCostRate_Call (line 2149) | type MockClient_UpdateProjectUserCostRate_Call struct
method Run (line 2159) | func (_c *MockClient_UpdateProjectUserCostRate_Call) Run(run func(upda...
method Return (line 2172) | func (_c *MockClient_UpdateProjectUserCostRate_Call) Return(project dt...
method RunAndReturn (line 2177) | func (_c *MockClient_UpdateProjectUserCostRate_Call) RunAndReturn(run ...
type MockClient_UpdateTask_Call (line 2209) | type MockClient_UpdateTask_Call struct
method Run (line 2219) | func (_c *MockClient_UpdateTask_Call) Run(run func(updateTaskParam api...
method Return (line 2232) | func (_c *MockClient_UpdateTask_Call) Return(task dto.Task, err error)...
method RunAndReturn (line 2237) | func (_c *MockClient_UpdateTask_Call) RunAndReturn(run func(updateTask...
type MockClient_UpdateTimeEntry_Call (line 2269) | type MockClient_UpdateTimeEntry_Call struct
method Run (line 2279) | func (_c *MockClient_UpdateTimeEntry_Call) Run(run func(updateTimeEntr...
method Return (line 2292) | func (_c *MockClient_UpdateTimeEntry_Call) Return(timeEntryImpl dto.Ti...
method RunAndReturn (line 2297) | func (_c *MockClient_UpdateTimeEntry_Call) RunAndReturn(run func(updat...
type MockClient_WorkspaceUsers_Call (line 2331) | type MockClient_WorkspaceUsers_Call struct
method Run (line 2341) | func (_c *MockClient_WorkspaceUsers_Call) Run(run func(workspaceUsersP...
method Return (line 2354) | func (_c *MockClient_WorkspaceUsers_Call) Return(users []dto.User, err...
method RunAndReturn (line 2359) | func (_c *MockClient_WorkspaceUsers_Call) RunAndReturn(run func(worksp...
FILE: internal/mocks/mock_Config.go
function NewMockConfig (line 16) | func NewMockConfig(t interface {
type MockConfig (line 29) | type MockConfig struct
method EXPECT (line 37) | func (_m *MockConfig) EXPECT() *MockConfig_Expecter {
method All (line 42) | func (_mock *MockConfig) All() map[string]interface{} {
method Get (line 88) | func (_mock *MockConfig) Get(s string) interface{} {
method GetBool (line 141) | func (_mock *MockConfig) GetBool(s string) bool {
method GetInt (line 192) | func (_mock *MockConfig) GetInt(s string) int {
method GetString (line 243) | func (_mock *MockConfig) GetString(s string) string {
method GetStringSlice (line 294) | func (_mock *MockConfig) GetStringSlice(s string) []string {
method GetWorkWeekdays (line 347) | func (_mock *MockConfig) GetWorkWeekdays() []string {
method InteractivePageSize (line 393) | func (_mock *MockConfig) InteractivePageSize() int {
method IsAllowNameForID (line 437) | func (_mock *MockConfig) IsAllowNameForID() bool {
method IsDebuging (line 481) | func (_mock *MockConfig) IsDebuging() bool {
method IsInteractive (line 525) | func (_mock *MockConfig) IsInteractive() bool {
method IsSearchProjectWithClientsName (line 569) | func (_mock *MockConfig) IsSearchProjectWithClientsName() bool {
method Language (line 613) | func (_mock *MockConfig) Language() language.Tag {
method LogLevel (line 657) | func (_mock *MockConfig) LogLevel() string {
method Save (line 701) | func (_mock *MockConfig) Save() error {
method SetBool (line 745) | func (_mock *MockConfig) SetBool(s string, b bool) {
method SetInt (line 791) | func (_mock *MockConfig) SetInt(s string, n int) {
method SetLanguage (line 837) | func (_mock *MockConfig) SetLanguage(tag language.Tag) {
method SetString (line 877) | func (_mock *MockConfig) SetString(s string, s1 string) {
method SetStringSlice (line 923) | func (_mock *MockConfig) SetStringSlice(s string, strings []string) {
method SetTimeZone (line 969) | func (_mock *MockConfig) SetTimeZone(location *time.Location) {
method TimeZone (line 1009) | func (_mock *MockConfig) TimeZone() *time.Location {
type MockConfig_Expecter (line 33) | type MockConfig_Expecter struct
method All (line 66) | func (_e *MockConfig_Expecter) All() *MockConfig_All_Call {
method Get (line 113) | func (_e *MockConfig_Expecter) Get(s interface{}) *MockConfig_Get_Call {
method GetBool (line 164) | func (_e *MockConfig_Expecter) GetBool(s interface{}) *MockConfig_GetB...
method GetInt (line 215) | func (_e *MockConfig_Expecter) GetInt(s interface{}) *MockConfig_GetIn...
method GetString (line 266) | func (_e *MockConfig_Expecter) GetString(s interface{}) *MockConfig_Ge...
method GetStringSlice (line 319) | func (_e *MockConfig_Expecter) GetStringSlice(s interface{}) *MockConf...
method GetWorkWeekdays (line 371) | func (_e *MockConfig_Expecter) GetWorkWeekdays() *MockConfig_GetWorkWe...
method InteractivePageSize (line 415) | func (_e *MockConfig_Expecter) InteractivePageSize() *MockConfig_Inter...
method IsAllowNameForID (line 459) | func (_e *MockConfig_Expecter) IsAllowNameForID() *MockConfig_IsAllowN...
method IsDebuging (line 503) | func (_e *MockConfig_Expecter) IsDebuging() *MockConfig_IsDebuging_Call {
method IsInteractive (line 547) | func (_e *MockConfig_Expecter) IsInteractive() *MockConfig_IsInteracti...
method IsSearchProjectWithClientsName (line 591) | func (_e *MockConfig_Expecter) IsSearchProjectWithClientsName() *MockC...
method Language (line 635) | func (_e *MockConfig_Expecter) Language() *MockConfig_Language_Call {
method LogLevel (line 679) | func (_e *MockConfig_Expecter) LogLevel() *MockConfig_LogLevel_Call {
method Save (line 723) | func (_e *MockConfig_Expecter) Save() *MockConfig_Save_Call {
method SetBool (line 758) | func (_e *MockConfig_Expecter) SetBool(s interface{}, b interface{}) *...
method SetInt (line 804) | func (_e *MockConfig_Expecter) SetInt(s interface{}, n interface{}) *M...
method SetLanguage (line 849) | func (_e *MockConfig_Expecter) SetLanguage(tag interface{}) *MockConfi...
method SetString (line 890) | func (_e *MockConfig_Expecter) SetString(s interface{}, s1 interface{}...
method SetStringSlice (line 936) | func (_e *MockConfig_Expecter) SetStringSlice(s interface{}, strings i...
method SetTimeZone (line 981) | func (_e *MockConfig_Expecter) SetTimeZone(location interface{}) *Mock...
method TimeZone (line 1033) | func (_e *MockConfig_Expecter) TimeZone() *MockConfig_TimeZone_Call {
type MockConfig_All_Call (line 61) | type MockConfig_All_Call struct
method Run (line 70) | func (_c *MockConfig_All_Call) Run(run func()) *MockConfig_All_Call {
method Return (line 77) | func (_c *MockConfig_All_Call) Return(stringToIfaceVal map[string]inte...
method RunAndReturn (line 82) | func (_c *MockConfig_All_Call) RunAndReturn(run func() map[string]inte...
type MockConfig_Get_Call (line 107) | type MockConfig_Get_Call struct
method Run (line 117) | func (_c *MockConfig_Get_Call) Run(run func(s string)) *MockConfig_Get...
method Return (line 130) | func (_c *MockConfig_Get_Call) Return(ifaceVal interface{}) *MockConfi...
method RunAndReturn (line 135) | func (_c *MockConfig_Get_Call) RunAndReturn(run func(s string) interfa...
type MockConfig_GetBool_Call (line 158) | type MockConfig_GetBool_Call struct
method Run (line 168) | func (_c *MockConfig_GetBool_Call) Run(run func(s string)) *MockConfig...
method Return (line 181) | func (_c *MockConfig_GetBool_Call) Return(b bool) *MockConfig_GetBool_...
method RunAndReturn (line 186) | func (_c *MockConfig_GetBool_Call) RunAndReturn(run func(s string) boo...
type MockConfig_GetInt_Call (line 209) | type MockConfig_GetInt_Call struct
method Run (line 219) | func (_c *MockConfig_GetInt_Call) Run(run func(s string)) *MockConfig_...
method Return (line 232) | func (_c *MockConfig_GetInt_Call) Return(n int) *MockConfig_GetInt_Call {
method RunAndReturn (line 237) | func (_c *MockConfig_GetInt_Call) RunAndReturn(run func(s string) int)...
type MockConfig_GetString_Call (line 260) | type MockConfig_GetString_Call struct
method Run (line 270) | func (_c *MockConfig_GetString_Call) Run(run func(s string)) *MockConf...
method Return (line 283) | func (_c *MockConfig_GetString_Call) Return(s1 string) *MockConfig_Get...
method RunAndReturn (line 288) | func (_c *MockConfig_GetString_Call) RunAndReturn(run func(s string) s...
type MockConfig_GetStringSlice_Call (line 313) | type MockConfig_GetStringSlice_Call struct
method Run (line 323) | func (_c *MockConfig_GetStringSlice_Call) Run(run func(s string)) *Moc...
method Return (line 336) | func (_c *MockConfig_GetStringSlice_Call) Return(strings []string) *Mo...
method RunAndReturn (line 341) | func (_c *MockConfig_GetStringSlice_Call) RunAndReturn(run func(s stri...
type MockConfig_GetWorkWeekdays_Call (line 366) | type MockConfig_GetWorkWeekdays_Call struct
method Run (line 375) | func (_c *MockConfig_GetWorkWeekdays_Call) Run(run func()) *MockConfig...
method Return (line 382) | func (_c *MockConfig_GetWorkWeekdays_Call) Return(strings []string) *M...
method RunAndReturn (line 387) | func (_c *MockConfig_GetWorkWeekdays_Call) RunAndReturn(run func() []s...
type MockConfig_InteractivePageSize_Call (line 410) | type MockConfig_InteractivePageSize_Call struct
method Run (line 419) | func (_c *MockConfig_InteractivePageSize_Call) Run(run func()) *MockCo...
method Return (line 426) | func (_c *MockConfig_InteractivePageSize_Call) Return(n int) *MockConf...
method RunAndReturn (line 431) | func (_c *MockConfig_InteractivePageSize_Call) RunAndReturn(run func()...
type MockConfig_IsAllowNameForID_Call (line 454) | type MockConfig_IsAllowNameForID_Call struct
method Run (line 463) | func (_c *MockConfig_IsAllowNameForID_Call) Run(run func()) *MockConfi...
method Return (line 470) | func (_c *MockConfig_IsAllowNameForID_Call) Return(b bool) *MockConfig...
method RunAndReturn (line 475) | func (_c *MockConfig_IsAllowNameForID_Call) RunAndReturn(run func() bo...
type MockConfig_IsDebuging_Call (line 498) | type MockConfig_IsDebuging_Call struct
method Run (line 507) | func (_c *MockConfig_IsDebuging_Call) Run(run func()) *MockConfig_IsDe...
method Return (line 514) | func (_c *MockConfig_IsDebuging_Call) Return(b bool) *MockConfig_IsDeb...
method RunAndReturn (line 519) | func (_c *MockConfig_IsDebuging_Call) RunAndReturn(run func() bool) *M...
type MockConfig_IsInteractive_Call (line 542) | type MockConfig_IsInteractive_Call struct
method Run (line 551) | func (_c *MockConfig_IsInteractive_Call) Run(run func()) *MockConfig_I...
method Return (line 558) | func (_c *MockConfig_IsInteractive_Call) Return(b bool) *MockConfig_Is...
method RunAndReturn (line 563) | func (_c *MockConfig_IsInteractive_Call) RunAndReturn(run func() bool)...
type MockConfig_IsSearchProjectWithClientsName_Call (line 586) | type MockConfig_IsSearchProjectWithClientsName_Call struct
method Run (line 595) | func (_c *MockConfig_IsSearchProjectWithClientsName_Call) Run(run func...
method Return (line 602) | func (_c *MockConfig_IsSearchProjectWithClientsName_Call) Return(b boo...
method RunAndReturn (line 607) | func (_c *MockConfig_IsSearchProjectWithClientsName_Call) RunAndReturn...
type MockConfig_Language_Call (line 630) | type MockConfig_Language_Call struct
method Run (line 639) | func (_c *MockConfig_Language_Call) Run(run func()) *MockConfig_Langua...
method Return (line 646) | func (_c *MockConfig_Language_Call) Return(tag language.Tag) *MockConf...
method RunAndReturn (line 651) | func (_c *MockConfig_Language_Call) RunAndReturn(run func() language.T...
type MockConfig_LogLevel_Call (line 674) | type MockConfig_LogLevel_Call struct
method Run (line 683) | func (_c *MockConfig_LogLevel_Call) Run(run func()) *MockConfig_LogLev...
method Return (line 690) | func (_c *MockConfig_LogLevel_Call) Return(s string) *MockConfig_LogLe...
method RunAndReturn (line 695) | func (_c *MockConfig_LogLevel_Call) RunAndReturn(run func() string) *M...
type MockConfig_Save_Call (line 718) | type MockConfig_Save_Call struct
method Run (line 727) | func (_c *MockConfig_Save_Call) Run(run func()) *MockConfig_Save_Call {
method Return (line 734) | func (_c *MockConfig_Save_Call) Return(err error) *MockConfig_Save_Call {
method RunAndReturn (line 739) | func (_c *MockConfig_Save_Call) RunAndReturn(run func() error) *MockCo...
type MockConfig_SetBool_Call (line 751) | type MockConfig_SetBool_Call struct
method Run (line 762) | func (_c *MockConfig_SetBool_Call) Run(run func(s string, b bool)) *Mo...
method Return (line 780) | func (_c *MockConfig_SetBool_Call) Return() *MockConfig_SetBool_Call {
method RunAndReturn (line 785) | func (_c *MockConfig_SetBool_Call) RunAndReturn(run func(s string, b b...
type MockConfig_SetInt_Call (line 797) | type MockConfig_SetInt_Call struct
method Run (line 808) | func (_c *MockConfig_SetInt_Call) Run(run func(s string, n int)) *Mock...
method Return (line 826) | func (_c *MockConfig_SetInt_Call) Return() *MockConfig_SetInt_Call {
method RunAndReturn (line 831) | func (_c *MockConfig_SetInt_Call) RunAndReturn(run func(s string, n in...
type MockConfig_SetLanguage_Call (line 843) | type MockConfig_SetLanguage_Call struct
method Run (line 853) | func (_c *MockConfig_SetLanguage_Call) Run(run func(tag language.Tag))...
method Return (line 866) | func (_c *MockConfig_SetLanguage_Call) Return() *MockConfig_SetLanguag...
method RunAndReturn (line 871) | func (_c *MockConfig_SetLanguage_Call) RunAndReturn(run func(tag langu...
type MockConfig_SetString_Call (line 883) | type MockConfig_SetString_Call struct
method Run (line 894) | func (_c *MockConfig_SetString_Call) Run(run func(s string, s1 string)...
method Return (line 912) | func (_c *MockConfig_SetString_Call) Return() *MockConfig_SetString_Ca...
method RunAndReturn (line 917) | func (_c *MockConfig_SetString_Call) RunAndReturn(run func(s string, s...
type MockConfig_SetStringSlice_Call (line 929) | type MockConfig_SetStringSlice_Call struct
method Run (line 940) | func (_c *MockConfig_SetStringSlice_Call) Run(run func(s string, strin...
method Return (line 958) | func (_c *MockConfig_SetStringSlice_Call) Return() *MockConfig_SetStri...
method RunAndReturn (line 963) | func (_c *MockConfig_SetStringSlice_Call) RunAndReturn(run func(s stri...
type MockConfig_SetTimeZone_Call (line 975) | type MockConfig_SetTimeZone_Call struct
method Run (line 985) | func (_c *MockConfig_SetTimeZone_Call) Run(run func(location *time.Loc...
method Return (line 998) | func (_c *MockConfig_SetTimeZone_Call) Return() *MockConfig_SetTimeZon...
method RunAndReturn (line 1003) | func (_c *MockConfig_SetTimeZone_Call) RunAndReturn(run func(location ...
type MockConfig_TimeZone_Call (line 1028) | type MockConfig_TimeZone_Call struct
method Run (line 1037) | func (_c *MockConfig_TimeZone_Call) Run(run func()) *MockConfig_TimeZo...
method Return (line 1044) | func (_c *MockConfig_TimeZone_Call) Return(location *time.Location) *M...
method RunAndReturn (line 1049) | func (_c *MockConfig_TimeZone_Call) RunAndReturn(run func() *time.Loca...
FILE: internal/mocks/mock_Factory.go
function NewMockFactory (line 17) | func NewMockFactory(t interface {
type MockFactory (line 30) | type MockFactory struct
method EXPECT (line 38) | func (_m *MockFactory) EXPECT() *MockFactory_Expecter {
method Client (line 43) | func (_mock *MockFactory) Client() (api.Client, error) {
method Config (line 98) | func (_mock *MockFactory) Config() cmdutil.Config {
method GetUserID (line 144) | func (_mock *MockFactory) GetUserID() (string, error) {
method GetWorkspace (line 197) | func (_mock *MockFactory) GetWorkspace() (dto.Workspace, error) {
method GetWorkspaceID (line 250) | func (_mock *MockFactory) GetWorkspaceID() (string, error) {
method UI (line 303) | func (_mock *MockFactory) UI() ui.UI {
method Version (line 349) | func (_mock *MockFactory) Version() cmdutil.Version {
type MockFactory_Expecter (line 34) | type MockFactory_Expecter struct
method Client (line 76) | func (_e *MockFactory_Expecter) Client() *MockFactory_Client_Call {
method Config (line 122) | func (_e *MockFactory_Expecter) Config() *MockFactory_Config_Call {
method GetUserID (line 175) | func (_e *MockFactory_Expecter) GetUserID() *MockFactory_GetUserID_Call {
method GetWorkspace (line 228) | func (_e *MockFactory_Expecter) GetWorkspace() *MockFactory_GetWorkspa...
method GetWorkspaceID (line 281) | func (_e *MockFactory_Expecter) GetWorkspaceID() *MockFactory_GetWorks...
method UI (line 327) | func (_e *MockFactory_Expecter) UI() *MockFactory_UI_Call {
method Version (line 371) | func (_e *MockFactory_Expecter) Version() *MockFactory_Version_Call {
type MockFactory_Client_Call (line 71) | type MockFactory_Client_Call struct
method Run (line 80) | func (_c *MockFactory_Client_Call) Run(run func()) *MockFactory_Client...
method Return (line 87) | func (_c *MockFactory_Client_Call) Return(client api.Client, err error...
method RunAndReturn (line 92) | func (_c *MockFactory_Client_Call) RunAndReturn(run func() (api.Client...
type MockFactory_Config_Call (line 117) | type MockFactory_Config_Call struct
method Run (line 126) | func (_c *MockFactory_Config_Call) Run(run func()) *MockFactory_Config...
method Return (line 133) | func (_c *MockFactory_Config_Call) Return(config cmdutil.Config) *Mock...
method RunAndReturn (line 138) | func (_c *MockFactory_Config_Call) RunAndReturn(run func() cmdutil.Con...
type MockFactory_GetUserID_Call (line 170) | type MockFactory_GetUserID_Call struct
method Run (line 179) | func (_c *MockFactory_GetUserID_Call) Run(run func()) *MockFactory_Get...
method Return (line 186) | func (_c *MockFactory_GetUserID_Call) Return(s string, err error) *Moc...
method RunAndReturn (line 191) | func (_c *MockFactory_GetUserID_Call) RunAndReturn(run func() (string,...
type MockFactory_GetWorkspace_Call (line 223) | type MockFactory_GetWorkspace_Call struct
method Run (line 232) | func (_c *MockFactory_GetWorkspace_Call) Run(run func()) *MockFactory_...
method Return (line 239) | func (_c *MockFactory_GetWorkspace_Call) Return(workspace dto.Workspac...
method RunAndReturn (line 244) | func (_c *MockFactory_GetWorkspace_Call) RunAndReturn(run func() (dto....
type MockFactory_GetWorkspaceID_Call (line 276) | type MockFactory_GetWorkspaceID_Call struct
method Run (line 285) | func (_c *MockFactory_GetWorkspaceID_Call) Run(run func()) *MockFactor...
method Return (line 292) | func (_c *MockFactory_GetWorkspaceID_Call) Return(s string, err error)...
method RunAndReturn (line 297) | func (_c *MockFactory_GetWorkspaceID_Call) RunAndReturn(run func() (st...
type MockFactory_UI_Call (line 322) | type MockFactory_UI_Call struct
method Run (line 331) | func (_c *MockFactory_UI_Call) Run(run func()) *MockFactory_UI_Call {
method Return (line 338) | func (_c *MockFactory_UI_Call) Return(uI ui.UI) *MockFactory_UI_Call {
method RunAndReturn (line 343) | func (_c *MockFactory_UI_Call) RunAndReturn(run func() ui.UI) *MockFac...
type MockFactory_Version_Call (line 366) | type MockFactory_Version_Call struct
method Run (line 375) | func (_c *MockFactory_Version_Call) Run(run func()) *MockFactory_Versi...
method Return (line 382) | func (_c *MockFactory_Version_Call) Return(version cmdutil.Version) *M...
method RunAndReturn (line 387) | func (_c *MockFactory_Version_Call) RunAndReturn(run func() cmdutil.Ve...
FILE: internal/mocks/simple_config.go
type SimpleConfig (line 12) | type SimpleConfig struct
method GetBool (line 32) | func (d *SimpleConfig) GetBool(n string) bool {
method SetBool (line 53) | func (*SimpleConfig) SetBool(_ string, _ bool) {
method GetInt (line 57) | func (d *SimpleConfig) GetInt(n string) int {
method SetInt (line 68) | func (*SimpleConfig) SetInt(_ string, _ int) {
method GetString (line 72) | func (d *SimpleConfig) GetString(n string) string {
method SetString (line 88) | func (*SimpleConfig) SetString(_, _ string) {
method GetStringSlice (line 92) | func (d *SimpleConfig) GetStringSlice(n string) []string {
method SetStringSlice (line 101) | func (*SimpleConfig) SetStringSlice(_ string, _ []string) {
method IsDebuging (line 105) | func (d *SimpleConfig) IsDebuging() bool {
method IsAllowNameForID (line 109) | func (d *SimpleConfig) IsAllowNameForID() bool {
method IsInteractive (line 113) | func (d *SimpleConfig) IsInteractive() bool {
method GetWorkWeekdays (line 117) | func (d *SimpleConfig) GetWorkWeekdays() []string {
method SetLanguage (line 121) | func (d *SimpleConfig) SetLanguage(l language.Tag) {
method Language (line 125) | func (d *SimpleConfig) Language() language.Tag {
method Get (line 129) | func (*SimpleConfig) Get(_ string) interface{} {
method All (line 133) | func (*SimpleConfig) All() map[string]interface{} {
method LogLevel (line 137) | func (d *SimpleConfig) LogLevel() string {
method TimeZone (line 142) | func (s *SimpleConfig) TimeZone() *time.Location {
method SetTimeZone (line 151) | func (s *SimpleConfig) SetTimeZone(loc *time.Location) {
method IsSearchProjectWithClientsName (line 157) | func (s *SimpleConfig) IsSearchProjectWithClientsName() bool {
method InteractivePageSize (line 163) | func (s *SimpleConfig) InteractivePageSize() int {
method Save (line 167) | func (*SimpleConfig) Save() error {
FILE: internal/testhlp/helper.go
function MustParseTime (line 6) | func MustParseTime(l, v string) time.Time {
FILE: pkg/cmd/client/add/add.go
function NewCmdAdd (line 15) | func NewCmdAdd(
FILE: pkg/cmd/client/add/add_test.go
function TestCmdAdd (line 17) | func TestCmdAdd(t *testing.T) {
function TestCmdAddReport (line 104) | func TestCmdAddReport(t *testing.T) {
FILE: pkg/cmd/client/client.go
function NewCmdClient (line 11) | func NewCmdClient(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/client/list/list.go
function NewCmdList (line 15) | func NewCmdList(
FILE: pkg/cmd/client/list/list_test.go
type report (line 17) | type report
function TestCmdList (line 19) | func TestCmdList(t *testing.T) {
FILE: pkg/cmd/client/util/util.go
type OutputFlags (line 13) | type OutputFlags struct
method Check (line 20) | func (of OutputFlags) Check() error {
function AddReportFlags (line 30) | func AddReportFlags(cmd *cobra.Command, of *OutputFlags) {
function Report (line 39) | func Report(cs []dto.Client, out io.Writer, of OutputFlags) error {
FILE: pkg/cmd/completion/completion.go
constant bash (line 14) | bash = "bash"
constant zsh (line 15) | zsh = "zsh"
constant fish (line 16) | fish = "fish"
constant powershell (line 17) | powershell = "powershell"
function NewCmdCompletion (line 21) | func NewCmdCompletion() *cobra.Command {
function genZshCompletion (line 89) | func genZshCompletion(cmd *cobra.Command, w io.Writer) error {
FILE: pkg/cmd/config/config.go
function NewCmdConfig (line 46) | func NewCmdConfig(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/config/get/get.go
function NewCmdGet (line 11) | func NewCmdGet(
FILE: pkg/cmd/config/get/get_test.go
function newCmd (line 17) | func newCmd(f cmdutil.Factory) *cobra.Command {
function TestGetCmdArgs (line 31) | func TestGetCmdArgs(t *testing.T) {
function TestGetCmdRun (line 61) | func TestGetCmdRun(t *testing.T) {
FILE: pkg/cmd/config/init/init.go
function queue (line 16) | func queue(
function NewCmdInit (line 29) | func NewCmdInit(f cmdutil.Factory) *cobra.Command {
function setTimezone (line 147) | func setTimezone(i ui.UI, config cmdutil.Config) func() error {
function setLanguage (line 171) | func setLanguage(i ui.UI, config cmdutil.Config) func() error {
function setWeekdays (line 205) | func setWeekdays(config cmdutil.Config, i ui.UI) (err error) {
function setUser (line 219) | func setUser(c api.Client, config cmdutil.Config, i ui.UI) error {
function setWorkspace (line 250) | func setWorkspace(c api.Client, config cmdutil.Config, i ui.UI) error {
function updateInt (line 276) | func updateInt(ui ui.UI, config cmdutil.Config, param, desc string,
function updateFlag (line 289) | func updateFlag(
FILE: pkg/cmd/config/init/init_test.go
function setStringFn (line 22) | func setStringFn(config *mocks.MockConfig, name, value string) *mock.Call {
function setBoolFn (line 33) | func setBoolFn(config *mocks.MockConfig, name string, first, value bool)...
function TestInitCmd (line 44) | func TestInitCmd(t *testing.T) {
function TestInitCmdCtrlC (line 237) | func TestInitCmdCtrlC(t *testing.T) {
function TestInitCmdCtrlCAtToken (line 264) | func TestInitCmdCtrlCAtToken(t *testing.T) {
FILE: pkg/cmd/config/list/list.go
function NewCmdList (line 11) | func NewCmdList(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/config/list/list_test.go
function TestListCmd (line 15) | func TestListCmd(t *testing.T) {
FILE: pkg/cmd/config/set/set.go
function NewCmdSet (line 17) | func NewCmdSet(
FILE: pkg/cmd/config/set/set_test.go
function TestSetCmdArgs (line 16) | func TestSetCmdArgs(t *testing.T) {
function TestSetCmdRun (line 40) | func TestSetCmdRun(t *testing.T) {
function TestSetCmdShouldFail (line 145) | func TestSetCmdShouldFail(t *testing.T) {
FILE: pkg/cmd/config/util/util.go
constant FormatYAML (line 14) | FormatYAML = "yaml"
constant FormatJSON (line 15) | FormatJSON = "json"
function AddReportFlags (line 18) | func AddReportFlags(cmd *cobra.Command, format *string) error {
function Report (line 25) | func Report(out io.Writer, format string, v interface{}) error {
FILE: pkg/cmd/project/add/add.go
function NewCmdAdd (line 18) | func NewCmdAdd(
FILE: pkg/cmd/project/add/add_test.go
function TestCmdAdd (line 19) | func TestCmdAdd(t *testing.T) {
function TestCmdAddReport (line 246) | func TestCmdAddReport(t *testing.T) {
FILE: pkg/cmd/project/edit/edit.go
function NewCmdEdit (line 20) | func NewCmdEdit(
FILE: pkg/cmd/project/edit/edit_test.go
type report (line 17) | type report
function TestEditCmd (line 19) | func TestEditCmd(t *testing.T) {
function TestEditCmdReport (line 311) | func TestEditCmdReport(t *testing.T) {
FILE: pkg/cmd/project/get/get.go
function NewCmdGet (line 20) | func NewCmdGet(
FILE: pkg/cmd/project/get/get_test.go
function TestCmdGet (line 17) | func TestCmdGet(t *testing.T) {
function TestCmdGetReport (line 218) | func TestCmdGetReport(t *testing.T) {
FILE: pkg/cmd/project/list/list.go
function NewCmdList (line 18) | func NewCmdList(
FILE: pkg/cmd/project/list/list_test.go
function TestCmdList (line 17) | func TestCmdList(t *testing.T) {
function TestCmdListReport (line 260) | func TestCmdListReport(t *testing.T) {
FILE: pkg/cmd/project/project.go
function NewCmdProject (line 13) | func NewCmdProject(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/project/util/util.go
type OutputFlags (line 14) | type OutputFlags struct
method Check (line 21) | func (of OutputFlags) Check() error {
function AddReportFlags (line 31) | func AddReportFlags(cmd *cobra.Command, of *OutputFlags) {
function Report (line 40) | func Report(list []dto.Project, out io.Writer, f OutputFlags) error {
function ReportOne (line 56) | func ReportOne(p dto.Project, out io.Writer, f OutputFlags) error {
FILE: pkg/cmd/root.go
function NewCmdRoot (line 22) | func NewCmdRoot(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/tag/tag.go
function NewCmdTag (line 15) | func NewCmdTag(f cmdutil.Factory) *cobra.Command {
function getTags (line 78) | func getTags(f cmdutil.Factory, name string, archived bool) ([]dto.Tag, ...
FILE: pkg/cmd/task/add/add.go
function NewCmdAdd (line 15) | func NewCmdAdd(
FILE: pkg/cmd/task/add/add_test.go
function TestCmdAdd (line 18) | func TestCmdAdd(t *testing.T) {
function TestCmdAddReport (line 296) | func TestCmdAddReport(t *testing.T) {
FILE: pkg/cmd/task/delete/delete.go
function NewCmdDelete (line 20) | func NewCmdDelete(
FILE: pkg/cmd/task/delete/delete_test.go
type report (line 17) | type report
function TestCmdDelete (line 19) | func TestCmdDelete(t *testing.T) {
function TestCmdDeleteReport (line 238) | func TestCmdDeleteReport(t *testing.T) {
FILE: pkg/cmd/task/done/done.go
function NewCmdDone (line 22) | func NewCmdDone(
FILE: pkg/cmd/task/done/done_test.go
function TestCmdDone (line 17) | func TestCmdDone(t *testing.T) {
function TestCmdDoneReport (line 356) | func TestCmdDoneReport(t *testing.T) {
FILE: pkg/cmd/task/edit/edit.go
function NewCmdEdit (line 20) | func NewCmdEdit(
FILE: pkg/cmd/task/edit/edit_test.go
function TestCmdEdit (line 18) | func TestCmdEdit(t *testing.T) {
function TestCmdEditReport (line 407) | func TestCmdEditReport(t *testing.T) {
FILE: pkg/cmd/task/list/list.go
function NewCmdList (line 16) | func NewCmdList(
FILE: pkg/cmd/task/list/list_test.go
type report (line 17) | type report
function TestCmdList (line 19) | func TestCmdList(t *testing.T) {
function TestCmdListReport (line 182) | func TestCmdListReport(t *testing.T) {
FILE: pkg/cmd/task/quick-add/quick-add.go
function NewCmdQuickAdd (line 19) | func NewCmdQuickAdd(
FILE: pkg/cmd/task/quick-add/quick-add_test.go
function TestCmdQuickAdd (line 17) | func TestCmdQuickAdd(t *testing.T) {
function TestCmdQuickAddReport (line 245) | func TestCmdQuickAddReport(t *testing.T) {
FILE: pkg/cmd/task/task.go
function NewCmdTask (line 15) | func NewCmdTask(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/task/util/read-flags.go
function TaskAddPropFlags (line 14) | func TaskAddPropFlags(cmd *cobra.Command, f cmdutil.Factory) {
type FlagsDTO (line 32) | type FlagsDTO struct
function TaskReadFlags (line 42) | func TaskReadFlags(cmd *cobra.Command, f cmdutil.Factory) (p FlagsDTO, e...
FILE: pkg/cmd/task/util/report.go
type OutputFlags (line 11) | type OutputFlags struct
method Check (line 19) | func (of OutputFlags) Check() error {
function TaskAddReportFlags (line 29) | func TaskAddReportFlags(cmd *cobra.Command, of *OutputFlags) {
function TaskReport (line 38) | func TaskReport(cmd *cobra.Command, of OutputFlags, tasks ...dto.Task) e...
FILE: pkg/cmd/time-entry/clone/clone.go
function NewCmdClone (line 17) | func NewCmdClone(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/time-entry/delete/delete.go
function NewCmdDelete (line 15) | func NewCmdDelete(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/time-entry/edit-multipple/edit-multiple.go
function NewCmdEditMultiple (line 16) | func NewCmdEditMultiple(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/time-entry/edit/edit.go
function NewCmdEdit (line 18) | func NewCmdEdit(
FILE: pkg/cmd/time-entry/edit/edit_test.go
function TestNewCmdEditWhenChangingProjectOrTask (line 17) | func TestNewCmdEditWhenChangingProjectOrTask(t *testing.T) {
FILE: pkg/cmd/time-entry/in/in.go
function NewCmdIn (line 19) | func NewCmdIn(
FILE: pkg/cmd/time-entry/in/in_test.go
function TestNewCmdIn_ShouldBeBothBillableAndNotBillable (line 22) | func TestNewCmdIn_ShouldBeBothBillableAndNotBillable(t *testing.T) {
function TestNewCmdIn_ShouldNotSetBillable_WhenNotAsked (line 60) | func TestNewCmdIn_ShouldNotSetBillable_WhenNotAsked(t *testing.T) {
function TestNewCmdIn_ShouldLookupProject_WithAndWithoutClient (line 159) | func TestNewCmdIn_ShouldLookupProject_WithAndWithoutClient(t *testing.T) {
FILE: pkg/cmd/time-entry/invoiced/invoiced.go
function NewCmdInvoiced (line 17) | func NewCmdInvoiced(f cmdutil.Factory) []*cobra.Command {
function changeInvoiced (line 81) | func changeInvoiced(
FILE: pkg/cmd/time-entry/manual/manual.go
function NewCmdManual (line 18) | func NewCmdManual(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/time-entry/out/out.go
function NewCmdOut (line 17) | func NewCmdOut(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/time-entry/report/last-day/last-day.go
function NewCmdLastDay (line 11) | func NewCmdLastDay(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/time-entry/report/last-month/last-month.go
function NewCmdLastMonth (line 11) | func NewCmdLastMonth(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/time-entry/report/last-week-day/last-week-day.go
function NewCmdLastWeekDay (line 17) | func NewCmdLastWeekDay(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/time-entry/report/last-week/last-week.go
function NewCmdLastWeek (line 11) | func NewCmdLastWeek(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/time-entry/report/report.go
function NewCmdReport (line 22) | func NewCmdReport(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/time-entry/report/this-month/this-month.go
function NewCmdThisMonth (line 11) | func NewCmdThisMonth(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/time-entry/report/this-week/this-week.go
function NewCmdThisWeek (line 11) | func NewCmdThisWeek(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/time-entry/report/today/today.go
function NewCmdToday (line 11) | func NewCmdToday(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/time-entry/report/today/today_test.go
function TestCmdToday (line 19) | func TestCmdToday(t *testing.T) {
FILE: pkg/cmd/time-entry/report/util/report.go
constant HelpNamesForIds (line 22) | HelpNamesForIds = util.HelpNamesForIds
constant HelpMoreInfoAboutPrinting (line 23) | HelpMoreInfoAboutPrinting = util.HelpMoreInfoAboutPrinting
type ReportFlags (line 27) | type ReportFlags struct
method Check (line 44) | func (rf ReportFlags) Check() error {
function NewReportFlags (line 68) | func NewReportFlags() ReportFlags {
function AddReportFlags (line 78) | func AddReportFlags(
function ReportWithRange (line 112) | func ReportWithRange(
function filterBilling (line 255) | func filterBilling(l []dto.TimeEntry, billable bool) []dto.TimeEntry {
function fillMissing (line 266) | func fillMissing(first, last time.Time) []dto.TimeEntry {
FILE: pkg/cmd/time-entry/report/util/report_flag_test.go
function TestReportFlags_Check (line 10) | func TestReportFlags_Check(t *testing.T) {
FILE: pkg/cmd/time-entry/report/util/reportwithrange_test.go
function newDate (line 19) | func newDate(s string) time.Time {
function TestReportWithRange (line 24) | func TestReportWithRange(t *testing.T) {
FILE: pkg/cmd/time-entry/report/yesterday/yesterday.go
function NewCmdYesterday (line 11) | func NewCmdYesterday(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/time-entry/show/show.go
function NewCmdShow (line 14) | func NewCmdShow(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/time-entry/split/split.go
function NewCmdSplit (line 21) | func NewCmdSplit(
FILE: pkg/cmd/time-entry/split/split_test.go
function TestNewCmdSplitShouldFail (line 19) | func TestNewCmdSplitShouldFail(t *testing.T) {
function TestNewCmdSplitShouldCreateNewEntries (line 146) | func TestNewCmdSplitShouldCreateNewEntries(t *testing.T) {
FILE: pkg/cmd/time-entry/timeentry.go
function NewCmdTimeEntry (line 23) | func NewCmdTimeEntry(f cmdutil.Factory) (cmds []*cobra.Command) {
FILE: pkg/cmd/time-entry/util/create.go
function FillMissingBillableFn (line 9) | func FillMissingBillableFn(c api.Client) Step {
function CreateTimeEntryFn (line 43) | func CreateTimeEntryFn(c api.Client) Step {
FILE: pkg/cmd/time-entry/util/description-completer.go
type DescriptionSuggestFn (line 16) | type DescriptionSuggestFn
type descriptionCompleter (line 19) | type descriptionCompleter struct
method getDescriptions (line 66) | func (dc *descriptionCompleter) getDescriptions() []string {
method suggestFn (line 89) | func (dc *descriptionCompleter) suggestFn(toComplete string) []string {
function NewDescriptionCompleter (line 27) | func NewDescriptionCompleter(f cmdutil.Factory) DescriptionSuggestFn {
function newDescriptionAutoComplete (line 104) | func newDescriptionAutoComplete(f cmdutil.Factory) cmdcompl.SuggestFn {
FILE: pkg/cmd/time-entry/util/fill-with-flags.go
type flagSet (line 10) | type flagSet interface
function FillTimeEntryWithFlags (line 17) | func FillTimeEntryWithFlags(flags flagSet) Step {
FILE: pkg/cmd/time-entry/util/flags.go
function AddTimeEntryFlags (line 14) | func AddTimeEntryFlags(
function AddTimeEntryDateFlags (line 55) | func AddTimeEntryDateFlags(cmd *cobra.Command) {
FILE: pkg/cmd/time-entry/util/help.go
constant HelpTimeEntryNowIfNotSet (line 6) | HelpTimeEntryNowIfNotSet = "If no start time (`--when`) is set then the " +
constant HelpInteractiveByDefault (line 9) | HelpInteractiveByDefault = "By default, the CLI will ask the " +
constant HelpDateTimeFormats (line 19) | HelpDateTimeFormats = "" +
constant HelpTimeInputOnTimeEntry (line 31) | HelpTimeInputOnTimeEntry = "When setting a date/time input " +
constant HelpNamesForIds (line 36) | HelpNamesForIds = "To be able to use names of resources instead of its " +
constant HelpValidateIncomplete (line 44) | HelpValidateIncomplete = "By default, the CLI (and Clockify API) only " +
constant HelpMoreInfoAboutStarting (line 52) | HelpMoreInfoAboutStarting = "Use `clockify-cli in --help` for more " +
constant HelpMoreInfoAboutPrinting (line 55) | HelpMoreInfoAboutPrinting = "Use `clockify-cli report --help` for more " +
constant HelpTimeEntriesAliasForEdit (line 58) | HelpTimeEntriesAliasForEdit = "" +
FILE: pkg/cmd/time-entry/util/interactive.go
function GetDatesInteractiveFn (line 18) | func GetDatesInteractiveFn(f cmdutil.Factory) Step {
function askTimeEntryDatesInteractive (line 28) | func askTimeEntryDatesInteractive(
function GetPropsInteractiveFn (line 57) | func GetPropsInteractiveFn(
function askTimeEntryPropsInteractive (line 81) | func askTimeEntryPropsInteractive(
constant noProject (line 114) | noProject = "No Project"
function getProjectID (line 116) | func getProjectID(
constant noTask (line 184) | noTask = "No Task"
function getTaskID (line 186) | func getTaskID(
function getDescription (line 246) | func getDescription(
function getTagIDs (line 271) | func getTagIDs(
FILE: pkg/cmd/time-entry/util/interactive_test.go
function TestGetPropsInteractive_ShouldSkip_WhenDisabled (line 16) | func TestGetPropsInteractive_ShouldSkip_WhenDisabled(t *testing.T) {
function TestGetPropsInteractive_ShouldAskValues (line 30) | func TestGetPropsInteractive_ShouldAskValues(t *testing.T) {
function TestGetPropsInteractive_ShouldAllowEmptyValues (line 136) | func TestGetPropsInteractive_ShouldAllowEmptyValues(t *testing.T) {
function TestGetPropsInteractive_ShouldUseInputAsSelected (line 210) | func TestGetPropsInteractive_ShouldUseInputAsSelected(t *testing.T) {
function TestGetPropsInteractive_ShouldForceAnswer_WhenWorkspaceForces (line 292) | func TestGetPropsInteractive_ShouldForceAnswer_WhenWorkspaceForces(
function TestGetPropsInteractive_ShouldNotAsk_WhenThereAreNoOptions (line 385) | func TestGetPropsInteractive_ShouldNotAsk_WhenThereAreNoOptions(
function TestGetDatesInteractive_ShouldSkip_WhenDisabled (line 431) | func TestGetDatesInteractive_ShouldSkip_WhenDisabled(t *testing.T) {
function TestGetDatesInteractive_ShouldValidateString_WhenWrongFormat (line 444) | func TestGetDatesInteractive_ShouldValidateString_WhenWrongFormat(
function TestGetDatesInteractive_ShouldAccept_ValideTimeFormats (line 494) | func TestGetDatesInteractive_ShouldAccept_ValideTimeFormats(t *testing.T) {
FILE: pkg/cmd/time-entry/util/name-for-id.go
function GetAllowNameForIDsFn (line 11) | func GetAllowNameForIDsFn(config cmdutil.Config, c api.Client) Step {
function lookupProject (line 29) | func lookupProject(c api.Client, cnf cmdutil.Config) Step {
function lookupTask (line 43) | func lookupTask(c api.Client) Step {
function lookupTags (line 62) | func lookupTags(c api.Client) Step {
function disableErrorReporting (line 75) | func disableErrorReporting(cbs []Step) []Step {
FILE: pkg/cmd/time-entry/util/out-in-progress.go
function OutInProgressFn (line 12) | func OutInProgressFn(c api.Client) Step {
function out (line 18) | func out(c api.Client, w, u string, end time.Time) error {
function getErrorCode (line 30) | func getErrorCode(err error) int {
FILE: pkg/cmd/time-entry/util/report.go
type OutputFlags (line 15) | type OutputFlags struct
method Check (line 26) | func (of OutputFlags) Check() error {
function AddPrintMultipleTimeEntriesFlags (line 39) | func AddPrintMultipleTimeEntriesFlags(cmd *cobra.Command) {
function AddPrintTimeEntriesFlags (line 45) | func AddPrintTimeEntriesFlags(cmd *cobra.Command, of *OutputFlags) {
function PrintTimeEntryImpl (line 64) | func PrintTimeEntryImpl(
function PrintTimeEntry (line 88) | func PrintTimeEntry(
function updateTimeZone (line 106) | func updateTimeZone(tes []dto.TimeEntry, config cmdutil.Config) []dto.Ti...
function PrintTimeEntries (line 124) | func PrintTimeEntries(
FILE: pkg/cmd/time-entry/util/util.go
type TimeEntryDTO (line 13) | type TimeEntryDTO struct
type Step (line 29) | type Step
function skip (line 31) | func skip(te TimeEntryDTO) (TimeEntryDTO, error) {
function Do (line 37) | func Do(te TimeEntryDTO, cbs ...Step) (TimeEntryDTO, error) {
function compose (line 41) | func compose(cbs ...Step) Step {
function TimeEntryImplToDTO (line 56) | func TimeEntryImplToDTO(t dto.TimeEntryImpl) TimeEntryDTO {
function TimeEntryDTOToImpl (line 74) | func TimeEntryDTOToImpl(t TimeEntryDTO) dto.TimeEntryImpl {
FILE: pkg/cmd/time-entry/util/util_test.go
function TestDo_ShouldApplySteps_InOrder (line 21) | func TestDo_ShouldApplySteps_InOrder(t *testing.T) {
function TestImplToDTOAndBack (line 78) | func TestImplToDTOAndBack(t *testing.T) {
function TestTimeEntryDTOToImpl_ShouldFillMissingProperties (line 103) | func TestTimeEntryDTOToImpl_ShouldFillMissingProperties(t *testing.T) {
type flagSetMock (line 114) | type flagSetMock struct
method Changed (line 118) | func (f *flagSetMock) Changed(k string) bool {
method GetString (line 123) | func (f *flagSetMock) GetString(k string) (string, error) {
method GetStringSlice (line 131) | func (f *flagSetMock) GetStringSlice(k string) ([]string, error) {
function TestFillTimeEntryWithFlags_ShouldNotSetProperties_WhenNotChanged (line 139) | func TestFillTimeEntryWithFlags_ShouldNotSetProperties_WhenNotChanged(
function TestGetValidateTimeEntry_ShouldValidate_UsingSettingsAndConfigs (line 342) | func TestGetValidateTimeEntry_ShouldValidate_UsingSettingsAndConfigs(
function TestGetAllowNameForIDsFn_ShouldLookupEntityIDs_WhenFilled (line 575) | func TestGetAllowNameForIDsFn_ShouldLookupEntityIDs_WhenFilled(t *testin...
function TestGetAllowNameForIDsFn_ShouldNotLookupEntityIDs_WhenEmpty (line 619) | func TestGetAllowNameForIDsFn_ShouldNotLookupEntityIDs_WhenEmpty(
function TestGetAllowNameForIDsFn_ShouldNotLookup_WhenDisabled (line 637) | func TestGetAllowNameForIDsFn_ShouldNotLookup_WhenDisabled(t *testing.T) {
function TestGetAllowNameForIDsFn_ShouldFail_WhenEntitiesNotFound (line 654) | func TestGetAllowNameForIDsFn_ShouldFail_WhenEntitiesNotFound(t *testing...
function TestGetAllowNameForIDsFn_ShouldBeQuiet_WhenInteractive (line 714) | func TestGetAllowNameForIDsFn_ShouldBeQuiet_WhenInteractive(t *testing.T) {
function TestFillMissingBillableFn (line 749) | func TestFillMissingBillableFn(t *testing.T) {
FILE: pkg/cmd/time-entry/util/validate-closing.go
function ValidateClosingTimeEntry (line 12) | func ValidateClosingTimeEntry(f cmdutil.Factory) Step {
FILE: pkg/cmd/time-entry/util/validate.go
function GetValidateTimeEntryFn (line 14) | func GetValidateTimeEntryFn(f cmdutil.Factory) Step {
function validateTimeEntry (line 24) | func validateTimeEntry(te TimeEntryDTO, f cmdutil.Factory) error {
FILE: pkg/cmd/user/me/me.go
function NewCmdMe (line 15) | func NewCmdMe(
FILE: pkg/cmd/user/me/me_test.go
type report (line 16) | type report
function TestCmdMe (line 18) | func TestCmdMe(t *testing.T) {
FILE: pkg/cmd/user/user.go
function NewCmdUser (line 16) | func NewCmdUser(
FILE: pkg/cmd/user/user_test.go
type report (line 17) | type report
function TestCmdUser (line 19) | func TestCmdUser(t *testing.T) {
FILE: pkg/cmd/user/util/util.go
type OutputFlags (line 13) | type OutputFlags struct
method Check (line 19) | func (of OutputFlags) Check() error {
function AddReportFlags (line 28) | func AddReportFlags(cmd *cobra.Command, of *OutputFlags) {
function Report (line 36) | func Report(u []dto.User, out io.Writer, of OutputFlags) error {
FILE: pkg/cmd/version/version.go
function NewCmdVersion (line 11) | func NewCmdVersion(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/version/version_test.go
function TestVersion (line 14) | func TestVersion(t *testing.T) {
FILE: pkg/cmd/workspace/workspace.go
function NewCmdWorkspace (line 12) | func NewCmdWorkspace(f cmdutil.Factory) *cobra.Command {
FILE: pkg/cmd/workspace/workspace_test.go
function TestCmdWorkspaces (line 17) | func TestCmdWorkspaces(t *testing.T) {
FILE: pkg/cmdcompl/flags.go
function AddFixedSuggestionsToFlag (line 8) | func AddFixedSuggestionsToFlag(cmd *cobra.Command, flagName string, va V...
type SuggestFn (line 24) | type SuggestFn
function process (line 26) | func process(va ValidArgs, err error) ([]string, cobra.ShellCompDirectiv...
function AddSuggestionsToFlag (line 36) | func AddSuggestionsToFlag(cmd *cobra.Command, flagName string, suggestFn...
function EmptySuggestionFuncion (line 45) | func EmptySuggestionFuncion(_ *cobra.Command, _ []string, _ string) (Val...
function CombineSuggestionsToArgs (line 50) | func CombineSuggestionsToArgs(fns ...SuggestFn) func(cmd *cobra.Command,...
FILE: pkg/cmdcompl/valid-args.go
type ValidArgs (line 8) | type ValidArgs interface
function EmptyValidArgs (line 22) | func EmptyValidArgs() ValidArgs {
type ValidArgsMap (line 26) | type ValidArgsMap
method Set (line 28) | func (va ValidArgsMap) Set(k, v string) ValidArgsMap {
method OnlyArgs (line 33) | func (va ValidArgsMap) OnlyArgs() []string {
method IntoUseOptions (line 45) | func (va ValidArgsMap) IntoUseOptions() string {
method IntoUse (line 49) | func (va ValidArgsMap) IntoUse() string {
method IntoValidArgs (line 53) | func (va ValidArgsMap) IntoValidArgs() []string {
method Long (line 61) | func (va ValidArgsMap) Long() string {
type ValidArgsSlide (line 70) | type ValidArgsSlide
method IntoUseOptions (line 72) | func (va ValidArgsSlide) IntoUseOptions() string {
method IntoUse (line 76) | func (va ValidArgsSlide) IntoUse() string {
method IntoValidArgs (line 80) | func (va ValidArgsSlide) IntoValidArgs() []string {
method OnlyArgs (line 84) | func (va ValidArgsSlide) OnlyArgs() []string {
FILE: pkg/cmdcomplutil/client.go
function NewClientAutoComplete (line 12) | func NewClientAutoComplete(f factory) cmdcompl.SuggestFn {
FILE: pkg/cmdcomplutil/factory.go
type config (line 5) | type config interface
type factory (line 10) | type factory interface
FILE: pkg/cmdcomplutil/project.go
function NewProjectAutoComplete (line 15) | func NewProjectAutoComplete(f factory, config config) cmdcompl.SuggestFn {
function makeFilter (line 76) | func makeFilter(toComplete string, config config) func(dto.Project) bool {
FILE: pkg/cmdcomplutil/project_test.go
function TestNewProjectAutoComplete (line 18) | func TestNewProjectAutoComplete(t *testing.T) {
FILE: pkg/cmdcomplutil/tag.go
function NewTagAutoComplete (line 12) | func NewTagAutoComplete(f factory) cmdcompl.SuggestFn {
FILE: pkg/cmdcomplutil/task.go
function NewTaskAutoComplete (line 12) | func NewTaskAutoComplete(f factory, onlyActive bool) cmdcompl.SuggestFn {
FILE: pkg/cmdcomplutil/user.go
function NewUserAutoComplete (line 13) | func NewUserAutoComplete(f factory) cmdcompl.SuggestFn {
FILE: pkg/cmdcomplutil/workspace.go
function NewWorspaceAutoComplete (line 12) | func NewWorspaceAutoComplete(f factory) cmdcompl.SuggestFn {
FILE: pkg/cmdutil/args.go
function RequiredNamedArgs (line 12) | func RequiredNamedArgs(names ...string) cobra.PositionalArgs {
FILE: pkg/cmdutil/args_test.go
function TestRequiredNamedArgs (line 12) | func TestRequiredNamedArgs(t *testing.T) {
FILE: pkg/cmdutil/config.go
constant CONF_WORKWEEK_DAYS (line 17) | CONF_WORKWEEK_DAYS = "workweek-days"
constant CONF_INTERACTIVE (line 18) | CONF_INTERACTIVE = "interactive"
constant CONF_ALLOW_NAME_FOR_ID (line 19) | CONF_ALLOW_NAME_FOR_ID = "allow-name-for-id"
constant CONF_SEARCH_PROJECTS_WITH_CLIENT_NAME (line 20) | CONF_SEARCH_PROJECTS_WITH_CLIENT_NAME = "search-project-with-client"
constant CONF_USER_ID (line 21) | CONF_USER_ID = "user.id"
constant CONF_WORKSPACE (line 22) | CONF_WORKSPACE = "workspace"
constant CONF_TOKEN (line 23) | CONF_TOKEN = "token"
constant CONF_ALLOW_INCOMPLETE (line 24) | CONF_ALLOW_INCOMPLETE = "allow-incomplete"
constant CONF_SHOW_TASKS (line 25) | CONF_SHOW_TASKS = "show-task"
constant CONF_SHOW_CUSTOM_FIELDS (line 26) | CONF_SHOW_CUSTOM_FIELDS = "show-custom-fields"
constant CONF_SHOW_CLIENT (line 27) | CONF_SHOW_CLIENT = "show-client"
constant CONF_DESCR_AUTOCOMP (line 28) | CONF_DESCR_AUTOCOMP = "description-autocomplete"
constant CONF_DESCR_AUTOCOMP_DAYS (line 29) | CONF_DESCR_AUTOCOMP_DAYS = "description-autocomplete-days"
constant CONF_SHOW_TOTAL_DURATION (line 30) | CONF_SHOW_TOTAL_DURATION = "show-total-duration"
constant CONF_LOG_LEVEL (line 31) | CONF_LOG_LEVEL = "log-level"
constant CONF_ALLOW_ARCHIVED_TAGS (line 32) | CONF_ALLOW_ARCHIVED_TAGS = "allow-archived-tags"
constant CONF_INTERACTIVE_PAGE_SIZE (line 33) | CONF_INTERACTIVE_PAGE_SIZE = "interactive-page-size"
constant CONF_LANGUAGE (line 34) | CONF_LANGUAGE = "lang"
constant CONF_TIMEZONE (line 35) | CONF_TIMEZONE = "time-zone"
constant CONF_API_URL (line 36) | CONF_API_URL = "api-url"
constant LOG_LEVEL_NONE (line 40) | LOG_LEVEL_NONE = "none"
constant LOG_LEVEL_DEBUG (line 41) | LOG_LEVEL_DEBUG = "debug"
constant LOG_LEVEL_INFO (line 42) | LOG_LEVEL_INFO = "info"
type Config (line 46) | type Config interface
type config (line 106) | type config struct
method IsSearchProjectWithClientsName (line 116) | func (c *config) IsSearchProjectWithClientsName() bool {
method InteractivePageSize (line 120) | func (c *config) InteractivePageSize() int {
method LogLevel (line 128) | func (c *config) LogLevel() string {
method GetBool (line 138) | func (*config) GetBool(param string) bool {
method SetBool (line 142) | func (*config) SetBool(p string, b bool) {
method GetString (line 146) | func (*config) GetString(param string) string {
method SetString (line 150) | func (*config) SetString(p, s string) {
method GetInt (line 154) | func (*config) GetInt(param string) int {
method SetInt (line 158) | func (*config) SetInt(p string, i int) {
method GetStringSlice (line 162) | func (*config) GetStringSlice(param string) []string {
method SetStringSlice (line 166) | func (*config) SetStringSlice(p string, ss []string) {
method IsDebuging (line 170) | func (c *config) IsDebuging() bool {
method GetWorkWeekdays (line 174) | func (c *config) GetWorkWeekdays() []string {
method IsAllowNameForID (line 178) | func (c *config) IsAllowNameForID() bool {
method IsInteractive (line 182) | func (c *config) IsInteractive() bool {
method SetLanguage (line 186) | func (c *config) SetLanguage(l language.Tag) {
method Language (line 191) | func (c *config) Language() language.Tag {
method Get (line 209) | func (*config) Get(p string) interface{} {
method All (line 213) | func (*config) All() map[string]interface{} {
method Save (line 217) | func (*config) Save() error {
method SetTimeZone (line 253) | func (c *config) SetTimeZone(tz *time.Location) {
method TimeZone (line 259) | func (c *config) TimeZone() *time.Location {
function configFunc (line 233) | func configFunc() func() (c Config) {
function GetWeekdays (line 240) | func GetWeekdays() []string {
FILE: pkg/cmdutil/errors.go
type FlagError (line 4) | type FlagError struct
method Error (line 8) | func (fe *FlagError) Error() string {
method Unwrap (line 12) | func (fe *FlagError) Unwrap() error {
function FlagErrorWrap (line 16) | func FlagErrorWrap(err error) *FlagError {
FILE: pkg/cmdutil/factory.go
type Factory (line 13) | type Factory interface
type factory (line 32) | type factory struct
method Version (line 44) | func (f *factory) Version() Version {
method Config (line 48) | func (f *factory) Config() Config {
method Client (line 52) | func (f *factory) Client() (api.Client, error) {
method UI (line 56) | func (f *factory) UI() ui.UI {
method GetUserID (line 60) | func (f *factory) GetUserID() (string, error) {
method GetWorkspaceID (line 64) | func (f *factory) GetWorkspaceID() (string, error) {
method GetWorkspace (line 68) | func (f *factory) GetWorkspace() (dto.Workspace, error) {
function NewFactory (line 72) | func NewFactory(v Version) Factory {
function getUserIDFunc (line 90) | func getUserIDFunc(f Factory) func() (string, error) {
function getWorkspaceFunc (line 118) | func getWorkspaceFunc(f Factory) func() (dto.Workspace, error) {
function getWorkspaceIDFunc (line 150) | func getWorkspaceIDFunc(f Factory) func() (string, error) {
function clientFunc (line 175) | func clientFunc(f Factory) func() (api.Client, error) {
function getUi (line 218) | func getUi(f Factory) func() ui.UI {
FILE: pkg/cmdutil/flags.go
function XorFlag (line 12) | func XorFlag(exclusiveFlags map[string]bool) error {
function XorFlagSet (line 34) | func XorFlagSet(f *pflag.FlagSet, exclusiveFlags ...string) error {
FILE: pkg/cmdutil/flags_test.go
type testcase (line 12) | type testcase struct
function testcases (line 18) | func testcases() []testcase {
function TestXorFlag (line 70) | func TestXorFlag(t *testing.T) {
function TestXorFlagSet (line 86) | func TestXorFlagSet(t *testing.T) {
FILE: pkg/cmdutil/project.go
function AddProjectFlags (line 10) | func AddProjectFlags(cmd *cobra.Command, f Factory) {
FILE: pkg/cmdutil/version.go
type Version (line 4) | type Version struct
FILE: pkg/output/client/csv.go
function ClientsCSVPrint (line 12) | func ClientsCSVPrint(clients []dto.Client, out io.Writer) error {
FILE: pkg/output/client/default.go
function ClientPrint (line 13) | func ClientPrint(cs []dto.Client, w io.Writer) error {
FILE: pkg/output/client/json.go
function ClientJSONPrint (line 11) | func ClientJSONPrint(t dto.Client, w io.Writer) error {
function ClientsJSONPrint (line 16) | func ClientsJSONPrint(t []dto.Client, w io.Writer) error {
FILE: pkg/output/client/quiet.go
function ClientPrintQuietly (line 11) | func ClientPrintQuietly(cs []dto.Client, w io.Writer) error {
FILE: pkg/output/client/template.go
function ClientPrintWithTemplate (line 11) | func ClientPrintWithTemplate(format string) func([]dto.Client, io.Writer...
FILE: pkg/output/project/csv.go
function ProjectsCSVPrint (line 11) | func ProjectsCSVPrint(ps []dto.Project, out io.Writer) error {
FILE: pkg/output/project/default.go
function ProjectPrint (line 15) | func ProjectPrint(ps []dto.Project, w io.Writer) error {
FILE: pkg/output/project/json.go
function ProjectsJSONPrint (line 11) | func ProjectsJSONPrint(t []dto.Project, w io.Writer) error {
function ProjectJSONPrint (line 16) | func ProjectJSONPrint(t dto.Project, w io.Writer) error {
FILE: pkg/output/project/quiet.go
function ProjectPrintQuietly (line 11) | func ProjectPrintQuietly(ps []dto.Project, w io.Writer) error {
FILE: pkg/output/project/template.go
function ProjectPrintWithTemplate (line 11) | func ProjectPrintWithTemplate(format string) func([]dto.Project, io.Writ...
FILE: pkg/output/tag/default.go
function TagPrint (line 11) | func TagPrint(ts []dto.Tag, w io.Writer) error {
FILE: pkg/output/tag/quiet.go
function TagPrintQuietly (line 11) | func TagPrintQuietly(ts []dto.Tag, w io.Writer) error {
FILE: pkg/output/tag/template.go
function TagPrintWithTemplate (line 11) | func TagPrintWithTemplate(format string) func([]dto.Tag, io.Writer) error {
FILE: pkg/output/task/csv.go
function TasksCSVPrint (line 11) | func TasksCSVPrint(ts []dto.Task, out io.Writer) error {
FILE: pkg/output/task/default.go
function TaskPrint (line 13) | func TaskPrint(ts []dto.Task, w io.Writer) error {
FILE: pkg/output/task/json.go
function TasksJSONPrint (line 11) | func TasksJSONPrint(t []dto.Task, w io.Writer) error {
FILE: pkg/output/task/quiet.go
function TaskPrintQuietly (line 11) | func TaskPrintQuietly(ts []dto.Task, w io.Writer) error {
FILE: pkg/output/task/template.go
function TaskPrintWithTemplate (line 11) | func TaskPrintWithTemplate(format string) func([]dto.Task, io.Writer) er...
FILE: pkg/output/time-entry/csv.go
function TimeEntriesCSVPrint (line 13) | func TimeEntriesCSVPrint(timeEntries []dto.TimeEntry, out io.Writer) err...
FILE: pkg/output/time-entry/default.go
function sumTimeEntriesDuration (line 16) | func sumTimeEntriesDuration(ts []dto.TimeEntry) time.Duration {
constant TimeFormatFull (line 31) | TimeFormatFull = "2006-01-02 15:04:05"
constant TimeFormatSimple (line 32) | TimeFormatSimple = "15:04:05"
type TimeEntryOutputOptions (line 37) | type TimeEntryOutputOptions struct
method WithTimeFormat (line 57) | func (teo TimeEntryOutputOptions) WithTimeFormat(
method WithShowTasks (line 65) | func (teo TimeEntryOutputOptions) WithShowTasks() TimeEntryOutputOptio...
method WithShowCustomFields (line 71) | func (teo TimeEntryOutputOptions) WithShowCustomFields() TimeEntryOutp...
method WithShowClients (line 77) | func (teo TimeEntryOutputOptions) WithShowClients() TimeEntryOutputOpt...
method WithTotalDuration (line 84) | func (teo TimeEntryOutputOptions) WithTotalDuration() TimeEntryOutputO...
function NewTimeEntryOutputOptions (line 46) | func NewTimeEntryOutputOptions() TimeEntryOutputOptions {
function TimeEntriesPrint (line 90) | func TimeEntriesPrint(
function tagsToStringSlice (line 190) | func tagsToStringSlice(tags []dto.Tag) []string {
function durationToString (line 200) | func durationToString(d time.Duration) string {
function customFieldsToStringSlice (line 204) | func customFieldsToStringSlice(customFields []dto.CustomField) []string {
FILE: pkg/output/time-entry/default_test.go
function TestTimeEntriesDefaultPrint (line 15) | func TestTimeEntriesDefaultPrint(t *testing.T) {
FILE: pkg/output/time-entry/duration.go
function timeEntriesTotalDurationOnly (line 14) | func timeEntriesTotalDurationOnly(
function TimeEntriesTotalDurationOnlyAsFloat (line 25) | func TimeEntriesTotalDurationOnlyAsFloat(
function TimeEntriesTotalDurationOnlyFormatted (line 40) | func TimeEntriesTotalDurationOnlyFormatted(
FILE: pkg/output/time-entry/duration_test.go
function TestTimeEntriesTotalDurationOnlyAsFloat_ShouldUseUserLanguage (line 15) | func TestTimeEntriesTotalDurationOnlyAsFloat_ShouldUseUserLanguage(
FILE: pkg/output/time-entry/json.go
function TimeEntryJSONPrint (line 11) | func TimeEntryJSONPrint(t dto.TimeEntry, w io.Writer) error {
function TimeEntriesJSONPrint (line 16) | func TimeEntriesJSONPrint(t []dto.TimeEntry, w io.Writer) error {
FILE: pkg/output/time-entry/markdown.go
function TimeEntriesMarkdownPrint (line 14) | func TimeEntriesMarkdownPrint(tes []dto.TimeEntry, w io.Writer) error {
FILE: pkg/output/time-entry/markdown_test.go
function TestTimeEntriesMarkdownPrint (line 15) | func TestTimeEntriesMarkdownPrint(t *testing.T) {
FILE: pkg/output/time-entry/quiet.go
function TimeEntriesPrintQuietly (line 11) | func TimeEntriesPrintQuietly(timeEntries []dto.TimeEntry, w io.Writer) e...
FILE: pkg/output/time-entry/template.go
function TimeEntriesPrintWithTemplate (line 12) | func TimeEntriesPrintWithTemplate(
FILE: pkg/output/user/default.go
function UserPrint (line 11) | func UserPrint(users []dto.User, w io.Writer) error {
FILE: pkg/output/user/json.go
function UserJSONPrint (line 11) | func UserJSONPrint(u dto.User, w io.Writer) error {
FILE: pkg/output/user/quiet.go
function UserPrintQuietly (line 11) | func UserPrintQuietly(users []dto.User, w io.Writer) error {
FILE: pkg/output/user/template.go
function UserPrintWithTemplate (line 11) | func UserPrintWithTemplate(format string) func([]dto.User, io.Writer) er...
FILE: pkg/output/util/color.go
function ColorToTermColor (line 10) | func ColorToTermColor(hex string) []int {
FILE: pkg/output/util/template.go
function formatTime (line 16) | func formatTime(f string) func(time.Time) string {
function firstOrNow (line 91) | func firstOrNow(ts []time.Time) time.Time {
function diff (line 98) | func diff(s, e time.Time) dto.Duration {
function NewTemplate (line 102) | func NewTemplate(format string) (*template.Template, error) {
FILE: pkg/output/workspace/default.go
function WorkspacePrint (line 11) | func WorkspacePrint(
FILE: pkg/output/workspace/quiet.go
function WorkspacePrintQuietly (line 11) | func WorkspacePrintQuietly(ws []dto.Workspace, w io.Writer) error {
FILE: pkg/output/workspace/template.go
function WorkspacePrintWithTemplate (line 11) | func WorkspacePrintWithTemplate(
FILE: pkg/search/client.go
function GetClientsByName (line 10) | func GetClientsByName(
function GetClientByName (line 54) | func GetClientByName(
FILE: pkg/search/errors.go
type ErrNotFound (line 10) | type ErrNotFound struct
method Error (line 16) | func (e ErrNotFound) Error() string {
FILE: pkg/search/find.go
type named (line 10) | type named interface
function findByName (line 17) | func findByName(
FILE: pkg/search/find_test.go
function TestSearchOnList (line 9) | func TestSearchOnList(t *testing.T) {
FILE: pkg/search/project.go
function GetProjectByName (line 11) | func GetProjectByName(
type namedStruct (line 67) | type namedStruct struct
method GetID (line 72) | func (c namedStruct) GetID() string {
method GetName (line 76) | func (c namedStruct) GetName() string {
function filterClientProjects (line 80) | func filterClientProjects(
function GetProjectsByName (line 117) | func GetProjectsByName(
FILE: pkg/search/tag.go
function GetTagsByName (line 9) | func GetTagsByName(
FILE: pkg/search/task.go
function GetTaskByName (line 10) | func GetTaskByName(
function GetTasksByName (line 33) | func GetTasksByName(
FILE: pkg/search/user.go
function GetUsersByName (line 10) | func GetUsersByName(
FILE: pkg/timeentryhlp/timeentry.go
constant AliasCurrent (line 15) | AliasCurrent = "current"
constant AliasLast (line 16) | AliasLast = "last"
constant AliasLatest (line 17) | AliasLatest = "latest"
function GetLatestEntryEntry (line 21) | func GetLatestEntryEntry(
function mayNotFound (line 28) | func mayNotFound(tei *dto.TimeEntryImpl, err error) (
function GetTimeEntry (line 43) | func GetTimeEntry(
FILE: pkg/timehlp/range.go
function GetMonthRange (line 6) | func GetMonthRange(ref time.Time) (first, last time.Time) {
function GetWeekRange (line 14) | func GetWeekRange(ref time.Time) (first, last time.Time) {
FILE: pkg/timehlp/relative.go
function relativeToTime (line 17) | func relativeToTime(timeString string) (t time.Time, err error) {
function relativeColonTimeToDuration (line 35) | func relativeColonTimeToDuration(s string) (d time.Duration, err error) {
function relativeUnitDescriptiveTimeToDuration (line 56) | func relativeUnitDescriptiveTimeToDuration(s string) (
FILE: pkg/timehlp/time.go
constant FullTimeFormat (line 10) | FullTimeFormat = "2006-01-02 15:04:05"
constant SimplerTimeFormat (line 11) | SimplerTimeFormat = "2006-01-02 15:04"
constant OnlyTimeFormat (line 12) | OnlyTimeFormat = "15:04:05"
constant SimplerOnlyTimeFormat (line 13) | SimplerOnlyTimeFormat = "15:04"
constant SimplerOnlyTimeFormatWL (line 14) | SimplerOnlyTimeFormatWL = "5:04"
constant NowTimeFormat (line 15) | NowTimeFormat = "now"
constant SimplestOnlyTimeFormat (line 16) | SimplestOnlyTimeFormat = "1504"
constant SimplestOnlyTimeFormatWL (line 17) | SimplestOnlyTimeFormatWL = "504"
function ConvertToTime (line 29) | func ConvertToTime(timeString string) (t time.Time, err error) {
function normalizeFormats (line 79) | func normalizeFormats(timeString string) string {
FILE: pkg/timehlp/time_test.go
function TestParseTime (line 11) | func TestParseTime(t *testing.T) {
function TestFailParseTime (line 50) | func TestFailParseTime(t *testing.T) {
FILE: pkg/timehlp/util.go
function TruncateDate (line 6) | func TruncateDate(t time.Time) time.Time {
function TruncateDateWithTimezone (line 12) | func TruncateDateWithTimezone(t time.Time, l *time.Location) time.Time {
function Today (line 19) | func Today() time.Time {
function Now (line 25) | func Now() time.Time {
FILE: pkg/ui/color.go
function HEX (line 8) | func HEX(hex string) (RGB, error) {
type RGB (line 26) | type RGB
method R (line 28) | func (c RGB) R() int {
method G (line 32) | func (c RGB) G() int {
method B (line 36) | func (c RGB) B() int {
method Values (line 40) | func (c RGB) Values() []int {
FILE: pkg/ui/ui.go
type FileReader (line 16) | type FileReader interface
type FileWriter (line 22) | type FileWriter interface
function NewUI (line 28) | func NewUI(in FileReader, out FileWriter, err io.Writer) UI {
type UI (line 37) | type UI interface
type ui (line 66) | type ui struct
method SetPageSize (line 70) | func (u *ui) SetPageSize(p uint) UI {
method AskForValidText (line 112) | func (u *ui) AskForValidText(
method AskForText (line 136) | func (u *ui) AskForText(message string, opts ...InputOption) (string, ...
method AskForDateTime (line 181) | func (u *ui) AskForDateTime(
method AskForDateTimeOrNil (line 211) | func (u *ui) AskForDateTimeOrNil(
method AskForInt (line 230) | func (u *ui) AskForInt(message string, d int) (int, error) {
method AskFromOptions (line 252) | func (u *ui) AskFromOptions(message string, options []string, d string...
method AskManyFromOptions (line 267) | func (u *ui) AskManyFromOptions(
method Confirm (line 299) | func (u *ui) Confirm(message string, d bool) (bool, error) {
function selectFilter (line 78) | func selectFilter(filter, value string, _ int) bool {
function askString (line 82) | func askString(p survey.Prompt, options ...survey.AskOpt) (string, error) {
function WithSuggestion (line 88) | func WithSuggestion(fn func(toComplete string) []string) InputOption {
function WithHelp (line 95) | func WithHelp(help string) InputOption {
function WithDefault (line 102) | func WithDefault(d string) InputOption {
type InputOption (line 109) | type InputOption
type timeAnswer (line 148) | type timeAnswer struct
method validate (line 153) | func (ans *timeAnswer) validate(v interface{}) error {
method WriteAnswer (line 163) | func (ans *timeAnswer) WriteAnswer(_ string, v interface{}) error {
type convertTime (line 178) | type convertTime
FILE: strhlp/strhlp.go
function Normalize (line 15) | func Normalize(s string) string {
function InSlice (line 31) | func InSlice(needle string, list []string) bool {
function Search (line 37) | func Search(s string, list []string) int {
function Map (line 49) | func Map(f func(string) string, s []string) []string {
function Filter (line 59) | func Filter(f func(string) bool, s []string) []string {
function Unique (line 70) | func Unique(ss []string) []string {
function ListForHumans (line 85) | func ListForHumans(s []string) string {
function PadSpace (line 95) | func PadSpace(s string, size int) string {
function IsSimilar (line 107) | func IsSimilar(filter string) func(string) bool {
FILE: strhlp/strhlp_test.go
function TestNormalize (line 11) | func TestNormalize(t *testing.T) {
function TestInSlice (line 25) | func TestInSlice(t *testing.T) {
function TestSearch (line 68) | func TestSearch(t *testing.T) {
function TestMap (line 110) | func TestMap(t *testing.T) {
function TestFilter (line 143) | func TestFilter(t *testing.T) {
function TestUnique (line 181) | func TestUnique(t *testing.T) {
function TestPadSpace (line 216) | func TestPadSpace(t *testing.T) {
function TestListForHumans (line 254) | func TestListForHumans(t *testing.T) {
function TestIsSimilar (line 284) | func TestIsSimilar(t *testing.T) {
Condensed preview — 216 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (990K chars).
[
{
"path": ".deepsource.toml",
"chars": 213,
"preview": "version = 1\n\n[[analyzers]]\nname = \"go\"\nenabled = true\n\n [analyzers.meta]\n import_root = \"github.com/lucassabreu/clocki"
},
{
"path": ".github/workflows/golangci-lint.yml",
"chars": 529,
"preview": "name: golangci-lint\non:\n push:\n tags:\n - v*\n branches:\n - main\n pull_request:\npermissions:\n contents:"
},
{
"path": ".github/workflows/release.yml",
"chars": 1501,
"preview": "name: goreleaser\n\non:\n pull_request:\n push:\n tags:\n - \"*\"\n\njobs:\n goreleaser:\n runs-on: ubuntu-latest\n "
},
{
"path": ".github/workflows/test-unit.yaml",
"chars": 1045,
"preview": "name: Unit Tests\non:\n pull_request:\n push:\n tags:\n - v*\n branches:\n - main\njobs:\n tests:\n runs-on:"
},
{
"path": ".gitignore",
"chars": 95,
"preview": "build/\ndist/\nsnap.login\n/clockify-cli\nsite/content/commands/\nsite/public/\nsite/content/license\n"
},
{
"path": ".gitmodules",
"chars": 142,
"preview": "[submodule \"site/themes/hugo-theme-relearn\"]\n\tpath = site/themes/hugo-theme-relearn\n\turl = https://github.com/McShelby/h"
},
{
"path": ".goreleaser.yml",
"chars": 2157,
"preview": "version: 2\n\nbuilds:\n - env:\n - CGO_ENABLED=0\n goos:\n - windows\n - linux\n - darwin\n hooks:\n "
},
{
"path": ".mockery.yaml",
"chars": 369,
"preview": "dir: internal/mocks\ntemplate: testify\ntemplate-data:\n unroll-variadic: true\npackages:\n github.com/lucassabreu/clockify"
},
{
"path": ".nvimrc",
"chars": 277,
"preview": "set spell\nset spelllang=en\nset textwidth=79\nset colorcolumn=80\nlet g:goyo_width = 103\n\nautocmd FileType markdown setloca"
},
{
"path": "CHANGELOG.md",
"chars": 49702,
"preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Change"
},
{
"path": "CONTRIBUTING.md",
"chars": 3401,
"preview": "# Contributing\n\nThank you for the interest in Contributing to Clockify CLI.\n\nWe accept pull requests for bug fixes and f"
},
{
"path": "LICENSE",
"chars": 11379,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "Makefile",
"chars": 2560,
"preview": "export GO111MODULE=on\nMAIN_PKG=./cmd/clockify-cli\n\n# Absolutely awesome: http://marmelab.com/blog/2016/02/29/auto-docume"
},
{
"path": "README.md",
"chars": 3864,
"preview": "\n========"
},
{
"path": "api/client.go",
"chars": 43325,
"preview": "package api\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.co"
},
{
"path": "api/client_test.go",
"chars": 5167,
"preview": "package api_test\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/l"
},
{
"path": "api/dto/dto.go",
"chars": 13531,
"preview": "package dto\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Error api errors\ntype Error struct {\n\tMessage string `json:\"messag"
},
{
"path": "api/dto/request.go",
"chars": 13016,
"preview": "package dto\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n)\n\n// D"
},
{
"path": "api/httpClient.go",
"chars": 3027,
"preview": "package api\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/"
},
{
"path": "api/logger.go",
"chars": 674,
"preview": "package api\n\n// Logger for the Client\ntype Logger interface {\n\tPrint(v ...interface{})\n\tPrintf(format string, v ...inter"
},
{
"path": "api/project_test.go",
"chars": 29632,
"preview": "package api_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu/clock"
},
{
"path": "api/tag_test.go",
"chars": 2625,
"preview": "package api_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu/clockify-cli/"
},
{
"path": "api/task_test.go",
"chars": 4764,
"preview": "package api_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu/clock"
},
{
"path": "api/timeentry_test.go",
"chars": 3043,
"preview": "package api_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu/clockify-cli/"
},
{
"path": "cmd/clockify-cli/main.go",
"chars": 4223,
"preview": "package main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\n\t\"github.com/AlecAivazis/survey/v2/terminal\"\n\t\"github."
},
{
"path": "cmd/gendocs/main.go",
"chars": 1354,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/p"
},
{
"path": "cmd/release/main.go",
"chars": 5852,
"preview": "package main\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\ntype Version struct {"
},
{
"path": "docs/project-layout.md",
"chars": 2874,
"preview": "# Clockify CLI Project Layout\n\nThe project is organized in the following folders and important files:\n\n- [`cmd/`](../cmd"
},
{
"path": "go.mod",
"chars": 2240,
"preview": "module github.com/lucassabreu/clockify-cli\n\ngo 1.24\n\nrequire (\n\tgithub.com/AlecAivazis/survey/v2 v2.3.7\n\tgithub.com/Make"
},
{
"path": "go.sum",
"chars": 13136,
"preview": "github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=\ngithub.com/AlecAivazis/survey/v2"
},
{
"path": "internal/consoletest/test.go",
"chars": 2565,
"preview": "package consoletest\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/Netflix/go-expect\"\n\t\"github.com/hinshun/vt"
},
{
"path": "internal/mocks/gen.go",
"chars": 247,
"preview": "package mocks\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmdutil\"\n)\n"
},
{
"path": "internal/mocks/mock_Client.go",
"chars": 75757,
"preview": "// Code generated by mockery; DO NOT EDIT.\n// github.com/vektra/mockery\n// template: testify\n\npackage mocks\n\nimport (\n\t\""
},
{
"path": "internal/mocks/mock_Config.go",
"chars": 27446,
"preview": "// Code generated by mockery; DO NOT EDIT.\n// github.com/vektra/mockery\n// template: testify\n\npackage mocks\n\nimport (\n\t\""
},
{
"path": "internal/mocks/mock_Factory.go",
"chars": 10194,
"preview": "// Code generated by mockery; DO NOT EDIT.\n// github.com/vektra/mockery\n// template: testify\n\npackage mocks\n\nimport (\n\t\""
},
{
"path": "internal/mocks/simple_config.go",
"chars": 3909,
"preview": "package mocks\n\nimport (\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmdutil\"\n\t\"golang.org/x/text/language\"\n)\n\n// "
},
{
"path": "internal/testhlp/helper.go",
"chars": 208,
"preview": "package testhlp\n\nimport \"time\"\n\n// MustParseTime will parse a string as time.Time or panic\nfunc MustParseTime(l, v strin"
},
{
"path": "netlify.toml",
"chars": 135,
"preview": "[build.environment]\nGO_VERSION = \"1.24\"\nHUGO_VERSION = \"0.156.0\"\n\n[build]\n command = \"make site-build\"\n publish = "
},
{
"path": "pkg/cmd/client/add/add.go",
"chars": 1881,
"preview": "package add\n\nimport (\n\t\"io\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/l"
},
{
"path": "pkg/cmd/client/add/add_test.go",
"chars": 3852,
"preview": "package add_test\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabr"
},
{
"path": "pkg/cmd/client/client.go",
"chars": 539,
"preview": "package client\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmd/client/add\"\n\t\"github.com/lucassabreu/clockify-cli"
},
{
"path": "pkg/cmd/client/list/list.go",
"chars": 2365,
"preview": "package list\n\nimport (\n\t\"io\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/"
},
{
"path": "pkg/cmd/client/list/list_test.go",
"chars": 6198,
"preview": "package list_test\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassab"
},
{
"path": "pkg/cmd/client/util/util.go",
"chars": 1403,
"preview": "package util\n\nimport (\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.com/lucassabreu/clockify-cli/pkg/c"
},
{
"path": "pkg/cmd/completion/completion.go",
"chars": 2266,
"preview": "package completion\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockif"
},
{
"path": "pkg/cmd/config/config.go",
"chars": 2898,
"preview": "package config\n\nimport (\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmd/config/get\"\n\tin"
},
{
"path": "pkg/cmd/config/get/get.go",
"chars": 985,
"preview": "package get\n\nimport (\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmd/config/util\"\n\t\"git"
},
{
"path": "pkg/cmd/config/get/get_test.go",
"chars": 4188,
"preview": "package get_test\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clo"
},
{
"path": "pkg/cmd/config/init/init.go",
"chars": 7776,
"preview": "package init\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu/cl"
},
{
"path": "pkg/cmd/config/init/init_test.go",
"chars": 8397,
"preview": "package init_test\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/AlecAivazis/survey/v2/terminal\"\n\t\"gith"
},
{
"path": "pkg/cmd/config/list/list.go",
"chars": 1177,
"preview": "package list\n\nimport (\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmd/config/util\"\n\t\"gi"
},
{
"path": "pkg/cmd/config/list/list_test.go",
"chars": 2259,
"preview": "package list_test\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/cl"
},
{
"path": "pkg/cmd/config/set/set.go",
"chars": 1847,
"preview": "package set\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli"
},
{
"path": "pkg/cmd/config/set/set_test.go",
"chars": 4473,
"preview": "package set_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/internal/mocks\"\n\t\"github.c"
},
{
"path": "pkg/cmd/config/util/util.go",
"chars": 862,
"preview": "package util\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmdcompl\"\n"
},
{
"path": "pkg/cmd/project/add/add.go",
"chars": 3429,
"preview": "package add\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/hex\"\n\t\"io\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/c"
},
{
"path": "pkg/cmd/project/add/add_test.go",
"chars": 7952,
"preview": "package add_test\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com"
},
{
"path": "pkg/cmd/project/edit/edit.go",
"chars": 5860,
"preview": "package edit\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-c"
},
{
"path": "pkg/cmd/project/edit/edit_test.go",
"chars": 9892,
"preview": "package edit_test\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassab"
},
{
"path": "pkg/cmd/project/get/get.go",
"chars": 2803,
"preview": "package get\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cl"
},
{
"path": "pkg/cmd/project/get/get_test.go",
"chars": 7199,
"preview": "package get_test\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabr"
},
{
"path": "pkg/cmd/project/list/list.go",
"chars": 4621,
"preview": "package list\n\nimport (\n\t\"io\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/"
},
{
"path": "pkg/cmd/project/list/list_test.go",
"chars": 8396,
"preview": "package list_test\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassab"
},
{
"path": "pkg/cmd/project/project.go",
"chars": 747,
"preview": "package project\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmd/project/add\"\n\t\"github.com/lucassabreu/clockify-c"
},
{
"path": "pkg/cmd/project/util/util.go",
"chars": 1691,
"preview": "package util\n\nimport (\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.com/lucassabreu/clockify-cli"
},
{
"path": "pkg/cmd/root.go",
"chars": 2793,
"preview": "package cmd\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmd/client\"\n\t\"github.com/lucassabreu/clockify-cli/pkg/cm"
},
{
"path": "pkg/cmd/tag/tag.go",
"chars": 2480,
"preview": "package tag\n\nimport (\n\t\"os\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/l"
},
{
"path": "pkg/cmd/task/add/add.go",
"chars": 2789,
"preview": "package add\n\nimport (\n\t\"io\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/l"
},
{
"path": "pkg/cmd/task/add/add_test.go",
"chars": 9034,
"preview": "package add_test\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/l"
},
{
"path": "pkg/cmd/task/delete/delete.go",
"chars": 3044,
"preview": "package del\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cl"
},
{
"path": "pkg/cmd/task/delete/delete_test.go",
"chars": 8187,
"preview": "package del_test\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabr"
},
{
"path": "pkg/cmd/task/done/done.go",
"chars": 4150,
"preview": "package done\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-c"
},
{
"path": "pkg/cmd/task/done/done_test.go",
"chars": 11246,
"preview": "package done_test\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassab"
},
{
"path": "pkg/cmd/task/edit/edit.go",
"chars": 4156,
"preview": "package edit\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-c"
},
{
"path": "pkg/cmd/task/edit/edit_test.go",
"chars": 11796,
"preview": "package edit_test\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/"
},
{
"path": "pkg/cmd/task/list/list.go",
"chars": 2393,
"preview": "package list\n\nimport (\n\t\"io\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/"
},
{
"path": "pkg/cmd/task/list/list_test.go",
"chars": 6592,
"preview": "package list_test\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassab"
},
{
"path": "pkg/cmd/task/quick-add/quick-add.go",
"chars": 2592,
"preview": "package quickadd\n\nimport (\n\t\"io\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github."
},
{
"path": "pkg/cmd/task/quick-add/quick-add_test.go",
"chars": 7865,
"preview": "package quickadd_test\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/luca"
},
{
"path": "pkg/cmd/task/task.go",
"chars": 944,
"preview": "package task\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmd/task/add\"\n\tdel \"github.com/lucassabreu/clockify-cli"
},
{
"path": "pkg/cmd/task/util/read-flags.go",
"chars": 2801,
"preview": "package util\n\nimport (\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmdcompl\"\n\t\"github.com/lucassabreu/clockify-cl"
},
{
"path": "pkg/cmd/task/util/report.go",
"chars": 1506,
"preview": "package util\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmdutil\""
},
{
"path": "pkg/cmd/time-entry/clone/clone.go",
"chars": 4839,
"preview": "package clone\n\nimport (\n\t\"strings\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"g"
},
{
"path": "pkg/cmd/time-entry/delete/delete.go",
"chars": 2777,
"preview": "package del\n\nimport (\n\t\"errors\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.c"
},
{
"path": "pkg/cmd/time-entry/edit/edit.go",
"chars": 4465,
"preview": "package edit\n\nimport (\n\t\"io\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/"
},
{
"path": "pkg/cmd/time-entry/edit/edit_test.go",
"chars": 3951,
"preview": "package edit_test\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/l"
},
{
"path": "pkg/cmd/time-entry/edit-multipple/edit-multiple.go",
"chars": 6040,
"preview": "package editmultiple\n\nimport (\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmd/time-entr"
},
{
"path": "pkg/cmd/time-entry/in/in.go",
"chars": 6696,
"preview": "package in\n\nimport (\n\t\"io\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.co"
},
{
"path": "pkg/cmd/time-entry/in/in_test.go",
"chars": 7865,
"preview": "package in_test\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"gith"
},
{
"path": "pkg/cmd/time-entry/invoiced/invoiced.go",
"chars": 3743,
"preview": "package invoiced\n\nimport (\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/luc"
},
{
"path": "pkg/cmd/time-entry/manual/manual.go",
"chars": 3064,
"preview": "package manual\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmd"
},
{
"path": "pkg/cmd/time-entry/out/out.go",
"chars": 3333,
"preview": "package out\n\nimport (\n\t\"errors\"\n\t\"time\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\""
},
{
"path": "pkg/cmd/time-entry/report/last-day/last-day.go",
"chars": 1174,
"preview": "package lastday\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmd/time-entry/report/util\"\n\t\"github.com/lucassabreu"
},
{
"path": "pkg/cmd/time-entry/report/last-month/last-month.go",
"chars": 893,
"preview": "package lastmonth\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmd/time-entry/report/util\"\n\t\"github.com/lucassabr"
},
{
"path": "pkg/cmd/time-entry/report/last-week/last-week.go",
"chars": 907,
"preview": "package lastweek\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmd/time-entry/report/util\"\n\t\"github.com/lucassabre"
},
{
"path": "pkg/cmd/time-entry/report/last-week-day/last-week-day.go",
"chars": 2001,
"preview": "package lastweekday\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/c"
},
{
"path": "pkg/cmd/time-entry/report/report.go",
"chars": 11177,
"preview": "package report\n\nimport (\n\t\"time\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\tlastday \"github.com/lucassabreu/clockify-cli/pkg/cm"
},
{
"path": "pkg/cmd/time-entry/report/this-month/this-month.go",
"chars": 870,
"preview": "package thismonth\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmd/time-entry/report/util\"\n\t\"github.com/lucassabr"
},
{
"path": "pkg/cmd/time-entry/report/this-week/this-week.go",
"chars": 862,
"preview": "package thisweek\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmd/time-entry/report/util\"\n\t\"github.com/lucassabre"
},
{
"path": "pkg/cmd/time-entry/report/today/today.go",
"chars": 815,
"preview": "package today\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmd/time-entry/report/util\"\n\t\"github.com/lucassabreu/c"
},
{
"path": "pkg/cmd/time-entry/report/today/today_test.go",
"chars": 6029,
"preview": "package today_test\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"githu"
},
{
"path": "pkg/cmd/time-entry/report/util/report.go",
"chars": 6907,
"preview": "package util\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"sort\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassab"
},
{
"path": "pkg/cmd/time-entry/report/util/report_flag_test.go",
"chars": 1716,
"preview": "package util_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmd/time-entry/report/util\"\n\t\"github.c"
},
{
"path": "pkg/cmd/time-entry/report/util/reportwithrange_test.go",
"chars": 22630,
"preview": "package util_test\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucass"
},
{
"path": "pkg/cmd/time-entry/report/yesterday/yesterday.go",
"chars": 837,
"preview": "package yesterday\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmd/time-entry/report/util\"\n\t\"github.com/lucassabr"
},
{
"path": "pkg/cmd/time-entry/show/show.go",
"chars": 2237,
"preview": "package show\n\nimport (\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmd/time-entry/util\"\n"
},
{
"path": "pkg/cmd/time-entry/split/split.go",
"chars": 4948,
"preview": "package split\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clock"
},
{
"path": "pkg/cmd/time-entry/split/split_test.go",
"chars": 8102,
"preview": "package split_test\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/"
},
{
"path": "pkg/cmd/time-entry/timeentry.go",
"chars": 1654,
"preview": "package timeentry\n\nimport (\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.com/lucassabreu/clockify-cli/"
},
{
"path": "pkg/cmd/time-entry/util/create.go",
"chars": 1407,
"preview": "package util\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/api\"\n)\n\n// FillMissingBillableFn returns a step that derive"
},
{
"path": "pkg/cmd/time-entry/util/description-completer.go",
"chars": 2814,
"preview": "package util\n\nimport (\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu/clockify-"
},
{
"path": "pkg/cmd/time-entry/util/fill-with-flags.go",
"chars": 1872,
"preview": "package util\n\nimport (\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmdutil\"\n\t\"github.com/lucassabreu/clockify-cli"
},
{
"path": "pkg/cmd/time-entry/util/flags.go",
"chars": 2271,
"preview": "package util\n\nimport (\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmdcompl\"\n\t\"github.com/lucassabreu/clockify-cl"
},
{
"path": "pkg/cmd/time-entry/util/help.go",
"chars": 2693,
"preview": "package util\n\nimport \"github.com/lucassabreu/clockify-cli/pkg/timeentryhlp\"\n\nconst (\n\tHelpTimeEntryNowIfNotSet = \"If no "
},
{
"path": "pkg/cmd/time-entry/util/interactive.go",
"chars": 6812,
"preview": "package util\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode/utf8\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t"
},
{
"path": "pkg/cmd/time-entry/util/interactive_test.go",
"chars": 13150,
"preview": "package util\n\nimport (\n\t\"testing\"\n\n\t\"github.com/AlecAivazis/survey/v2/terminal\"\n\t\"github.com/lucassabreu/clockify-cli/ap"
},
{
"path": "pkg/cmd/time-entry/util/name-for-id.go",
"chars": 1697,
"preview": "package util\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmdutil\"\n\t\"g"
},
{
"path": "pkg/cmd/time-entry/util/out-in-progress.go",
"chars": 680,
"preview": "package util\n\nimport (\n\t\"errors\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu/clockify-c"
},
{
"path": "pkg/cmd/time-entry/util/report.go",
"chars": 4787,
"preview": "package util\n\nimport (\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu/clockify-cli/a"
},
{
"path": "pkg/cmd/time-entry/util/util.go",
"chars": 2344,
"preview": "// util package provides reusable functionality to the commands under\n// pkg/cmd/time-entry, be it editing, creating, or"
},
{
"path": "pkg/cmd/time-entry/util/util_test.go",
"chars": 18962,
"preview": "package util\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu"
},
{
"path": "pkg/cmd/time-entry/util/validate-closing.go",
"chars": 755,
"preview": "package util\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmdu"
},
{
"path": "pkg/cmd/time-entry/util/validate.go",
"chars": 1394,
"preview": "package util\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu/"
},
{
"path": "pkg/cmd/user/me/me.go",
"chars": 1937,
"preview": "package me\n\nimport (\n\t\"io\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.co"
},
{
"path": "pkg/cmd/user/me/me_test.go",
"chars": 3616,
"preview": "package me_test\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.com/lucass"
},
{
"path": "pkg/cmd/user/user.go",
"chars": 2758,
"preview": "package user\n\nimport (\n\t\"io\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/"
},
{
"path": "pkg/cmd/user/user_test.go",
"chars": 4737,
"preview": "package user_test\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassab"
},
{
"path": "pkg/cmd/user/util/util.go",
"chars": 1162,
"preview": "package util\n\nimport (\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.com/lucassabreu/clockify-cli/pkg/c"
},
{
"path": "pkg/cmd/version/version.go",
"chars": 510,
"preview": "package version\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmdutil\"\n\t\"github.com/spf13/cobra\"\n)\n\n// New"
},
{
"path": "pkg/cmd/version/version_test.go",
"chars": 1416,
"preview": "package version_test\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmd/version\"\n\t\""
},
{
"path": "pkg/cmd/workspace/workspace.go",
"chars": 1517,
"preview": "package workspace\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmdutil\"\n\toutput \"github.com/lucassabreu/clockify-"
},
{
"path": "pkg/cmd/workspace/workspace_test.go",
"chars": 3270,
"preview": "package workspace_test\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabr"
},
{
"path": "pkg/cmdcompl/flags.go",
"chars": 1985,
"preview": "package cmdcompl\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\n// AddFixedSuggestionsToFlag add fixed suggestions to a flag\nfun"
},
{
"path": "pkg/cmdcompl/valid-args.go",
"chars": 1714,
"preview": "package cmdcompl\n\nimport (\n\t\"sort\"\n\t\"strings\"\n)\n\ntype ValidArgs interface {\n\t// IntoUse will return a string with a comp"
},
{
"path": "pkg/cmdcomplutil/client.go",
"chars": 1061,
"preview": "package cmdcomplutil\n\nimport (\n\t\"strings\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu/clockify-"
},
{
"path": "pkg/cmdcomplutil/factory.go",
"chars": 254,
"preview": "package cmdcomplutil\n\nimport \"github.com/lucassabreu/clockify-cli/api\"\n\ntype config interface {\n\tIsAllowNameForID() bool"
},
{
"path": "pkg/cmdcomplutil/project.go",
"chars": 2340,
"preview": "package cmdcomplutil\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu/cl"
},
{
"path": "pkg/cmdcomplutil/project_test.go",
"chars": 6313,
"preview": "package cmdcomplutil_test\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucass"
},
{
"path": "pkg/cmdcomplutil/tag.go",
"chars": 1031,
"preview": "package cmdcomplutil\n\nimport (\n\t\"strings\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu/clockify-"
},
{
"path": "pkg/cmdcomplutil/task.go",
"chars": 1134,
"preview": "package cmdcomplutil\n\nimport (\n\t\"strings\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu/clockify-"
},
{
"path": "pkg/cmdcomplutil/user.go",
"chars": 1053,
"preview": "package cmdcomplutil\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu/cl"
},
{
"path": "pkg/cmdcomplutil/workspace.go",
"chars": 853,
"preview": "package cmdcomplutil\n\nimport (\n\t\"strings\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu/clockify-"
},
{
"path": "pkg/cmdutil/args.go",
"chars": 705,
"preview": "package cmdutil\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/strhlp\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/spf13/cobr"
},
{
"path": "pkg/cmdutil/args_test.go",
"chars": 1956,
"preview": "package cmdutil_test\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmdutil\"\n\t\"github.com/spf"
},
{
"path": "pkg/cmdutil/config.go",
"chars": 7025,
"preview": "package cmdutil\n\nimport (\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/strhlp\"\n\t\"gith"
},
{
"path": "pkg/cmdutil/errors.go",
"chars": 304,
"preview": "package cmdutil\n\n// FlagError happens when a non-cobra validation fails\ntype FlagError struct {\n\terr error\n}\n\nfunc (fe *"
},
{
"path": "pkg/cmdutil/factory.go",
"chars": 4192,
"preview": "package cmdutil\n\nimport (\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu/clockify-cli"
},
{
"path": "pkg/cmdutil/flags.go",
"chars": 874,
"preview": "package cmdutil\n\nimport (\n\t\"sort\"\n\n\t\"github.com/lucassabreu/clockify-cli/strhlp\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/s"
},
{
"path": "pkg/cmdutil/flags_test.go",
"chars": 2239,
"preview": "package cmdutil_test\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmdutil\"\n\t\"github.com/spf"
},
{
"path": "pkg/cmdutil/project.go",
"chars": 517,
"preview": "package cmdutil\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/pkg/cmdcompl\"\n\t\"github.com/lucassabreu/clockify-cli/pkg/"
},
{
"path": "pkg/cmdutil/version.go",
"chars": 150,
"preview": "package cmdutil\n\n// Version register which is the CLI tag, commit and build date\ntype Version struct {\n\tTag string\n\tC"
},
{
"path": "pkg/output/client/csv.go",
"chars": 541,
"preview": "package client\n\nimport (\n\t\"encoding/csv\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n)\n\n// ClientsCSVPr"
},
{
"path": "pkg/output/client/default.go",
"chars": 689,
"preview": "package client\n\nimport (\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.com/olekukonko/tablewriter"
},
{
"path": "pkg/output/client/json.go",
"chars": 370,
"preview": "package client\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n)\n\n// ClientJSONPrint wi"
},
{
"path": "pkg/output/client/quiet.go",
"chars": 320,
"preview": "package client\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n)\n\n// ClientPrintQuietly will only"
},
{
"path": "pkg/output/client/template.go",
"chars": 540,
"preview": "package client\n\nimport (\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.com/lucassabreu/clockify-cli/pkg"
},
{
"path": "pkg/output/project/csv.go",
"chars": 572,
"preview": "package project\n\nimport (\n\t\"encoding/csv\"\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n)\n\n// ProjectsCSVPrint w"
},
{
"path": "pkg/output/project/default.go",
"chars": 868,
"preview": "package project\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.com/lucassabreu/cl"
},
{
"path": "pkg/output/project/json.go",
"chars": 377,
"preview": "package project\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n)\n\n// ProjectsJSONPrint"
},
{
"path": "pkg/output/project/quiet.go",
"chars": 324,
"preview": "package project\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n)\n\n// ProjectPrintQuietly will on"
},
{
"path": "pkg/output/project/template.go",
"chars": 547,
"preview": "package project\n\nimport (\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.com/lucassabreu/clockify-cli/pk"
},
{
"path": "pkg/output/tag/default.go",
"chars": 449,
"preview": "package tag\n\nimport (\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.com/olekukonko/tablewriter\"\n)\n\n// T"
},
{
"path": "pkg/output/tag/quiet.go",
"chars": 308,
"preview": "package tag\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n)\n\n// TagPrintQuietly will only print"
},
{
"path": "pkg/output/tag/template.go",
"chars": 527,
"preview": "package tag\n\nimport (\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.com/lucassabreu/clockify-cli/pkg/ou"
},
{
"path": "pkg/output/task/csv.go",
"chars": 495,
"preview": "package task\n\nimport (\n\t\"encoding/csv\"\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n)\n\n// TasksCSVPrint will pr"
},
{
"path": "pkg/output/task/default.go",
"chars": 615,
"preview": "package task\n\nimport (\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.com/olekukonko/tablewriter\"\n"
},
{
"path": "pkg/output/task/json.go",
"chars": 228,
"preview": "package task\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n)\n\n// TasksJSONPrint will "
},
{
"path": "pkg/output/task/quiet.go",
"chars": 312,
"preview": "package task\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n)\n\n// TaskPrintQuietly will only pri"
},
{
"path": "pkg/output/task/template.go",
"chars": 530,
"preview": "package task\n\nimport (\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.com/lucassabreu/clockify-cli/pkg/o"
},
{
"path": "pkg/output/time-entry/csv.go",
"chars": 1549,
"preview": "package timeentry\n\nimport (\n\t\"encoding/csv\"\n\t\"io\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n)\n\n"
},
{
"path": "pkg/output/time-entry/default.go",
"chars": 4994,
"preview": "package timeentry\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"gith"
},
{
"path": "pkg/output/time-entry/default_test.go",
"chars": 3347,
"preview": "package timeentry_test\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabr"
},
{
"path": "pkg/output/time-entry/duration.go",
"chars": 1034,
"preview": "package timeentry\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"golang.org/x/text/lan"
},
{
"path": "pkg/output/time-entry/duration_test.go",
"chars": 1260,
"preview": "package timeentry_test\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\ttimeentr"
},
{
"path": "pkg/output/time-entry/json.go",
"chars": 393,
"preview": "package timeentry\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n)\n\n// TimeEntryJSONPr"
},
{
"path": "pkg/output/time-entry/markdown.go",
"chars": 353,
"preview": "package timeentry\n\nimport (\n\t_ \"embed\"\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n)\n\n//go:embed markdown.gotm"
},
{
"path": "pkg/output/time-entry/markdown.gotmpl.md",
"chars": 1909,
"preview": "{{- $project := \"\" -}}\n{{- if eq .Project nil }}\n {{- $project = \"No Project\" -}}\n{{- else -}}\n {{- $project = concat "
},
{
"path": "pkg/output/time-entry/markdown_test.go",
"chars": 11253,
"preview": "package timeentry_test\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/MakeNowJust/heredoc\"\n\t\"github.com/lucassabr"
},
{
"path": "pkg/output/time-entry/quiet.go",
"chars": 363,
"preview": "package timeentry\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n)\n\n// TimeEntriesPrintQuietly w"
},
{
"path": "pkg/output/time-entry/template.go",
"chars": 742,
"preview": "package timeentry\n\nimport (\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.com/lucassabreu/clockify-cli/"
},
{
"path": "pkg/output/user/default.go",
"chars": 577,
"preview": "package user\n\nimport (\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.com/olekukonko/tablewriter\"\n)\n\n// "
},
{
"path": "pkg/output/user/json.go",
"chars": 235,
"preview": "package user\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n)\n\n// UserJSONPrint will p"
},
{
"path": "pkg/output/user/quiet.go",
"chars": 321,
"preview": "package user\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n)\n\n// UserPrintQuietly will only pri"
},
{
"path": "pkg/output/user/template.go",
"chars": 541,
"preview": "package user\n\nimport (\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.com/lucassabreu/clockify-cli/pkg/o"
},
{
"path": "pkg/output/util/color.go",
"chars": 420,
"preview": "package util\n\nimport (\n\t\"os\"\n\n\t\"github.com/lucassabreu/clockify-cli/pkg/ui\"\n)\n\n// ColorToTermColor coverts HEX color to "
},
{
"path": "pkg/output/util/template.go",
"chars": 2358,
"preview": "package util\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"strings\"\n\t\"text/template\"\n\t\"time\"\n\n\t\"github.com/lucassabreu/clockify-"
},
{
"path": "pkg/output/workspace/default.go",
"chars": 674,
"preview": "package workspace\n\nimport (\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.com/olekukonko/tablewriter\"\n)"
},
{
"path": "pkg/output/workspace/quiet.go",
"chars": 332,
"preview": "package workspace\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n)\n\n// WorkspacePrintQuietly wil"
},
{
"path": "pkg/output/workspace/template.go",
"chars": 559,
"preview": "package workspace\n\nimport (\n\t\"io\"\n\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"github.com/lucassabreu/clockify-cli/"
},
{
"path": "pkg/search/client.go",
"chars": 1439,
"preview": "package search\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"golang.org/x/sync/errgroup\"\n)\n\n// GetClientsByName"
},
{
"path": "pkg/search/errors.go",
"chars": 711,
"preview": "package search\n\nimport (\n\t\"sort\"\n\n\t\"github.com/lucassabreu/clockify-cli/strhlp\"\n)\n\n// ErrNotFound represents a fail to i"
},
{
"path": "pkg/search/find.go",
"chars": 722,
"preview": "package search\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\n\t\"github.com/lucassabreu/clockify-cli/strhlp\"\n)\n\ntype named interface {\n\t"
},
{
"path": "pkg/search/find_test.go",
"chars": 1375,
"preview": "package search\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestSearchOnList(t *testing.T) {\n\tent"
},
{
"path": "pkg/search/project.go",
"chars": 3457,
"preview": "package search\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/lucassabreu/clockify-cli/api/dto\"\n\t\"git"
},
{
"path": "pkg/search/tag.go",
"chars": 836,
"preview": "package search\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"golang.org/x/sync/errgroup\"\n)\n\n// GetTagsByName re"
},
{
"path": "pkg/search/task.go",
"chars": 1318,
"preview": "package search\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"golang.org/x/sync/errgroup\"\n)\n\n// GetTaskByName wi"
},
{
"path": "pkg/search/user.go",
"chars": 867,
"preview": "package search\n\nimport (\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"golang.org/x/sync/errgroup\"\n)\n\n// GetUsersByName r"
},
{
"path": "pkg/timeentryhlp/timeentry.go",
"chars": 2393,
"preview": "package timeentryhlp\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/lucassabreu/clockify-cli/api\"\n\t\"github.com/luc"
},
{
"path": "pkg/timehlp/range.go",
"chars": 480,
"preview": "package timehlp\n\nimport \"time\"\n\n// GetMonthRange given a time it returns the first and last date of a month\nfunc GetMont"
},
{
"path": "pkg/timehlp/relative.go",
"chars": 1584,
"preview": "package timehlp\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n)\n\nvar ErrInvalidReliveTime = errors.N"
}
]
// ... and 16 more files (download for full content)
About this extraction
This page contains the full source code of the lucassabreu/clockify-cli GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 216 files (852.5 KB), approximately 265.3k tokens, and a symbol index with 1259 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.