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

Progressbar as String #131

Closed
TheFox opened this issue Mar 28, 2017 · 12 comments

Comments

@TheFox
Copy link

@TheFox TheFox commented Mar 28, 2017

I do not want to print it to STDOUT. I want only one String of the bar.

So I tried this:

bar_options = {
	:total => 10,
	:starting_at => 4,
	:length => 10,
	:format => '%B',
	:progress_mark => '#',
	:remainder_mark => '-',
	:autostart => false,
}
bar = ProgressBar.create(bar_options)

puts
puts "TO STRING"
puts

bar_s = bar.to_s

puts
puts "bar_s '#{bar_s}'"

I want to use bar_s for further actions. But it's at progress 0%. Even if I use :starting_at => 4 the value is ----------.

So I changed it to this:

bar_options = {
	:total => 10,
	:length => 10,
	:format => '%B',
	:progress_mark => '#',
	:remainder_mark => '-',
	:autostart => false,
}
bar = ProgressBar.create(bar_options)
bar.progress = 4

puts
puts "TO STRING"
puts

bar_s = bar.to_s

puts
puts "bar_s '#{bar_s}'"

bar_s is now ####------ but I don't want to print it to STDOUT on executing bar.progress = 4.

So I changed it to this:

sio = StringIO.new

bar_options = {
	:total => 10,
	:length => 10,
	:format => '%B',
	:progress_mark => '#',
	:remainder_mark => '-',
	:autostart => false,
	:output => sio,
}
bar = ProgressBar.create(bar_options)
bar.progress = 4

puts
puts "TO STRING"
puts

bar_s = bar.to_s

puts
puts "bar_s '#{bar_s}'"

As expected it doesn't print anything to STDOUT anymore, but bar_s is now Progress: ||. It ignores all options. Why? It has no progress. Why? It ignores bar.progress = 4. Why?

Even if I read it from sio it's wrong:

sio.seek(0)
bar_s = sio.read

bar_s has now \x0a\x0aProgress: | as value.

Is StringIO the wrong type to just write the bar to a String? How can I get the bar as one single String?

@dannypaz

This comment has been minimized.

Copy link

@dannypaz dannypaz commented May 1, 2017

@TheFox: A friend of mine (@brucec5) helped me troubleshoot the same exact issue. Since StringIO does not have a window size (not a terminal), the format of the output goes back to a default formatter, which is exactly the output you are seeing!

Relevant code is here: https://github.com/jfelchner/ruby-progressbar/blob/master/lib/ruby-progressbar/outputs/non_tty.rb#L29

One Your solution would be to override the non_tty file, add the correct formatter, and specify a (hopefully conservative) window size.

Hope this helps!

Props to @brucec5 !!!!

UPDATE:

Additionally, the output is determined on creation of the progress bar here

@dannypaz

This comment has been minimized.

Copy link

@dannypaz dannypaz commented May 1, 2017

To go a step further, here is a sample implementation that will:

  1. remove ProgressBar from outputting info the STDOUT
  2. output the information we expect through to_s with a specified formatter
module Example
  class ProgressBar
    PROGRESS_FORMAT = "Progress: [%B] [%c/%C]".freeze

    # This is required. This is our fake output class that will handle any
    # output and shove it straight into the void.
    class FakeOutput
      def self.tty?() true end
      def self.print(str) str end
      def self.flush() self end
    end

    def initialize(total)
      @bar ||= ::ProgressBar.create(
        total: total,
        format: PROGRESS_FORMAT,
        # TODO: Specify the length of the progress bar! This is required.
        length: 100,
        output: FakeOutput
      )
    end

    def progress(amount = 1)
      @bar.progress += amount
      puts @bar
    end
  end
end

And the associated usage:

>> bar = Example::ProgressBar.new(100)
=> #<Example::ProgressBar:0x007fea5d6940c0 @bar=#<ProgressBar:0/100>>
>> bar.progress
Progress: [                                                                                ] [1/100]
>> bar.progress
Progress: [=                                                                               ] [2/100]
>> bar.progress
Progress: [==                                                                              ] [3/100]
>> bar.progress(3)
Progress: [====                                                                            ] [6/100]
@jfelchner

This comment has been minimized.

Copy link
Owner

@jfelchner jfelchner commented May 2, 2017

Can you all elaborate on the use case here? I have to admit this looks completely convoluted to me 😂

What is the point of getting the actual string of the progress bar rather than querying the progress bar object itself?

@TheFox

This comment has been minimized.

Copy link
Author

@TheFox TheFox commented May 2, 2017

