timeprice 0.1.0 → 0.1.2

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: 8b1b427948a0943958a53ce1820f72eb27b7dc41de61c9e216194aae9c03183d
4
- data.tar.gz: 5daac2f9428564420379cc33db7893a8b9dbf0f0344355c1439636e8abf647a7
3
+ metadata.gz: b0ebaf1340f0abefa6b4dcae31f7d2f2d22021155d24ebc258f15b0495f7a480
4
+ data.tar.gz: 6068762094c6008c72f4dd7becb10e1e8cb0d0c17b668234e8e0dff84c494cc2
5
5
  SHA512:
6
- metadata.gz: 14ec61a1a5cc71b33fb0de18bbbfc19ec152697d4f8ddcd2f14cf2d739e80571512ccb067d939f60207c0b2abc2971a03f32272cd2468d273782a1022d9d0dbb
7
- data.tar.gz: 5d7fae9eb6583d38f2245830aa66a0ce61fec10aa02cdec600005aea185635d0312c80a7d3bec62f0067a42651839a2af4de4d3c67335b1eb2a649dfe35b2419
6
+ metadata.gz: fa08a556599384d4ae292064b2342bfdc6976eac3fea784afb7426d623fdd0c8ee43001135bd2f7c3a065e735db8dd6d347459144e44ee702f30505ee23c3c1f
7
+ data.tar.gz: 9fd49be55c8790b4a1adfd9350fdd2e6eed82cdcbf8e41c2dd3b1e1514e5b81f9ffa71dc0a9b530738ac2f2d6fa22e969c240bd81dac043e4a4b8e23b21b6924
data/CHANGELOG.md CHANGED
@@ -5,6 +5,30 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.1.2] - 2026-05-11
9
+
10
+ ### Added
11
+ - RuboCop with `rubocop-rake` + `rubocop-rspec`, wired into Rake (`rake default` runs spec + rubocop) and CI (separate `RuboCop` job alongside `RSpec`).
12
+
13
+ ### Changed
14
+ - `DataLoader.load_cpi` now distinguishes between "country isn't supported" (`UnsupportedCountry`) and "data file is missing on disk" (`DataNotFound` with the path the loader looked at). Previously both surfaced as `UnsupportedCountry`, masking install / `TIMEPRICE_DATA_ROOT` misconfigurations.
15
+ - `Timeprice.exchange` now rejects invalid calendar dates (e.g. `2021-02-29`) with `ArgumentError` instead of leaking a `Date::Error`. Honors the public error contract.
16
+ - Trimmed `ZERO_DECIMAL_CURRENCIES` to currencies actually supported by the gem (JPY, VND). Removed aspirational entries (KRW, IDR, HUF, CLP).
17
+ - Inline source comments now reference README sections (`README.md "Compare semantics"`) instead of `PLAN.md` (which is intentionally not shipped in the gem).
18
+ - `CONTRIBUTING.md` updated to match the single-Ruby CI; both `rspec` and `rubocop` must be green.
19
+
20
+
21
+
22
+ ### Changed
23
+ - CLI output formatting: currency-aware decimals (no `.0000` on JPY/VND), magnitude-aware FX rate precision (no `91.180000` for a 91.18 rate).
24
+ - `granularity` is omitted from human output when it's `monthly` (the happy path); surfaced only when the result used annual data.
25
+ - Error messages hint at supported values: `Unsupported country: "FR" (supported: US, UK, EU, JP, VN)`; out-of-range CPI errors include the actual coverage range.
26
+ - Validate FX currencies against the supported list up front instead of failing with a generic "no FX rate" message.
27
+ - Tightened CLI command descriptions so `timeprice help` fits in a standard terminal.
28
+
29
+ ### Fixed
30
+ - Hide Thor's built-in `tree` command — it was leaking into `timeprice help` as an internal-looking debug command.
31
+
8
32
  ## [0.1.0] - 2026-05-11
9
33
 
10
34
  ### Added
data/lib/timeprice/cli.rb CHANGED
@@ -6,15 +6,23 @@ require_relative "../timeprice"
6
6
 
7
7
  module Timeprice
8
8
  class CLI < Thor
9
+ # Thor 1.5 ships a built-in `tree` command on every subclass. Strip it
10
+ # from this subclass — it's an internal debugging aid that leaks into
11
+ # our help output. all_commands inherits from the base Thor class, so
12
+ # filter it on read.
13
+ def self.all_commands
14
+ super.except("tree")
15
+ end
16
+
9
17
  class_option :json, type: :boolean, default: false, desc: "Output result as JSON"
