Skip to content

Commit

Permalink
clock component integration (#318)
Browse files Browse the repository at this point in the history
* Integrate `clock` component into `console`
* Integrate `clock` component into `dependency_injection` as an extension
* Add `clock` as a dependency to `framework`
  • Loading branch information
Blacksmoke16 authored Oct 7, 2023
1 parent f0b9ff8 commit 3f8bbfa
Show file tree
Hide file tree
Showing 10 changed files with 72 additions and 98 deletions.
5 changes: 5 additions & 0 deletions src/components/console/shard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,8 @@ description: |
authors:
- George Dietrich <george@dietrich.app>

dependencies:
athena-clock:
github: athena-framework/clock
version: ~> 0.1.0
33 changes: 7 additions & 26 deletions src/components/console/spec/helper/progress_bar_spec.cr
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
require "../spec_helper"

private class MockClock
include Athena::Console::ClockInterface

def initialize(@now : Time); end

def now : Time
@now
end

def sleep(seconds : Int32) : Nil
@now += seconds.seconds
end
end

struct ProgressBarTest < ASPEC::TestCase
@col_size : String?
@clock : MockClock
@clock : ACLK::Spec::MockClock

def initialize
@col_size = ENV["COLUMNS"]?
ENV["COLUMNS"] = "120"
@clock = MockClock.new Time.utc
@clock = ACLK::Spec::MockClock.new
end

protected def tear_down : Nil
Expand Down Expand Up @@ -69,8 +55,7 @@ struct ProgressBarTest < ASPEC::TestCase
end

def test_regular_time_estimation : Nil
bar = ACON::Helper::ProgressBar.new self.output, 1_200, 0
bar.clock = @clock
bar = ACON::Helper::ProgressBar.new self.output, 1_200, 0, clock: @clock

bar.start
bar.advance
Expand All @@ -82,8 +67,7 @@ struct ProgressBarTest < ASPEC::TestCase
end

def test_resumed_time_estimation : Nil
bar = ACON::Helper::ProgressBar.new self.output, 1_200, 0
bar.clock = @clock
bar = ACON::Helper::ProgressBar.new self.output, 1_200, 0, clock: @clock

bar.start at: 599
bar.advance
Expand Down Expand Up @@ -897,8 +881,7 @@ struct ProgressBarTest < ASPEC::TestCase
end

def test_min_and_max_seconds_between_redraws : Nil
bar = ACON::Helper::ProgressBar.new output = self.output
bar.clock = @clock
bar = ACON::Helper::ProgressBar.new output = self.output, clock: @clock
bar.redraw_frequency = 1
bar.minimum_seconds_between_redraws = 5
bar.maximum_seconds_between_redraws = 10
Expand All @@ -919,8 +902,7 @@ struct ProgressBarTest < ASPEC::TestCase
end

def test_max_seconds_between_redraws : Nil
bar = ACON::Helper::ProgressBar.new output = self.output, minimum_seconds_between_redraws: 0
bar.clock = @clock
bar = ACON::Helper::ProgressBar.new output = self.output, minimum_seconds_between_redraws: 0, clock: @clock
bar.redraw_frequency = 4 # Disable step based redraw
bar.start

Expand Down Expand Up @@ -948,8 +930,7 @@ struct ProgressBarTest < ASPEC::TestCase
end

def test_min_seconds_between_redraws : Nil
bar = ACON::Helper::ProgressBar.new output = self.output, minimum_seconds_between_redraws: 0
bar.clock = @clock
bar = ACON::Helper::ProgressBar.new output = self.output, minimum_seconds_between_redraws: 0, clock: @clock
bar.redraw_frequency = 1
bar.minimum_seconds_between_redraws = 1
bar.start
Expand Down
26 changes: 5 additions & 21 deletions src/components/console/spec/helper/progress_indicator_spec.cr
Original file line number Diff line number Diff line change
@@ -1,29 +1,14 @@
require "../spec_helper"

private class MockClock
include Athena::Console::ClockInterface

def initialize(@now : Time); end

def now : Time
@now
end

def sleep(seconds : Time::Span) : Nil
@now += seconds
end
end

struct ProgressIndicatorTest < ASPEC::TestCase
@clock : MockClock
@clock : ACLK::Spec::MockClock

def initialize
@clock = MockClock.new Time.utc
@clock = ACLK::Spec::MockClock.new
end

def test_default_indicator : Nil
indicator = ACON::Helper::ProgressIndicator.new output = self.output
indicator.clock = @clock
indicator = ACON::Helper::ProgressIndicator.new output = self.output, clock: @clock

indicator.start "Starting..."
@clock.sleep 101.milliseconds
Expand Down Expand Up @@ -65,7 +50,7 @@ struct ProgressIndicatorTest < ASPEC::TestCase
end

def test_non_decorated : Nil
indicator = ACON::Helper::ProgressIndicator.new output = self.output decorated: false
indicator = ACON::Helper::ProgressIndicator.new output = self.output(decorated: false)

indicator.start "Starting..."
indicator.advance
Expand All @@ -84,8 +69,7 @@ struct ProgressIndicatorTest < ASPEC::TestCase
end

def test_custom_indicator_values : Nil
indicator = ACON::Helper::ProgressIndicator.new output = self.output, indicator_values: %w(a b c)
indicator.clock = @clock
indicator = ACON::Helper::ProgressIndicator.new output = self.output, indicator_values: %w(a b c), clock: @clock

indicator.start "Starting..."
@clock.sleep 101.milliseconds
Expand Down
4 changes: 3 additions & 1 deletion src/components/console/spec/spec_helper.cr
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
require "spec"

require "athena-spec"
require "../src/athena-console"
require "../src/spec"

require "athena-spec"
require "athena-clock/spec"

require "./fixtures/commands/io"
require "./fixtures/**"

Expand Down
2 changes: 2 additions & 0 deletions src/components/console/src/athena-console.cr
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
require "ecr"

require "athena-clock"

require "./annotations"
require "./application"
require "./command"
Expand Down
41 changes: 15 additions & 26 deletions src/components/console/src/helper/progress_bar.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@ abstract class Athena::Console::Output; end

require "../output/interface"

# :nodoc:
#
# TODO: Consider creating a `Clock` component.
module Athena::Console::ClockInterface
abstract def now : Time
end

# When executing longer-running commands, it can be helpful to show progress information that updates as the command runs:
#
# ![Progress Bar](../../../img/progress_bar.gif)
Expand Down Expand Up @@ -290,15 +283,6 @@ end
# 68/100 [===================>--------] 68%
# ```
class Athena::Console::Helper::ProgressBar
# :nodoc:
class Clock
include Athena::Console::ClockInterface

def now : Time
Time.utc
end
end

# Represents the built in progress bar formats.
#
# See [Built-In Formats][Athena::Console::Helper::ProgressBar--built-in-formats] for more information.
Expand Down Expand Up @@ -410,7 +394,7 @@ class Athena::Console::Helper::ProgressBar
end,

"memory" => PlaceholderFormatter.new { |_| (GC.stats.heap_size - GC.stats.free_bytes).humanize_bytes },
"elapsed" => PlaceholderFormatter.new { |bar| ACON::Helper.format_time bar.clock.now.to_unix - bar.start_time },
"elapsed" => PlaceholderFormatter.new { |bar| ACON::Helper.format_time bar.clock.now - bar.start_time },
"current" => PlaceholderFormatter.new { |bar| bar.progress.to_s.rjust bar.step_width, ' ' },
"max" => PlaceholderFormatter.new(&.max_steps.to_s),
"percent" => PlaceholderFormatter.new { |bar| (bar.progress_percent * 100).floor.to_i.to_s },
Expand All @@ -434,12 +418,10 @@ class Athena::Console::Helper::ProgressBar
@messages : Hash(String, String) = Hash(String, String).new
@placeholder_formatters : Hash(String, PlaceholderFormatter) = Hash(String, PlaceholderFormatter).new

protected property clock : Athena::Console::ClockInterface = Clock.new
protected getter clock : ACLK::Interface

# Returns the time the progress bar was started as a Unix epoch.
#
# TODO: Make this return `Time`.
getter start_time : Int64
getter start_time : Time

# Returns the width of the progress bar in pixels.
#
Expand Down Expand Up @@ -488,7 +470,14 @@ class Athena::Console::Helper::ProgressBar
# See [Controlling Rendering][Athena::Console::Helper::ProgressBar--controlling-rendering] for more information.
setter maximum_seconds_between_redraws : Float64 = 1

def initialize(output : ACON::Output::Interface, max : Int32? = nil, minimum_seconds_between_redraws : Float64 = 0.04)
def initialize(
output : ACON::Output::Interface,
max : Int32? = nil,
minimum_seconds_between_redraws : Float64 = 0.04,

# Use a monotonic clock by default since its better for measuring time
@clock : ACLK::Interface = ACLK::Monotonic.new
)
if output.is_a? ACON::Output::ConsoleOutputInterface
output = output.error_output
end
Expand All @@ -509,7 +498,7 @@ class Athena::Console::Helper::ProgressBar
@redraw_frequency = nil
end

@start_time = @clock.now.to_unix
@start_time = @clock.now
@cursor = ACON::Cursor.new @output

self.max_steps = max || 0
Expand Down Expand Up @@ -579,14 +568,14 @@ class Athena::Console::Helper::ProgressBar
def estimated : Float64
return 0.0 if @step.zero? || @step == @starting_step

((@clock.now.to_unix - @start_time) / (@step - @starting_step) * @max).round
((@clock.now - @start_time).total_seconds / (@step - @starting_step) * @max).round
end

# Returns an estimated total amount of time in seconds needed for the progress bar to complete.
def remaining : Float64
return 0.0 if @step.zero?

((@clock.now.to_unix - @start_time) / (@step - @starting_step) * (@max - @step)).round 0
((@clock.now - @start_time).total_seconds / (@step - @starting_step) * (@max - @step)).round 0
end

# Returns the amount of time in seconds until the progress bar is completed.
Expand Down Expand Up @@ -641,7 +630,7 @@ class Athena::Console::Helper::ProgressBar
# Optionally sets the maximum number of steps to *max*, or `nil` to leave unchanged.
# Optionally starts the progress bar *at* the provided step.
def start(max : Int32? = nil, at start_at : Int32 = 0) : Nil
@start_time = @clock.now.to_unix
@start_time = @clock.now
@step = start_at
@starting_step = start_at

Expand Down
36 changes: 15 additions & 21 deletions src/components/console/src/helper/progress_indicator.cr
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,6 @@
#
# NOTE: Placeholder customization is global and would affect any indicator used after calling `.set_placeholder_formatter`.
class Athena::Console::Helper::ProgressIndicator
# :nodoc:
class Clock
include Athena::Console::ClockInterface

def now : Time
Time.utc
end
end

# Represents the built in progress indicator formats.
#
# See [Built-In Formats][Athena::Console::Helper::ProgressIndicator--built-in-formats] for more information.
Expand Down Expand Up @@ -167,7 +158,7 @@ class Athena::Console::Helper::ProgressIndicator

private def self.init_placeholder_formatters : Hash(String, PlaceholderFormatter)
{
"elapsed" => PlaceholderFormatter.new { |indicator| ACON::Helper.format_time indicator.clock.now.to_unix - indicator.start_time },
"elapsed" => PlaceholderFormatter.new { |indicator| ACON::Helper.format_time indicator.clock.now - indicator.start_time },
"indicator" => PlaceholderFormatter.new { |indicator| indicator.indicator_values[indicator.indicator_index % indicator.indicator_values.size] },
"memory" => PlaceholderFormatter.new { (GC.stats.heap_size - GC.stats.free_bytes).humanize_bytes },
"message" => PlaceholderFormatter.new(&.message.to_s),
Expand All @@ -176,22 +167,25 @@ class Athena::Console::Helper::ProgressIndicator

protected getter indicator_values : Indexable(String)
protected getter indicator_index : Int32 = 0
protected getter start_time : Int64
protected getter start_time : Time

protected getter message : String? = nil
protected property clock : Athena::Console::ClockInterface = Clock.new
protected getter clock : ACLK::Interface

@output : ACON::Output::Interface
@format : Format
@indicator_change_interval : Int32
@indicator_change_interval : Time::Span
@started : Bool = false
@indicator_update_time : Int64 = 0
@indicator_update_time : Time = Time::UNIX_EPOCH

def initialize(
@output : ACON::Output::Interface,
format : ACON::Helper::ProgressIndicator::Format? = nil,
indicator_change_interval_milliseconds : Int32 = 100,
indicator_values : Indexable(String)? = nil
indicator_change_interval : Time::Span = 100.milliseconds,
indicator_values : Indexable(String)? = nil,

# Use a monotonic clock by default since its better for measuring time
@clock : ACLK::Interface = ACLK::Monotonic.new
)
indicator_values ||= ["-", "\\", "|", "/"]

Expand All @@ -201,8 +195,8 @@ class Athena::Console::Helper::ProgressIndicator

@format = format || determine_best_format
@indicator_values = indicator_values
@start_time = @clock.now.to_unix
@indicator_change_interval = indicator_change_interval_milliseconds
@start_time = @clock.now
@indicator_change_interval = indicator_change_interval
end

# Sets the *message* to display alongside the indicator.
Expand All @@ -216,8 +210,8 @@ class Athena::Console::Helper::ProgressIndicator

@message = message
@started = true
@start_time = @clock.now.to_unix
@indicator_update_time = @clock.now.to_unix_ms + @indicator_change_interval
@start_time = @clock.now
@indicator_update_time = @clock.now + @indicator_change_interval
@indicator_index = 0

self.display
Expand All @@ -229,7 +223,7 @@ class Athena::Console::Helper::ProgressIndicator

return unless @output.decorated?

current_time = @clock.now.to_unix_ms
current_time = @clock.now

return if current_time < @indicator_update_time

Expand Down
13 changes: 13 additions & 0 deletions src/components/dependency_injection/src/ext/clock.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# TODO: Clean this up once https://github.com/crystal-lang/crystal/issues/12965 is resolved
{% skip_file unless @top_level.has_constant?("Athena") && Athena.has_constant?("Clock") && Athena::Clock.has_constant?("Interface") %}

@[ADI::Register(name: "clock", alias: ACLK::Interface, factory: "create")]
class Athena::Clock
# :nodoc:
#
# There a better way to handle this?
# By default the `ACLK::Interface` causes some infinite recursion due to this service being aliased to the interface
def self.create : self
new
end
end
9 changes: 6 additions & 3 deletions src/components/framework/shard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ authors:
- George Dietrich <george@dietrich.app>

dependencies:
athena-event_dispatcher:
github: athena-framework/event-dispatcher
version: ~> 0.2.0
athena-clock:
github: athena-framework/clock
version: ~> 0.1.0
athena-config:
github: athena-framework/config
version: ~> 0.3.0
Expand All @@ -29,6 +29,9 @@ dependencies:
athena-dependency_injection:
github: athena-framework/dependency-injection
version: ~> 0.3.5
athena-event_dispatcher:
github: athena-framework/event-dispatcher
version: ~> 0.2.0
athena-negotiation:
github: athena-framework/negotiation
version: ~> 0.1.0
Expand Down
1 change: 1 addition & 0 deletions src/components/framework/src/athena.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ require "ecr"
require "http/server"
require "json"

require "athena-clock"
require "athena-config"
require "athena-console"
require "athena-event_dispatcher"
Expand Down

0 comments on commit 3f8bbfa

Please sign in to comment.