-
Notifications
You must be signed in to change notification settings - Fork 0
/
taskadd
executable file
·241 lines (200 loc) · 5.78 KB
/
taskadd
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
#!/usr/bin/env bash
#
# taskadd
# taskwarrior wrapper, templated "task add", path labels, auto flags
#
# args
# arg1: prj/subprj/.../label
# arg?: extra args for taskw
# argN: "description"
# $TASKADD: additional args for taskw
# $TASKPRJ: default prj
# args matching ^https?:// will be added as annotations
#
# https://github.com/smemsh/taskwtools/
# https://spdx.org/licenses/GPL-2.0
#
##############################################################################
invname=${0##*/}
setenv () { local v=$1; shift; IFS= eval $v="\$*"; }
setarr () { local v=$1; shift; eval $v=\(\"\$@\"\); }
warn () { echo "$invname: ${FUNCNAME[1]}: $*" >&2; }
bomb () { warn "$@"; false; exit; }
# check that a label string has sanity: only alnums, one or more slashes
assoc_sane ()
{
# at least one slash i.e. project/label (can have any number)
if ! [[ $1 =~ / ]]; then
false; return; fi
# only alphanumerics and dashes allowed
if [[ $1 =~ [^[:alnum:]/-] ]]; then
false; return; fi
}
assoc_split ()
{
echo -n ${1//\//$'\x20'}
}
# returns just the label component (last word) of an assoc string
assoc_label ()
{
local -a assocs
local IFS; unset IFS
(($# == 1)) || bomb "one arg only"
assocs=(`assoc_split $1`)
printf ${assocs[@]: -1: 1} # spaces needed for negative offset
}
assoc_split_project ()
{
local -a assocs
local IFS; unset IFS
(($# == 1)) || bomb "one arg only"
assocs=(`assoc_split $1`)
unset "assocs[$((${#assocs[@]} - 1))]"
echo -n "${assocs[@]}"
}
task_assoc_exists ()
{
[[ $(task $fqtask _uuids) || $(task $prjtask _uuids) ]]
}
# we like tasks to fit 80 characters including the label
desc_sane ()
{
local label=$1
local desc="$2"
local full="$label: $desc"
local maxchars=79
if (($# != 2)); then
false; return; fi
if ((${#full} > maxchars)); then
warn "exceeds maximum chars (${#full}/$maxchars)"
false; return; fi
}
usage_exit ()
{
printf %s \
"TASKADD='+foo +bar' " \
"taskadd " \
"prj/subprj/label " \
"+baz +qux " \
"'description, etc'" \
;
exit
}
sanitize_args ()
{
if [[ (($# == 0)) || $1 == '-h' || $1 == '--help' ]]; then
usage_exit; fi
# first arg given is always in the form prj/subprj/.../label
assoc_sane $1 || bomb "given assoc '$1' is not sane"
}
parse_args ()
{
local argn i
declare -g addfile dostart
for ((i = 1; i <= $#; i++))
do if [[ ${!i:0:1} == '-' ]]; then
case ${!i} in
(-e|--edit) let addfile++;;
(-s|--start) let dostart++;;
(*) break;;
esac
else break; fi; done
shift $((--i))
setenv assoc "${TASKPRJ}/$1" # project in env var, gets prepended
setenv assoc "${assoc//\/\//\/}" # eliminate dupe slashes from join
setenv assoc "${assoc#/}" # eliminate any initial slash
shift
sanitize_args "$assoc" "$@"
# last slash-delimited component is the tw uda we call "label" and the
# inital ones form the builtin-attr "project" hierarchy
#
setenv label `assoc_label $assoc`
setarr projects `assoc_split_project $assoc`
# args in the middle are extra args to be added to taskw line
for ((argn = 1; argn < $#; argn++)); do
if [[ ${!argn} =~ ^--$ ]]
then let argn++; break
elif [[ ${!argn} =~ ^https?:// ]]
then urls+=("${!argn}")
else extra+=("${!argn}")
fi
done
# last arg is (or all args after '--' are) the description
shift $((argn - 1))
setenv desc "$*"
if ((${#desc} < 20)); then
read -n1 -p "warning: short desc ${#desc}, proceed (y/N) ? "
echo
if [[ ${REPLY:-n} != y ]]
then bomb "aborting without action"; fi
fi
# fully qualified task with project hierarchy and label
prjstring=$(IFS=.; printf "${projects[*]}")
setenv fqtask "project:$prjstring label:$label"
setenv prjtask "project:$prjstring.$label"
# allow injection of additional command line args from user-set
# persistent environment variable
#
extra+=($TASKADD)
desc_sane $label "$desc" ||
bomb "description failed sanity test"
}
# creates a new taskwarrior task with only uuid and description set
# returns uuid to the caller as a string on stdout
#
task_new_stub ()
{
local uuid
uuid=`uuid -v4`
if ! task import <(
printf '[{"uuid":"%s", "description":"-"}]' $uuid
) &>/dev/null
then false; exit; fi
printf $uuid
}
main ()
{
local i uuid
local prj pprj
declare -g assoc label desc fqtask
declare -g projects=() extra=() urls=()
parse_args "$@" || bomb "arguments parsed incorrectly"
task_assoc_exists $assoc &&
bomb "association already exists"
uuid=`task_new_stub` ||
bomb "failed stub create (uuid gen or task import failed)"
task $uuid modify $fqtask ${extra[@]} -- "$desc" ||
bomb "task attribute additions failed, residual: $uuid"
# edit the attached .rst file via taskopen if cli arg '-e' given
if ((addfile))
then task $uuid annotate -- file:; taskopen -e $uuid; fi
# add any urls given before description as annotations
for ((i = 0; i < ${#urls[@]}; i++))
do task $uuid annotate -- "${urls[i]}"; done
# either --start or show id to user so they can further manipulate
((dostart)) && task $uuid start || task $uuid _unique id
# XXX TODO
#
# for the given assoc path, ensure a chain of real tasks with the given
# project hierarchy that depend on each other: adding "foo/bar/baz/qux"
# makes:
# 1: proj:foo.bar.baz label:qux
# 2: proj:foo.bar label:baz depends:1
# 3: proj:foo label:bar depends:2
# 4: label:foo depends:3
#
#for ((i = ${#projects[@]} - 1; i; i--)); do
# setenv last $prj
# setenv prj ${projects[i]}
# echo $last
# echo $prj
#done
# if, for an assoc given as 'foo/bar/baz' (project:foo.bar label:baz),
# there is an existing exact match project:foo label:bar, then
# automatically add ourselves as a child dependency of that task, which
# lets us use hierarchy in the given full label identifier to imply
# parentage and automatically add dependencies without a manual second
# pass and having to figure out what the parent task is
}
main "$@"