humane 0.6.0 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +52 -55
- data/lib/humane/size.rb +30 -0
- data/lib/humane/time.rb +64 -0
- data/lib/humane/version.rb +1 -1
- data/lib/humane.rb +2 -2
- metadata +6 -6
- data/lib/humane/size_formatter.rb +0 -25
- data/lib/humane/time_formatter.rb +0 -59
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b0e7b2d5c4485d84a102a56c7586e7a61e62312729b1978d8221a24cc7d23917
|
|
4
|
+
data.tar.gz: c51ce6b56f73e1d859c9cb65e4a096cee6030c37fe4664c30ab16da13193d9a3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d3477e12b897535a507f1cd95c3b2c57619e71bce6f3d320c89eafde5621a5fb382f18eb9fa10a570d9edc511d2b62fe0c6ba6e65338432730ef5a1baf64b2ed
|
|
7
|
+
data.tar.gz: 8be3d6ed6747f5adcc66edc8eca5dc2ce7ea3fd4252af19a1e376787333a6af6a1d039590b49c885ecbe3594eb351364be7aa0e734b805bf4ad795fe2c9f47cf
|
data/README.md
CHANGED
|
@@ -5,47 +5,25 @@
|
|
|
5
5
|
[](https://github.com/woodie/humane-ruby/releases/latest)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
complicated to drop in.
|
|
8
|
+
Human-readable file sizes (1000-based math, capitalized labels, the way Mac
|
|
9
|
+
Finder displays them) and relative times (`"3 minutes ago"`, `"in 3 hours"`)
|
|
10
|
+
for Ruby and Go HTML templates -- as simple to drop in as ActionView's own
|
|
11
|
+
helpers, with output that's consistent with
|
|
12
|
+
[`humane`](https://github.com/woodie/humane) (Go) and
|
|
13
|
+
[`humane-swift`](https://github.com/woodie/humane-swift).
|
|
15
14
|
|
|
16
15
|
```ruby
|
|
17
16
|
require "humane"
|
|
18
17
|
|
|
19
|
-
Humane
|
|
18
|
+
Humane.human_size(225_935) # "226 KB"
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Both methods also accept positional arguments, matching `humane` (Go)'s
|
|
26
|
-
positional-only calling convention:
|
|
20
|
+
mtime = Time.now - 180
|
|
21
|
+
Humane.time_ago(mtime) # "3 minutes ago" -- relative to the real clock
|
|
27
22
|
|
|
28
|
-
|
|
29
|
-
Humane
|
|
30
|
-
time_formatter.string(Time.now - 180, Time.now) # "3 minutes ago"
|
|
23
|
+
now = Time.now
|
|
24
|
+
Humane.distance_in_time(mtime, now) # "3 minutes ago" -- explicit relative_to, for tests
|
|
31
25
|
```
|
|
32
26
|
|
|
33
|
-
Corresponding functions in Swift will have consistent output.
|
|
34
|
-
|
|
35
|
-
```swift
|
|
36
|
-
import Foundation
|
|
37
|
-
|
|
38
|
-
ByteCountFormatter.string(fromByteCount: Int64(225935), countStyle: .file) // "226 KB"
|
|
39
|
-
|
|
40
|
-
let formatter = RelativeDateTimeFormatter(); formatter.unitsStyle = .full
|
|
41
|
-
formatter.localizedString(for: time, relativeTo: now) // "3 minutes ago"
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
If you're writing Swift directly rather than calling Foundation by hand,
|
|
45
|
-
[`humane-swift`](https://github.com/woodie/humane-swift) wraps these same two
|
|
46
|
-
formatters with the identical API shape -- including the
|
|
47
|
-
`includeSeconds`/`approximate` options below.
|
|
48
|
-
|
|
49
27
|
## Install
|
|
50
28
|
|
|
51
29
|
```
|
|
@@ -58,33 +36,52 @@ or in a `Gemfile`:
|
|
|
58
36
|
gem "humane"
|
|
59
37
|
```
|
|
60
38
|
|
|
61
|
-
##
|
|
39
|
+
## `distance_in_time` and `time_ago`
|
|
62
40
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
ActionView's wording on top of it, not a replacement for it. Both off by
|
|
66
|
-
default, so `Humane::TimeFormatter.new` and calling `RelativeDateTimeFormatter`
|
|
67
|
-
directly always agree:
|
|
41
|
+
Two entry points, same naming split as ActionView's own
|
|
42
|
+
`distance_of_time_in_words`/`time_ago_in_words`:
|
|
68
43
|
|
|
69
|
-
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
44
|
+
- **`distance_in_time(at, relative_to, **opts)`** -- the explicit,
|
|
45
|
+
fully-tested core. Takes both times, so it's what specs should call.
|
|
46
|
+
- **`time_ago(at, **opts)`** -- a one-argument convenience for the common
|
|
47
|
+
"drop into a view" case. Supplies `Time.now` as `relative_to` internally;
|
|
48
|
+
everything else is identical to `distance_in_time`.
|
|
49
|
+
|
|
50
|
+
Both share the same keyword options and recommended defaults, already
|
|
51
|
+
matching ActionView's own `distance_of_time_in_words` defaults -- pass none
|
|
52
|
+
at all and you get them for free:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
Humane.distance_in_time(at, relative_to) # approximate: true, include_seconds: false
|
|
56
|
+
Humane.time_ago(at) # same defaults, relative_to is Time.now
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- **`approximate`** (default `true`): prefixes `"about"`/`"in about"` on the
|
|
60
|
+
hour-scale buckets (1 hour, and 2..24 hours), matching ActionView's
|
|
61
|
+
`distance_of_time_in_words` wording for those buckets exactly (down to its
|
|
62
|
+
44:30/89:30 rounding cutoffs), through the "1 day" bucket.
|
|
63
|
+
- **`include_seconds`** (default `false`): under 30 seconds, collapses to
|
|
64
|
+
`"less than a minute ago"`/`"in less than a minute"` instead of an exact
|
|
65
|
+
second count. Matches ActionView's `include_seconds` default.
|
|
66
|
+
- **`when_nil`** (default `nil`): if `at` is `nil`, both methods return this
|
|
67
|
+
value without formatting -- for a scan, download, or other record that
|
|
68
|
+
doesn't have a timestamp yet.
|
|
78
69
|
|
|
79
70
|
```ruby
|
|
80
|
-
Humane
|
|
81
|
-
|
|
71
|
+
Humane.distance_in_time(t, now, approximate: false) # "15 hours ago", not "about 15 hours ago"
|
|
72
|
+
Humane.time_ago(nil, when_nil: "an unknown time") # "an unknown time"
|
|
82
73
|
```
|
|
83
74
|
|
|
84
75
|
## Scope
|
|
85
76
|
|
|
86
|
-
Finder's
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
77
|
+
Finder's byte-count style, and a numeric (non-calendar-aware) relative time
|
|
78
|
+
style through the "1 day" bucket -- that's the whole surface area today.
|
|
79
|
+
Alternate size units/styles and a `:named` style (`"yesterday"`,
|
|
80
|
+
calendar-boundary-aware) aren't implemented -- contributions welcome.
|
|
81
|
+
|
|
82
|
+
## Development
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
bundle exec standardrb
|
|
86
|
+
bundle exec rspec -fd spec
|
|
87
|
+
```
|
data/lib/humane/size.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Humane
|
|
4
|
+
UNITS = %w[KB MB GB TB PB EB].freeze
|
|
5
|
+
|
|
6
|
+
# Returns byte_count as a Finder-style human-readable string.
|
|
7
|
+
#
|
|
8
|
+
# Humane.human_size(225_935) #=> "226 KB"
|
|
9
|
+
def self.human_size(byte_count)
|
|
10
|
+
return "Zero KB" if byte_count.zero?
|
|
11
|
+
return (byte_count == 1) ? "1 byte" : "#{byte_count} bytes" if byte_count < 1000
|
|
12
|
+
|
|
13
|
+
exponent = [(Math.log(byte_count) / Math.log(1000)).to_i, UNITS.size].min
|
|
14
|
+
value = byte_count / (1000.0**exponent)
|
|
15
|
+
|
|
16
|
+
"#{format_significant(value, 3)} #{UNITS[exponent - 1]}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Rounds value to sig_figs significant figures and trims trailing
|
|
20
|
+
# fractional zeros (and the decimal point itself, if nothing remains
|
|
21
|
+
# after it) -- see docs/COMMENTS.md.
|
|
22
|
+
def self.format_significant(value, sig_figs)
|
|
23
|
+
magnitude = Math.log10(value).floor + 1
|
|
24
|
+
decimals = [sig_figs - magnitude, 0].max
|
|
25
|
+
|
|
26
|
+
formatted = format("%.#{decimals}f", value)
|
|
27
|
+
formatted.include?(".") ? formatted.sub(/0+\z/, "").sub(/\.\z/, "") : formatted
|
|
28
|
+
end
|
|
29
|
+
private_class_method :format_significant
|
|
30
|
+
end
|
data/lib/humane/time.rb
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Humane
|
|
4
|
+
# Formats one time relative to another the way ActionView's
|
|
5
|
+
# distance_of_time_in_words does for wording, but direction-aware like
|
|
6
|
+
# RelativeDateTimeFormatter -- "X ago"/"in X", chosen automatically rather
|
|
7
|
+
# than requiring the caller to know which applies ahead of time. This is
|
|
8
|
+
# the explicit, fully-tested core -- see .time_ago below for the
|
|
9
|
+
# one-argument convenience. See docs/COMMENTS.md.
|
|
10
|
+
#
|
|
11
|
+
# Humane.distance_in_time(Time.now - 180, Time.now) #=> "3 minutes ago"
|
|
12
|
+
def self.distance_in_time(at, relative_to, approximate: true, include_seconds: false, when_nil: nil)
|
|
13
|
+
return when_nil if at.nil?
|
|
14
|
+
|
|
15
|
+
seconds = relative_to - at
|
|
16
|
+
future = seconds.negative?
|
|
17
|
+
seconds = seconds.abs
|
|
18
|
+
|
|
19
|
+
if !include_seconds && seconds < 30
|
|
20
|
+
return future ? "in less than a minute" : "less than a minute ago"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
if include_seconds && seconds < 60
|
|
24
|
+
return wrap(pluralize(seconds.to_i, "second"), future: future)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Buckets come from distance_in_minutes, not raw seconds re-divided per unit -- see docs/COMMENTS.md.
|
|
28
|
+
distance_in_minutes = (seconds / 60.0).round
|
|
29
|
+
|
|
30
|
+
text, approximable =
|
|
31
|
+
case distance_in_minutes
|
|
32
|
+
when 1 then ["1 minute", false]
|
|
33
|
+
when 2..44 then [pluralize(distance_in_minutes, "minute"), false]
|
|
34
|
+
when 45..89 then ["1 hour", true]
|
|
35
|
+
when 90..1439 then [pluralize((distance_in_minutes / 60.0).round, "hour"), true]
|
|
36
|
+
when 1440..2519 then ["1 day", false]
|
|
37
|
+
else [pluralize((distance_in_minutes / 1440.0).round, "day"), false]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
text = "about #{text}" if approximate && approximable
|
|
41
|
+
wrap(text, future: future)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Returns at relative to the current time -- a convenience for the common
|
|
45
|
+
# "drop into a view" case, modeled on ActionView's time_ago_in_words
|
|
46
|
+
# wrapping distance_of_time_in_words with Time.now. Use .distance_in_time
|
|
47
|
+
# directly when the reference time needs to be explicit (tests, a fixed
|
|
48
|
+
# point other than now).
|
|
49
|
+
#
|
|
50
|
+
# Humane.time_ago(Time.now - 180) #=> "3 minutes ago"
|
|
51
|
+
def self.time_ago(at, **opts)
|
|
52
|
+
distance_in_time(at, Time.now, **opts)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.wrap(text, future:)
|
|
56
|
+
future ? "in #{text}" : "#{text} ago"
|
|
57
|
+
end
|
|
58
|
+
private_class_method :wrap
|
|
59
|
+
|
|
60
|
+
def self.pluralize(count, unit)
|
|
61
|
+
(count == 1) ? "1 #{unit}" : "#{count} #{unit}s"
|
|
62
|
+
end
|
|
63
|
+
private_class_method :pluralize
|
|
64
|
+
end
|
data/lib/humane/version.rb
CHANGED
data/lib/humane.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: humane
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- John Woodell
|
|
@@ -9,8 +9,8 @@ bindir: bin
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
|
-
description: Finder-
|
|
13
|
-
|
|
12
|
+
description: Finder-style file sizes and ActionView-flavored relative dates for Ruby,
|
|
13
|
+
consistent with the humane (Go) and humane-swift siblings.
|
|
14
14
|
email:
|
|
15
15
|
- woodie@netpress.com
|
|
16
16
|
executables: []
|
|
@@ -20,8 +20,8 @@ files:
|
|
|
20
20
|
- LICENSE
|
|
21
21
|
- README.md
|
|
22
22
|
- lib/humane.rb
|
|
23
|
-
- lib/humane/
|
|
24
|
-
- lib/humane/
|
|
23
|
+
- lib/humane/size.rb
|
|
24
|
+
- lib/humane/time.rb
|
|
25
25
|
- lib/humane/version.rb
|
|
26
26
|
homepage: https://github.com/woodie/humane-ruby
|
|
27
27
|
licenses:
|
|
@@ -45,5 +45,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
45
45
|
requirements: []
|
|
46
46
|
rubygems_version: 3.6.9
|
|
47
47
|
specification_version: 4
|
|
48
|
-
summary:
|
|
48
|
+
summary: Finder-style file sizes and relative dates, as simple as ActionView
|
|
49
49
|
test_files: []
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Humane
|
|
4
|
-
# Formats byte counts the way Finder does: 1000-based math, capitalized unit labels.
|
|
5
|
-
class SizeFormatter
|
|
6
|
-
UNITS = %w[B KB MB GB TB PB EB].freeze
|
|
7
|
-
|
|
8
|
-
# Returns from_byte_count as a Finder-style human-readable string; accepts positional or from_byte_count: keyword argument -- see docs/COMMENTS.md.
|
|
9
|
-
def string(positional_from_byte_count = nil, from_byte_count: nil)
|
|
10
|
-
from_byte_count ||= positional_from_byte_count
|
|
11
|
-
raise ArgumentError, "from_byte_count is required" unless from_byte_count
|
|
12
|
-
|
|
13
|
-
return "#{from_byte_count} B" if from_byte_count < 1000
|
|
14
|
-
|
|
15
|
-
exponent = [(Math.log(from_byte_count) / Math.log(1000)).to_i, UNITS.size - 1].min
|
|
16
|
-
rounded = (from_byte_count / (1000.0**exponent) * 10).round / 10.0
|
|
17
|
-
|
|
18
|
-
if rounded < 10
|
|
19
|
-
format("%.1f %s", rounded, UNITS[exponent])
|
|
20
|
-
else
|
|
21
|
-
format("%.0f %s", rounded, UNITS[exponent])
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Humane
|
|
4
|
-
# Formats one time relative to another the way Finder-adjacent tools do.
|
|
5
|
-
class TimeFormatter
|
|
6
|
-
# include_seconds shows exact seconds under a minute instead of collapsing to "less than a minute ago/in less than a minute". Defaults to false, matching ActionView's include_seconds.
|
|
7
|
-
# approximate prefixes "about"/"in about" on the hour-scale buckets (1 hour, and 2..24 hours), matching ActionView's distance_of_time_in_words wording for those buckets. Defaults to false. See docs/COMMENTS.md and humane-ruby issue #1 for the full bucket table this ports.
|
|
8
|
-
def initialize(include_seconds: false, approximate: false)
|
|
9
|
-
@include_seconds = include_seconds
|
|
10
|
-
@approximate = approximate
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
# Returns at relative to relative_to; accepts positional or at:/relative_to: keyword arguments -- see docs/COMMENTS.md.
|
|
14
|
-
def string(positional_at = nil, positional_relative_to = nil, at: nil, relative_to: nil)
|
|
15
|
-
at ||= positional_at
|
|
16
|
-
relative_to ||= positional_relative_to
|
|
17
|
-
raise ArgumentError, "at is required" unless at
|
|
18
|
-
raise ArgumentError, "relative_to is required" unless relative_to
|
|
19
|
-
|
|
20
|
-
seconds = relative_to - at
|
|
21
|
-
future = seconds.negative?
|
|
22
|
-
seconds = seconds.abs
|
|
23
|
-
|
|
24
|
-
if !@include_seconds && seconds < 30
|
|
25
|
-
return future ? "in less than a minute" : "less than a minute ago"
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
if @include_seconds && seconds < 60
|
|
29
|
-
return wrap(pluralize(seconds.to_i, "second"), future: future)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Buckets come from distance_in_minutes, not raw seconds re-divided per unit -- see docs/COMMENTS.md.
|
|
33
|
-
distance_in_minutes = (seconds / 60.0).round
|
|
34
|
-
|
|
35
|
-
text, approximable =
|
|
36
|
-
case distance_in_minutes
|
|
37
|
-
when 1 then ["1 minute", false]
|
|
38
|
-
when 2..44 then [pluralize(distance_in_minutes, "minute"), false]
|
|
39
|
-
when 45..89 then ["1 hour", true]
|
|
40
|
-
when 90..1439 then [pluralize((distance_in_minutes / 60.0).round, "hour"), true]
|
|
41
|
-
when 1440..2519 then ["1 day", false]
|
|
42
|
-
else [pluralize((distance_in_minutes / 1440.0).round, "day"), false]
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
text = "about #{text}" if @approximate && approximable
|
|
46
|
-
wrap(text, future: future)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
private
|
|
50
|
-
|
|
51
|
-
def wrap(text, future:)
|
|
52
|
-
future ? "in #{text}" : "#{text} ago"
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def pluralize(count, unit)
|
|
56
|
-
count == 1 ? "1 #{unit}" : "#{count} #{unit}s"
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|