-
-
Notifications
You must be signed in to change notification settings - Fork 640
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add task parameters #32
Conversation
Is there anyway we could leave YAML to handle it for us? E.g.:
A bit more readable perhaps, I assume this would be equivalent:
|
cyclic.go
Outdated
if err != nil { | ||
return true | ||
} | ||
d = call.Name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest we do this even earlier, when we parse the YAML/JSON/TOML. This would allow us to let YAML/TOML/JSON handle quotes for parameter values, and thus confuse the user less. This would also allow us to deprecate the "^" syntax in favour of a more explicit one. For instance we could say we create a new type "instruction":
type Task struct {
Cmds []instruction
Deps []instruction
...
}
type instruction struct {
IsShellCmd bool
Task string // holds name of task or full shell command.
Vars map[string]string
}
func (instr *instruction) UnmarshalJSON(data []byte) error {
m := make(map[string]string)
err := json.Unmarshal(data, m)
if err =! nil {
instr.IsShellCmd = true
return json.Unmarshal(data, &instr.Task)
}
instr.IsShellCmd = false
instr.Task = m["task"] // the task name, including an empty value (""), will be validated later.
delete(m["task"])
instr.Vars = m
}
}
// Equivalent for YAML/TOML if needed...
I don't know if YAML/TOML will "fall-back" to convert data to JSON and parse it to UnamrshalJSON if an UnmarshalJSON function is found? that would be crazy performance wise, but functional wise it might make sense if they did?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe even more explicit if we do this...
taskA:
cmds:
- echo "I am a string"
- task: taskB
vars:
- PARAM1: I am a string with spaces
env:
- ENV1: |
We could even override the env, but let's skip
that for the first implementation not to complicate
things to much.
But at same time the indentation here is starting to get very deep!
@smyrman Pushed. Using the more verbose version (with Feedback would be awesome. Task is in need of a few refactorings. Good that we have tests to ensure everything keep working while doing these changes. |
Looks great. One question is if "params" is really the best name for usage in the YAML files, or if it should be called "vars" to give the end-user fewer terms to remember. A good test for what's the best thing to do would be to write the READEM documentation and see what name makes the documentation easiest to write/read. I believe that if you find you need to use a lot of documentation real-estate to explain a feature, it's often designed or named slightly wrong. Likewise, if it becomes very easy to document, the design and naming is probably good. |
Cmds []string | ||
Deps []string | ||
Cmds []*Cmd | ||
Deps []*Dep |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless there is any case where it makes sense to have nil
cmds/deps it's probably better to not use pointers here.
Why?
Code wise it's usually cleaner.
As an FYI: Memory allocation / performance wise, it's usually better to have all the actual structs within the slice, so that all the memory for these objects will be allocated in one big slice of bytes and are more likely to be accessed faster in for-loops due to pipelining. Also there is less memory fractions and less garbage-collection work to do. This only really matters in tight loops for huge programs though; it's not going to matter for task.
@@ -38,8 +38,8 @@ type Tasks map[string]*Task | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless the semantics of a nil task is needed, or unless you often need to retrieve a task pointer, you could concider to use map[string]Task. It will have some consequences for how you can write to the tasks though, as this will then fetch out a copy:
t, ok := e.tasks[name]
command.go
Outdated
type Cmd struct { | ||
Cmd string | ||
Task string | ||
Params Params |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on what's easiest to document in the README.
If you need to rename "parms" -> "vars" for the end-user only, you could probably add a struct tag here and in dep, if your yaml lib supports it:
Params Params `yaml:"vars"`
task.go
Outdated
} | ||
|
||
func (e *Executor) isUpToDateStatus(ctx context.Context, task string) (bool, error) { | ||
func (e *Executor) isUpToDateStatus(ctx context.Context, task string, params Params) (bool, error) { | ||
t := e.Tasks[task] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's considered clean code (by some author called Robert C. Martin) to try to keep the number of positional parameters in a function to a minimum. Often just 1, or maybe 2, or in worst case 3. Generally I think you here handle the trade-offs well, but I will give some tips that you could consider if you feel it's necessary, or otherwise feel free to ignore for now.
Since all the helper methods that need params, also needs the task name, one way of reducing the number of positional parameters, could be to use the Deps
struct in place of task and params. It might require another type name to not seem confusing in this context, e.g. TaskReference
, but that will work pretty well in the original struct where it's used as well (Deps []TaskReference
). It could even be given a helper function that fetches the task (with Vars
overwritten by params) from an Executor instance. This also lets you deal with the lookup-error handling once.
Something similar could be done for when you need to reference a Cmd, where you basically use the same struct but include and Index int
for looking up
type TaskReference struct{
Task string
Params Params
}
// Task returns a copy of the task in e with Vars updated with values from Params,
// or an error if the task don't exist.
func (tr *TaskReference) Task(e *Executor) (*Task, error) {
}
type CmdReference struct{
TaskReference
Cmd int
}
// Cmd returns a copy of the command in e with all template variables replaces by
// intended values.
func (tr *CmdReference) Cmd(e *Executor) (*Cmd, error) {
}
Don't be afraid to ignore any of my comments above btw. They are just comments. Your code is good. |
@smyrman I appreciate your comments! Some I already had in mind, like the I think I'll change I'm still not sure about the final syntax. Any thought? task:
cmds:
- task: task-name
PARAM: my-param vs. task:
cmds:
- task: task-name
vars:
PARAM: my-param The second one is more verbose, but also more future-proof (if we ever have more properties other than |
I think I'll keep the verbose version. One can use YAML magic to use less lines: task:
cmds:
- task: task-name
vars: {PARAM: my-param, ANOTHER_PARAM: value} |
I think the verbose syntax is good. It also opens up for overriding "env" in the future without compromising backwards-compatibility. |
Basic implementation of #31
Supported both in
cmds
anddeps
.TODO:
Support space in parameters. I'm thinking of using quotes for that.^task PARAM="value with spaces"
~~In this case, it should also be possible to sanitize a quote:^task PARAM="value with \"quotes\""
. Any bettersimpler idea for this?$
prefix?