Classes/StrasbourgTransport.ps1
|
<# .SYNOPSIS Classes for this module describing CTS stops, lines, departures and other helpers #> using namespace System using namespace System.Collections.Generic using namespace System.Collections.Concurrent using namespace System.Management.Automation # Base class for objects whose string representation includes ANSI escape codes class Formatted { [Int] VisibleLength() { return [Formatted]::VisibleLength($this.ToString()) } [Int] VisibleLength([Object]$ToStringParam) { return [Formatted]::VisibleLength($this.ToString($ToStringParam)) } hidden static [Int] VisibleLength([String]$String) { return ($String -replace '\e\[[\d;]+m').Length } [String] PadLeft([Int]$TotalWidth) { return [Formatted]::Pad($this.ToString(), $TotalWidth, $true) } [String] PadLeft([Int]$TotalWidth, [Object]$ToStringParam) { return [Formatted]::Pad($this.ToString($ToStringParam), $TotalWidth, $true) } [String] PadRight([Int]$TotalWidth) { return [Formatted]::Pad($this.ToString(), $TotalWidth, $false) } [String] PadRight([Int]$TotalWidth, [Object]$ToStringParam) { return [Formatted]::Pad($this.ToString($ToStringParam), $TotalWidth, $false) } hidden static [String] Pad([String]$Text, [Int]$TotalWidth, [Bool]$PadLeft) { $PadLength = $TotalWidth - [Formatted]::VisibleLength($Text) if ($PadLength -le 0) { return $Text } elseif ($PadLeft) { return (' ' * $PadLength + $Text) } else { return ($Text + ' ' * $PadLength) } } } # Serializable snapshot of the stop cache for disk persistence class StopFileCache { [DateTime] $ValidUntil [StopInfo[]] $Stops [LineRawInfo[]] $Lines } # Raw line data from the API response class LineRawInfo { [String] $Id [String] $Name [String] $Color [Bool] $Dark [List[Destination]] $Destinations } # A destination served by a line at a given set of stops class Destination { [String] $Line [Byte] $Direction [String] $Name [List[String]] $Stops } # Singleton in-memory cache of all stops, lines and destinations class StopCache { [DateTime] $ValidUntil [ConcurrentDictionary[String, StopInfo]] $Stops [ConcurrentDictionary[String, LineInfo]] $Lines [ConcurrentDictionary[String, List[Destination]]] $Destinations [Bool] $Ready hidden static [StopCache] $StopCache = [StopCache]::new() static [StopCache] $Instance = [StopCache]::Get() hidden StopCache() { $this.Init(0) } hidden static [StopCache] Get() { return [StopCache]::StopCache } Init([DateTime]$ValidUntil) { $this.ValidUntil = $ValidUntil $this.Ready = $false $this.Stops = [ConcurrentDictionary[String, StopInfo]]::new() $this.Lines = [ConcurrentDictionary[String, LineInfo]]::new() $this.Destinations = [ConcurrentDictionary[String, List[Destination]]]::new() } } # Display metadata for a line class LineInfo { [String] $Name [String] $DisplayName [String] $Description LineInfo([LineRawInfo]$Line) { $this.Name = $Line.Id $this.Description = $Line.Name $PSStyle = [PSStyle]::Instance $Background = $PSStyle.Background.FromRgb('0x' + $Line.Color) $Foreground = $Line.Dark ? $PSStyle.Foreground.White : $PSStyle.Foreground.Black $this.DisplayName = $PSStyle.Bold + $Background + $Foreground + ' ' + $this.Name + ' ' + $PSStyle.Reset } } # A transport line with its display label and destinations class Line : Formatted { [String] $Name [String] $DisplayName [String] $Description [String[]] $Destinations hidden [LineInfo] $LineInfo hidden static [Hashtable] $DynamicProperty = @{ Name = { $this.LineInfo.Name } DisplayName = { $this.LineInfo.DisplayName } Description = { $this.LineInfo.Description } } hidden static Line() { $UpdateParam = @{ TypeName = [Line].Name MemberType = 'ScriptProperty' Force = $true WhatIf = $false Confirm = $false } [Line]::DynamicProperty.GetEnumerator() | ForEach-Object { Update-TypeData @UpdateParam -MemberName $_.Key -Value $_.Value } } [String] ToString() { return $this.DisplayName + " `u{279C} " + ($this.Destinations -join ';') } } # Raw stop data from the API response class StopInfo { [String] $Id [String] $Name } # A physical stop with its associated lines class Stop { [String] $Id [String] $Name [Line[]] $Lines hidden [StopInfo] $StopInfo hidden static [Hashtable] $DynamicProperty = @{ Id = { $this.StopInfo.Id } Name = { $this.StopInfo.Name } } hidden static Stop() { $UpdateParam = @{ TypeName = [Stop].Name MemberType = 'ScriptProperty' Force = $true WhatIf = $false Confirm = $false } [Stop]::DynamicProperty.GetEnumerator() | ForEach-Object { Update-TypeData @UpdateParam -MemberName $_.Key -Value $_.Value } } [String] ToString() { return $this.Name + ' (' + ($this.Lines.Name -join ';') + ')' } } # Singleton in-memory cache of real-time departures per stop class DepartureCache { [ConcurrentDictionary[String, List[DepartureInfo]]] $Departures hidden static [DepartureCache] $DepartureCache = [DepartureCache]::new() static [DepartureCache] $Instance = [DepartureCache]::Get() hidden DepartureCache() { $this.Departures = [ConcurrentDictionary[String, List[DepartureInfo]]]::new() } hidden static [DepartureCache] Get() { return [DepartureCache]::DepartureCache } } # Cached departure data for a single line-destination pair class DepartureInfo { [DateTime] $ValidUntil [String] $Line [String] $Destination [DepartureTime[]] $Times } # A departure event for a stop, line and destination class Departure { [String] $Stop [Line] $Line [DepartureTime[]] $Times } # A single departure time, either live or scheduled class DepartureTime : Formatted { [DateTime] $Time [Bool] $Live [String] ToString() { return $this.ToString(0) } [String] ToString([DateTime]$ReferenceTime) { if ($ReferenceTime -eq 0) { $TimeText = $this.Time.ToString('HH:mm:ss') } else { $TimeDiff = $this.Time - $ReferenceTime if ($TimeDiff -le [TimeSpan]::FromSeconds(10)) { $TimeText = "`u{2B63}`u{2B63}" } else { $TimeText = '{0}:{1:d2}' -f [Math]::Floor($TimeDiff.TotalMinutes), $TimeDiff.Seconds } } $PSStyle = [PSStyle]::Instance if ($this.Live) { return $PSStyle.Bold + $TimeText + $PSStyle.BoldOff } else { return $PSStyle.Underline + $TimeText + $PSStyle.UnderlineOff } } } |