unit_measurements 0.1.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -1
- data/Gemfile.lock +1 -1
- data/README.md +440 -0
- data/lib/unit_measurements/base.rb +35 -0
- data/lib/unit_measurements/errors/parse_error.rb +14 -0
- data/lib/unit_measurements/errors/unit_already_defined_error.rb +14 -0
- data/lib/unit_measurements/errors/unit_error.rb +14 -0
- data/lib/unit_measurements/formatter.rb +28 -0
- data/lib/unit_measurements/measurement.rb +103 -0
- data/lib/unit_measurements/normalizer.rb +73 -0
- data/lib/unit_measurements/parser.rb +58 -0
- data/lib/unit_measurements/unit.rb +79 -0
- data/lib/unit_measurements/unit_group.rb +51 -0
- data/lib/unit_measurements/unit_group_builder.rb +56 -0
- data/lib/unit_measurements/unit_groups/all.rb +6 -0
- data/lib/unit_measurements/unit_groups/length.rb +12 -0
- data/lib/unit_measurements/unit_groups/weight.rb +10 -0
- data/lib/unit_measurements/version.rb +1 -1
- data/lib/unit_measurements.rb +8 -0
- data/units.md +32 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 24b2504d7feb77f54138260f7e0947bd86e00225dbe3c6d9e575da3050a24eeb
|
4
|
+
data.tar.gz: 7d9b84113fbf2a2cd5ca1c5a46467296b15f1bf1016c1775b854098245998cee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b5fc7c6e1b2b580fcd5477941e239e8d06a79416b90a7c36145f8d50d32c40c7430b83273038b2974ec51df90673e46672207f8d8ae975c31e99b3612cd3b446
|
7
|
+
data.tar.gz: 7730a9813366e4b36dbf98873a575cf89a33963ee998da6c84d7e6b5fd7c7f5c197530e23f1acaeb2a44e339667f9665d7e3bc62c5164579df06765e01426d85
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,27 @@
|
|
1
|
+
## [1.1.0](https://github.com/shivam091/unit_measurements/compare/v1.0.0...v1.1.0) - 2023-08-14
|
2
|
+
|
3
|
+
### What's new
|
4
|
+
|
5
|
+
- Added `#format` method to format quantity of the measurement.
|
6
|
+
|
7
|
+
----------
|
8
|
+
|
9
|
+
## [1.0.0](https://github.com/shivam091/unit_measurements/compare/v0.1.0...v1.0.0) - 2023-09-14
|
10
|
+
|
11
|
+
### What's new
|
12
|
+
|
13
|
+
- Added support to build unit groups.
|
14
|
+
- Added unit group for `length` units.
|
15
|
+
- Added unit group for `weight` units.
|
16
|
+
- Added support to build `si` units.
|
17
|
+
- Added support to parse `Complex`, `Rational`, `Scientific` numbers, and `ratios`.
|
18
|
+
- Added support to convert quantity between two units using `#convert_to`, `#convert_to!`, and `#parse` methods.
|
19
|
+
|
20
|
+
----------
|
21
|
+
|
1
22
|
## [0.1.0] - 2023-09-13
|
2
23
|
|
3
|
-
|
24
|
+
### Initial release
|
4
25
|
|
5
26
|
-----------
|
6
27
|
|
data/Gemfile.lock
CHANGED
data/README.md
ADDED
@@ -0,0 +1,440 @@
|
|
1
|
+
# Unit Measurements
|
2
|
+
|
3
|
+
A library that encapsulate measurements and their units in Ruby.
|
4
|
+
|
5
|
+
[![Ruby](https://github.com/shivam091/unit_measurements/actions/workflows/main.yml/badge.svg)](https://github.com/shivam091/unit_measurements/actions/workflows/main.yml)
|
6
|
+
[![Gem Version](https://badge.fury.io/rb/unit_measurements.svg)](https://badge.fury.io/rb/unit_measurements)
|
7
|
+
[![Gem Downloads](https://img.shields.io/gem/dt/unit_measurements.svg)](http://rubygems.org/gems/unit_measurements)
|
8
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/b8aec9bffa356d108784/maintainability)](https://codeclimate.com/github/shivam091/unit_measurements/maintainability)
|
9
|
+
[![Test Coverage](https://api.codeclimate.com/v1/badges/b8aec9bffa356d108784/test_coverage)](https://codeclimate.com/github/shivam091/unit_measurements/test_coverage)
|
10
|
+
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/shivam091/unit_measurements/blob/main/LICENSE)
|
11
|
+
|
12
|
+
**Harshal V. Ladhe, M.Sc. Computer Science.**
|
13
|
+
|
14
|
+
## Introduction
|
15
|
+
|
16
|
+
Many technical applications need use of specialized calculations at some point of time.
|
17
|
+
Frequently, these calculations require unit conversions to ensure accurate
|
18
|
+
results. Needless to say, this is a pain to properly keep track of, and is prone
|
19
|
+
to numerous errors.
|
20
|
+
|
21
|
+
## Solution
|
22
|
+
|
23
|
+
The `unit_measurements` gem is designed to simplify the handling of units for scientific calculations.
|
24
|
+
|
25
|
+
## Advantages
|
26
|
+
|
27
|
+
1. Provides easy conversion between units.
|
28
|
+
2. Built in support for various [unit groups](units.md).
|
29
|
+
3. Lightweight and easily extensible to include other units and conversions.
|
30
|
+
4. Can convert `complex`, `rational`, `fractions`, `exponents`, `scientific notations`, and `ratios`.
|
31
|
+
|
32
|
+
## Disclaimer
|
33
|
+
|
34
|
+
_The unit conversions presented in `unit_measurements` are provided for reference and general informational purposes.
|
35
|
+
While we aim to offer accurate conversions, we cannot guarantee their precision in all scenarios.
|
36
|
+
Users are advised to cross-verify conversions as needed for their specific use cases._
|
37
|
+
|
38
|
+
## Minimum Requirements
|
39
|
+
|
40
|
+
* Ruby 3.2.2+ (https://www.ruby-lang.org/en/downloads/branches/)
|
41
|
+
|
42
|
+
## Installation
|
43
|
+
|
44
|
+
If using bundler, first add this line to your application's Gemfile:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
gem "unit_measurements"
|
48
|
+
```
|
49
|
+
|
50
|
+
And then execute:
|
51
|
+
|
52
|
+
`$ bundle install`
|
53
|
+
|
54
|
+
Or otherwise simply install it yourself as:
|
55
|
+
|
56
|
+
`$ gem install unit_measurements`
|
57
|
+
|
58
|
+
## Usage
|
59
|
+
|
60
|
+
The **`UnitMeasurements::Measurement`** class is responsible for conversion of quantity to various compatble units.
|
61
|
+
|
62
|
+
Measurements can't be initialized or converted to other units directly with the `UnitMeasurements::Measurement` class,
|
63
|
+
but rather with the unit group classes viz., `UnitMeasurements::Weight`, `UnitMeasurements::Length`, etc.
|
64
|
+
|
65
|
+
**Initialize a measurement:**
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
UnitMeasurements::Weight.new(1, :kg)
|
69
|
+
#=> 1 kg
|
70
|
+
```
|
71
|
+
|
72
|
+
**Converting to other units:**
|
73
|
+
|
74
|
+
This gem allows you to convert among units of same unit group.
|
75
|
+
You can convert measurement to other unit using `#convert_to` (aliased as `#to`)
|
76
|
+
or `#convert_to!` (aliased as `#to!`) methods.
|
77
|
+
|
78
|
+
You can use `#convert_to` as:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
UnitMeasurements::Weight.new(1, :kg).convert_to(:g)
|
82
|
+
#=> 1000 g
|
83
|
+
```
|
84
|
+
|
85
|
+
If you want to modify measurement object itself, you can use `#convert_to!` method as:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
UnitMeasurements::Weight.new(1, :kg).convert_to!(:g)
|
89
|
+
#=> 1000 g
|
90
|
+
```
|
91
|
+
|
92
|
+
You can also chain call of `#convert_to` and `#convert_to!` methods as:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
UnitMeasurements::Weight.new(1, :kg).convert_to(:g).convert_to(:t).convert_to!(:q)
|
96
|
+
#=> 0.01 q
|
97
|
+
```
|
98
|
+
|
99
|
+
**Parse string without having to split out the quantity and source unit:**
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
UnitMeasurements::Weight.parse("1 kg")
|
103
|
+
#=> 1 kg
|
104
|
+
```
|
105
|
+
|
106
|
+
**Parse string that mentions quantity, source unit, and target unit:**
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
UnitMeasurements::Weight.parse("1 kg to g")
|
110
|
+
#=> 1000 g
|
111
|
+
UnitMeasurements::Weight.parse("1 kg as g")
|
112
|
+
#=> 1000 g
|
113
|
+
UnitMeasurements::Weight.parse("1 kg in g")
|
114
|
+
#=> 1000 g
|
115
|
+
```
|
116
|
+
|
117
|
+
**Parse rational numbers, source unit, and (or) target unit:**
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
UnitMeasurements::Weight.new(Rational(2, 3), :kg).convert_to(:g)
|
121
|
+
#=> 666.666666666667 g
|
122
|
+
UnitMeasurements::Weight.new("2/3", :kg).convert_to(:g)
|
123
|
+
#=> 666.666666666667 g
|
124
|
+
UnitMeasurements::Weight.parse("2/3 kg").convert_to(:g)
|
125
|
+
#=> 666.666666666667 g
|
126
|
+
UnitMeasurements::Weight.parse("2/3 kg to g")
|
127
|
+
#=> 666.666666666667 g
|
128
|
+
```
|
129
|
+
|
130
|
+
**Parse complex numbers, source unit, and (or) target unit:**
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
UnitMeasurements::Weight.new(Complex(2, 3), :kg).convert_to(:g)
|
134
|
+
#=> 2000.0+3000.0i g
|
135
|
+
UnitMeasurements::Weight.new("2+3i", :kg).convert_to(:g)
|
136
|
+
#=> 2000.0+3000.0i g
|
137
|
+
UnitMeasurements::Weight.parse("2+3i kg").convert_to(:g)
|
138
|
+
#=> 2000.0+3000.0i g
|
139
|
+
UnitMeasurements::Weight.parse("2+3i kg to g")
|
140
|
+
#=> 2000.0+3000.0i g
|
141
|
+
```
|
142
|
+
|
143
|
+
**Parse scientific numbers, source unit, and (or) target unit:**
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
UnitMeasurements::Weight.new(BigDecimal(2), :kg).convert_to(:g)
|
147
|
+
#=> 2000 g
|
148
|
+
UnitMeasurements::Weight.new(0.2e1, :kg).convert_to(:g)
|
149
|
+
#=> 2000 g
|
150
|
+
UnitMeasurements::Weight.parse("0.2e1 kg").convert_to(:g)
|
151
|
+
#=> 2000 g
|
152
|
+
UnitMeasurements::Weight.parse("0.2e1 kg to g")
|
153
|
+
#=> 2000 g
|
154
|
+
```
|
155
|
+
|
156
|
+
**Parse ratios, source unit, and (or) target unit:**
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
UnitMeasurements::Weight.new("1:2", :kg).convert_to(:g)
|
160
|
+
#=> 500 g
|
161
|
+
UnitMeasurements::Weight.parse("1:2 kg").convert_to(:g)
|
162
|
+
#=> 500 g
|
163
|
+
UnitMeasurements::Weight.parse("1:2 kg to g")
|
164
|
+
#=> 500 g
|
165
|
+
```
|
166
|
+
|
167
|
+
**Parse fractional notations, source unit, and (or) target unit:**
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
UnitMeasurements::Weight.new("1/2", :kg).convert_to(:g)
|
171
|
+
#=> 500 g
|
172
|
+
UnitMeasurements::Weight.parse("1/2 kg").convert_to(:g)
|
173
|
+
#=> 500 g
|
174
|
+
UnitMeasurements::Weight.parse("1/2 kg to g")
|
175
|
+
#=> 500 g
|
176
|
+
UnitMeasurements::Weight.new("½", :kg).convert_to(:g)
|
177
|
+
#=> 500 g
|
178
|
+
UnitMeasurements::Weight.parse("½ kg").convert_to(:g)
|
179
|
+
#=> 500 g
|
180
|
+
UnitMeasurements::Weight.parse("½ kg to g")
|
181
|
+
#=> 500 g
|
182
|
+
```
|
183
|
+
|
184
|
+
**Parse mixed fractional notations, source unit, and (or) target unit:**
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
UnitMeasurements::Weight.new("2 1/2", :kg).convert_to(:g)
|
188
|
+
#=> 2500 g
|
189
|
+
UnitMeasurements::Weight.parse("2 1/2 kg").convert_to(:g)
|
190
|
+
#=> 2500 g
|
191
|
+
UnitMeasurements::Weight.parse("2 1/2 kg to g")
|
192
|
+
#=> 2500 g
|
193
|
+
UnitMeasurements::Weight.new("2 ½", :kg).convert_to(:g)
|
194
|
+
#=> 2500 g
|
195
|
+
UnitMeasurements::Weight.parse("2 ½ kg").convert_to(:g)
|
196
|
+
#=> 2500 g
|
197
|
+
UnitMeasurements::Weight.parse("2 ½ kg to g")
|
198
|
+
#=> 2500 g
|
199
|
+
```
|
200
|
+
|
201
|
+
Supported special characters for fractional notations are `¼`, `½`, `¾`, `⅓`, `⅔`, `⅕`, `⅖`, `⅗`, `⅘`, `⅙`, `⅚`, `⅐`, `⅛`, `⅜`, `⅝`, `⅞`, `⅑`, `⅒`, `↉`, `⁄`.
|
202
|
+
|
203
|
+
**Parse exponents, source unit, and (or) target unit:**
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
UnitMeasurements::Weight.new("2e+2", :kg).convert_to(:g)
|
207
|
+
#=> 200000 g
|
208
|
+
UnitMeasurements::Weight.parse("2e² kg").convert_to(:g)
|
209
|
+
#=> 200000 g
|
210
|
+
UnitMeasurements::Weight.parse("2e+2 kg to g")
|
211
|
+
#=> 200000 g
|
212
|
+
UnitMeasurements::Weight.new("2e⁺²", :kg).convert_to(:g)
|
213
|
+
#=> 200000 g
|
214
|
+
UnitMeasurements::Weight.parse("2e⁺2 kg").convert_to(:g)
|
215
|
+
#=> 200000 g
|
216
|
+
UnitMeasurements::Weight.parse("2e⁻² kg to g")
|
217
|
+
#=> 20 g
|
218
|
+
```
|
219
|
+
|
220
|
+
Supported special characters for exponents are `⁰`, `¹`, `²`, `³`, `⁴`, `⁵`, `⁶`, `⁷`, `⁸`, `⁹`, `⁺`, `⁻`.
|
221
|
+
|
222
|
+
**Formatting measurement:**
|
223
|
+
|
224
|
+
If you want to format measurement to certain format, you can use `#format` method.
|
225
|
+
If format is not specified, it defaults to `"%.2<value>f %<unit>s"`.
|
226
|
+
|
227
|
+
```ruby
|
228
|
+
UnitMeasurements::Weight.parse("2 kg").to(:st).format
|
229
|
+
#=> "0.31 st"
|
230
|
+
UnitMeasurements::Weight.parse("2 kg").to(:st).format("%.4<quantity>f %<unit>s")
|
231
|
+
#=> "0.3149 st"
|
232
|
+
UnitMeasurements::Weight.parse("2 kg").to(:st).format("%.4<quantity>f")
|
233
|
+
#=> "0.3149"
|
234
|
+
```
|
235
|
+
|
236
|
+
**Extract the unit and the quantity from measurement:**
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
weight = UnitMeasurements::Weight.new(1.0, :kg)
|
240
|
+
weight.quantity
|
241
|
+
#=> 0.1e1
|
242
|
+
weight.unit
|
243
|
+
#=> #<UnitMeasurements::Unit: kg (kilogram, kilogramme, kilogrammes, kilograms)>
|
244
|
+
```
|
245
|
+
|
246
|
+
**See all units of the unit group:**
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
UnitMeasurements::Weight.units
|
250
|
+
#=> [#<UnitMeasurements::Unit: g (gram, gramme, grammes, grams)>, ..., ...]
|
251
|
+
```
|
252
|
+
|
253
|
+
**See names of all valid units of the unit group:**
|
254
|
+
|
255
|
+
```ruby
|
256
|
+
UnitMeasurements::Weight.unit_names
|
257
|
+
#=> ["g", "kg", "lb", "oz", ...]
|
258
|
+
```
|
259
|
+
|
260
|
+
**See all valid units of the unit group along with their aliases:**
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
UnitMeasurements::Weight.unit_names_with_aliases
|
264
|
+
#=> ["g", "gram", "gramme", "grammes", "grams", "kg", "kilogram", "kilogramme", "kilogrammes", "kilograms", "lb", "ounce", "ounces", "oz", "pound", "pounds", ...]
|
265
|
+
```
|
266
|
+
|
267
|
+
**Finding units within the unit group:**
|
268
|
+
|
269
|
+
You can use `#unit_for` or `#unit_for!` (aliased as `#[]`) to find units within
|
270
|
+
the unit group. `#unit_for!` method returns error if a unit is not present in the
|
271
|
+
unit group.
|
272
|
+
|
273
|
+
```ruby
|
274
|
+
UnitMeasurements::Weight.unit_for(:g)
|
275
|
+
#=> #<UnitMeasurements::Unit: g (gram, gramme, grammes, grams)>
|
276
|
+
UnitMeasurements::Weight.unit_for(:z)
|
277
|
+
#=> nil
|
278
|
+
UnitMeasurements::Weight.unit_for!(:g)
|
279
|
+
#=> #<UnitMeasurements::Unit: g (gram, gramme, grammes, grams)>
|
280
|
+
UnitMeasurements::Weight.unit_for!(:z)
|
281
|
+
#=> Invalid unit: 'z'. (UnitMeasurements::UnitError)
|
282
|
+
```
|
283
|
+
|
284
|
+
**Finding whether the unit is defined within the unit group:**
|
285
|
+
|
286
|
+
```ruby
|
287
|
+
UnitMeasurements::Weight.defined?(:g)
|
288
|
+
#=> true
|
289
|
+
UnitMeasurements::Weight.defined?(:kg)
|
290
|
+
#=> true
|
291
|
+
UnitMeasurements::Weight.defined?(:gramme)
|
292
|
+
#=> false
|
293
|
+
```
|
294
|
+
|
295
|
+
**Check if the unit is a valid unit or alias within the unit group:**
|
296
|
+
|
297
|
+
```ruby
|
298
|
+
UnitMeasurements::Weight.unit_or_alias?(:g)
|
299
|
+
#=> true
|
300
|
+
UnitMeasurements::Weight.unit_or_alias?(:kg)
|
301
|
+
#=> true
|
302
|
+
UnitMeasurements::Weight.unit_or_alias?(:gramme)
|
303
|
+
#=> true
|
304
|
+
```
|
305
|
+
|
306
|
+
## Units
|
307
|
+
|
308
|
+
The **`UnitMeasurements::Unit`** class is used to represent the units for a measurement.
|
309
|
+
|
310
|
+
### SI units support
|
311
|
+
|
312
|
+
There is support for SI units through the use of `si_unit` method.
|
313
|
+
Units declared through it will have automatic support for all SI prefixes:
|
314
|
+
|
315
|
+
| Multiplying Factor | SI Prefix | Scientific Notation |
|
316
|
+
| ----------------------------------------- | ---------- | ------------------- |
|
317
|
+
| 1 000 000 000 000 000 000 000 000 000 000 | quetta (Q) | 10^30 |
|
318
|
+
| 1 000 000 000 000 000 000 000 000 000 | ronna (R) | 10^27 |
|
319
|
+
| 1 000 000 000 000 000 000 000 000 | yotta (Y) | 10^24 |
|
320
|
+
| 1 000 000 000 000 000 000 000 | zetta (Z) | 10^21 |
|
321
|
+
| 1 000 000 000 000 000 000 | exa (E) | 10^18 |
|
322
|
+
| 1 000 000 000 000 000 | peta (P) | 10^15 |
|
323
|
+
| 1 000 000 000 000 | tera (T) | 10^12 |
|
324
|
+
| 1 000 000 000 | giga (G) | 10^9 |
|
325
|
+
| 1 000 000 | mega (M) | 10^6 |
|
326
|
+
| 1 000 | kilo (k) | 10^3 |
|
327
|
+
| 1 00 | hecto (h) | 10^2 |
|
328
|
+
| 1 0 | deca (da) | 10^1 |
|
329
|
+
| 0.1 | deci (d) | 10^-1 |
|
330
|
+
| 0.01 | centi (c) | 10^-2 |
|
331
|
+
| 0.001 | milli (m) | 10^-3 |
|
332
|
+
| 0.000 001 | micro (µ) | 10^-6 |
|
333
|
+
| 0.000 000 001 | nano (n) | 10^-9 |
|
334
|
+
| 0.000 000 000 001 | pico (p) | 10^-12 |
|
335
|
+
| 0.000 000 000 000 001 | femto (f) | 10^-15 |
|
336
|
+
| 0.000 000 000 000 000 001 | atto (a) | 10^-18 |
|
337
|
+
| 0.000 000 000 000 000 000 001 | zepto (z) | 10^-21 |
|
338
|
+
| 0.000 000 000 000 000 000 000 001 | yocto (y) | 10^-24 |
|
339
|
+
| 0.000 000 000 000 000 000 000 000 001 | ronto (r) | 10^-27 |
|
340
|
+
| 0.000 000 000 000 000 000 000 000 000 001 | quecto (q) | 10^-30 |
|
341
|
+
|
342
|
+
### Bundled units
|
343
|
+
|
344
|
+
There are tons of units that are bundled in `unit_measurements`. You can check them out [here](units.md).
|
345
|
+
|
346
|
+
### Specifing units
|
347
|
+
|
348
|
+
By default, `unit_measurements` ships with all the unit groups and this happens automatically
|
349
|
+
when requiring the gem in the following manner.
|
350
|
+
|
351
|
+
```ruby
|
352
|
+
require "unit_measurements"
|
353
|
+
```
|
354
|
+
|
355
|
+
**You can skip these unit groups and only [build your own unit groups](#building-new-unit-groups) by doing:**
|
356
|
+
|
357
|
+
```ruby
|
358
|
+
require "unit_measurements/base"
|
359
|
+
```
|
360
|
+
|
361
|
+
or simply
|
362
|
+
|
363
|
+
```ruby
|
364
|
+
gem "unit_measurements", require: "unit_measurements/base"
|
365
|
+
```
|
366
|
+
|
367
|
+
**You can also use unit groups in your application as per your need as:**
|
368
|
+
|
369
|
+
```ruby
|
370
|
+
require "unit_measurements/base"
|
371
|
+
|
372
|
+
require "unit_measurements/unit_groups/length"
|
373
|
+
require "unit_measurements/unit_groups/weight"
|
374
|
+
require "unit_measurements/unit_groups/volume"
|
375
|
+
```
|
376
|
+
|
377
|
+
or
|
378
|
+
|
379
|
+
```ruby
|
380
|
+
gem "unit_measurements", require: ["unit_measurements/base", "unit_measurements/unit_groups/length"]
|
381
|
+
```
|
382
|
+
|
383
|
+
### Building new unit groups
|
384
|
+
|
385
|
+
This library provides simpler way to build your own unit groups. To build new unit group,
|
386
|
+
use `UnitMeasurements.build` in order to define base units and conversion units within it:
|
387
|
+
|
388
|
+
If you build unit using `base` method, base unit automatically gets set for the unit group.
|
389
|
+
|
390
|
+
```ruby
|
391
|
+
UnitMeasurements::Time = UnitMeasurements.build do
|
392
|
+
# Add a base unit to the group.
|
393
|
+
base :s, aliases: [:second, :seconds]
|
394
|
+
|
395
|
+
# Add other units to the group, along with their conversion multipliers against
|
396
|
+
# base unit.
|
397
|
+
unit :min, value: 60.0, aliases: [:minute, :minutes]
|
398
|
+
|
399
|
+
# You can also specify conversion string if it's converted against a unit other
|
400
|
+
# than the unit group's base unit.
|
401
|
+
unit :h, value: "60 min", aliases: [:hour, :hours]
|
402
|
+
end
|
403
|
+
```
|
404
|
+
|
405
|
+
If the unit is supporting [si prefixes](#si-units-support), you can use `si_unit` method to build it.
|
406
|
+
If you build unit using `si_unit`, Base unit is automatically added to the group along with all SI prefixes for it.
|
407
|
+
|
408
|
+
```ruby
|
409
|
+
UnitMeasurements::Time = UnitMeasurements.build do
|
410
|
+
# Add a SI unit to the unit group
|
411
|
+
si_unit :s, aliases: [:second, :seconds]
|
412
|
+
|
413
|
+
unit :min, value: "60 s", aliases: [:minute, :minutes]
|
414
|
+
end
|
415
|
+
```
|
416
|
+
|
417
|
+
All units allow aliases, as long as they are unique. Unit symbol can be used to
|
418
|
+
define the unit as long as it is unique. All unit names are case sensitive.
|
419
|
+
|
420
|
+
### Namespaces
|
421
|
+
|
422
|
+
All unit groups and their definition classes are namespaced by default, but can be aliased in your application.
|
423
|
+
|
424
|
+
```ruby
|
425
|
+
Weight = UnitMeasurements::Weight
|
426
|
+
Length = UnitMeasurements::Length
|
427
|
+
Volume = UnitMeasurements::Volume
|
428
|
+
```
|
429
|
+
|
430
|
+
## Contributing
|
431
|
+
|
432
|
+
1. Fork it
|
433
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
434
|
+
3. Commit your changes (`git commit -am "Add some feature"`)
|
435
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
436
|
+
5. Create new Pull Request
|
437
|
+
|
438
|
+
## License
|
439
|
+
|
440
|
+
Copyright 2023 [Harshal V. LADHE](https://github.com/shivam091), Released under the [MIT License](http://opensource.org/licenses/MIT).
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# -*- frozen_string_literal: true -*-
|
3
|
+
# -*- warn_indent: true -*-
|
4
|
+
|
5
|
+
require "active_support/all"
|
6
|
+
require "unit_measurements/version"
|
7
|
+
|
8
|
+
module UnitMeasurements
|
9
|
+
class << self
|
10
|
+
def build(&block)
|
11
|
+
builder = UnitGroupBuilder.new
|
12
|
+
builder.instance_eval(&block)
|
13
|
+
|
14
|
+
Class.new(Measurement) do
|
15
|
+
class << self
|
16
|
+
attr_reader :unit_group
|
17
|
+
end
|
18
|
+
|
19
|
+
@unit_group = builder.build
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
require "unit_measurements/unit_group_builder"
|
26
|
+
require "unit_measurements/unit"
|
27
|
+
require "unit_measurements/unit_group"
|
28
|
+
require "unit_measurements/normalizer"
|
29
|
+
require "unit_measurements/parser"
|
30
|
+
require "unit_measurements/formatter"
|
31
|
+
require "unit_measurements/measurement"
|
32
|
+
|
33
|
+
require "unit_measurements/errors/unit_error"
|
34
|
+
require "unit_measurements/errors/unit_already_defined_error"
|
35
|
+
require "unit_measurements/errors/parse_error"
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# -*- frozen_string_literal: true -*-
|
3
|
+
# -*- warn_indent: true -*-
|
4
|
+
|
5
|
+
module UnitMeasurements
|
6
|
+
class ParseError < BaseError
|
7
|
+
attr_reader :string
|
8
|
+
|
9
|
+
def initialize(string)
|
10
|
+
@string = string
|
11
|
+
super("Unable to parse: '#{string}'.")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# -*- frozen_string_literal: true -*-
|
3
|
+
# -*- warn_indent: true -*-
|
4
|
+
|
5
|
+
module UnitMeasurements
|
6
|
+
class UnitAlreadyDefinedError < BaseError
|
7
|
+
attr_reader :unit
|
8
|
+
|
9
|
+
def initialize(unit)
|
10
|
+
@unit = unit
|
11
|
+
super("Unit already defined: '#{unit}'.")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# -*- frozen_string_literal: true -*-
|
3
|
+
# -*- warn_indent: true -*-
|
4
|
+
|
5
|
+
module UnitMeasurements
|
6
|
+
class UnitError < BaseError
|
7
|
+
attr_reader :unit
|
8
|
+
|
9
|
+
def initialize(unit)
|
10
|
+
@unit = unit
|
11
|
+
super("Invalid unit: '#{unit}'.")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# -*- frozen_string_literal: true -*-
|
3
|
+
# -*- warn_indent: true -*-
|
4
|
+
|
5
|
+
module UnitMeasurements
|
6
|
+
module Formatter
|
7
|
+
# The default format for formatting measurements.
|
8
|
+
DEFAULT_FORMAT = "%.2<quantity>f %<unit>s".freeze
|
9
|
+
|
10
|
+
# Formats measurement to certain formatted string specified by +format+.
|
11
|
+
# If +format+ is not specified, it uses +DEFAULT_FORMAT+ for
|
12
|
+
# formatting the measurement
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# UnitMeasurements::Weight.parse("2 kg").to(:st).format
|
16
|
+
# => "0.31 st"
|
17
|
+
# UnitMeasurements::Weight.parse("2 kg").to(:st).format("%.4<quantity>f %<unit>s")
|
18
|
+
# => "0.3149 st"
|
19
|
+
#
|
20
|
+
# @param [String] format
|
21
|
+
#
|
22
|
+
# @return [String]
|
23
|
+
def format(format = nil)
|
24
|
+
kwargs = {quantity: quantity, unit: unit.to_s}
|
25
|
+
(format || DEFAULT_FORMAT) % kwargs
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# -*- frozen_string_literal: true -*-
|
3
|
+
# -*- warn_indent: true -*-
|
4
|
+
|
5
|
+
module UnitMeasurements
|
6
|
+
class Measurement
|
7
|
+
include Formatter
|
8
|
+
CONVERSION_STRING_REGEXP = /(.+?)\s?(?:\s+(?:in|to|as)\s+(.+)|\z)/i.freeze
|
9
|
+
|
10
|
+
attr_reader :quantity, :unit
|
11
|
+
|
12
|
+
def initialize(quantity, unit)
|
13
|
+
raise BaseError, "Quantity cannot be blank." if quantity.blank?
|
14
|
+
raise BaseError, "Unit cannot be blank." if unit.blank?
|
15
|
+
|
16
|
+
@quantity = convert_quantity(quantity)
|
17
|
+
@unit = unit_from_unit_or_name!(unit)
|
18
|
+
end
|
19
|
+
|
20
|
+
def convert_to(target_unit)
|
21
|
+
target_unit = unit_from_unit_or_name!(target_unit)
|
22
|
+
|
23
|
+
return self if target_unit == unit
|
24
|
+
|
25
|
+
conversion_factor = (unit.conversion_factor / target_unit.conversion_factor)
|
26
|
+
self.class.new((quantity * conversion_factor), target_unit)
|
27
|
+
end
|
28
|
+
alias_method :to, :convert_to
|
29
|
+
|
30
|
+
def convert_to!(target_unit)
|
31
|
+
measurement = convert_to(target_unit)
|
32
|
+
@quantity, @unit = measurement.quantity, measurement.unit
|
33
|
+
self
|
34
|
+
end
|
35
|
+
alias_method :to!, :convert_to!
|
36
|
+
|
37
|
+
def inspect(dump: false)
|
38
|
+
return super() if dump
|
39
|
+
|
40
|
+
to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
"#{humanized_quantity} #{unit.to_s}"
|
45
|
+
end
|
46
|
+
|
47
|
+
class << self
|
48
|
+
extend Forwardable
|
49
|
+
|
50
|
+
def unit_group
|
51
|
+
raise "`Measurement` does not have a `unit_group` object. You cannot directly use `Measurement`. Instead, build a new unit group by calling `UnitMeasurements.build`."
|
52
|
+
end
|
53
|
+
|
54
|
+
def_delegators :unit_group, :units, :unit_names, :unit_with_name_and_aliases,
|
55
|
+
:unit_names_with_aliases, :unit_for, :unit_for!, :defined?,
|
56
|
+
:unit_or_alias?, :[]
|
57
|
+
|
58
|
+
def parse(input)
|
59
|
+
input = Normalizer.normalize(input)
|
60
|
+
source, target = input.match(CONVERSION_STRING_REGEXP)&.captures
|
61
|
+
target ? _parse(source).convert_to(target) : _parse(source)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def _parse(string)
|
67
|
+
quantity, unit = Parser.parse(string)
|
68
|
+
new(quantity, unit)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def convert_quantity(quantity)
|
75
|
+
case quantity
|
76
|
+
when Float
|
77
|
+
BigDecimal(quantity, Float::DIG)
|
78
|
+
when Integer
|
79
|
+
Rational(quantity)
|
80
|
+
when String
|
81
|
+
quantity = Normalizer.normalize(quantity)
|
82
|
+
quantity, _ = Parser.parse(quantity)
|
83
|
+
quantity
|
84
|
+
else
|
85
|
+
quantity
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def unit_from_unit_or_name!(value)
|
90
|
+
value.is_a?(Unit) ? value : self.class.unit_group.unit_for!(value)
|
91
|
+
end
|
92
|
+
|
93
|
+
def humanized_quantity
|
94
|
+
case quantity
|
95
|
+
when Complex
|
96
|
+
quantity
|
97
|
+
when Numeric
|
98
|
+
num = quantity.to_r
|
99
|
+
num.denominator == 1 ? num.numerator.to_s : num.to_f.to_s
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# -*- frozen_stringing_literal: true -*-
|
3
|
+
# -*- warn_indent: true -*-
|
4
|
+
|
5
|
+
module UnitMeasurements
|
6
|
+
class Normalizer
|
7
|
+
FRACTIONS_SYMBOLS = {
|
8
|
+
"¼" => "1/4",
|
9
|
+
"½" => "1/2",
|
10
|
+
"¾" => "3/4",
|
11
|
+
"⅓" => "1/3",
|
12
|
+
"⅔" => "2/3",
|
13
|
+
"⅕" => "1/5",
|
14
|
+
"⅖" => "2/5",
|
15
|
+
"⅗" => "3/5",
|
16
|
+
"⅘" => "4/5",
|
17
|
+
"⅙" => "1/6",
|
18
|
+
"⅚" => "5/6",
|
19
|
+
"⅐" => "1/7",
|
20
|
+
"⅛" => "1/8",
|
21
|
+
"⅜" => "3/8",
|
22
|
+
"⅝" => "5/8",
|
23
|
+
"⅞" => "7/8",
|
24
|
+
"⅑" => "1/9",
|
25
|
+
"⅒" => "1/10",
|
26
|
+
"↉" => "0/3",
|
27
|
+
"⁄" => "/"
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
EXPONENTS_SYMBOLS = {
|
31
|
+
"⁰" => "0",
|
32
|
+
"¹" => "1",
|
33
|
+
"²" => "2",
|
34
|
+
"³" => "3",
|
35
|
+
"⁴" => "4",
|
36
|
+
"⁵" => "5",
|
37
|
+
"⁶" => "6",
|
38
|
+
"⁷" => "7",
|
39
|
+
"⁸" => "8",
|
40
|
+
"⁹" => "9",
|
41
|
+
"⁺" => "+",
|
42
|
+
"⁻" => "-",
|
43
|
+
}
|
44
|
+
|
45
|
+
FRACTION_REGEX = /(#{FRACTIONS_SYMBOLS.keys.join("|")})/
|
46
|
+
EXPONENT_REGEX = /([\d]+[Ee]?[+-]?)(#{EXPONENTS_SYMBOLS.keys.join("|")})/
|
47
|
+
RATIO_REGEX = /([\d]+):([\d]+)/
|
48
|
+
|
49
|
+
class << self
|
50
|
+
def normalize(string)
|
51
|
+
string.dup.tap do |str|
|
52
|
+
if str =~ Regexp.new(FRACTION_REGEX)
|
53
|
+
FRACTIONS_SYMBOLS.each do |search, replace|
|
54
|
+
str.gsub!(search) { " #{replace}" }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
if str =~ Regexp.new(EXPONENT_REGEX)
|
59
|
+
EXPONENTS_SYMBOLS.each do |search, replace|
|
60
|
+
str.gsub!(search) { "#{replace}" }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if str =~ Regexp.new(RATIO_REGEX)
|
65
|
+
str.gsub!(RATIO_REGEX) { "#{$1.to_i}/#{$2.to_i}" }
|
66
|
+
end
|
67
|
+
|
68
|
+
str.strip!
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# -*- frozen_stringing_literal: true -*-
|
3
|
+
# -*- warn_indent: true -*-
|
4
|
+
|
5
|
+
module UnitMeasurements
|
6
|
+
class Parser
|
7
|
+
UNIT_REGEX = /([^\d\s\/].*)/.freeze
|
8
|
+
|
9
|
+
SCIENTIFIC_NUMBER = /([+-]?\d*\.?\d+(?:[Ee][+-]?)?\d*)/.freeze
|
10
|
+
RATIONAL_NUMBER = /([+-]?\d+\s+)?((\d+)\/(\d+))/.freeze
|
11
|
+
COMPLEX_NUMBER = /#{SCIENTIFIC_NUMBER}#{SCIENTIFIC_NUMBER}i/.freeze
|
12
|
+
|
13
|
+
SCIENTIFIC_REGEX = /\A#{SCIENTIFIC_NUMBER}\s*#{UNIT_REGEX}?\z/.freeze
|
14
|
+
RATIONAL_REGEX = /\A#{RATIONAL_NUMBER}\s*#{UNIT_REGEX}?\z/.freeze
|
15
|
+
COMPLEX_REGEX = /\A#{COMPLEX_NUMBER}\s*#{UNIT_REGEX}?\z/.freeze
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def parse(string)
|
19
|
+
case string
|
20
|
+
when COMPLEX_REGEX then parse_complex(string)
|
21
|
+
when SCIENTIFIC_REGEX then parse_scientific(string)
|
22
|
+
when RATIONAL_REGEX then parse_rational(string)
|
23
|
+
else raise ParseError, string
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def parse_complex(string)
|
30
|
+
real, imaginary, unit = string.match(COMPLEX_REGEX)&.captures
|
31
|
+
quantity = Complex(real.to_f, imaginary.to_f)
|
32
|
+
|
33
|
+
[quantity, unit]
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse_scientific(string)
|
37
|
+
whole, unit = string.match(SCIENTIFIC_REGEX)&.captures
|
38
|
+
quantity = whole.to_f
|
39
|
+
|
40
|
+
[quantity, unit]
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse_rational(string)
|
44
|
+
whole, _, numerator, denominator, unit = string.match(RATIONAL_REGEX)&.captures
|
45
|
+
|
46
|
+
if numerator && denominator
|
47
|
+
numerator = numerator.to_f + (denominator.to_f * whole.to_f)
|
48
|
+
denominator = denominator.to_f
|
49
|
+
quantity = Rational(numerator, denominator).to_f
|
50
|
+
else
|
51
|
+
quantity = whole.to_f
|
52
|
+
end
|
53
|
+
|
54
|
+
[quantity, unit]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# -*- frozen_string_literal: true -*-
|
3
|
+
# -*- warn_indent: true -*-
|
4
|
+
|
5
|
+
require "set"
|
6
|
+
|
7
|
+
module UnitMeasurements
|
8
|
+
class Unit
|
9
|
+
attr_reader :name, :value, :aliases, :unit_group
|
10
|
+
|
11
|
+
def initialize(name, value:, aliases:, unit_group: nil)
|
12
|
+
@name = name.to_s.freeze
|
13
|
+
@value = value
|
14
|
+
@aliases = Set.new(aliases.sort.map(&:to_s).map(&:freeze)).freeze
|
15
|
+
@unit_group = unit_group
|
16
|
+
end
|
17
|
+
|
18
|
+
def with(name: nil, value: nil, aliases: nil, unit_group: nil)
|
19
|
+
self.class.new(
|
20
|
+
(name || self.name),
|
21
|
+
value: (value || self.value),
|
22
|
+
aliases: (aliases || self.aliases),
|
23
|
+
unit_group: (unit_group || self.unit_group)
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def names
|
28
|
+
(aliases + [name]).sort.freeze
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
name
|
33
|
+
end
|
34
|
+
|
35
|
+
def inspect
|
36
|
+
aliases = "(#{@aliases.join(", ")})" if @aliases.any?
|
37
|
+
"#<#{self.class.name}: #{name} #{aliases}>"
|
38
|
+
end
|
39
|
+
|
40
|
+
def conversion_factor
|
41
|
+
if value.is_a?(String)
|
42
|
+
measurement_value, measurement_unit = Parser.parse(value)
|
43
|
+
conversion_factor = unit_group.unit_for!(measurement_unit).conversion_factor
|
44
|
+
conversion_factor * measurement_value
|
45
|
+
else
|
46
|
+
value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
SI_PREFIXES = [
|
53
|
+
["q", %w(quecto), 1e-30],
|
54
|
+
["r", %w(ronto), 1e-27],
|
55
|
+
["y", %w(yocto), 1e-24],
|
56
|
+
["z", %w(zepto), 1e-21],
|
57
|
+
["a", %w(atto), 1e-18],
|
58
|
+
["f", %w(femto), 1e-15],
|
59
|
+
["p", %w(pico), 1e-12],
|
60
|
+
["n", %w(nano), 1e-9],
|
61
|
+
["μ", %w(micro), 1e-6],
|
62
|
+
["m", %w(milli), 1e-3],
|
63
|
+
["c", %w(centi), 1e-2],
|
64
|
+
["d", %w(deci), 1e-1],
|
65
|
+
["da", %w(deca deka), 1e+1],
|
66
|
+
["h", %w(hecto), 1e+2],
|
67
|
+
["k", %w(kilo), 1e+3],
|
68
|
+
["M", %w(mega), 1e+6],
|
69
|
+
["G", %w(giga), 1e+9],
|
70
|
+
["T", %w(tera), 1e+12],
|
71
|
+
["P", %w(peta), 1e+15],
|
72
|
+
["E", %w(exa), 1e+18],
|
73
|
+
["Z", %w(zetta), 1e+21],
|
74
|
+
["Y", %w(yotta), 1e+24],
|
75
|
+
["R", %w(ronna), 1e+27],
|
76
|
+
["Q", %w(quetta), 1e+30]
|
77
|
+
].map(&:freeze).freeze
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# -*- frozen_string_literal: true -*-
|
3
|
+
# -*- warn_indent: true -*-
|
4
|
+
|
5
|
+
module UnitMeasurements
|
6
|
+
class UnitGroup
|
7
|
+
attr_reader :units
|
8
|
+
|
9
|
+
def initialize(units)
|
10
|
+
@units = units.map { |unit| unit.with(unit_group: self) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def unit_for(name)
|
14
|
+
unit_name_to_unit(name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def unit_for!(name)
|
18
|
+
unit = unit_for(name)
|
19
|
+
raise UnitError, name unless unit
|
20
|
+
unit
|
21
|
+
end
|
22
|
+
alias_method :[], :unit_for!
|
23
|
+
|
24
|
+
def unit_with_name_and_aliases
|
25
|
+
units.each_with_object({}) do |unit, hash|
|
26
|
+
unit.names.each { |name| hash[name.to_s] = unit }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def unit_names
|
31
|
+
units.map(&:name).sort
|
32
|
+
end
|
33
|
+
|
34
|
+
def unit_names_with_aliases
|
35
|
+
units.flat_map(&:names).sort
|
36
|
+
end
|
37
|
+
|
38
|
+
def unit_name_to_unit(name)
|
39
|
+
unit_with_name_and_aliases[name.to_s]
|
40
|
+
end
|
41
|
+
|
42
|
+
def defined?(name)
|
43
|
+
unit = unit_for(name)
|
44
|
+
unit ? unit.name == name.to_s : false
|
45
|
+
end
|
46
|
+
|
47
|
+
def unit_or_alias?(name)
|
48
|
+
!!unit_for(name)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# -*- frozen_string_literal: true -*-
|
3
|
+
# -*- warn_indent: true -*-
|
4
|
+
|
5
|
+
module UnitMeasurements
|
6
|
+
class UnitGroupBuilder
|
7
|
+
attr_reader :units
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@units = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def base(name, aliases: [])
|
14
|
+
@units << build_unit(name, value: 1.0, aliases: aliases)
|
15
|
+
end
|
16
|
+
|
17
|
+
def unit(name, value: 1.0, aliases: [])
|
18
|
+
@units << build_unit(name, value: value, aliases: aliases)
|
19
|
+
end
|
20
|
+
|
21
|
+
def si_unit(name, value: 1.0, aliases: [])
|
22
|
+
@units += build_si_units(name, value: value, aliases: aliases)
|
23
|
+
end
|
24
|
+
|
25
|
+
def build
|
26
|
+
UnitGroup.new(@units)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def build_si_units(name, value:, aliases:)
|
32
|
+
si_units = [build_unit(name, value: value, aliases: aliases)]
|
33
|
+
|
34
|
+
Unit::SI_PREFIXES.each do |short_prefix, long_prefix, multiplier|
|
35
|
+
si_aliases = long_prefix.product(aliases.to_a).flat_map do |prefix, unit|
|
36
|
+
aliases.map { |alias_unit| prefix + alias_unit.to_s }
|
37
|
+
end
|
38
|
+
si_units << build_unit("#{short_prefix}#{name}", value: "#{multiplier} #{name}", aliases: si_aliases)
|
39
|
+
end
|
40
|
+
si_units
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_unit(name, value:, aliases:)
|
44
|
+
unit = Unit.new(name, value: value, aliases: aliases)
|
45
|
+
check_for_duplicate_unit_names!(unit)
|
46
|
+
unit
|
47
|
+
end
|
48
|
+
|
49
|
+
def check_for_duplicate_unit_names!(unit)
|
50
|
+
names = @units.flat_map(&:names)
|
51
|
+
if names.any? { |name| unit.names.include?(name) }
|
52
|
+
raise UnitAlreadyDefinedError.new(unit.name)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# -*- frozen_string_literal: true -*-
|
3
|
+
# -*- warn_indent: true -*-
|
4
|
+
|
5
|
+
UnitMeasurements::Length = UnitMeasurements.build do
|
6
|
+
si_unit :m, aliases: %i[meter metre meters metres]
|
7
|
+
|
8
|
+
unit :in, value: "25.4 mm", aliases: %i[" inch inches]
|
9
|
+
unit :ft, value: "12 in", aliases: %i[' foot feet]
|
10
|
+
unit :yd, value: "3 ft", aliases: %i[yard yards]
|
11
|
+
unit :mi, value: "1760 yd", aliases: %i[mile miles]
|
12
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# -*- frozen_string_literal: true -*-
|
3
|
+
# -*- warn_indent: true -*-
|
4
|
+
|
5
|
+
UnitMeasurements::Weight = UnitMeasurements.build do
|
6
|
+
si_unit :g, aliases: %i[gram grams gramme grammes]
|
7
|
+
|
8
|
+
unit :q, value: "100 kg", aliases: %i[quintal quintals]
|
9
|
+
unit :t, value: "10 q", aliases: %i[tonne tonnes metric\ tonne metric\ tonnes]
|
10
|
+
end
|
data/lib/unit_measurements.rb
CHANGED
data/units.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# Bundled unit groups and units
|
2
|
+
|
3
|
+
As there are lots of units bundled with `unit_measurements`, we recommend you to check below list of
|
4
|
+
bundled units before converting your measurements.
|
5
|
+
|
6
|
+
**Notes:**
|
7
|
+
1. Base unit for each unit group is highlighted.
|
8
|
+
2. Unit numbers suffixed with `*` support all [SI prefixes](README.md#si-units-support).
|
9
|
+
|
10
|
+
Below are the units which are bundled in the unit_measurements.
|
11
|
+
|
12
|
+
## 1. Length/Distance
|
13
|
+
|
14
|
+
These units are defined in `UnitMeasurements::Length`.
|
15
|
+
|
16
|
+
| # | Symbol | Aliases |
|
17
|
+
|:--|:--|:--|
|
18
|
+
| **1\*** | **m** | **meter, metre, meters, metres** |
|
19
|
+
| 2 | in | ", inch, inches |
|
20
|
+
| 3 | ft | ', foot, feet |
|
21
|
+
| 4 | yd | yard, yards |
|
22
|
+
| 5 | mi | mile, miles |
|
23
|
+
|
24
|
+
## 2. Weight/Mass
|
25
|
+
|
26
|
+
These units are defined in `UnitMeasurements::Weight`.
|
27
|
+
|
28
|
+
| # | Symbol | Aliases |
|
29
|
+
|--|--|--|
|
30
|
+
| **1\*** | **g** | **gram, grams, gramme, grammes** |
|
31
|
+
| 2 | q | quintal, quintals |
|
32
|
+
| 3 | t | tonne, tonnes, metric tonne, metric tonnes |
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unit_measurements
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Harshal LADHE
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-09-
|
11
|
+
date: 2023-09-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -100,10 +100,26 @@ files:
|
|
100
100
|
- Gemfile
|
101
101
|
- Gemfile.lock
|
102
102
|
- LICENSE
|
103
|
+
- README.md
|
103
104
|
- Rakefile
|
104
105
|
- lib/unit_measurements.rb
|
106
|
+
- lib/unit_measurements/base.rb
|
107
|
+
- lib/unit_measurements/errors/parse_error.rb
|
108
|
+
- lib/unit_measurements/errors/unit_already_defined_error.rb
|
109
|
+
- lib/unit_measurements/errors/unit_error.rb
|
110
|
+
- lib/unit_measurements/formatter.rb
|
111
|
+
- lib/unit_measurements/measurement.rb
|
112
|
+
- lib/unit_measurements/normalizer.rb
|
113
|
+
- lib/unit_measurements/parser.rb
|
114
|
+
- lib/unit_measurements/unit.rb
|
115
|
+
- lib/unit_measurements/unit_group.rb
|
116
|
+
- lib/unit_measurements/unit_group_builder.rb
|
117
|
+
- lib/unit_measurements/unit_groups/all.rb
|
118
|
+
- lib/unit_measurements/unit_groups/length.rb
|
119
|
+
- lib/unit_measurements/unit_groups/weight.rb
|
105
120
|
- lib/unit_measurements/version.rb
|
106
121
|
- unit_measurements.gemspec
|
122
|
+
- units.md
|
107
123
|
homepage: https://github.com/shivam091/unit_measurements
|
108
124
|
licenses:
|
109
125
|
- MIT
|