10
18
 
11
19
  def self.exit_on_failure?
12
20
  true
13
21
  end
14
22
 
15
- desc "inflation AMOUNT", "Adjust AMOUNT for inflation between two dates"
16
- method_option :from, type: :string, required: true, desc: "Source date (YYYY or YYYY-MM)"
17
- method_option :to, type: :string, required: true, desc: "Target date (YYYY or YYYY-MM)"
23
+ desc "inflation AMOUNT", "Inflation-adjust an amount between two dates"
24
+ method_option :from, type: :string, required: true, desc: "Source date (YYYY or YYYY-MM)"
25
+ method_option :to, type: :string, required: true, desc: "Target date (YYYY or YYYY-MM)"
18
26
  method_option :country, type: :string, required: true, desc: "Country code (US, UK, EU, JP, VN)"
19
27
  def inflation(amount)
20
28
  with_error_handling do
@@ -28,7 +36,7 @@ module Timeprice
28
36
  end
29
37
  end
30
38
 
31
- desc "fx AMOUNT FROM_CURRENCY TO_CURRENCY", "Convert AMOUNT between currencies on a given date"
39
+ desc "fx AMOUNT FROM TO", "Convert an amount between currencies on a date"
32
40
  method_option :date, type: :string, required: true, desc: "Date (YYYY-MM-DD)"
33
41
  def fx(amount, from_currency, to_currency)
34
42
  with_error_handling do
@@ -58,14 +66,14 @@ module Timeprice
58
66
  end
59
67
  end
60
68
 
61
- desc "sources", "List bundled data sources, licenses, attribution, and coverage"
69
+ desc "sources", "List bundled data sources and coverage"
62
70
  def sources
63
71
  list = Timeprice::Sources.list
64
72
  if options[:json]
65
73
  say JSON.generate(list)
66
74
  else
67
75
  list.each do |s|
68
- say "#{s[:name]}"
76
+ say s[:name].to_s
69
77
  say " id: #{s[:id]}"
70
78
  say " license: #{s[:license]}"
71
79
  say " license_url: #{s[:license_url]}"
@@ -86,21 +94,22 @@ module Timeprice
86
94
  end
87
95
 
88
96
  no_commands do
97
+ # Currencies with no minor unit — render whole numbers, no decimals.
98
+ ZERO_DECIMAL_CURRENCIES = %w[JPY VND].freeze
99
+
89
100
  def with_error_handling
90
101
  yield
91
102
  rescue Timeprice::Error => e
92
103
  warn "Error: #{e.message}"
93
104
  exit 1
94
105
  rescue ArgumentError => e
95
- # Bad numeric/date format from Float() or library parsers — treat as user error.
96
106
  warn "Error: #{e.message}"
97
107
  exit 1
98
108
  end
99
109
 
100
- # Accepts "1995 USD" or "USD 1995" — order-agnostic.
101
- # Returns [currency, year_string] tuple matching Timeprice.compare's API.
102
110
  def parse_compare_token(token, label:)
103
111
  raise ArgumentError, "#{label} is required" if token.nil? || token.strip.empty?
112
+
104
113
  parts = token.strip.split(/\s+/)
105
114
  unless parts.size == 2
106
115
  raise ArgumentError,
@@ -115,15 +124,39 @@ module Timeprice
115
124
  [currency.upcase, year]
116
125
  end
117
126
 
127
+ def fmt_money(amount, currency)
128
+ decimals = ZERO_DECIMAL_CURRENCIES.include?(currency.to_s.upcase) ? 0 : 2
129
+ format("%.#{decimals}f", amount)
130
+ end
131
+
132
+ def fmt_rate(rate)
133
+ abs = rate.to_f.abs
134
+ decimals = if abs >= 1000 then 0
135
+ elsif abs >= 100 then 2
136
+ elsif abs >= 10 then 3
137
+ else 4
138
+ end
139
+ format("%.#{decimals}f", rate)
140
+ end
141
+
142
+ # Granularity is loud noise on the happy path. Only surface it when the
143
+ # answer actually used annual data — that's where users want a heads-up.
144
+ def granularity_suffix(granularity)
145
+ return "" if granularity == :monthly
146
+
147
+ " (granularity: #{granularity})"
148
+ end
149
+
118
150
  def emit_inflation(result)
