Skip to content
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

Make terminal table more responsive #7391

Closed

Conversation

hjanuschka
Copy link
Collaborator

@hjanuschka hjanuschka commented Dec 8, 2016

this trys to fix the ugliness of terminal table on small terminals - by making tables fit to terminal width.
in addition to the variable size, i also did a middle_truncate.

so instead of very long va... now very ... value is displayed.

Large

large

Medium

medium

Small

small

Ruby 1.9.3 added 'io/console' to the standard library.
the additional require should not be a problem.

to quickly test it use this ruby script:

require "fastlane_core"
FastlaneCore::PrintTable.print_values(config:
  { wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb bbbbbbbbbbbbbbbbbbbbbbb bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
      wwwwwwwwwwwwwwwwwwwwwwwwxw: "ccc bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb bbbbbbbbbbbbbbbbbbbbbbb bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" },

                                   hide_keys: [],
                                       title: "Summary SUM")

@@ -31,4 +31,14 @@ def truncate(truncate_at, options = {})

"#{self[0, stop]}#{omission}"
end

# Base taken from: http://stackoverflow.com/a/12202205/1945875
def middle_truncate(length = 20, options = {})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a good way to detect the initial terminal width?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KrauseFx
this is just a generic function.
in the table printer itself - the terminal width is used

tcols = IO.console.winsize[1]
max_length = tcols

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, cool 👍

@milch
Copy link
Collaborator

milch commented Dec 12, 2016

This is a really good idea, however, I would also like an option to disable this (e.g. when running on CI) so that users have at least an option to see the whole thing for debugging purposes.

@hjanuschka
Copy link
Collaborator Author

@milch ok done -

truncate = false if Helper.is_ci? || FastlaneCore::Env.truthy?("DO_NOT_TRUNCATE_TABLES")

sorry for the force push, as i had to rebase for the truthy? stuff.

@milch
Copy link
Collaborator

milch commented Dec 13, 2016

sorry for the force push

No worries, I haven't reviewed yet 👍

FastlaneCore::Env.truthy?("DO_NOT_TRUNCATE_TABLES")

This is a bit nitpicky but mostly we'd like to prefix ENV-vars. Can we call it FL_NO_TRUNCATE_TABLES (or something)?

@KrauseFx KrauseFx changed the title make terminal table resposnive, and do middle_truncate Make terminal table more responsive Dec 13, 2016
@@ -18,7 +18,10 @@ def print_values(config: nil, title: nil, hide_keys: [], mask_keys: [])
rows = self.collect_rows(options: options, hide_keys: hide_keys.map(&:to_s), mask_keys: mask_keys.map(&:to_s), prefix: '')

params = {}
params[:rows] = limit_row_size(rows)
truncate = false if Helper.is_ci? || FastlaneCore::Env.truthy?("DO_NOT_TRUNCATE_TABLES")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of DO_NOT, we've used SKIP_... until now

@KrauseFx
Copy link
Member

This looks super promising, ideally it would also be used for the action table: #7811

@hjanuschka
Copy link
Collaborator Author

@KrauseFx feedback addressed.
should i do the action table thing in this PR or in a seperate?

@hjanuschka
Copy link
Collaborator Author

@KrauseFx ok i updated it to also work with newlines instead of the .. stuff (defaulting to: ....)

here is a sample to test:

require "fastlane_core"



FastlaneCore::PrintTable.print_values(config:
  { wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb bbbbbbbbbbbbbbbbbbbbbbb bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
      wwwwwwwwwwwwwwwwwwwwwwwwxw: "ccc bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb bbbbbbbbbbbbbbbbbbbbbbb bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" },

                                   hide_keys: [],
                                       title: "Summary SUM")


FastlaneCore::PrintTable.print_values(config:
                                         { wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb bbbbbbbbbbbbbbbbbbbbbbb bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
                                             wwwwwwwwwwwwwwwwwwwwwwwwxw: "ccc bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb bbbbbbbbbbbbbbbbbbbbbbb bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" },

                                                                          hide_keys: [],
                                                                              title: "Summary SUM",
                                                                              transform: :newline)

bildschirmfoto 2017-01-11 um 20 54 17

and i also added support for this in the actions_list.rb:

