-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmeds.rb
More file actions
executable file
·389 lines (351 loc) · 11.5 KB
/
Copy pathmeds.rb
File metadata and controls
executable file
·389 lines (351 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
#!/usr/bin/env ruby
# ----------------------------------------------------------------------------- #
# File: meds.rb
# Description: prints when a medicine will be finishing and sends alert
# Also, allow input and update of meds.
# Author: j kepler
# Date: 2018-02-28 - 23:13
# Last update: 2019-05-27 10:15
# License: MIT License
# ----------------------------------------------------------------------------- #
# CHANGELOG:
# 2018-10-21 - readline suddenly not working, values don't show during "mod" and go as nil
# 2018-12-08 - write to log file so I know when last I bought something. sometimes I am
# out of medicine but the software shows I have it.
# This does not give an exact idea of how much I bought, just the stock on that day
# which could be a correction.
# 2019-01-12 - Put mod in a loop since i modify several at a shot
# 2019-02-13 - Display only med name in mod menu, and use smenu in place of fzf
# since we get a matrix like menu
# ----------------------------------------------------------------------------- #
# TODO:
#
# ----------------------------------------------------------------------------- #
require 'pp'
require 'readline'
require 'date'
require 'color' # see ~/work/projects/common/color.rb
# print color("Hello there black reverse on yellow\n", "black", "on_yellow", "reverse")
# --- some common stuff --- # {{{
# Taken from imdb/seen.rb
# readline version of gets
def input(prompt="", newline=false)
prompt += "\n" if newline
Readline.readline(prompt, true).squeeze(" ").strip
end
# edit a variable and return value as in zsh vared.
# newstock = vared(newstock, "Enter current stock: ")
## 2019-01-02 - this has stopped working. we should revert to OLD_vared (see seen.rb)
def vared var, prompt=">"
Readline.pre_input_hook = -> do
Readline.insert_text var
Readline.redisplay
# Remove the hook right away.
Readline.pre_input_hook = nil
end
begin
str = Readline.readline(prompt, false)
rescue Exception => e
return nil
end
str
end
# # taken from sed.rb in bugzy
# read the given filename into an array
def _read filename
d = []
File.open(filename).each { |line|
# remove blank lines. NOTE this may not be needed in other programs
next if line.chomp == ""
d << line
}
return d
end
# write the given array to the filename
def _write filename, array
File.open(filename, "w") do |file|
array.each { |row| file.puts row }
end
end
#
# --- end common stuff # }}}
# include? exist? each_pair split gsub join empty?
# this reads the file in a loop.
# It should not print, just put the data into a data structure
# so it can be called for either complete printing, or sending an alert via crontab
# etc.
def read_file_in_loop filename # {{{
sep = '~'
ctr = 0
file_a = []
File.open(filename).each { |line|
line = line.chomp
next if line =~ /^$/
# 2019-02-08 - maybe we can comment off a med that is paused or discontinued
next if line =~ /^#/
ctr += 1
next if ctr <= 1
cols = line.split(sep)
name = cols[0]
daily = cols[1].to_f
stock = cols[2].to_i
as_on = cols[3]
as_on_jd = Date.parse(cols[3]).jd
# 2019-02-08 - if we are not taking a medicine, i have made the stock zero
next if daily == 0
finish_on_jd = as_on_jd + (stock / daily)
finish_on = Date.jd(finish_on_jd)
balance = (stock/daily) - (Date.today.jd - as_on_jd)
balance = balance.to_i
# left is how many tablets are left
left = (balance*daily).to_i
file_a << [ name, daily, stock, as_on, finish_on, balance, left ]
}
return file_a
end # }}}
def print_all file_a # {{{
# sort by left (how many days left)
file_a.sort_by! { |s| s[5] }
format='%-25s %5s %5s %-12s %-12s %10s %6s'
puts color(format % [ "Medicine", "daily", "stock", "as_on" , "finish_on" , "days_left", "left"], "yellow", "on_black", "bold")
file_a.each_with_index {|cols, ix|
name = cols[0]
daily = cols[1]
stock = cols[2]
as_on = cols[3]
finish_on = cols[4]
balance = cols[5]
left = cols[6]
bg = "on_black"
fg = "white"
att = "normal"
code, fgcolor, bgcolor, attrib = case balance
when 0..5
["B", "red", bg, "bold"]
when 6..12
["C", "white", bg, att]
when 13..1000
["C", "green", bg, att]
when -1000..-1
["A", fg, bg, "reverse"]
else
["?", "yellow", "on_red", att]
end
puts color(format % [ name, daily, stock, as_on , finish_on , balance, left], fgcolor, bgcolor, attrib)
}
end # }}}
def change_line filename, argv=[] # {{{
# argv can have name of med or pattern and balance for today
while true
## argv is being repeated. that is an issue
num, line = select_row filename, argv
return unless num
puts line.join("\t")
stock = line[2]
# if user passed a number then ask if stock to be replaced, else prompt
if argv.count == 2
newstock = argv[1]
else
newstock = stock
end
savedval = newstock
puts "stock was #{line[2]} as on #{line[3]}. You passed #{newstock}"
newstock = vared(newstock, "Enter current stock: ")
newstock = savedval if newstock.nil? or newstock == "" ## 2018-10-21 - readline not working
puts "Got :#{newstock}:" if $opt_debug
puts "Got nil :#{newstock}:" if newstock.nil? or newstock == ""
raise ArgumentError, "Newstock nil" unless newstock
# allow user to edit date, default to today
newdate = Date.today.to_s
savedval = newdate
newdate = vared(newdate, "Enter as on date #{savedval}: ")
newdate = savedval if newdate.nil? or newdate == "" ## 2018-10-21 - readline not working
print "How much did you buy: "
bought = $stdin.gets
if bought
bought = bought.chomp.to_i
else
bought = 0
end
puts "Got :#{newdate}:" if $opt_debug
puts "line is #{num}" if $opt_debug
puts "Bought is #{bought}" if $opt_debug
raise ArgumentError, "Newdate nil" unless newdate
newline = line.dup
newline[2] = newstock
newline[3] = newdate
replace_line filename, num, newline
log_line newline, bought
puts
print ">> Modify another item? y/n: "
yesno = $stdin.gets.chomp
if yesno != "y"
break
end
end # while
end # }}}
def replace_line filename, lineno, newline # {{{
sep = "~"
arr = _read filename
num = lineno.to_i - 1
arr[num] = newline.join(sep)
_write(filename, arr)
end # }}}
## log the line to a file with date so we know when we entered what
## @param newline array of medicine name, stock, date
def log_line newline, bought
sep = "~"
newline << bought
str = newline.join(sep)
File.open($logfile, 'a') {|f| f.puts(str) } # or f.puts or f << str
end
# prompt user with rows for selection
# return selected row in an array
def select_row filename, argv=[] # {{{
myarg = argv.first
sep = "~"
## display med names to user, this displays entire line with a number
#str=%x{ nl #{filename} | fzf --query="#{myarg}" -1 -0}
# prompt user with medicine names. Some meds have spaces in it, so -W tells smenu not to separate on that.
# Reject header row with tail, and reject commented out meds
str = %x{ cut -f1 -d~ #{filename} | grep -v "^#" | tail -n +2 | sort | smenu -t -W$'\n' }
return nil,nil if str.nil? or str.chomp.size == 0
# 2019-02-13 - now we only display medicine name,so get the rest of the row
str = %x{ grep -n "#{str}" #{filename} }
#tmp = str.chomp.split("\t")
#puts str
tmp = str.chomp.split(":")
#puts tmp[0]
#puts tmp[1]
# returns lineno, and array containing rest
return tmp[0], tmp[1].split(sep)
end # }}}
# adds a new medicine to the file.
# TODO should check if already present
def add_line filename, argv=[] # {{{
name, dosage, stock, newdate = argv
unless name
print "Enter name of medicine: "
name = gets.chomp
end
unless dosage
dosage = vared("1", "Enter dosage: ")
end
stock = vared("0", "Enter stock: ") unless stock
unless newdate
newdate = Date.today.to_s
newdate = vared(newdate, "Enter as on date: ")
end
str = "#{name}~#{dosage}~#{stock}~#{newdate}"
puts str if $opt_debug
append_to_file filename, str
end # }}}
def append_to_file filename, line # {{{
open(filename, 'a') do |f|
f.puts line
end
end # }}}
def alert_me filename, args=[] # {{{
file_a = read_file_in_loop filename
arr = []
# sort by balance
file_a.sort_by! { |s| s[5] }
file_a.each_with_index do |cols, _ix|
name, daily, stock, as_on, finish_on, balance, left = cols
if balance < 12
#arr << "#{name} will finish on #{finish_on}. #{balance} days left."
arr << "%-28s will finish on #{finish_on}. #{balance} days left." % [name, finish_on, balance]
end
end
return if arr.empty?
# send contents of arr as a message using mail.sh.
str = arr.join("\n")
email_id = ENV['MAIL_ID']
puts email_id if $opt_debug
if $opt_cron
#out = %x{ echo "#{str}" | ~/bin/mail.sh -s "Medicine alert!" #{email_id} 2>&1}
out = %x{ echo "#{str}" | /Users/rahul/bin/mail.sh -s "Medicine alert" #{email_id} }
# checking this 2018-03-25 - since cron is not sending it at all.
#puts str
#puts "#{out}"
else
puts str
puts "#{out}"
end
# install as crontab
end # }}}
def find_line array, patt # {{{
array.each_with_index {|e, ix|
name = e.first
if name =~ /name/
return e
end
}
return nil
end # }}}
if __FILE__ == $0
include Color
filename = nil
$opt_verbose = false
$opt_debug = false
$opt_quiet = false
$opt_cron = false
begin
# http://www.ruby-doc.org/stdlib/libdoc/optparse/rdoc/classes/OptionParser.html
require 'optparse'
## TODO filename should come in from -f option
filename = File.expand_path("~/work/projects/meds/meds.txt");
$logfile = File.expand_path("~/work/projects/meds/meds.log");
options = {}
subtext = <<HELP
Commonly used command are:
mod : update stocks. mod dilzem 45
low : report on medicines running low (less than 10 days stock)
add : add a new medicine. e.g., add aspirin 1 25
HELP
OptionParser.new do |opts|
opts.banner = "Usage: #{$0} [options]"
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
options[:verbose] = v
$opt_verbose = v
end
opts.on("--crontab", "Send email, don't display on stdout") do |v|
$opt_cron = v
end
opts.on("--debug", "Show debug info") do
options[:debug] = true
$opt_debug = true
end
opts.on("-q", "--quiet", "Run quietly") do |v|
$opt_quiet = true
end
opts.separator ""
opts.separator subtext
end.parse!
p options if $opt_debug
p ARGV if $opt_debug
# --- if processing just one file ---------
unless File.exist? filename
$stderr.puts "File: #{filename} does not exist. Aborting"
exit 1
end
if ARGV.count == 0
file_a = read_file_in_loop filename
print_all file_a
exit 0
end
command = ARGV.shift
case command
when "add","a"
add_line filename, ARGV
when "mod","m"
change_line filename, ARGV
when "low","l"
alert_me filename, ARGV
else
puts "don't know how to handle #{command}!"
exit 1
end
ensure
end
end