119
151
  if options[:json]
120
152
  say JSON.generate(result.to_h)
121
153
  else
154
+ ccy = result.country_currency_label
122
155
  say format(
123
- "%.2f %s in %s is %.2f %s in %s (%s, granularity: %s)",
124
- result.original_amount, result.country_currency_label,
125
- result.from, result.amount, result.country_currency_label,
126
- result.to, result.country, result.granularity
156
+ "%s %s in %s is %s %s in %s [%s]%s",
157
+ fmt_money(result.original_amount, ccy), ccy, result.from,
158
+ fmt_money(result.amount, ccy), ccy, result.to,
159
+ result.country, granularity_suffix(result.granularity)
127
160
  )
128
161
  end
129
162
  end
@@ -133,12 +166,12 @@ module Timeprice
133
166
  say JSON.generate(result.to_h)
134
167
  else
135
168
  line = format(
136
- "%.2f %s on %s = %.2f %s (rate: %.4f)",
137
- result.original_amount, result.from, result.date,
138
- result.amount, result.to, result.rate
169
+ "%s %s on %s = %s %s (rate: %s)",
170
+ fmt_money(result.original_amount, result.from), result.from, result.date,
171
+ fmt_money(result.amount, result.to), result.to, fmt_rate(result.rate)
139
172
  )
140
173
  if result.effective_date && result.effective_date != result.date
141
- line += " (effective date: #{result.effective_date} fallback)"
174
+ line += " [effective: #{result.effective_date}, fallback]"
142
175
  end
143
176
  say line
144
177
  end
@@ -149,14 +182,16 @@ module Timeprice
149
182
  say JSON.generate(result.to_h)
150
183
  else
151
184
  say format(
152
- "%.2f %s in %s -> %.2f %s in %s",
153
- result.original_amount, result.from_currency, result.from_date,
154
- result.amount, result.to_currency, result.to_date
185
+ "%s %s in %s -> %s %s in %s",
186
+ fmt_money(result.original_amount, result.from_currency), result.from_currency, result.from_date,
187
+ fmt_money(result.amount, result.to_currency), result.to_currency, result.to_date
155
188
  )
156
189
  say format(
157
- " steps: convert at %s (fx rate %.6f) -> %.4f %s, then inflate in %s (cpi ratio %.6f, granularity: %s)",
158
- result.from_date, result.fx_rate, result.converted_amount,
159
- result.to_currency, result.country, result.cpi_ratio, result.granularity
190
+ " steps: %s %s -> %s %s (fx %s on %s), then inflate in %s x%.4f%s",
191
+ fmt_money(result.original_amount, result.from_currency), result.from_currency,
192
+ fmt_money(result.converted_amount, result.to_currency), result.to_currency,
193
+ fmt_rate(result.fx_rate), result.from_date,
194
+ result.country, result.cpi_ratio, granularity_suffix(result.granularity)
160
195
  )
161
196
  end
162
197
  end
@@ -16,7 +16,7 @@ module Timeprice
16
16
  # Compare combines FX and inflation across two (currency, date) points.
17
17
  #
18
18
  # CONVENTION (critical): convert at SOURCE date first, then inflate in
19
- # destination currency. See PLAN.md §2 last bullet and §7.
19
+ # destination currency. See README.md "Compare semantics" section.
20
20
  #
21
21
  # This preserves purchasing-power equivalence in the destination economy.
22
22
  # The naive alternative (inflate in source currency first, then convert at
@@ -32,7 +32,7 @@ module Timeprice
32
32
  "GBP" => "UK",
33
33
  "EUR" => "EU",
34
34
  "JPY" => "JP",
35
- "VND" => "VN"
35
+ "VND" => "VN",
36
36
  }.freeze
37
37
 
38
38
  module_function
@@ -78,7 +78,7 @@ module Timeprice
78
78
  to_date: to_date.to_s,
79
79
  country: to_country,
80
80
  fx_rate: fx_result.rate,
81
- cpi_ratio: infl.to_index.to_f / infl.from_index.to_f,
81
+ cpi_ratio: infl.to_index.to_f / infl.from_index,
82
82
  converted_amount: converted,
83
83
  granularity: infl.granularity
84
84
  )