fastlane actions

bildschirmfoto 2017-01-11 um 20 55 15

and in detailed action descr.:

fastlane action adb

bildschirmfoto 2017-01-11 um 20 56 22

@jinjorge
Copy link
Contributor

is there a way to make the line wraps happen at word boundaries?

from the screenshot above there are several words that are broken up from one line to the next

@hjanuschka
Copy link
Collaborator Author

@jinjorge good point, but i think there are cases where really long words would not be wrapped and resulting in a ugly table, you have a idea on how to solve that?

@jinjorge
Copy link
Contributor

@hjanuschka It's a numbers game - there are a lot more sub 5-6 letter words than really long words. Probably best to optimize for the majority and see how things fall out.

To you question on how to solve it - I don't know, though I will do some research.

@hjanuschka hjanuschka force-pushed the terminal_table_responsive branch 2 times, most recently from b74e04e to e3fdf48 Compare January 23, 2017 21:28
@hjanuschka
Copy link
Collaborator Author

test's pass now, ready for new review! @jinjorge i am unsure on how to solve it without breaking the output.

@jinjorge
Copy link
Contributor

a regular expression match (\b) for word boundary.

@hjanuschka
Copy link
Collaborator Author

@jinjorge got it working !!! respecting whole word, but if too long forcing to break it! ;)

