asdf-vm / asdf

Extendable version manager with support for Ruby, Node.js, Elixir, Erlang & more
https://asdf-vm.com/
MIT License
21.96k stars 780 forks source link

Wrap terminal line output to 80 characters #646

Open jsejcksn opened 4 years ago

jsejcksn commented 4 years ago

Scannability would be improved greatly if lines were wrapped at 80 characters.

Here is example terminal output from the command asdf help. The longest unwrapped line is 125 characters:

asdf help
version: v0.7.6-6207e42

MANAGE PLUGINS
  asdf plugin add <name> [<git-url>]       Add a plugin from the plugin repo OR,
 add a Git repo
                                           as a plugin by specifying the name an
d repo url
  asdf plugin list [--urls] [--refs]       List installed plugins. Optionally sh
ow git urls and git-ref.
  asdf plugin list all                     List plugins registered on asdf-plugi
ns repository with URLs
  asdf plugin remove <name>                Remove plugin and package versions
  asdf plugin update <name> [<git-ref>]    Update a plugin to latest commit or a
 particular git-ref.
  asdf plugin update --all                 Update all plugins

MANAGE PACKAGES
  asdf install [<name> <version>]          Install a specific version of a packa
ge or,
                                           with no arguments, install all the pa
ckage
                                           versions listed in the .tool-versions
 file
  asdf uninstall <name> <version>          Remove a specific version of a packag
e
  asdf current                             Display current version set or being 
used for all packages
  asdf current <name>                      Display current version set or being 
used for package
  asdf where <name> [<version>]            Display install path for an installed
 or current version
  asdf which <command>                     Display the path to an executable
  asdf shell <name> <version>              Set the package version in the curren
t shell
  asdf local <name> <version>              Set the package local version
  asdf global <name> <version>             Set the package global version
  asdf list <name>                         List installed versions of a package
  asdf list all <name>                     List all versions of a package

UTILS
  asdf exec <command> [args..]             Executes the command shim for current
 version
  asdf env <command> [util]                Runs util (default: `env`) inside the
 environment used for command shim execution.
  asdf reshim <name> <version>             Recreate shims for version of a packa
ge
  asdf shim-versions <command>             List on which plugins and versions is
 command available
  asdf update                              Update asdf to the latest stable rele
ase
  asdf update --head                       Update asdf to the latest on the mast
er branch

"Late but latest"
-- Rajinikanth
Stratus3D commented 4 years ago

Thanks for opening this issue. I think we should limit line lengths to 80 characters so they can fit on terminals of the traditional size.

I'm usually working on a 15 inch screen or smaller, and like to have two terminals side by side, which means lines need to be less than 90-100 characters to not wrap.

jthegedus commented 4 years ago

A bash utility that handled this and the column issue in https://github.com/asdf-vm/asdf/issues/661 would be amazing.

jsejcksn commented 4 years ago

Wouldn't the util need to be aware of all of the indentation formats used by all of the commands in order to automatically wrap them?

For example, the help command output above looks like it uses a static indent of 41 characters for the descriptions. Are all the other output types in this format?

Or is there maybe an existing tool for generating tabular text content wrapped at 80 chars?

jthegedus commented 4 years ago

@jsejcksn https://github.com/asdf-vm/asdf/pull/742 should resolve the wrapping issue for the asdf help command. Though I am looking to revamp our output formatting for all content that is >1 column of information.

How do we feel about not necessarily a hard cap at 80 chars, but a solution that wraps by terminal width at the time of execution?

Additionally, how do we feel about rendering multiple columns as alternating rows, eg:

➜ asdf plugin list --urls
PLUGIN      URL
deno        https://github.com/asdf-community/asdf-deno.git
firebase    https://github.com/jthegedus/asdf-firebase.git
gcloud      https://github.com/jthegedus/asdf-gcloud.git
golang      https://github.com/kennyp/asdf-golang.git
gradle      https://github.com/rfrancis/asdf-gradle.git
hadolint    https://github.com/looztra/asdf-hadolint.git
java        https://github.com/halcyon/asdf-java.git
maven       https://github.com/halcyon/asdf-maven.git
nodejs      https://github.com/asdf-vm/asdf-nodejs.git
ocaml       https://github.com/asdf-community/asdf-ocaml.git
python      https://github.com/danhper/asdf-python.git
shellcheck  https://github.com/luizm/asdf-shellcheck.git
terraform   https://github.com/Banno/asdf-hashicorp.git

Becoming

➜ asdf plugin list --urls
PLUGIN
URL
deno
https://github.com/asdf-community/asdf-deno.git
firebase
https://github.com/jthegedus/asdf-firebase.git
gcloud
https://github.com/jthegedus/asdf-gcloud.git
golang
https://github.com/kennyp/asdf-golang.git
gradle
https://github.com/rfrancis/asdf-gradle.git
hadolint
https://github.com/looztra/asdf-hadolint.git
java
https://github.com/halcyon/asdf-java.git
maven
https://github.com/halcyon/asdf-maven.git
nodejs
https://github.com/asdf-vm/asdf-nodejs.git
ocaml
https://github.com/asdf-community/asdf-ocaml.git
python
https://github.com/danhper/asdf-python.git
shellcheck
https://github.com/luizm/asdf-shellcheck.git
terraform
https://github.com/Banno/asdf-hashicorp.git