@dannypaz Thank you for this informations. I have not tested your code yet. I will. Just to be correct, the object is called StringIO instead of String.IO as mentioned in your first comment. ;)

@jfelchner My use case: I'm making a time tracking CLI tool, called Timr. Where you can create tasks and an estimation for each task. When you add an estimation to a task you also get an progressbar on the info page to visualize the time left you have for this task. Since Timr is no interactive programm I do not need a "#{progressbar}\r"-loop rather than one single "#{progressbar}\n" string. It's like (the most) Git commands: you start it, the process prints informations (for example how far the progress is and a progressbar) and then the process ends. I do not need any "\r"s or "\n"s in my progressbar string because I do not want to explicitly show the progressbar. I also want to print other informations to the user, before and after the progressbar.

@dannypaz

This comment has been minimized.

Copy link

@dannypaz dannypaz commented May 2, 2017

@jfelchner I don't blame your feelings. The use-case is having progress bar/status in a system log with code containing multiple ruby forks/subprocesses.

My reasoning for this code is because non-tty does not support custom formatters, which is what myself and @TheFox were trying to use. We would see the correct output in a terminal window, but once exported, either through StringIO or into a file, it would change due to .detect.

tty vs. non-tty is something that makes sense, but was not the behavior I was expecting.

@jfelchner

This comment has been minimized.

Copy link
Owner

@jfelchner jfelchner commented May 2, 2017

@TheFox @dannypaz your use case is extremely specific and not something I'd generally change the codebase for, but here's what I'm willing to do for you all (because it's very little code change, is fairly general, and has almost zero performance cost).

I can change Output.detect to check to see if options[:output] is a subclass of ProgressBar::Output. If it is, it will instantiate it with all the options that the current output classes receive. In this way, you can create whatever kind of outputter you'd like (as long as it subclasses ProgressBar::Output). If you want to do something extremely specific (as you seem to be doing), all you'll have to do is override the appropriate methods and you're off to the races.

For example:

class MyOutputter < ProgressBar::Output::Tty
  def clear
  end

  # You will be REQUIRED override this if you want something other than the default IO stream
  def stream
    @stream = whatever_your_desired_io_object_is
  end

  def default_format
    'Progress: [%B] [%c/%C]'
  end

  def eol
    ''
  end
end

@bar ||= ::ProgressBar.create(
           total:  total,
           output: MyOutputter
         )

@bar.progress += 1 # this would output the bar to the stream with (I think) no `\r` or `\n`

Or whatever. :)

How's that sound?

@jfelchner

This comment has been minimized.

Copy link
Owner

@jfelchner jfelchner commented May 2, 2017

Technically if you wanted to get super crazy you could override initialize.

I want to be super clear though, I will make no attempt to follow Semver on the outputters. I will change the interface to whatever I want, whenever I want. I take Semver very seriously and I've managed to go years without a backward-incompatible change. I won't tie my hands for this small use case.

That said, the interface for the outputters hasn't changed in a couple years so you're fairly safe.

@jfelchner

This comment has been minimized.

Copy link
Owner

@jfelchner jfelchner commented Sep 14, 2017

@TheFox @dannypaz I didn't hear back from you all, but it was a minor change so it's going in 1.9 anyway.

Hope this helps.

@jfelchner jfelchner closed this Sep 14, 2017
@TheFox

This comment has been minimized.

Copy link
Author

@TheFox TheFox commented Sep 14, 2017

@jfelchner Sorry for not responding anymore. Unfortunately, I already hadn't the chance to test it. I implemented an simple version of a progressbar for my project. Thank you for your help.

@jfelchner

This comment has been minimized.

Copy link
Owner

@jfelchner jfelchner commented Sep 14, 2017

@TheFox no problem. I actually I went ahead and went a step further for you.

I added a Null outputter, which, if passed into the progress bar like so:

require 'ruby-progressbar/outputs/null'

progressbar = ProgressBar.create(output: ProgressBar::Outputs::Null)

will disable all outputting of the progressbar completely (or should anyway 😉). For this class I will make sure it's compatible if there are any changes to the base Output class, so it's safe to use.

If you use the above code, you should be able to still use progressbar.to_s to get the static bar for the purposes you need it for.

Hope this helps.

@jfelchner

This comment has been minimized.

Copy link
Owner

@jfelchner jfelchner commented Sep 14, 2017

Wiki article is up

@TheFox

This comment has been minimized.

Copy link
Author

@TheFox TheFox commented Sep 14, 2017

Nice! Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants
You can’t perform that action at this time.