bildschirmfoto 2017-01-24 um 08 03 29

  # Base taken from: https://www.ruby-forum.com/topic/57805
  def wordwrap(length  = 80)
     self.gsub!(/(\S{#{length}})(?=\S)/, '\1 ')
     self.scan(/.{1,#{length}}(?:\s+|$)/)
  end

@jinjorge
Copy link
Contributor

jinjorge commented Jan 24, 2017

👏 👏 Awesome!!!

milch
milch previously requested changes Feb 1, 2017
Copy link
Collaborator

@milch milch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me aside from a couple minor things. I really think we should merge this soon

return_array = []
tcols = IO.console.winsize[1]

col_count = rows.map(&:length).first || 1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is 1 being used as default here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i used to use 0 - but well we have always atleast one column, otherwise it crashes later on due to a division by zero 💃

value = col.to_s.dup
if transform == :truncate_middle
value = value.middle_truncate(max_value_length)
end
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use elsif here to more clearly show intent

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, going to adress this once back in private life 👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahhh sorry to fast reply, i think there are cases where transform is false (e.g: CI run)
so its neither :newline nor :truncate_middle - any other ideas?

@hjanuschka
Copy link
Collaborator Author

@milch feedback addressed, as we have the super awesome full-word split now i enabled :newline by default, and therefore the elsif worked out pretty good 👍

Copy link
Collaborator

@endocrimes endocrimes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like where this is going 👍

@@ -24,7 +24,14 @@ def print_values(config: nil, title: nil, hide_keys: [], mask_keys: [])
rows = self.collect_rows(options: options, hide_keys: hide_keys.map(&:to_s), mask_keys: mask_keys.map(&:to_s), prefix: '')

params = {}
params[:rows] = limit_row_size(rows)
transform = false if FastlaneCore::Env.truthy?("SKIP_TRANSFORM_TABLES")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FL_SKIP_TABLE_TRANSFORM?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


max_key_length = rows.map { |e| e[0].length }.max || 0
max_allowed_value_length = max_length - max_key_length - 7
rows.map do |e|
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rows.map do |row|? - single letter variable made it pretty difficult to infer when scan reading

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true point, fixed

terminal_table_padding = 4
max_length = tcols - (col_count * terminal_table_padding)

max_value_length = (max_length / col_count)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need parens here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we keep them?, i like it a bit more from point of readability.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to only prefer them when there are multiple expressions to make precedence easier to read, but sure.

new_row = []
e.each do |col|
value = col.to_s.dup
if transform == :truncate_middle
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to break this out into a transform_row/column function, because then appending to the row will be in a single place and be easier to maintain and test

i.e

def transform_row(transform, row)
  row.map do |column|
    transform_column(transform, column)
  end
end
def transform_column(transform, column)
  if transform == :truncate_middle
    ...
    value
  elseif transform == :newline
    ...
    value
end

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i am not 100% comfortable with this, as there is bunch a logic going on in :newline, colorization for example - lets keep it as it is, and try to finish this PR - as its a really long running one, and do the modularization in a seperate PR. (i just broke way too much with last minute quick-changes :sad:, last PRs)

Copy link
Collaborator Author

@hjanuschka hjanuschka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feedback adressed, master rebased and tty-screen used 👍

@@ -24,7 +24,14 @@ def print_values(config: nil, title: nil, hide_keys: [], mask_keys: [])
rows = self.collect_rows(options: options, hide_keys: hide_keys.map(&:to_s), mask_keys: mask_keys.map(&:to_s), prefix: '')

params = {}
params[:rows] = limit_row_size(rows)
transform = false if FastlaneCore::Env.truthy?("FL_SKIP_TABLE_TRANSFORM")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than overloading this, I'd maybe extract out a transform? method that is !FastlaneCore::Env.truthy?... and have the if statement be if transform? ... - this means you don't need to trace through mutating state.


rows.map do |row|
new_row = []
row.each do |col|
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as we only return one value from this, we could do new_row = row.map do |col|

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might also be easier to read if we break the contents of this block into a transform_column(column, truncation_style) method?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated 👍

giginet
giginet previously requested changes Mar 7, 2017
@@ -34,16 +41,60 @@ def print_values(config: nil, title: nil, hide_keys: [], mask_keys: [])
return params
end

def limit_row_size(rows, max_length = 100)
def colorize_array(array, colors)
value = ""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can write this logic more clearly.

array.map { |l| "#{colors.first[0]}#{l}#{colors.last[0]}" unless colors.empty? }.join('\n')

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this may not be equivalent, because it can create nil entries

@mfurtak
Copy link
Contributor

mfurtak commented Mar 27, 2017

Just checking in on the status of this PR - can the feedback be addressed? Everyone seems pretty positive about getting it in! 👍

@KrauseFx
Copy link
Member

Improving all fastlane tables is definitely something we should fix, it's a big issue currently. @hjanuschka, is that something you want to finish up? 🚀

@hjanuschka
Copy link
Collaborator Author

@KrauseFx yeah i want to see this in.
however i worked alot on this, and the open feedback, is are just nit-picks, and i am afraid i break everything to resolve the feedback (which as a personal point of view, is not mission critical in this case)

so, how about merging this, and later on the feedback suggesters go on with it?

@googlebot
Copy link

We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for the commit author(s). If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google.

@hjanuschka
Copy link
Collaborator Author

@googlebot thx for this, i accidentally commited with corporate email - as soon as we know how to proceed with this PR i am going to rebase then it should be fine 👍

@googlebot
Copy link

CLAs look good, thanks!

@googlebot googlebot added cla: yes and removed cla: no labels Apr 3, 2017
@hjanuschka
Copy link
Collaborator Author

master rebased, cla stuff sorted out.

@KrauseFx KrauseFx self-assigned this Apr 3, 2017
@@ -34,16 +41,60 @@ def print_values(config: nil, title: nil, hide_keys: [], mask_keys: [])
return params
end

def limit_row_size(rows, max_length = 100)
def colorize_array(array, colors)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is never called, should it be part of this PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whops, never mind, it is used

new_row << value
end
return_array << new_row
return_array << :separator
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have this here? This will result in a new line of dashes inbetween every row, making the table a lot bigger

@KrauseFx
Copy link
Member

KrauseFx commented Apr 7, 2017

screen shot 2017-04-06 at 7 06 57 pm

I think not having the separators for every row looks better 👍

@@ -48,15 +48,15 @@

value = FastlaneCore::PrintTable.print_values(config: @config, title: title, hide_keys: [:a_sensitive])
expect(value[:title]).to eq(title.green)
expect(value[:rows]).to eq([['cert_name', "asdf"], ['output', '..'], ["a_bool", true]])
expect(value[:rows]).to eq([["cert_name", "asdf"], :separator, ["output", ".."], :separator, ["a_bool", "true"]])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's clearly visible that the separator changes the existing behavior

@KrauseFx
Copy link
Member

KrauseFx commented Apr 7, 2017

Moved to #8809 (by mistake), I addressed all of your feedback, thanks. Also some more tests and code style changes.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants