-
-
Notifications
You must be signed in to change notification settings - Fork 398
Rewrite the output system #345
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
Conversation
I like the rationale behind this PR. +1 from my side. |
if output.JSONOrElse(platforms) { | ||
if globals.OutputFormat == "json" { | ||
feedback.PrintJSON(platforms) | ||
} else { |
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'll add a comment here but it's valid for all the occurrence of this snippet of course :-)
Is it better to write something like:
if feedback.PrintJSONOrElse(platforms) {
 outputInstalledCores(platforms)
}
or
if globals.OutputFormat == "json" {
feedback.PrintJSON(platforms)
} else {
outputInstalledCores(platforms)
}
This is boilerplate code that is duplicated everywhere (basically on each command that may potentially output json).
Every breaking change to the internals of the feedback
/globals.OutputFormat
, just to say, must be propagated on all occurrences of this snippet.
In this case, IMHO, less is more.
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.
Let me see if I can find a simple way to remove the boilerplate before defending it 😄
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.
Here is my proposal: let's move the responsibility to evaluate the format away from the commands by introducing a "results container" that's very thin but generic enough to support different formats and that would be used only when highly customized outputs are required.
PROS:
- The boilerplate is gone, cfr this example with no if-s at all. As a side effect, the format can be removed from the globals.
- This approach is future proof, any new format we should add would have very little to zero impact on the existing commands
- When a complex formatting logic is needed, that stays with the command that needs it
CONS:
- When the output is complex, you have to write a struct to provide the custom logic
- the feedback package gets more responsibilities but somebody has to decide which output to use
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.
PrintResult
is defined as:
func (fb *Feedback) PrintResult(res Result) {
but IIUC we need a function that accepts any type and do a runtime check to see if the argument is a Result
(unless we want to always wrap the result in a Result
but this will multiply the boilerplate by 10x), so something like this:
func (fb *Feedback) PrintResult(res interface{}) {
if r, ok := res.(Result); ok {
...print using JSON(r.Data()) or r.String()...
} else {
...print using JSON(res) or res.String()...
}
}
Anyway, besides the above, the issue is another: there isn't a generic way to print complex outputs.
One thing is a simple primitive type like int
or string
another is a Library
or a Board
or a []PortList
, they all have their custom print with tables and verbosity that most of the time shows a subset of the available fields, sorted in some random way, so you're basically in always in the worst case (where you need to wrap inside a Result
).
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.
No type assertion, you always get a Result
that can provide a string or an interface of some sort that's supposed to be marshalled (today in JSON, tomorrow we'll see). The "boilerplate" you add by always providing a Result
object is just a struct definition plus a method definition (you always have an equivalent of the String method).
Anyway, besides the above, the issue is another: there isn't a generic way to print complex outputs.
I know, I tentatively drafted a solution to see if I could remove the pattern
if globals.OutputFormat == "json" {
feedback.PrintJSON(ports)
} else {
outputListResp(ports)
}
but I'm fine with that. Instead I'm not fine with something like JSONOrElse
which is non-obvious, has a vague name and doesn't solve the root problem.
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.
but I'm fine with that. Instead I'm not fine with something like JSONOrElse which is non-obvious, has a vague name and doesn't solve the root problem.
Ok, let's keep the bigger if then, there is no point in discussing this further, we could always go back if we change our mind ;-)
I've added two comments, BTW it's great to see the |
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.
Excellent work!
This PR rewrites the whole logging and feedback system of the CLI. It also fixes #34
Rationale
This PR implements a blueprint where the logging system is detached from the output produced in response to user actions.
Logging
The logging system is used by low level components to leave marks at any step of the code execution and it's intended to be mainly consumed by machines. Structured logging will be encouraged and a review of what we're logging right now, the logging levels and the structure of the messages will be eventually needed. To avoid passing around a logger object, the default
logrus
instance is used (this hasn't changed, it was already the case before this PR).Feedback
We call "Feedback" the system used to report an output in response to an user action. For the time being, errors go to the standard error and any other information goes to standard output. To keep consistency across the source code, a new object called
Feedback
was added to provide afmt
-like interface the CLI commands can use to show messages to the user. The object interface is purposely minimal and there's almost no logic. To fill the gap with logging, when an error is sent back to the user through the Feedback API, the message is also forwarded to the default logger.User interface
To put the user in control of the logging and feedback systems, CLI options changed as following:
-v
,--verbose
option was added. When passed, the output of the logging system is displayed in the standard output. The logger output has colors to ease human browsing.--log-file
option was added. When passed, the logger will write messages to the given file.option was added to control which messages the log should forward. This applies to both the standard output when
-vis passed, and the file on disk when
--log-file` is passed.--debug
was removed. The closer option to that is nowarduino-cli -v --log-level debug
Additional notes
Almost 1k lines of code were removed for different reasons, most notably:
ascii tables are now provided by https://github.com/cheynewallace/tabby that's just 70 lines of code and relies on the standardtabwriter
formatter
was only partially used and very often used inconsistently (different calls for the same goal), theFormatter
is now used everywhereoutput
where swapped with more explicit code