humane 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d763c1ac13b418641448e26596e05164e13473257fa89c9dfbe4c752c31a780
4
- data.tar.gz: 17672a19ade63d69bc0531d798e432b69daafe242fd9b61f594867a8bef3ee6e
3
+ metadata.gz: b0e7b2d5c4485d84a102a56c7586e7a61e62312729b1978d8221a24cc7d23917
4
+ data.tar.gz: c51ce6b56f73e1d859c9cb65e4a096cee6030c37fe4664c30ab16da13193d9a3
5
5
  SHA512:
6
- metadata.gz: a1acce1f07be40fa5aad7073b099801465adc0af544acdeb27fcacc1562249144d809c5c83130447deec443714c768c377c94334a511d232acbe43a71558012a
7
- data.tar.gz: 617655da198133fd9d931e69ff43ae3e8cd670e56d674a42b787fb63d0f3b7aec5aabb530e22129fa5b67e2fbcc68ed7ec7d1702cea206cb7411951c9b6efb85
6
+ metadata.gz: d3477e12b897535a507f1cd95c3b2c57619e71bce6f3d320c89eafde5621a5fb382f18eb9fa10a570d9edc511d2b62fe0c6ba6e65338432730ef5a1baf64b2ed
7
+ data.tar.gz: 8be3d6ed6747f5adcc66edc8eca5dc2ce7ea3fd4252af19a1e376787333a6af6a1d039590b49c885ecbe3594eb351364be7aa0e734b805bf4ad795fe2c9f47cf
data/README.md CHANGED
@@ -15,8 +15,13 @@ helpers, with output that's consistent with
15
15
  ```ruby
16
16
  require "humane"
17
17
 
18
- Humane::SizeFormatter.human_size(225_935) # "226 KB"
19
- Humane::TimeFormatter.time_ago(Time.now - 180, Time.now) # "3 minutes ago"
18
+ Humane.human_size(225_935) # "226 KB"
19
+
20
+ mtime = Time.now - 180
21
+ Humane.time_ago(mtime) # "3 minutes ago" -- relative to the real clock
22
+
23
+ now = Time.now
24
+ Humane.distance_in_time(mtime, now) # "3 minutes ago" -- explicit relative_to, for tests
20
25
  ```
21
26
 
22
27
  ## Install
@@ -31,14 +36,24 @@ or in a `Gemfile`:
31
36
  gem "humane"
32
37
  ```
33
38
 
34
- ## `time_ago` options
39
+ ## `distance_in_time` and `time_ago`
40
+
41
+ Two entry points, same naming split as ActionView's own
42
+ `distance_of_time_in_words`/`time_ago_in_words`:
43
+
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`.
35
49
 
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:
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:
39
53
 
40
54
  ```ruby
41
- Humane::TimeFormatter.time_ago(at, relative_to) # approximate: true, include_seconds: false
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
42
57
  ```
43
58
 
44
59
  - **`approximate`** (default `true`): prefixes `"about"`/`"in about"` on the
@@ -48,13 +63,13 @@ Humane::TimeFormatter.time_ago(at, relative_to) # approximate: true, include_sec
48
63
  - **`include_seconds`** (default `false`): under 30 seconds, collapses to
49
64
  `"less than a minute ago"`/`"in less than a minute"` instead of an exact
50
65
  second count. Matches ActionView's `include_seconds` default.
51
- - **`when_nil`** (default `nil`): if `at` is `nil`, `time_ago` returns this
66
+ - **`when_nil`** (default `nil`): if `at` is `nil`, both methods return this
52
67
  value without formatting -- for a scan, download, or other record that
53
68
  doesn't have a timestamp yet.
54
69
 
55
70
  ```ruby
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"
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"
58
73
  ```
59
74
 
60
75
  ## Scope
@@ -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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Humane
4
- VERSION = "0.9.0"
4
+ VERSION = "0.9.3"
5
5
  end
data/lib/humane.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "humane/version"
4
- require_relative "humane/size_formatter"
5
- require_relative "humane/time_formatter"
4
+ require_relative "humane/size"
5
+ require_relative "humane/time"
6
6
 
7
7
  module Humane
8
8
  end
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.9.0
4
+ version: 0.9.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Woodell
@@ -20,8 +20,8 @@ files:
20
20
  - LICENSE
21
21
  - README.md
22
22
  - lib/humane.rb
23
- - lib/humane/size_formatter.rb
24
- - lib/humane/time_formatter.rb
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:
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Humane
4
- # Formats byte counts the way Finder does: 1000-based math, capitalized
5
- # unit labels, rounded to 3 significant figures. See docs/COMMENTS.md.
6
- class SizeFormatter
7
- UNITS = %w[KB MB GB TB PB EB].freeze
8
-
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
16
-
17
- exponent = [(Math.log(byte_count) / Math.log(1000)).to_i, UNITS.size].min
18
- value = byte_count / (1000.0**exponent)
19
-
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
31
-
32
- formatted = format("%.#{decimals}f", value)
33
- formatted.include?(".") ? formatted.sub(/0+\z/, "").sub(/\.\z/, "") : formatted
34
- end
35
- end
36
- end
37
- end
@@ -1,58 +0,0 @@
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. See
8
- # docs/COMMENTS.md.
9
- class TimeFormatter
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
25
-
26
- if include_seconds && seconds < 60
27
- return wrap(pluralize(seconds.to_i, "second"), future: future)
28
- end
29
-
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
46
-
47
- private
48
-
49
- def wrap(text, future:)
50
- future ? "in #{text}" : "#{text} ago"
51
- end
52
-
53
- def pluralize(count, unit)
54
- (count == 1) ? "1 #{unit}" : "#{count} #{unit}s"
55
- end
56
- end
57
- end
58
- end