Age | Commit message (Collapse) | Author |
|
* Increment version number
* Update CHANGELOG
* Change `VERSION` variable in main.go to a constant because this makes
more sense
|
|
Take the errors in `main()` that check the response body contents for
known error strings and put them in their respective functions in
"timetask/http.go".
Didn't really make sense to me that these functions were returning HTTP
responses that we weren't really using in a meaningful way. Instead they
should just do their thing and let us know if there was a problem.
That includes checking to see if there were any non-standard errors,
like the ones we had custom-built. Now all that handling and
error-making is self-contained, which feels much nicer.
|
|
Sprints will change with time while the other IDs of a project will stay
the same. Thus sprints, which live in the `Module` field, must be
updated regularly.
In order to facilitate that updating, instead of requiring users to get
those IDs directly from the TimeTask website every time, have them use
this command to get the names of sprints and their IDs. They can then
update the ID manually in their config file.
This code makes a request to the endpoint that returns module IDs for
the site (the site queries this via AJAX to update its interface).
The result of the request is some XML containing the modules and their
IDs. For now I'm just printing this out. We'll want to parse the XML and
display it in a nicer way.
|
|
Add some simple error handling for known responses from our TimeTask
HTTP requests. Check a couple of known strings to determine whether
there was an error. If so, exit with a failing error code.
Remove our old `log` statements. These were used during debugging to see
some output and check responses from TimeTask. We now have an idea of
what those responses are, and are handling some of the error cases. Thus
the log statements are no longer needed.
|
|
|
|
Remove the initial cap on Kingpin error messages for consistency in our
messages.
|
|
Otherwise, if users haven't created a config file and they run `--help`,
they'll get an error complaining that the config file doesn't exist
instead of helpful usage output.
|
|
* Change the initial capital to lowercase for consistency with the other
error messages
* Suggest using `--write-config` as a possible solution to the error (in
cases where the config file doesn't exist)
|
|
Given a string from `password_cmd`, we should execute it as a shell
command and assume the result is a password. That password then gets
submitted to TimeTask's login form for authentication.
Had to do a bit of wrangling to get the command to execute by
`exec.Command()`, but managed to get it working in a bit of a roundabout
way.
Found these resources in my searches:
- https://stackoverflow.com/questions/39930109/golang-execute-command#39930127
https://stackoverflow.com/questions/28783637/how-to-make-golang-execute-a-string
- https://stackoverflow.com/questions/20437336/how-to-execute-system-command-in-golang-with-unknown-arguments
- https://github.com/codeskyblue/go-sh
|
|
Thanks gofmt
|
|
In order to maintain a consistent format when echoing error messages,
use the Kingpin helpers to do so. Rewrite our error message code to this
effect. This has the added benefit of making our code shorter.
|
|
As far as I've been able to figure out, Kingpin doesn't have a mechanism
for dependent arguments or conditional requireds. Thus there's no way to
say "--position is only required if --write-config isn't passed".
In that case, write our own "required" check, to enforce the presence of
"--project" only if "--write-config" isn't passed. We duplicate the
message that Kingpin provides from `Required()` and leverage its error
formatting (we should probably use this for our other error messages
too).
The only difference is that `--position=POSITION` won't appear on the
first line of the Help text, which would have emphasised the fact that
it's required. It's possible to configure Kingpin's help text via
templates, but I don't think that's worth the trouble at this point.
|
|
Ensure that we're not actually submitting a time entry if the
`--write-config` flag was passed. In that case, we just want to write
the empty config file and exit successfully.
|
|
We shouldn't automatically force writing the config file. Only do so if
the user asks for it to be done.
NOTE: there's a problem here, because `-p` is required but it shouldn't
be in this specific case.
|
|
|
|
I figured it would be a good idea to make this function and
`loadConfig()` consistent. Since `loadConfig()` is private, make this
one private also.
|
|
Now that we have a 'config.go' file, it makes more sense for these two
to live in that file.
Change `loadConfig()` to return an error instead of printing it to the
log.
|
|
If the function results in an error, print it and exit.
|
|
A new function that will write a new config.toml file to the
XDG_CONFIG_HOME directory. Currently it checks to see whether our config
file is present. If not and our config directory isn't present, it
creates it.
Still need to get this to actually write the config file.
Also, we won't want to call it by default in main() like we're doing
now. Will likely want to hide it behind a `--write-config` flag.
|
|
|
|
Didn't really like having the application version intermixed with
regular code. This separates it and makes it easier to see.
|
|
I had forgotten that time spent can be a decimal. Make this field a
float and change related code accordingly:
* --time flag
* `NewTimeEntry()` `time` argument
* `buildSubmissionParams()` can't use `strconv.Itoa` as that's an
integer function. Instead we use `FormatFloat`. Truncate time parsing
to two decimal places because to me that seems like enough. Who's
going to say they spent `0.324` hours on something? Also, in order to
be able to properly submit values to TimeTask (at least on the edition
I use), the times must be written in European/French format, with
commas (`,`) as decimal separators. Do a string replace to get this,
as the float formatter will give us a period (`.`) separator.
|
|
If no date is passed in, default to the current date. Otherwise, parse
the date into a time that can be used to create the `TimeEntry`.
* Rename `date` to `date_str` to allow us to use `date` for the
`time.Time` that gets sent to `NewTimeEntry()`.
* If parsing fails, print an error message and exit.
* In order to use the `err` variable without redefining `date` on line
66, define it at the top of the function.
|
|
Forgot to do this in 8d802bff08826523371ab5e951d85d0c0396ccc8.
Also, the reason why I made --description's short form `-m` is because I
wanted `-d` for date. The `-m` is supposed to be like "message", like in
git-commit.
|
|
If the user specifies a project alias that doesn't exist in their
config.toml file, we should fail early and message the user about it.
|
|
Make it easier to comment out the HTTP requests when testing command
line argument parsing. To test, we're just printing the time entry with:
log.Printf("%+v\n", time_entry)
Fill in the values from our new command line argument variables. These
need to be dereferenced as they come out of Kingpin as pointers.
Haven't completely worked out how to deal with the 'date' argument yet
so leaving it for later.
Need to fail early if `project_alias` isn't recorgnised.
|
|
* Use the Kingpin library to give us POSIX command line argument parsing
with a nice interface
* Add arguments for the project alias (specified in config.toml), time
spent, date, and description
* Add a version, required by Kingpin
|
|
This code was used for the old multiple time entry submission version of
this program. Remove it now that the program only submits single time
entries.
We'll have similar argument handling code for user data, and the time
entry submission code is already present here.
|
|
|
|
|
|
This field wasn't parsing correctly because of it's
non-automatically-parseable format.
|
|
Previously we were just printing the headers. Now print the bodies to
allow us to inspect the result.
Will probably want to add some handling that messages the user about
authentication problems.
|
|
main.go:
* Login as the configured user (haven't yet handled making `PasswordCmd`
an actual shell command)
* Create a test time entry
* Submit that time entry using `SubmitTimeEntry()`
http.go:
* Create a `baseURL` global that stores the base TimeTask URL to make
requests to
* Return an `http.Client` from `Login()` that can then be passed to
`SubmitTimeEntry()` to reuse the login session. Needed to return a
pointer to allow us to return `nil` from the first error handler in
the function. Don't like that at all, but we're just trying to get it
to work at this point.
* Actually make an HTTP POST request in `SubmitTimeEntry()` using the
given HTTP Client and existing time entry submission params
* Take an `http.Client` argument in `SubmitTimeEntry()` to allow us to
use a logged-in session to POST. Otherwise we'd be locked out.
* Change `v` variable to `values` in `SubmitTimeEntry()` for better
readability
|
|
Require the config.toml file to come with a `[profile]` hash. This
allows us to get the "person_id" for correct submission to TimeTask.
Additionally, add a TOML tag to `PersonID` in `Profile` to allow it to
be decoded.
|
|
This corresponds to a "project" entry in the new config2.toml file. (See
13c84cd9973458750305c72a919cf921d9b22b04).
Instead of decoding generic `interface{}`s as projects from the TOML,
make them a real type.
The reason why we're using `int`s where we used to use strings is that
the new TOML format will have users write IDs directly in the config
file, instead of having the program automatically search for those IDs
and use them as we had previously designed. Maybe we'll bring that
functionality back at some point, but for now it's too bothersome to
implement and I don't consider it worth the trouble.
|
|
Half get rid of a lot of code. I don't like and don't want to use our
old field types. Get rid of them and the code in 'http.go' that depends
on them.
Also get rid of the time entry submission code in 'main.go' as that's
going to be redone.
|
|
* Get rid of the 'yaml' import since we're now using 'toml' instead.
* Get rid of commented code that's no longer relevant.
* Get rid of test code that checked that the config loaded
correctly.
|
|
Construct a new config format, written in TOML. Read that format in
when starting the program. This new format has the benefit of using
project name aliases as keys. The goal will be to allow users to send
one of those aliases as a command line argument to the program,
and thus to have the program post a TimeTask entry for that project.
Here's an idea of what the new format looks like:
[auth]
username = "example"
password_cmd = ""
[projects.myprojectalias]
client = ...
project = ...
module = ...
task = ...
work_type = ...
time = 7
billable = true
[projects.project2]
client = ...
project = ...
module = ...
task = ...
work_type = ...
time = 7
billable = true
Eventually, we'll need to remove the `interface{}` from the
`Projects` map value and replace it with a real type, but this was just
to test that it was possible to get us a nice map from the TOML.
|
|
Use a GitHub path instead of my custom project path to allow us to
publish the project.
|
|
A function to generate a weekly time sheet.
Add a new `defaults` key to the config.yml file. Looks like this:
defaults:
client:
project:
module:
task:
work_type:
time:
billable:
This will be used to fill in default values when a timesheet is
generated.
|
|
|
|
We'll be needing to refer to these fields as named types, so create
types for them and reference them in the config object.
|
|
Expect a timesheet file as the last argument to the program. Parse the
contents into `TimeEntry` objects. `TimeEntry`ies will then be able to
be POSTed to Time Task to submit times.
The time entries input file is a YAML document in this format:
- client: A client
project: A project
module: A module
task: A task
work_type: type
date: 2017-03-06
time: 7
billable: true
description:
It contains an array where each element is a time entry.
Had a lot of trouble parsing the date into a `time.Time`. Finally
realised that my first and biggest problem was somehow I was importing
`yaml.v1` instead of `yaml.v2`, and thus my `UnmarshalYAML` function was
never getting called.
Wanted a way to get the time as a string and parse it myself into a
time. At first tried using an `UnmarshalText` function:
type Time time.Time
func (t *Time) UnmarshalText(text []byte) error {
parsed, err := time.Parse("2006-01-02", string(text))
if err == nil {
*t = Time(parsed)
}
return err
}
But in order to do that I had to make a type alias to `time.Time`. Doing
so was not ideal, because then I'd have to convert my `Time` into a
`time.Time` any time I wanted to use it for real.
Ended up going with a suggestion from here:
https://mlafeldt.github.io/blog/decoding-yaml-in-go/
Creating an auxiliary struct in `UnmarshalYAML` to unmarshal the date
into a string and then parse it myself as a date. I don't really like it
because it's a lot of ceremony just to parse one type myself, but can't
come up with a better solution right now so there you have it.
|
|
* Extract the config loading lines from `main` so we can give them a name
* Make `config` available globally
|
|
Set up a configuration object which gets read from a YAML config file.
Currently using an uncommitted test file that looks like this (with some
data filled in:
auth:
username:
password_cmd:
fields:
person_id:
clients:
- id:
name:
projects:
- id:
name:
modules:
- id:
name:
tasks:
- id:
name:
work_types:
- id:
name:
The program just outputs the config object so I can see whether it's
working. The data will then be used to associate ids for time
submission.
|