on terminals where the screen width is less than required for horizontal layout?

jsejcksn commented 4 years ago

How do we feel about not necessarily a hard cap at 80 chars, but a solution that wraps by terminal width at the time of execution?

If, by that, you mean a responsively-formatted output in which "it will wrap the contents to 80 chars if the terminal is 80 columns, or 90 chars if the terminal is 90 columns, etc." then I think that's a fantastic idea. I'm very curious how you plan to accomplish this if you are doing it in a POSIX-compliant way, and there are many projects which would benefit from such a formatter.

Alternating rows:

...on terminals where the screen width is less than required for horizontal layout?

I think that a consistently-formatted output might be important for parsers. I have used the output of various asdf commands to guide automated scripts (extracting versions, URLs, etc.). If you decide to use the alternating format, I think an extra newline character before each name will improve readability greatly.

Finally, this makes me wonder: Is all of this data available in a machine-readable format somewhere? Or it is the responsibility of each plugin to source and correctly format the output of commands like asdf list all <plugin>?

jthegedus commented 4 years ago

If, by that, you mean a responsively-formatted output in which "it will wrap the contents to 80 chars if the terminal is 80 columns, or 90 chars if the terminal is 90 columns, etc."

Yes, that is the idea. It would not be an external package, just some more complex awk. Looking at our formatting, we only have a few instances where we render multiple columns of data, the issue I have with columns is wrapping within a column. I would like our help.txt to not be formatted inline (2nd column wraps within the column of text). Given we have a known set of outputs, we don't need to be generic as tools like column are, and thus, have no need to build a new tool or increase our dependency list. Just have more complex code here.

To be clear, I would like line wrapping to be mostly handled by the terminal. There's just times, like with the help text, where wrapping within a column is what is desired, not wrapping to the start of the next line as would happen with multiple columns where there's overflow. If the content can be wrapped in a better way without inline formatting, then (I think) we should. For instance, 80 cols is a standard width and is a fine starting point, but by wrapping at 80 our help.txt is now 18 rows taller (totaling 55 rows) than without wrapping. I personally use a wide terminal that is not very tall, so this is cumbersome. If there's an opportunity to be flexible, then I think we should discuss it.

I think that a consistently-formatted output might be important for parsers.

I had not considered this. Good point. POSIX [:space:] character class is apparently all whitespace, including newlines, so perhaps REGEX-based parsing wouldn't be affected by such a formatting change. :thinking:

I will spend more time thinking on this as I do have ideas for outputs with more columns of information that would not be 80 col friendly by default, perhaps I could use a --less flag instead of changing the format.

Is all of this data available in a machine-readable format somewhere? Or it is the responsibility of each plugin to source and correctly format the output of commands like asdf list all ?

Each plugin needs to follow specific output guidelines for each command and then the core asdf takes that data and renders it. So the terminal output formatting would be within the core.

:new: Another question while I am looking at the output formatting, some of our outputs have headings, some do not. I feel we should be consistent. What are peoples feelings on these differences: ```shell ➜ asdf list deno 1.0.0 1.0.5 firebase 8.4.1 ``` Weird formatting. Should probably be either of: ```shell ➜ asdf list PLUGIN VERSION deno 1.0.0 1.0.5 firebase 8.4.1 ``` or ```shell ➜ asdf list deno 1.0.0 1.0.5 firebase 8.4.1 ``` --- ```shell ➜ asdf list python 3.8.2 ``` ```shell ➜ asdf plugin list deno firebase python ``` ```shell ➜ asdf plugin list --urls deno https://github.com/asdf-community/asdf-deno.git firebase https://github.com/jthegedus/asdf-firebase.git python https://github.com/danhper/asdf-python.git ``` Columnar data, no column titles. ```shell ➜ asdf plugin list --refs deno master 3860217 firebase master fdab358 python master b544ac9 ``` Columnar data, no column titles, last column isn't spaced consistently (just happen to align because all on `master`). ```shell ➜ asdf plugin list --refs --urls deno https://github.com/asdf-community/asdf-deno.git master 3860217 firebase https://github.com/jthegedus/asdf-firebase.git master fdab358 python https://github.com/danhper/asdf-python.git master b544ac9 ``` Columnar data, no column titles, third & fourth columns aren't spaced consistently. ```shell ➜ asdf plugin list all 1password https://github.com/samtgarson/asdf-1password.git adr-tools https://gitlab.com/td7x/asdf/adr-tools.git aks-engine https://github.com/robsonpeixoto/asdf-aks-engine.git alp https://github.com/asdf-community/asdf-alp.git ... ``` Columnar data, no column titles. ```shell ➜ asdf current deno 1.0.0 (set by /home/jthegedus/.tool-versions) version 8.0.2 is not installed for firebase firebase python 3.8.2 (set by /home/jthegedus/.tool-versions) ``` Errors break rendering flow, see https://github.com/asdf-vm/asdf/issues/528. Columnar data, no column titles. Any thoughts? While it would break some scripts and is annoying to trim, I prefer column titles. I also prefer titles everywhere instead of only sometimes. Spacing changes should be easily compatible if people are using Regex. Perhaps this should be another issue.
jsejcksn commented 4 years ago