@@ -27,9 +27,16 @@ module Timeprice
27
27
  def load_cpi(country)
28
28
  @cpi_cache ||= {}
29
29
  key = country.to_s.downcase
30
+ code = country.to_s.upcase
30
31
  @cpi_cache[[data_root, key]] ||= begin
32
+ raise UnsupportedCountry, code unless SUPPORTED_COUNTRIES.include?(code)
33
+
31
34
  path = File.join(data_root, "cpi", "#{key}.json")
32
- raise UnsupportedCountry, country.to_s.upcase unless File.exist?(path)
35
+ unless File.exist?(path)
36
+ raise DataNotFound, "CPI data file missing for #{code} (looked in #{path}). " \
37
+ "Check TIMEPRICE_DATA_ROOT or reinstall the gem."
38
+ end
39
+
33
40
  parse_with_schema(path)
34
41
  end
35
42
  end
@@ -40,6 +47,7 @@ module Timeprice
40
47
  @fx_cache[[data_root, key]] ||= begin
41
48
  path = File.join(data_root, "fx", "usd", "#{key}.json")
42
49
  raise DataNotFound, "No FX data for year #{key}" unless File.exist?(path)
50
+
43
51
  parse_with_schema(path)
44
52
  end
45
53
  end
@@ -49,9 +57,8 @@ module Timeprice
49
57
  def parse_with_schema(path)
50
58
  data = JSON.parse(File.read(path))
51
59
  version = data["schema_version"]
52
- unless version == SUPPORTED_SCHEMA_VERSION
53
- raise UnsupportedSchemaVersion.new(version, path)
54
- end
60
+ raise UnsupportedSchemaVersion.new(version, path) unless version == SUPPORTED_SCHEMA_VERSION
61
+
55
62
  data
56
63
  end
57
64
  end
@@ -3,12 +3,15 @@
3
3
  module Timeprice
4
4
  class Error < StandardError; end
5
5
 
6
+ SUPPORTED_COUNTRIES = %w[US UK EU JP VN].freeze
7
+ SUPPORTED_CURRENCIES = %w[USD GBP EUR JPY VND].freeze
8
+
6
9
  class UnsupportedCountry < Error
7
10
  attr_reader :country
8
11
 
9
12
  def initialize(country)
10
13
  @country = country
11
- super("Unsupported country: #{country.inspect}")
14
+ super("Unsupported country: #{country.inspect} (supported: #{SUPPORTED_COUNTRIES.join(", ")})")
12
15
  end
13
16
  end
14
17
 
@@ -17,7 +20,7 @@ module Timeprice
17
20
 
18
21
  def initialize(currency)
19
22
  @currency = currency
20
- super("Unsupported currency: #{currency.inspect}")
23
+ super("Unsupported currency: #{currency.inspect} (supported: #{SUPPORTED_CURRENCIES.join(", ")})")
21
24
  end
22
25
  end
23
26
 
@@ -20,6 +20,9 @@ module Timeprice
20
20
  def convert(amount:, from:, to:, date:)
21
21
  from = from.to_s.upcase
22
22
  to = to.to_s.upcase
23
+ raise UnsupportedCurrency, from unless SUPPORTED_CURRENCIES.include?(from)
24
+ raise UnsupportedCurrency, to unless SUPPORTED_CURRENCIES.include?(to)
25
+
23
26
  d = parse_date(date)
24
27
 
25
28
  rate, eff_date = resolve_rate(from, to, d)
@@ -75,8 +78,10 @@ module Timeprice
75
78
  end
76
79
  rates_for_day = year_data.dig("rates", candidate.to_s)
77
80
  next unless rates_for_day
81
+
78
82
  rate = rates_for_day[currency]
79
83
  next unless rate
84
+
80
85
  return [rate.to_f, candidate]
81
86
  end
82
87
  raise DataNotFound, "No FX rate for USD->#{currency} on or before #{d}"
@@ -89,7 +94,12 @@ module Timeprice
89
94
  unless date.match?(/\A\d{4}-\d{2}-\d{2}\z/)
90
95
  raise ArgumentError, "Invalid date format: #{date.inspect} (use YYYY-MM-DD)"
91
96
  end
92
- Date.parse(date)
97
+
98
+ begin
99
+ Date.parse(date)
100
+ rescue Date::Error
101
+ raise ArgumentError, "Invalid date: #{date.inspect} is not a real calendar date"
102
+ end
93
103
  else
