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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd64d51ef7949fafc8a03a0e9108d059ac0eff61dda04b4c482d0f2a4255a9ea
4
- data.tar.gz: e00ccca7b07dee2b94693c26903f96eebdf885a3d71bf7699459b595b037863c
3
+ metadata.gz: dbe396226726ddd1bfb5f24169d319d8a7254f617ee84561ccb0213566cbd1ea
4
+ data.tar.gz: a5331926a9c5f2229f679b184558e5310ee04737cd41242851063ddc972302d0
5
5
  SHA512:
6
- metadata.gz: a9f0e9c40dadeebbc81a1dbcfdc5f152724cc970e8dc141aa09237e157a7b3b16f9c0ae68db1361858b23984cb8740a6f812f16ac7fa1f1e9599a2d89d1a3a38
7
- data.tar.gz: b71f208a50b552a2a956affcbbcb9210be78687b6cb3edc1a8320dd22107c77038be900d60d0e22862825f694b07404369e7c1cefb580da3d10eb16f45f1a2a8
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
- Either require declares where the unit classes are without reading them. A unit
59
- file is read when its class is first named, so a program dealing in seconds and
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
 
@@ -1,3 +1,3 @@
1
1
  module Duration
2
- VERSION = '0.7.1'
2
+ VERSION = '0.8.0'
3
3
  end
@@ -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.7.1
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