I think headings are useful.

➜ asdf list                    
deno
  1.0.0
  1.0.5
firebase
  8.4.1

I don’t think that first example is weird. This example seems odd though:

➜ asdf list                    
deno
1.0.0
1.0.5
firebase
8.4.1 

Does asdf strictly require semver version strings? If not, then how do I know that firebase and 8.4.1 aren’t deno versions? Unique visual formatting (fixed indentation / relative positioning / etc.) of different data types is important, I think.

jthegedus commented 4 years ago

@jsejcksn

Does asdf strictly require semver version strings? If not, then how do I know that firebase and 8.4.1 aren’t deno versions?

EDITED below here:

It doesn't require semver version strings. EG:

java           adopt-openjdk-11.0.6+10  (set by /home/jthegedus/.tool-versions)

Good points again, thanks for you input.

I think headings are useful.

For clarity, I meant column headings, not the weird list with indentation:

PLUGIN      VERSION
deno        1.0.0
deno        1.0.5
firebase    8.4.1

I've been saying weird, but I mean inconsistent. Cols with headings is explicit and better uses both horizontal and vertical space

jthegedus commented 4 years ago

Ironically, GitHub comments are now capped at ~120 chars instead of 80 :wink:

Stratus3D commented 4 years ago

Couple thoughts:

jsejcksn commented 4 years ago

+1 for a machine-readable-formatted option using a flag. I suggest JSON.

jthegedus commented 4 years ago

I also think that a flag to indicate output useful for scripting would be good.

I do not like --porcelain as the flag name because in Git the option --porcelain can designate inverting the command output format target. That is, if the command would output human-readable information then --porcelain will output a machine-readable format. If the command was "porcelain" by default and output machine-readable by default, then --porcelain will output human-readable.

@jsejcksn what I've meant with machine-readable is that there's a standard, known format sent to stdout/stderr. That is, no headers, no forced wrapping of content. I don't think we'll be tackling outputting in JSON. If each row of data is predictably formatted, then simple Shell scripting tools like grep,sed,awk, cut etc is what I would be expecting people to use.

What I am thinking is this:

I'm leaning away from wrapping content within columns as I initially proposed.


Thinking out loud, but perhaps in the default human-readable output we don't limit to 80 char and instead also introduce a flag for limiting to 80 char width. I say this because some of the command output we have is very difficult to limit to 80 chars. As an example, asdf current outputs 3 columns of data, the second of which is a version of a plugin installed. In the case of Java, the average length of a version is 30 chars, with the longest being 48 chars alone. The third column is a Path to the tool-versions file or debug information. All data sources for these columns, except the debug information, come from the user (plugin name can be anything they want) or the plugin itself.

With printf we can truncate output in columns easily and achieve some semblance of 80 char limit while still outputting useful information, but the users would need to understand we're truncating the output and how to get the actual values.

Ultimately, I'm leaning more towards not truncating the data, letting the rows be as wide as they want, and then if provided a flag (or ASDF config value, or we detect Shell columns) we can use a different printf format that does truncate. Essentially, swapping out the format arg to printf. The flag --less comes to mind.

jsejcksn commented 4 years ago

a standard, known format

Does this mean something like TSV with variable-length whitespace instead of single tabs?

JSON was just a suggestion, but if it's machine-readable I think adhering to any standard with a specification would be preferable to a consistent, arbitrary format. That way, no one has to write a new parser.

jthegedus commented 4 years ago

Does this mean something like TSV with variable-length whitespace instead of single tabs?

Yes, TSV or space-delimited with variable-length whitespace. Again, my proposal is to make it essentially the same as the human readable without header rows and a known number of cols and separator. Though we could investigate a separate output just for machines along the lines of git --porcelain

That way, no one has to write a new parser.

Our goal here would be to align with existing shell applications that allow simple scripting and piping. I wouldn't put that in the same bucket as parsing nor --porcelain in the same bucket as defining yet another JSON spec. Targeting simple scripting and piping is pedestrian IMO.

As an example, here is what git status --porcelain outputs when run against a repo with an untracked file:

asdf-jthegedus on  fix/update-all-plugins [?] 
➜ git status            
On branch fix/update-all-plugins
Your branch is up to date with 'origin/fix/update-all-plugins'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        test.txt

nothing added to commit but untracked files present (use "git add" to track)

asdf-jthegedus on  fix/update-all-plugins [?] 
➜ git status --porcelain
?? test.txt

The output is intended to be machine-readable, but certainly not a data-interchange format like JSON

jsejcksn commented 4 years ago

Thanks for explaining; I understand better.

Since there are two modes:

...is there any disadvantage to utilizing a standardized interchange format (like TSV, JSON, etc.) for the latter?

jthegedus commented 4 years ago

I was leaning towards simple TSV for the --porcelain flag.