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