humane 0.6.0 → 0.9.0
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 +39 -57
- data/lib/humane/size_formatter.rb +25 -13
- data/lib/humane/time_formatter.rb +45 -46
- data/lib/humane/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4d763c1ac13b418641448e26596e05164e13473257fa89c9dfbe4c752c31a780
|
|
4
|
+
data.tar.gz: 17672a19ade63d69bc0531d798e432b69daafe242fd9b61f594867a8bef3ee6e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a1acce1f07be40fa5aad7073b099801465adc0af544acdeb27fcacc1562249144d809c5c83130447deec443714c768c377c94334a511d232acbe43a71558012a
|
|
7
|
+
data.tar.gz: 617655da198133fd9d931e69ff43ae3e8cd670e56d674a42b787fb63d0f3b7aec5aabb530e22129fa5b67e2fbcc68ed7ec7d1702cea206cb7411951c9b6efb85
|
data/README.md
CHANGED
|
@@ -5,47 +5,20 @@
|
|
|
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::SizeFormatter.
|
|
20
|
-
|
|
21
|
-
time_formatter = Humane::TimeFormatter.new
|
|
22
|
-
time_formatter.string(at: Time.now - 180, relative_to: Time.now) # "3 minutes ago"
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Both methods also accept positional arguments, matching `humane` (Go)'s
|
|
26
|
-
positional-only calling convention:
|
|
27
|
-
|
|
28
|
-
```ruby
|
|
29
|
-
Humane::SizeFormatter.new.string(225_935) # "226 KB"
|
|
30
|
-
time_formatter.string(Time.now - 180, Time.now) # "3 minutes ago"
|
|
31
|
-
```
|
|
32
|
-
|
|
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"
|
|
18
|
+
Humane::SizeFormatter.human_size(225_935) # "226 KB"
|
|
19
|
+
Humane::TimeFormatter.time_ago(Time.now - 180, Time.now) # "3 minutes ago"
|
|
42
20
|
```
|
|
43
21
|
|
|
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
22
|
## Install
|
|
50
23
|
|
|
51
24
|
```
|
|
@@ -58,33 +31,42 @@ or in a `Gemfile`:
|
|
|
58
31
|
gem "humane"
|
|
59
32
|
```
|
|
60
33
|
|
|
61
|
-
##
|
|
34
|
+
## `time_ago` options
|
|
35
|
+
|
|
36
|
+
`time_ago`'s recommended defaults already match ActionView's own
|
|
37
|
+
`distance_of_time_in_words` defaults -- pass no keyword arguments at all and
|
|
38
|
+
you get them for free:
|
|
62
39
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
default, so `Humane::TimeFormatter.new` and calling `RelativeDateTimeFormatter`
|
|
67
|
-
directly always agree:
|
|
40
|
+
```ruby
|
|
41
|
+
Humane::TimeFormatter.time_ago(at, relative_to) # approximate: true, include_seconds: false
|
|
42
|
+
```
|
|
68
43
|
|
|
69
|
-
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
44
|
+
- **`approximate`** (default `true`): prefixes `"about"`/`"in about"` on the
|
|
45
|
+
hour-scale buckets (1 hour, and 2..24 hours), matching ActionView's
|
|
46
|
+
`distance_of_time_in_words` wording for those buckets exactly (down to its
|
|
47
|
+
44:30/89:30 rounding cutoffs), through the "1 day" bucket.
|
|
48
|
+
- **`include_seconds`** (default `false`): under 30 seconds, collapses to
|
|
49
|
+
`"less than a minute ago"`/`"in less than a minute"` instead of an exact
|
|
50
|
+
second count. Matches ActionView's `include_seconds` default.
|
|
51
|
+
- **`when_nil`** (default `nil`): if `at` is `nil`, `time_ago` returns this
|
|
52
|
+
value without formatting -- for a scan, download, or other record that
|
|
53
|
+
doesn't have a timestamp yet.
|
|
78
54
|
|
|
79
55
|
```ruby
|
|
80
|
-
Humane::TimeFormatter.
|
|
81
|
-
|
|
56
|
+
Humane::TimeFormatter.time_ago(t, now, approximate: false) # "15 hours ago", not "about 15 hours ago"
|
|
57
|
+
Humane::TimeFormatter.time_ago(nil, now, when_nil: "an unknown time") # "an unknown time"
|
|
82
58
|
```
|
|
83
59
|
|
|
84
60
|
## Scope
|
|
85
61
|
|
|
86
|
-
Finder's
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
62
|
+
Finder's byte-count style, and a numeric (non-calendar-aware) relative time
|
|
63
|
+
style through the "1 day" bucket -- that's the whole surface area today.
|
|
64
|
+
Alternate size units/styles and a `:named` style (`"yesterday"`,
|
|
65
|
+
calendar-boundary-aware) aren't implemented -- contributions welcome.
|
|
66
|
+
|
|
67
|
+
## Development
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
bundle exec standardrb
|
|
71
|
+
bundle exec rspec -fd spec
|
|
72
|
+
```
|
|
@@ -1,24 +1,36 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Humane
|
|
4
|
-
# Formats byte counts the way Finder does: 1000-based math, capitalized
|
|
4
|
+
# Formats byte counts the way Finder does: 1000-based math, capitalized
|
|
5
|
+
# unit labels, rounded to 3 significant figures. See docs/COMMENTS.md.
|
|
5
6
|
class SizeFormatter
|
|
6
|
-
UNITS = %w[
|
|
7
|
+
UNITS = %w[KB MB GB TB PB EB].freeze
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
class << self
|
|
10
|
+
# Returns byte_count as a Finder-style human-readable string.
|
|
11
|
+
#
|
|
12
|
+
# Humane::SizeFormatter.human_size(225_935) #=> "226 KB"
|
|
13
|
+
def human_size(byte_count)
|
|
14
|
+
return "Zero KB" if byte_count.zero?
|
|
15
|
+
return (byte_count == 1) ? "1 byte" : "#{byte_count} bytes" if byte_count < 1000
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
exponent = [(Math.log(byte_count) / Math.log(1000)).to_i, UNITS.size].min
|
|
18
|
+
value = byte_count / (1000.0**exponent)
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
"#{format_significant(value, 3)} #{UNITS[exponent - 1]}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
# Rounds value to sig_figs significant figures and trims trailing
|
|
26
|
+
# fractional zeros (and the decimal point itself, if nothing remains
|
|
27
|
+
# after it) -- see docs/COMMENTS.md.
|
|
28
|
+
def format_significant(value, sig_figs)
|
|
29
|
+
magnitude = Math.log10(value).floor + 1
|
|
30
|
+
decimals = [sig_figs - magnitude, 0].max
|
|
17
31
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
else
|
|
21
|
-
format("%.0f %s", rounded, UNITS[exponent])
|
|
32
|
+
formatted = format("%.#{decimals}f", value)
|
|
33
|
+
formatted.include?(".") ? formatted.sub(/0+\z/, "").sub(/\.\z/, "") : formatted
|
|
22
34
|
end
|
|
23
35
|
end
|
|
24
36
|
end
|
|
@@ -1,59 +1,58 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Humane
|
|
4
|
-
# Formats one time relative to another the way
|
|
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. See
|
|
8
|
+
# docs/COMMENTS.md.
|
|
5
9
|
class TimeFormatter
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
10
|
+
class << self
|
|
11
|
+
# Returns at relative to relative_to as a human-readable string. If at
|
|
12
|
+
# is nil, returns when_nil without formatting -- see docs/COMMENTS.md.
|
|
13
|
+
#
|
|
14
|
+
# Humane::TimeFormatter.time_ago(Time.now - 180, Time.now) #=> "3 minutes ago"
|
|
15
|
+
def time_ago(at, relative_to, approximate: true, include_seconds: false, when_nil: nil)
|
|
16
|
+
return when_nil if at.nil?
|
|
17
|
+
|
|
18
|
+
seconds = relative_to - at
|
|
19
|
+
future = seconds.negative?
|
|
20
|
+
seconds = seconds.abs
|
|
21
|
+
|
|
22
|
+
if !include_seconds && seconds < 30
|
|
23
|
+
return future ? "in less than a minute" : "less than a minute ago"
|
|
24
|
+
end
|
|
31
25
|
|
|
32
|
-
|
|
33
|
-
|
|
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]
|
|
26
|
+
if include_seconds && seconds < 60
|
|
27
|
+
return wrap(pluralize(seconds.to_i, "second"), future: future)
|
|
43
28
|
end
|
|
44
29
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
30
|
+
# Buckets come from distance_in_minutes, not raw seconds re-divided per unit -- see docs/COMMENTS.md.
|
|
31
|
+
distance_in_minutes = (seconds / 60.0).round
|
|
32
|
+
|
|
33
|
+
text, approximable =
|
|
34
|
+
case distance_in_minutes
|
|
35
|
+
when 1 then ["1 minute", false]
|
|
36
|
+
when 2..44 then [pluralize(distance_in_minutes, "minute"), false]
|
|
37
|
+
when 45..89 then ["1 hour", true]
|
|
38
|
+
when 90..1439 then [pluralize((distance_in_minutes / 60.0).round, "hour"), true]
|
|
39
|
+
when 1440..2519 then ["1 day", false]
|
|
40
|
+
else [pluralize((distance_in_minutes / 1440.0).round, "day"), false]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
text = "about #{text}" if approximate && approximable
|
|
44
|
+
wrap(text, future: future)
|
|
45
|
+
end
|
|
48
46
|
|
|
49
|
-
|
|
47
|
+
private
|
|
50
48
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
def wrap(text, future:)
|
|
50
|
+
future ? "in #{text}" : "#{text} ago"
|
|
51
|
+
end
|
|
54
52
|
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
def pluralize(count, unit)
|
|
54
|
+
(count == 1) ? "1 #{unit}" : "#{count} #{unit}s"
|
|
55
|
+
end
|
|
57
56
|
end
|
|
58
57
|
end
|
|
59
58
|
end
|
data/lib/humane/version.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.0
|
|
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: []
|
|
@@ -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: []
|