/ Pbar / src / module.jl
module.jl
  1  using Term.Progress
  2  using Term: Term
  3  using TimeTicks: now, Millisecond, Second, DateTime, Lang
  4  using .Lang: toggle!, @preset, @precomp
  5  using .Lang.DocStringExtensions
  6  
  7  @doc "Stores the timestamp of the last render in the progress bar."
  8  const last_render = Ref(DateTime(0))
  9  @doc "Stores the minimum time difference required between two render updates."
 10  const min_delta = Ref(Millisecond(0))
 11  @doc "Holds a reference to the current progress bar or `nothing` if no progress bar is active."
 12  const pbar = Ref{Union{Nothing,ProgressBar}}(nothing)
 13  @doc "Holds a lock to avoid flickering when updating the progress bar."
 14  const pbar_lock = ReentrantLock()
 15  
 16  @doc """ Represents a job that is currently running in the progress bar.
 17  
 18  $(FIELDS)
 19  
 20  The `RunningJob` struct holds a `ProgressJob`, a counter, and a timestamp of when it was last updated.
 21  The `job` field is of type `ProgressJob` which represents the job that is currently running.
 22  The `counter` field is an integer that defaults to 1 and is used to keep track of the progress of the job.
 23  The `updated_at` field is a `DateTime` object that stores the timestamp of when the job was last updated.
 24  """
 25  @kwdef mutable struct RunningJob
 26      job::ProgressJob
 27      counter::Int = 1
 28      updated_at::DateTime = now()
 29  end
 30  
 31  @doc """ Clears the current progress bar.
 32  
 33  $(TYPEDSIGNATURES)
 34  
 35  The `clearpbar` function stops all jobs in the current progress bar, empties the job list, and then stops the progress bar itself. It uses a lock to ensure thread safety during these operations.
 36  """
 37  function clearpbar(pb=pbar[])
 38      !isnothing(pb) && @lock pbar_lock begin
 39          for j in pb.jobs
 40              stop!(j)
 41          end
 42          empty!(pb.jobs)
 43          stop!(pb)
 44      end
 45  end
 46  
 47  @doc """ Initializes a new progress bar.
 48  
 49  $(TYPEDSIGNATURES)
 50  
 51  The `pbar!` function first clears any existing progress bar, then creates a new `ProgressBar` with the provided arguments. The `transient` argument defaults to `true`, and `columns` defaults to `:default`.
 52  """
 53  function pbar!(; transient=true, columns=:default, kwargs...)
 54      clearpbar()
 55      pbar[] = ProgressBar(; transient, columns, kwargs...)
 56  end
 57  
 58  function _doinit()
 59      pbar!()
 60      @debug "Pbar: Loaded."
 61  end
 62  
 63  @doc "Initializes the progress bar."
 64  macro pbinit!()
 65      :($(_doinit)())
 66  end
 67  
 68  @doc "The last update timestamp."
 69  const plu = esc(:pb_last_update)
 70  @doc "The current job being rendered."
 71  const pbj = esc(:pb_job)
 72  
 73  @doc "Toggles pbar transient flag"
 74  transient!(pb=pbar[]) = @lock pbar_lock toggle!(pb, :transient)
 75  
 76  @doc "Set the update frequency globally."
 77  frequency!(v) = @lock pbar_lock min_delta[] = v
 78  
 79  # This prevents flickering when we render too frequently
 80  @doc "Renders the progress bar if enough time has passed since the last render."
 81  function dorender(pb, t=now())
 82      @lock pbar_lock if t - last_render[] > min_delta[]
 83          render(pb)
 84          last_render[] = t
 85          yield()
 86          true
 87      end
 88      false
 89  end
 90  
 91  @doc "Starts a new job in the progress bar."
 92  function startjob!(pb, desc="", N=nothing)
 93      @lock pbar_lock begin
 94          job = let j = addjob!(pb; description=desc, N, transient=true)
 95              RunningJob(; job=j)
 96          end
 97          pb.running || begin
 98              start!(pb)
 99              dorender(pb)
100          end
101          yield()
102          job
103      end
104  end
105  
106  @doc "Instantiate a progress bar:
107  
108  $(TYPEDSIGNATURES)
109  
110  - `data`: `length(data)` determines the bar total
111  - `unit`: what unit the display
112  - `desc`: description will appear over the progressbar
113  "
114  macro pbar!(data, desc="", unit="") # use_finalizer=false)
115      @pbinit!
116      data = esc(data)
117      desc = esc(desc)
118      unit = esc(unit)
119      quote
120          $pbj = startjob!($pbar[], $desc, length($data))
121      end
122  end
123  
124  @doc "Complete a job."
125  function complete!(pb, j, force=true)
126      if !isnothing(j.N)
127          if j.finished || j.N != j.i
128              update!(j; i=j.N - j.i)
129              dorender(pb)
130          end
131          if force || !j.transient
132              if j in pb.jobs
133                  removejob!(pb, j)
134              end
135          end
136      end
137      nothing
138  end
139  
140  @doc "Stops the progress bar."
141  macro pbstop!()
142      quote
143          @lock pbar_lock isempty($pbar[].jobs) && $stop!($pbar[])
144          nothing
145      end
146  end
147  
148  @doc "Same as `@pbar!` but with implicit closing.
149  
150  $(TYPEDSIGNATURES)
151  
152  The first argument should be the collection to iterate over.
153  Optional kw arguments:
154  - `desc`: description
155  "
156  macro withpbar!(data, args...)
157      @pbinit!
158      data = esc(data)
159      desc = unit = ""
160      code = nothing
161      for a in args
162          if a.head == :(=)
163              if a.args[1] == :desc
164                  desc = esc(a.args[2])
165              elseif a.args[1] == :unit
166                  unit = esc(a.args[2])
167              end
168          else
169              code = esc(a)
170          end
171      end
172      quote
173          local $pbj = startjob!($pbar[], $desc, length($data))
174          local iserror = false
175          try
176              $code
177          catch e
178              if e isa InterruptException
179                  rethrow(e)
180              else
181                  iserror = true
182              end
183          finally
184              pbclose!($pbj.job, $pbar[])
185          end
186      end
187  end
188  
189  @doc "Single update to the progressbar with the new value."
190  macro pbupdate!(n=1, args...)
191      n = esc(n)
192      quote
193          $pbar[].running && let t = $now()
194              if t - $last_render[] > $(min_delta[]) # min_delta should not mutate
195                  update!($pbj.job; i=$pbj.counter)
196                  dorender($pbar[], t)
197                  $pbj.counter = $n
198              else
199                  $pbj.counter += $n
200              end
201          end
202          nothing
203      end
204  end
205  
206  @doc """ Terminates the progress bar.
207  
208  $(TYPEDSIGNATURES)
209  
210  The `pbclose!` function completes all jobs in the progress bar and then stops the progress bar itself.
211  """
212  function pbclose!(pb::ProgressBar=pbar[], all=true)
213      all && foreach(j -> complete!(pb, j), pb.jobs)
214      stop!(pb)
215      nothing
216  end
217  
218  @doc "Stops the progress bar after completing the job."
219  function pbclose!(job, pb=pbar[])
220      complete!(pb, job)
221      @pbstop!
222      nothing
223  end
224  
225  @doc "Calls `pbclose!` on the global progress bar."
226  macro pbclose!()
227      quote
228          $pbclose!($pbj.job, $pbar[])
229      end
230  end
231  
232  
233  export @pbar!, @pbupdate!, @pbclose!, @pbstop!, @pbinit!, transient!, @withpbar!, @track