duration.rb 0.7.1 → 0.8.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/CHANGELOG +10 -0
- data/README.md +83 -3
- data/lib/Duration/VERSION.rb +1 -1
- data/lib/duration-classes.rb +27 -0
- data/test/duration-classes_test.rb +45 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dbe396226726ddd1bfb5f24169d319d8a7254f617ee84561ccb0213566cbd1ea
|
|
4
|
+
data.tar.gz: a5331926a9c5f2229f679b184558e5310ee04737cd41242851063ddc972302d0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4ba07081f0c9743d1aeace1c275f4f0d773048d082b01041fb6638fa8a38a22a27372d55864d71d74aa548e3fe6cdc542ef5f13139d4409f3fff4f9d7b163932
|
|
7
|
+
data.tar.gz: 266ce00b778ed46d6334c115f0c6802969989fffd953be313b1277b70feeef34648cad9ef1fea790912e2aa0d5788bee30bb3bcd8e3ef05834952c3db9d80f16
|
data/CHANGELOG
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 20260822
|
|
4
|
+
|
|
5
|
+
0.8.0: + duration-classes.rb, the unit classes at the top level.
|
|
6
|
+
|
|
7
|
+
1. + lib/duration-classes.rb: `require 'duration-classes'` names the nine unit classes at the top level, so a program can write Days where it would otherwise write Duration::Days. It is the counterpart to the Numeric sugar — that one adds nine methods, this one nine constants — and opt-in by require in the same way. Common, Relative and VERSION stay where they are: the first two are what a type check names, and VERSION is a word an including program is likely to want for itself. The motivation is a collision rather than ergonomics: Duration::Common and CoinMarketCap::Common are both mine, and a program which reaches for a top-level `include Duration` alongside another library carrying the same name gets whichever came last, silently and by include order. A Duration::Classes module, included rather than required, was built first and withdrawn: it worked and stayed lazy, but autoload verifies that the file it loads defines the constant it was registered against, so each of the nine unit files had to end with an assignment into it — nine lines of coupling, nine duplicated declarations, and a test to police them, carried permanently against a convenience. The eagerness above was accepted as the cheaper price.
|
|
8
|
+
2. + test/duration-classes_test.rb: that the nine are named and only the nine, that the classes are not renamed, and that Common, Relative and VERSION are left alone; and, in a process of its own since each turns upon what the top level held before the require, that a name the program gave first is taken with a warning and a name given after is reopened rather than replaced.
|
|
9
|
+
3. ~ README.md: ~ Loading, which listed the requires without saying that each stands alone — the opt-in files requiring the core themselves, so one line is a whole answer — and which now has three of them to be read that way; + Naming the classes, under it, for the four ways to reach a unit class and the trap in three of them. That the prefix is needed only for type checks and dispatch, construction going through the sugar; that duration-classes is eager and cannot be otherwise, a constant assignment having no body to defer resolution into where a method has one, so it reads all nine units where the sugar reads the two a program uses; that naming the units yourself stays lazy and costs the library nothing; that it assigns where an include shadows, so a name the program had given is replaced with a warning while a name given after the require silently reopens the gem's class; that it is eager, the derivation of which sits in the file's own comment rather than the README, being a thing wanted by whoever edits it and not by whoever requires it; and that a top-level `include Duration` puts twelve constants into global scope, where a name never given resolves silently rather than raising and a second library carrying the same name wins by include order alone.
|
|
10
|
+
4. ~ Duration::VERSION: /0.7.1/0.8.0/
|
|
11
|
+
|
|
12
|
+
|
|
3
13
|
## 20260822
|
|
4
14
|
|
|
5
15
|
0.7.1: + test/duration-numeric_test.rb.
|
data/README.md
CHANGED
|
@@ -49,15 +49,95 @@ require 'duration' # the duration classes only
|
|
|
49
49
|
|
|
50
50
|
require 'duration-numeric' # opt in to the Numeric sugar (5.minutes, etc.),
|
|
51
51
|
# which monkeypatches Numeric
|
|
52
|
+
|
|
53
|
+
require 'duration-classes' # opt in to the unit classes at the top level
|
|
54
|
+
# (Days rather than Duration::Days)
|
|
52
55
|
```
|
|
53
56
|
|
|
57
|
+
Those are three ways in rather than three lines to write. Each stands alone, the
|
|
58
|
+
two opt-in files requiring the core themselves, so `require 'duration-classes'` is
|
|
59
|
+
a complete line and wants no `require 'duration'` above it. They are independent of
|
|
60
|
+
each other, so a program which wants both the sugar and the top-level names asks
|
|
61
|
+
for both.
|
|
62
|
+
|
|
54
63
|
The examples below use the `Numeric` sugar, so they assume
|
|
55
64
|
`require 'duration-numeric'`. Without it durations are constructed directly with
|
|
56
65
|
`Duration::Minutes.new(5)` and so on.
|
|
57
66
|
|
|
58
|
-
|
|
59
|
-
file is read when its class is first named, so a program dealing in seconds
|
|
60
|
-
minutes reads the files for seconds and minutes and leaves the rest unread.
|
|
67
|
+
The first two requires declare where the unit classes are without reading them. A
|
|
68
|
+
unit file is read when its class is first named, so a program dealing in seconds
|
|
69
|
+
and minutes reads the files for seconds and minutes and leaves the rest unread.
|
|
70
|
+
The third is different, and says so below.
|
|
71
|
+
|
|
72
|
+
### Naming the classes
|
|
73
|
+
|
|
74
|
+
The unit classes are `Duration::Days` and its siblings. With the sugar,
|
|
75
|
+
construction never names one — `45.days` reaches the same class — so the prefix is
|
|
76
|
+
left for the two places a constant is unavoidable, type checks and dispatch —
|
|
77
|
+
`value.is_a?(Duration::Common)`, or a `case` over the units.
|
|
78
|
+
|
|
79
|
+
`require 'duration-classes'` takes the nine unit classes and names them at the top
|
|
80
|
+
level, so those read as `Days` and `Hours`. It is the counterpart to the `Numeric`
|
|
81
|
+
sugar — that one adds nine methods, this one nine constants — and like it, it is
|
|
82
|
+
opt-in by require rather than something `require 'duration'` does to you.
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
require 'duration-classes'
|
|
86
|
+
|
|
87
|
+
Days.new(45) # => Duration::Days(45)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The classes keep their own names, so `inspect` is unchanged. `Common`, `Relative`
|
|
91
|
+
and `VERSION` stay where they are: the first two are what a type check names, and
|
|
92
|
+
`VERSION` is a word an including program is likely to want for itself.
|
|
93
|
+
|
|
94
|
+
**It is eager**, where the sugar is not: requiring it reads all nine unit files,
|
|
95
|
+
against the two a sugar-only program reads. A constant assignment resolves at once
|
|
96
|
+
where a method body waits to be called, so it cannot be otherwise — the price of
|
|
97
|
+
asking for all nine names, charged only to a program which asks.
|
|
98
|
+
|
|
99
|
+
**It assigns into the top level**, so a name already taken is replaced — with a
|
|
100
|
+
warning, at least. The order without a warning is the one to watch:
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
require 'duration-classes'
|
|
104
|
+
class Days # not a class of your own — this reopens
|
|
105
|
+
def self.mine; end # Duration::Days and adds a method to it
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`class Days` finds the name bound and reopens what it names, so a program which
|
|
110
|
+
meant to declare its own has extended the gem's instead. Where a program has its own
|
|
111
|
+
vocabulary to protect, reach for the units through `Duration::`.
|
|
112
|
+
|
|
113
|
+
Where fewer names will do, naming them yourself costs nothing and stays lazy:
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
Days = Duration::Days
|
|
117
|
+
Hours = Duration::Hours
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Two files are read and two names are taken, and the pair reads as a declaration of
|
|
121
|
+
which units this program deals in — the same thing the loading story above says
|
|
122
|
+
about the files.
|
|
123
|
+
|
|
124
|
+
`include Duration` is the remaining way in, and the last to reach for. Inside a
|
|
125
|
+
class it is scoped and unremarkable, and it brings `Common` along, which
|
|
126
|
+
`duration-classes` does not:
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
class Portfolio
|
|
130
|
+
include Duration
|
|
131
|
+
def hold_period; Days.new(45); end
|
|
132
|
+
def duration?(value); value.is_a?(Common); end
|
|
133
|
+
end
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
At the top level it includes into `Object`, putting all twelve constants into
|
|
137
|
+
global scope — the nine units, `Common`, `Relative` and `VERSION`. A constant of
|
|
138
|
+
your own still wins there, but a name you never gave resolves silently rather than
|
|
139
|
+
raising, and a second library included the same way and carrying the same name wins
|
|
140
|
+
by include order alone. `Common` is the likeliest to be met twice.
|
|
61
141
|
|
|
62
142
|
## Usage
|
|
63
143
|
|
data/lib/Duration/VERSION.rb
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# duration-classes.rb
|
|
2
|
+
|
|
3
|
+
# The unit classes at the top level, for a program which would rather write
|
|
4
|
+
# Days than Duration::Days. Opt-in by require, as the Numeric sugar is, and
|
|
5
|
+
# the counterpart to it: that one adds nine methods, this one nine constants.
|
|
6
|
+
#
|
|
7
|
+
# It takes only the classes. Common, Relative and VERSION stay where they are,
|
|
8
|
+
# the first two being what a type check names and the third being a word an
|
|
9
|
+
# including program is likely to want for itself.
|
|
10
|
+
#
|
|
11
|
+
# Unlike the sugar it is eager, and cannot be otherwise: a method body naming
|
|
12
|
+
# Duration::Seconds is not evaluated until the method is called, where a
|
|
13
|
+
# constant assignment resolves its right-hand side at once and so reads every
|
|
14
|
+
# unit. Requiring this file therefore costs the whole library, which is the
|
|
15
|
+
# price of asking for all nine names by name.
|
|
16
|
+
|
|
17
|
+
require_relative 'duration'
|
|
18
|
+
|
|
19
|
+
Nanoseconds = Duration::Nanoseconds
|
|
20
|
+
Microseconds = Duration::Microseconds
|
|
21
|
+
Milliseconds = Duration::Milliseconds
|
|
22
|
+
Seconds = Duration::Seconds
|
|
23
|
+
Minutes = Duration::Minutes
|
|
24
|
+
Hours = Duration::Hours
|
|
25
|
+
Days = Duration::Days
|
|
26
|
+
Weeks = Duration::Weeks
|
|
27
|
+
Months = Duration::Months
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# test/duration-classes_test.rb
|
|
2
|
+
|
|
3
|
+
require_relative './test_helper'
|
|
4
|
+
require_relative '../lib/duration-classes'
|
|
5
|
+
|
|
6
|
+
describe 'duration-classes' do
|
|
7
|
+
it "names the unit classes at the top level" do
|
|
8
|
+
_(Days.new(45).to_s).must_equal '45 days'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "names every class Duration holds, and only those" do
|
|
12
|
+
units = Duration.constants.select{|name| Duration.const_get(name).is_a?(Class)}
|
|
13
|
+
_(units.all?{|name| Object.const_defined?(name, false)}).must_equal true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "does not rename the classes" do
|
|
17
|
+
_(Days.name).must_equal 'Duration::Days'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "leaves Common, Relative and VERSION where they are" do
|
|
21
|
+
_([:Common, :Relative, :VERSION].none?{|name| Object.const_defined?(name, false)}).must_equal true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# These two want a process of its own: each turns upon what the top level held
|
|
25
|
+
# before the require, and this one has already required it.
|
|
26
|
+
describe "meeting a name the program had given" do
|
|
27
|
+
def ruby(source)
|
|
28
|
+
lib = File.expand_path('../lib', __dir__)
|
|
29
|
+
IO.popen(['ruby', "-I#{lib}", '-e', source], err: [:child, :out]){|io| io.read}.strip
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "takes the name and says so, where the program named it first" do
|
|
33
|
+
output = ruby('class Days; def self.mine; end; end; require "duration-classes"; ' \
|
|
34
|
+
'print Days.respond_to?(:mine)')
|
|
35
|
+
_(output).must_match(/already initialized constant Days/)
|
|
36
|
+
_(output).must_match(/false\z/)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "is reopened rather than replaced, where the program names it after" do
|
|
40
|
+
_(ruby('require "duration-classes"; class Days; def self.mine; end; end; ' \
|
|
41
|
+
'print [Days.name, Days.respond_to?(:mine)].inspect')) \
|
|
42
|
+
.must_equal('["Duration::Days", true]')
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: duration.rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- thoran
|
|
@@ -78,6 +78,7 @@ files:
|
|
|
78
78
|
- lib/Duration/Seconds.rb
|
|
79
79
|
- lib/Duration/VERSION.rb
|
|
80
80
|
- lib/Duration/Weeks.rb
|
|
81
|
+
- lib/duration-classes.rb
|
|
81
82
|
- lib/duration-numeric.rb
|
|
82
83
|
- lib/duration.rb
|
|
83
84
|
- test/Duration/Common_test.rb
|
|
@@ -92,6 +93,7 @@ files:
|
|
|
92
93
|
- test/Duration/Seconds_test.rb
|
|
93
94
|
- test/Duration/VERSION_test.rb
|
|
94
95
|
- test/Duration/Weeks_test.rb
|
|
96
|
+
- test/duration-classes_test.rb
|
|
95
97
|
- test/duration-numeric_test.rb
|
|
96
98
|
- test/gemspec_test.rb
|
|
97
99
|
- test/loading_test.rb
|