gopromotional-siplog 1.0.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +62 -0
- data/exe/gopromotional-siplog +5 -0
- data/lib/gopromotional/siplog/cli.rb +101 -0
- data/lib/gopromotional/siplog/version.rb +7 -0
- data/lib/gopromotional/siplog.rb +135 -0
- data/lib/gopromotional-siplog.rb +1 -0
- metadata +53 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c77cd57e02d0c41931dd6367c603da79ebed4e63bd203f210012106ee73033a1
|
|
4
|
+
data.tar.gz: f84acc29f6379dd680ebbbb02f09b673256a83a0f03b2d8f20e392ea4b4faeaa
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3f69688b133665d606eeef617269a9ccda4529be668755623224afc9f2705a8e750d71c5872361e7c40d8534aaeb9437a1c1e85c039faaa3fa03857ddca2a969
|
|
7
|
+
data.tar.gz: c9eeda9d8482e3d33e050db35444783ed0e47d564f97966c3df08cdc9578ad46cbeb1547894a2f16d9bc8bb809c0333b8293319564adec0c42abd302e02aa3c5
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 GoPromotional
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# gopromotional-siplog
|
|
2
|
+
|
|
3
|
+
A small hydration tracker: estimate a daily water goal from weight, exercise and weather, log drinks in ml, cl, litres or US fl oz, and see today's progress, the last 7 days, your average and your streak. The log is a plain JSON file.
|
|
4
|
+
|
|
5
|
+
- **Daily goal** estimate: weight × 33 ml + 12 ml per active minute (+500 ml in hot weather), rounded to 50 ml. A general guide, not medical advice.
|
|
6
|
+
- **Flexible amounts**: `250`, `500ml`, `33cl`, `0.5l`, `16oz`.
|
|
7
|
+
- **Progress**, **7-day history**, **average** and **streak** of days on target.
|
|
8
|
+
- **Undo** the last entry, **set** your own goal.
|
|
9
|
+
- Plain **JSON log** (`{goal, entries:[{date, ml}]}`), the same format in every language version.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
gem install gopromotional-siplog
|
|
15
|
+
# or in a Gemfile:
|
|
16
|
+
gem "gopromotional-siplog"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Command line
|
|
20
|
+
|
|
21
|
+
```console
|
|
22
|
+
$ gopromotional-siplog goal --weight 70 --activity 30 --hot
|
|
23
|
+
Suggested daily water goal: 3150 ml (a general guide, not medical advice).
|
|
24
|
+
|
|
25
|
+
$ gopromotional-siplog set-goal 2.5l
|
|
26
|
+
Daily goal set to 2500 ml.
|
|
27
|
+
|
|
28
|
+
$ gopromotional-siplog add 500ml --date 2026-10-19
|
|
29
|
+
Added 500 ml. 500/2500 ml (20%).
|
|
30
|
+
|
|
31
|
+
$ gopromotional-siplog add 16oz --date 2026-10-19
|
|
32
|
+
Added 473 ml. 973/2500 ml (39%).
|
|
33
|
+
|
|
34
|
+
$ gopromotional-siplog status --date 2026-10-19
|
|
35
|
+
2026-10-19: 973/2500 ml [########............] 39% - 1527 ml to go
|
|
36
|
+
Streak: 0 day(s)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Every command accepts `--json` for machine-readable output. Run `gopromotional-siplog --help` for all options.
|
|
40
|
+
|
|
41
|
+
Data is kept in `~/.gopromotional-siplog.json` (set `GOPROMOTIONAL_SIPLOG_FILE` or pass `--file` to use another file). The JSON format is the same across the npm, PyPI, crates.io and RubyGems versions, so you can switch tools without losing your log.
|
|
42
|
+
|
|
43
|
+
## Library
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
require "gopromotional/siplog"
|
|
47
|
+
S = GoPromotional::Siplog
|
|
48
|
+
|
|
49
|
+
S.daily_goal(weight_kg: 70, activity_minutes: 30, hot: true) # => 3150
|
|
50
|
+
S.parse_amount("16oz") # => 473
|
|
51
|
+
log = S::HydrationLog.new(goal: 2000)
|
|
52
|
+
log.add("500ml", "2026-10-19"); log.add(250, "2026-10-19")
|
|
53
|
+
log.progress("2026-10-19")[:percent] # => 38
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Pure Ruby, no runtime dependencies, Ruby 3.0+. `require "gopromotional-siplog"` also works.
|
|
57
|
+
|
|
58
|
+
## Links
|
|
59
|
+
|
|
60
|
+
- Website: [branded water bottles for event marketing](https://www.gopromotional.co.uk/branded-products/branded-water-bottles)
|
|
61
|
+
- Source code: [https://github.com/nbkdevs/GoPromotional](https://github.com/nbkdevs/GoPromotional) (`packages/water-bottles/rubygems`)
|
|
62
|
+
- Licence: MIT
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
require_relative "../siplog"
|
|
5
|
+
|
|
6
|
+
module GoPromotional
|
|
7
|
+
module Siplog
|
|
8
|
+
# Command line interface: goal | add | status | week | undo | set-goal
|
|
9
|
+
module CLI
|
|
10
|
+
USAGE = <<~TXT
|
|
11
|
+
Usage: gopromotional-siplog <command> [options]
|
|
12
|
+
|
|
13
|
+
Commands:
|
|
14
|
+
goal --weight KG [--activity MIN] [--hot] suggest a daily water goal
|
|
15
|
+
add <amount> log a drink: 250, 500ml, 0.5l, 33cl, 16oz
|
|
16
|
+
status today's progress and streak
|
|
17
|
+
week last 7 days and average
|
|
18
|
+
undo remove the last entry for the day
|
|
19
|
+
set-goal <ml> set the daily goal
|
|
20
|
+
|
|
21
|
+
Options: --file PATH (default $GOPROMOTIONAL_SIPLOG_FILE or ~/.gopromotional-siplog.json),
|
|
22
|
+
--date YYYY-MM-DD, --json, -v/--version, -h/--help
|
|
23
|
+
TXT
|
|
24
|
+
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
def default_file
|
|
28
|
+
env = ENV.fetch("GOPROMOTIONAL_SIPLOG_FILE", "")
|
|
29
|
+
env.empty? ? File.join(Dir.home, ".gopromotional-siplog.json") : env
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def bar(pct)
|
|
33
|
+
("#" * [20, (pct / 5.0 + 0.5).to_i].min).ljust(20, ".")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def run(argv, out: $stdout, err: $stderr)
|
|
37
|
+
o = { json: false, activity: 0.0, hot: false }
|
|
38
|
+
parser = OptionParser.new do |p|
|
|
39
|
+
p.banner = USAGE
|
|
40
|
+
p.on("--json") { o[:json] = true }
|
|
41
|
+
p.on("--file PATH") { |v| o[:file] = v }
|
|
42
|
+
p.on("--date DATE") { |v| o[:date] = v }
|
|
43
|
+
p.on("--weight KG", Float) { |v| o[:weight] = v }
|
|
44
|
+
p.on("--activity MIN", Float) { |v| o[:activity] = v }
|
|
45
|
+
p.on("--hot") { o[:hot] = true }
|
|
46
|
+
p.on("-v", "--version") { out.puts VERSION; return 0 }
|
|
47
|
+
p.on("-h", "--help") { out.puts USAGE; return 0 }
|
|
48
|
+
end
|
|
49
|
+
args = parser.parse(argv)
|
|
50
|
+
cmd = args.shift
|
|
51
|
+
(out.puts USAGE; return 0) if cmd.nil?
|
|
52
|
+
day = o[:date] || Date.today.iso8601
|
|
53
|
+
path = o[:file] || default_file
|
|
54
|
+
if cmd == "goal"
|
|
55
|
+
raise ArgumentError, "goal needs --weight KG" unless o[:weight]
|
|
56
|
+
r = { goal: Siplog.daily_goal(weight_kg: o[:weight], activity_minutes: o[:activity], hot: o[:hot]) }
|
|
57
|
+
text = "Suggested daily water goal: #{r[:goal]} ml (a general guide, not medical advice)."
|
|
58
|
+
else
|
|
59
|
+
log = File.exist?(path) ? HydrationLog.from_json(File.read(path, encoding: "utf-8")) : HydrationLog.new
|
|
60
|
+
save = -> { File.write(path, "#{log.to_json}\n") }
|
|
61
|
+
case cmd
|
|
62
|
+
when "add"
|
|
63
|
+
raise ArgumentError, "add needs an amount, e.g. 250 or 500ml" unless args.first
|
|
64
|
+
ml = log.add(args.join(" "), day).ml
|
|
65
|
+
save.call
|
|
66
|
+
r = log.progress(day)
|
|
67
|
+
text = "Added #{ml} ml. #{r[:total]}/#{r[:goal]} ml (#{r[:percent]}%)."
|
|
68
|
+
when "status"
|
|
69
|
+
r = log.progress(day)
|
|
70
|
+
text = "#{r[:date]}: #{r[:total]}/#{r[:goal]} ml [#{bar(r[:percent])}] #{r[:percent]}%" +
|
|
71
|
+
(r[:reached] ? " - goal reached" : " - #{r[:remaining]} ml to go") +
|
|
72
|
+
"\nStreak: #{log.streak(day)} day(s)"
|
|
73
|
+
when "week"
|
|
74
|
+
r = log.week(day)
|
|
75
|
+
text = r.map { |d| "#{d[:date]} #{d[:total].to_s.rjust(5)} ml #{d[:total] >= log.goal ? 'goal' : ''}" }.join("\n")
|
|
76
|
+
text += "\nAverage: #{log.average(day)} ml/day"
|
|
77
|
+
when "undo"
|
|
78
|
+
e = log.undo(day)
|
|
79
|
+
save.call
|
|
80
|
+
r = e&.to_h
|
|
81
|
+
text = e ? "Removed #{e.ml} ml from #{e.date}." : "Nothing to undo."
|
|
82
|
+
when "set-goal"
|
|
83
|
+
g = Siplog.parse_amount(args.join(" "))
|
|
84
|
+
raise ArgumentError, "goal must be between 500 and 6000 ml" unless (500..6000).cover?(g)
|
|
85
|
+
log.goal = g
|
|
86
|
+
save.call
|
|
87
|
+
r = { goal: g }
|
|
88
|
+
text = "Daily goal set to #{g} ml."
|
|
89
|
+
else
|
|
90
|
+
raise ArgumentError, %(Unknown command "#{cmd}". Run with --help.)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
out.puts(o[:json] ? JSON.pretty_generate(r) : text)
|
|
94
|
+
0
|
|
95
|
+
rescue ArgumentError, OptionParser::ParseError, SystemCallError, JSON::ParserError => e
|
|
96
|
+
err.puts "Error: #{e.message}"
|
|
97
|
+
2
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
require "json"
|
|
5
|
+
require_relative "siplog/version"
|
|
6
|
+
|
|
7
|
+
module GoPromotional
|
|
8
|
+
# Hydration helpers: daily water goal estimate, amount parsing and a day-by-day water log.
|
|
9
|
+
module Siplog
|
|
10
|
+
ML_PER_US_FL_OZ = 29.5735
|
|
11
|
+
ML_PER_ACTIVE_MINUTE = 12
|
|
12
|
+
HOT_WEATHER_ML = 500
|
|
13
|
+
FACTORS = { "ml" => 1, "cl" => 10, "l" => 1000, "oz" => ML_PER_US_FL_OZ }.freeze
|
|
14
|
+
AMOUNT = /\A\s*(\d+(?:\.\d+)?)\s*(ml|cl|l|oz)?\s*\z/i
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
def round_half_up(x)
|
|
19
|
+
(x + 0.5).floor
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def iso(value)
|
|
23
|
+
return Date.today.iso8601 if value.nil?
|
|
24
|
+
return value.iso8601 if value.is_a?(Date)
|
|
25
|
+
s = value.to_s.strip
|
|
26
|
+
raise ArgumentError, %(Invalid date "#{value}" (expected YYYY-MM-DD)) unless s.match?(/\A\d{4}-\d{2}-\d{2}\z/)
|
|
27
|
+
Date.iso8601(s).iso8601
|
|
28
|
+
rescue Date::Error
|
|
29
|
+
raise ArgumentError, %(Invalid date "#{value}" (expected YYYY-MM-DD))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Rough daily water goal in ml (a general guide, not medical advice):
|
|
33
|
+
# weight x ml_per_kg + active minutes x 12 ml (+500 ml in hot weather), to the nearest 50 ml.
|
|
34
|
+
def daily_goal(weight_kg:, activity_minutes: 0, hot: false, ml_per_kg: 33)
|
|
35
|
+
raise ArgumentError, "weight_kg must be between 0 and 400" unless weight_kg.is_a?(Numeric) && weight_kg.positive? && weight_kg <= 400
|
|
36
|
+
raise ArgumentError, "activity_minutes must be >= 0" if activity_minutes.negative?
|
|
37
|
+
raw = weight_kg * ml_per_kg + activity_minutes * ML_PER_ACTIVE_MINUTE + (hot ? HOT_WEATHER_ML : 0)
|
|
38
|
+
(raw / 50.0 + 0.5).floor * 50
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Parse 250, "500ml", "0.5l", "33cl" or "16oz" into whole millilitres.
|
|
42
|
+
def parse_amount(value)
|
|
43
|
+
if value.is_a?(Numeric)
|
|
44
|
+
raise ArgumentError, "amount must be positive" unless value.positive?
|
|
45
|
+
return round_half_up(value)
|
|
46
|
+
end
|
|
47
|
+
m = AMOUNT.match(value.to_s)
|
|
48
|
+
raise ArgumentError, %(Cannot read amount "#{value}" (try 250, 500ml, 0.5l, 33cl or 16oz)) unless m
|
|
49
|
+
ml = round_half_up(m[1].to_f * FACTORS[(m[2] || "ml").downcase])
|
|
50
|
+
raise ArgumentError, "amount must be positive" unless ml.positive?
|
|
51
|
+
ml
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
Entry = Struct.new(:date, :ml) do
|
|
55
|
+
def to_h = { "date" => date, "ml" => ml }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# A day-by-day water log. The JSON file format ({goal, entries:[{date, ml}]}) is shared with the
|
|
59
|
+
# npm, PyPI and crates.io versions.
|
|
60
|
+
class HydrationLog
|
|
61
|
+
attr_accessor :goal
|
|
62
|
+
attr_reader :entries
|
|
63
|
+
|
|
64
|
+
def initialize(goal: 2000, entries: [])
|
|
65
|
+
@goal = goal
|
|
66
|
+
@entries = entries.map { |e| e.is_a?(Entry) ? e : Entry.new(Siplog.iso(e["date"] || e[:date]), Siplog.parse_amount(e["ml"] || e[:ml])) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def self.from_json(data)
|
|
70
|
+
d = data.is_a?(String) ? JSON.parse(data) : (data || {})
|
|
71
|
+
new(goal: d.fetch("goal", 2000), entries: d.fetch("entries", []))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def to_h = { "goal" => goal, "entries" => entries.map(&:to_h) }
|
|
75
|
+
def to_json(*_args) = JSON.pretty_generate(to_h)
|
|
76
|
+
|
|
77
|
+
def add(amount, day = nil)
|
|
78
|
+
e = Entry.new(Siplog.iso(day), Siplog.parse_amount(amount))
|
|
79
|
+
@entries << e
|
|
80
|
+
e
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Remove the most recent entry for a day.
|
|
84
|
+
def undo(day = nil)
|
|
85
|
+
d = Siplog.iso(day)
|
|
86
|
+
i = @entries.rindex { |e| e.date == d }
|
|
87
|
+
i && @entries.delete_at(i)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def clear_day(day = nil)
|
|
91
|
+
d = Siplog.iso(day)
|
|
92
|
+
before = @entries.size
|
|
93
|
+
@entries.reject! { |e| e.date == d }
|
|
94
|
+
before - @entries.size
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def total(day = nil)
|
|
98
|
+
d = Siplog.iso(day)
|
|
99
|
+
@entries.sum { |e| e.date == d ? e.ml : 0 }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def progress(day = nil, goal: nil)
|
|
103
|
+
g = goal || @goal
|
|
104
|
+
d = Siplog.iso(day)
|
|
105
|
+
t = total(d)
|
|
106
|
+
{ date: d, total: t, goal: g, percent: g.positive? ? (t * 100.0 / g + 0.5).floor : 0,
|
|
107
|
+
remaining: [0, g - t].max, reached: t >= g }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Totals for the 7 days ending on end_day, newest first.
|
|
111
|
+
def week(end_day = nil)
|
|
112
|
+
e = Date.iso8601(Siplog.iso(end_day))
|
|
113
|
+
(0..6).map { |i| { date: (e - i).iso8601, total: total(e - i) } }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Consecutive days meeting the goal, ending today (or yesterday if today is not done yet).
|
|
117
|
+
def streak(end_day = nil, goal: nil)
|
|
118
|
+
g = goal || @goal
|
|
119
|
+
d = Date.iso8601(Siplog.iso(end_day))
|
|
120
|
+
d -= 1 if total(d) < g
|
|
121
|
+
n = 0
|
|
122
|
+
while n < 3660 && total(d) >= g
|
|
123
|
+
n += 1
|
|
124
|
+
d -= 1
|
|
125
|
+
end
|
|
126
|
+
n
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def average(end_day = nil, days: 7)
|
|
130
|
+
e = Date.iso8601(Siplog.iso(end_day))
|
|
131
|
+
Siplog.round_half_up((0...days).sum { |i| total(e - i) } / days.to_f)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require_relative "gopromotional/siplog"
|
metadata
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: gopromotional-siplog
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- GoPromotional
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: 'A small hydration tracker: estimate a daily water goal from weight,
|
|
13
|
+
exercise and weather, log drinks in ml, cl, litres or US fl oz, and see today''s
|
|
14
|
+
progress, the last 7 days, your average and your streak. The log is a plain JSON
|
|
15
|
+
file.'
|
|
16
|
+
executables:
|
|
17
|
+
- gopromotional-siplog
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- LICENSE
|
|
22
|
+
- README.md
|
|
23
|
+
- exe/gopromotional-siplog
|
|
24
|
+
- lib/gopromotional-siplog.rb
|
|
25
|
+
- lib/gopromotional/siplog.rb
|
|
26
|
+
- lib/gopromotional/siplog/cli.rb
|
|
27
|
+
- lib/gopromotional/siplog/version.rb
|
|
28
|
+
homepage: https://www.gopromotional.co.uk/branded-products/branded-water-bottles
|
|
29
|
+
licenses:
|
|
30
|
+
- MIT
|
|
31
|
+
metadata:
|
|
32
|
+
homepage_uri: https://www.gopromotional.co.uk/branded-products/branded-water-bottles
|
|
33
|
+
source_code_uri: https://github.com/nbkdevs/GoPromotional/tree/main/packages/water-bottles/rubygems
|
|
34
|
+
bug_tracker_uri: https://github.com/nbkdevs/GoPromotional/issues
|
|
35
|
+
rubygems_mfa_required: 'true'
|
|
36
|
+
rdoc_options: []
|
|
37
|
+
require_paths:
|
|
38
|
+
- lib
|
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: '3.0'
|
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '0'
|
|
49
|
+
requirements: []
|
|
50
|
+
rubygems_version: 3.6.7
|
|
51
|
+
specification_version: 4
|
|
52
|
+
summary: Daily water goal estimate and a simple hydration log with streaks.
|
|
53
|
+
test_files: []
|