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
|
#!/usr/bin/env ruby
require 'optparse'
require 'digest'
require 'json'
require 'set'
PERSONAL_TIMESPAN_D = 30
WORK_TIMESPAN_D = 14
WORKTIME_DIR = "#{ENV['HOME']}/git/worktime".freeze
GOS_DIR = "#{ENV['HOME']}/.gosdir".freeze
MAX_PENDING_RANDOM_TASKS = 42
def maybe?
[true, false].sample
end
def run_from_personal_device?
`uname`.chomp == 'Linux'
end
def random_count
MAX_PENDING_RANDOM_TASKS - `task status:pending +random -work count`.to_i
end
def notes(notes_dirs, prefix, dry)
notes_dirs.each do |notes_dir|
Dir["#{notes_dir}/#{prefix}-*"].each do |notes_file|
match = File.read(notes_file).strip.match(/(?<due>\d+)? *(?<tag>[A-Z]?[a-z,-:]+) *(?<body>.*)/m)
next unless match
tags = match[:tag].split(',') + [prefix]
due = if match[:due].nil?
tags.include?('track') ? 'eow' : "#{rand(0..PERSONAL_TIMESPAN_D)}d"
else
"#{match[:due]}d"
end
yield tags, match[:body], due
File.delete(notes_file) unless dry
end
end
end
def random_quote(md_file)
tag = File.basename(md_file, '.md').downcase
lines = File.readlines(md_file)
match = lines.first.match(/\((\d+)\)/)
timespan = run_from_personal_device? ? PERSONAL_TIMESPAN_D : WORK_TIMESPAN_D
timespan = match ? match[1].to_i : timespan
quote = lines.select { |l| l.start_with? '*' }.map { |l| l.sub(/\* +/, '') }.sample
tags = [tag, 'random']
tags << 'work' if maybe? and maybe?
yield tags, quote.chomp, "#{rand(0..timespan)}d"
end
def run!(cmd, dry)
puts cmd
return if dry
puts `#{cmd}`
raise "Command '#{cmd}' failed with #{$?.exitstatus}" if $?.exitstatus != 0
rescue StandardError => e
puts "Error running command '#{cmd}': #{e.message}"
exit 1
end
def skill_add!(skills_str, dry)
skills_file = "#{WORKTIME_DIR}/skills.txt"
skills_str.split(',').map(&:strip).each { skills[_1.to_s.downcase] = _1 }
File.foreach(skills_file) do |line|
line.chomp!
skills[line.downcase] = line
end
File.open("#{skills_file}.tmp", 'w') do |file|
skills.each_value { |skill| file.puts(skill) }
end
return if dry
File.rename("#{skills_file}.tmp", skills_file)
end
def worklog_add!(tag, quote, due, dry)
file = "#{WORKTIME_DIR}/wl-#{Time.now.to_i}n.txt"
content = "#{due.chomp 'd'} #{tag} #{quote}"
puts "#{file}: #{content}"
File.write(file, content) unless dry
end
# Queue to Gos https://codeberg.org/snonux/gos
def gos_queue!(tags, message, dry)
tags.delete('share')
platforms = []
%w[linkedin li mastodon ma noop no].select { tags.include?(_1) }.each do |platform|
platforms << platform
tags.delete(platform)
end
unless platforms.empty?
platforms = %w[share] + platforms
tags = ["#{platforms.join(':')}"] + tags
end
tags = %w[share] + tags if tags.size == 1 && !tags.first.start_with?('share')
tags_str = tags.join(',')
message = "#{tags_str.empty? ? '' : "#{tags_str} "}#{message}"
file = "#{GOS_DIR}/#{Digest::MD5.hexdigest(message)}.txt"
puts "Writing #{file} with #{message}"
File.write(file, message) unless dry
end
def task_add!(tags, quote, due, dry)
if quote.empty?
puts 'Not adding task with empty quote'
return
end
if tags.include?('tr')
tags << 'track'
tags.delete('tr')
end
tags << 'work' if tags.include?('mentoring') || tags.include?('productivity')
tags.uniq!
if tags.include?('task')
run! "task #{quote}", dry
else
project = tags.find { |t| t =~ /^[A-Z]/ }
project = if project.nil?
''
else
tags.delete(project)
" project:#{project.downcase}"
end
priority = tags.include?('high') ? 'H' : ''
run! "task add due:#{due} priority:#{priority}#{project} +#{tags.join(' +')} '#{quote.gsub("'", '"')}'", dry
end
end
def task_schedule!(id, due, dry)
run! "timeout 5s task modify #{id} due:#{due}", dry
end
def filter_tasks(filter)
lines = `task #{filter} 2>/dev/null`.split("\n").drop(1)
lines.pop
lines.map { |foo| foo.split.first }.each do |id|
yield id if id.to_i.positive?
end
end
begin
opts = {
random_dir: "#{ENV['HOME']}/Notes/random",
notes_dirs: "#{ENV['HOME']}/Notes,#{ENV['HOME']}/Notes/Quicklogger,#{ENV['HOME']}/git/worktime",
dry_run: false,
no_random: false
}
opt_parser = OptionParser.new do |o|
o.banner = 'Usage: ruby taskwarriorfeeder.rb [options]'
o.on('-d', '--random-dir DIR', 'The random quotes directory') { |v| opts[:random_dir] = v }
o.on('-n', '--notes-dirs DIR1,DIR2,...', 'The notes directories') { |v| opts[:notes_dirs] = v }
o.on('-D', '--dry-run', 'Dry run mode') { opts[:dry_run] = true }
o.on('-R', '--no-randoms', 'No random entries') { opts[:no_random] = true }
o.on_tail('-h', '--help', 'Show this help message and exit') { puts o and exit }
end
opt_parser.parse!(ARGV)
(run_from_personal_device? ? %w[ql pl] : %w[wl]).each do |prefix|
notes(opts[:notes_dirs].split(','), prefix, opts[:dry_run]) do |tags, note, due|
if tags.include?('skill') || tags.include?('skills')
skill_add!(note, opts[:dry_run])
elsif tags.include? 'work'
worklog_add!(:log, note, due, opts[:dry_run])
elsif tags.any? { |tag| tag.start_with?('share') }
gos_queue!(tags, note, opts[:dry_run])
else
task_add!(tags, note, due, opts[:dry_run])
end
end
end
unless opts[:no_random]
count = random_count
Dir["#{opts[:random_dir]}/*.md"].shuffle.each do |md_file|
next unless maybe?
break if count <= 0
random_quote(md_file) do |tags, quote, due|
task_add!(tags, quote, due, opts[:dry_run])
count -= 1
end
end
end
if Dir.exist?(GOS_DIR) && !opts[:dry_run]
Dir["#{WORKTIME_DIR}/tw-gos-*.json"].each do |tw_gos|
JSON.parse(File.read(tw_gos)).each do |entry|
gos_queue!(entry['tags'], entry['description'], opts[:dry_run])
end
File.delete(tw_gos)
rescue StandardError => e
puts e
end
end
# Schedule track tasks to end of week
filter_tasks('+track due:') do |id|
task_schedule!(id, 'eow', opts[:dry_run])
end
# Randomly schedule other unscheduled tasks
filter_tasks('-unsched -nosched -meeting -track due:') do |id|
task_schedule!(id, "#{rand(0..PERSONAL_TIMESPAN_D)}d", opts[:dry_run])
end
end
|