94
104
  raise ArgumentError, "Invalid date: #{date.inspect}"
95
105
  end
@@ -27,7 +27,7 @@ module Timeprice
27
27
  from_index, from_gran = lookup_index(data, from)
28
28
  to_index, to_gran = lookup_index(data, to)
29
29
 
30
- ratio = to_index.to_f / from_index.to_f
30
+ ratio = to_index.to_f / from_index
31
31
  InflationResult.new(
32
32
  amount: amount.to_f * ratio,
33
33
  original_amount: amount.to_f,
@@ -58,18 +58,18 @@ module Timeprice
58
58
  [monthly[key], :monthly]
59
59
  else
60
60
  year = key[0, 4]
61
- if annual.key?(year)
62
- [annual[year], :annual]
63
- else
64
- raise DataNotFound, "No CPI data for #{key.inspect} in #{data["country"]}"
65
- end
61
+ raise DataNotFound, missing_cpi_message(key, data, monthly, annual) unless annual.key?(year)
62
+
63
+ [annual[year], :annual]
64
+
66
65
  end
67
66
  when /\A\d{4}\z/
68
67
  if annual.key?(key)
69
68
  [annual[key], :annual]
70
69
  else
71
70
  months = monthly.select { |k, _| k.start_with?("#{key}-") }
72
- raise DataNotFound, "No CPI data for #{key.inspect} in #{data["country"]}" if months.empty?
71
+ raise DataNotFound, missing_cpi_message(key, data, monthly, annual) if months.empty?
72
+
73
73
  avg = months.values.sum.to_f / months.size
74
74
  [avg, :annual_from_monthly_avg]
75
75
  end
@@ -78,11 +78,27 @@ module Timeprice
78
78
  end
79
79
  end
80
80
 
81
+ def missing_cpi_message(key, data, monthly, annual)
82
+ country = data["country"]
83
+ ranges = []
84
+ if monthly.any?
85
+ ks = monthly.keys.sort
86
+ ranges << "monthly #{ks.first}..#{ks.last}"
87
+ end
88
+ if annual.any?
89
+ ks = annual.keys.sort
90
+ ranges << "annual #{ks.first}..#{ks.last}"
91
+ end
92
+ hint = ranges.empty? ? "" : " (supported: #{ranges.join(", ")})"
93
+ "No CPI data for #{key.inspect} in #{country}#{hint}"
94
+ end
95
+
81
96
  # If either end fell back to annual_from_monthly_avg, propagate that label;
82
97
  # else if either is annual, propagate :annual; else :monthly.
83
98
  def merge_granularity(a, b)
84
99
  return :annual_from_monthly_avg if a == :annual_from_monthly_avg || b == :annual_from_monthly_avg
85
100
  return :annual if a == :annual || b == :annual
101
+
86
102
  :monthly
87
103
  end
88
104
  end
@@ -15,7 +15,7 @@ module Timeprice
15
15
  name: "U.S. Bureau of Labor Statistics — CPI-U (series CUUR0000SA0)",
16
16
  license: "U.S. Government work — public domain",
17
17
  license_url: "https://www.bls.gov/bls/linksite.htm",
18
- attribution: "Data: U.S. Bureau of Labor Statistics"
18
+ attribution: "Data: U.S. Bureau of Labor Statistics",
19
19
  },
20
20
  {
21
21
  id: "uk_cpi",
@@ -24,7 +24,7 @@ module Timeprice
24
24
  name: "UK Office for National Statistics — CPI all-items (series D7BT)",
25
25
  license: "Open Government Licence v3.0",
26
26
  license_url: "https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/",
27
- attribution: "Contains public sector information licensed under the Open Government Licence v3.0"
27
+ attribution: "Contains public sector information licensed under the Open Government Licence v3.0",
28
28
  },
29
29
  {
30
30
  id: "eu_hicp",
@@ -33,7 +33,7 @@ module Timeprice
33
33
  name: "Eurostat — HICP prc_hicp_midx (Euro area, all items)",
34
34
  license: "Eurostat reuse policy (free reuse with attribution)",
35
35
  license_url: "https://ec.europa.eu/eurostat/about-us/policies/copyright",
36
- attribution: "Source: Eurostat"
36
+ attribution: "Source: Eurostat",
37
37
  },
38
38
  {
39
39
  id: "jp_cpi",
@@ -42,7 +42,7 @@ module Timeprice
42
42
  name: "World Bank — FP.CPI.TOTL (annual, JP fallback)",
43
43
  license: "CC BY 4.0",
44
44
  license_url: "https://datacatalog.worldbank.org/public-licenses#cc-by",
45
- attribution: "Source: World Bank, FP.CPI.TOTL"
45
+ attribution: "Source: World Bank, FP.CPI.TOTL",
46
46
  },
47
47
  {
48
48
  id: "vn_cpi",
@@ -51,7 +51,7 @@ module Timeprice
51
51
  name: "World Bank — FP.CPI.TOTL (annual)",
52
52
  license: "CC BY 4.0",
53
53
  license_url: "https://datacatalog.worldbank.org/public-licenses#cc-by",
54
- attribution: "Source: World Bank, FP.CPI.TOTL"
54
+ attribution: "Source: World Bank, FP.CPI.TOTL",
55
55
  },
56
56
  {
57
57
  id: "fx_ecb",
@@ -60,7 +60,7 @@ module Timeprice
60
60
  name: "European Central Bank reference rates (via Frankfurter)",
61
61
  license: "ECB reference rates — free reuse",
62
62
  license_url: "https://www.ecb.europa.eu/services/disclaimer/html/index.en.html",
63
- attribution: "FX data: European Central Bank reference rates via Frankfurter"
63
+ attribution: "FX data: European Central Bank reference rates via Frankfurter",
64
64
  },
65
65
  {
66
66
  id: "fx_vnd",
@@ -69,8 +69,8 @@ module Timeprice
69
69
  name: "World Bank — PA.NUS.FCRF (VND annual average, broadcast daily)",
70
70
  license: "CC BY 4.0",
71
71
  license_url: "https://datacatalog.worldbank.org/public-licenses#cc-by",
72
- attribution: "VND FX: World Bank, PA.NUS.FCRF"
73
- }
72
+ attribution: "VND FX: World Bank, PA.NUS.FCRF",
73
+ },
74
74
  ].freeze
75
75
 
76
76
  module_function
@@ -105,6 +105,7 @@ module Timeprice
105
105
  root = File.join(DataLoader.data_root, "fx", "usd")
106
106
  years = Dir[File.join(root, "*.json")].map { |f| File.basename(f, ".json").to_i }.sort
107
107
  return "no data" if years.empty?
108
+
108
109
  case id
109
110
  when "fx_vnd"
110
111
  # VND broadcast-from-annual covers earlier years too.
@@ -113,14 +114,16 @@ module Timeprice
113
114
  d["rates"].any? { |_, v| v.key?("VND") }
114
115
  end
115
116
  return "no VND data" if with_vnd.empty?
117
+
116
118
  "USD↔VND #{with_vnd.first}..#{with_vnd.last}"
117
119
  else
118
120
  # ECB pairs (EUR/GBP/JPY) start 1999
119
121
  ecb_years = years.select do |y|
120
122
  d = JSON.parse(File.read(File.join(root, "#{y}.json")))
121
- d["rates"].any? { |_, v| (v.keys & %w[EUR GBP JPY]).any? }
123
+ d["rates"].any? { |_, v| v.keys.intersect?(%w[EUR GBP JPY]) }
122
124
  end
123
125
  return "no ECB data" if ecb_years.empty?
126
+
124
127
  "USD↔EUR/GBP/JPY daily #{ecb_years.first}..#{ecb_years.last}"
125
128
  end
126
129
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Timeprice
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timeprice
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick
@@ -51,6 +51,48 @@ dependencies:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: '3.13'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rubocop
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.69'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.69'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rubocop-rake
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0.6'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '0.6'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rubocop-rspec
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.3'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '3.3'
54
96
  description: Offline historical inflation & FX for Ruby - bundled data, no API keys,
55
97
  monthly auto-refresh.
56
98
  email:
@@ -130,6 +172,7 @@ metadata:
130
172
  bug_tracker_uri: https://github.com/patrick204nqh/timeprice/issues
131
173
  changelog_uri: https://github.com/patrick204nqh/timeprice/blob/main/CHANGELOG.md
132
174
  github_repo: patrick204nqh/timeprice
175
+ rubygems_mfa_required: 'true'
133
176
  rdoc_options: []
134
177
  require_paths:
135
178
  - lib