Repair broken JCL construction for a few switches
jira-search.sh
--------------
o Declare more array vars for gathering input.
o Replace declare with local within functions.
o Refactored arg detection to set parsting state variables.
o Replace inline AND/OR construction with list join on and/or.
Too many edge cases can leave a AND/OR token prefix or suffix
breaking the JCL query line.
o do_labels logic updated to handle --label-is-empty and construct
--excl and --incl arguments as standalone paren wrapped terms.
jira-search/getopt/detect-modifiers.sh
--------------------------------------
o when -and, -or, -in, -excl, -incl, -is-empty switch modifiers are
detected set global switch parsing state variables.
Signed-off-by: Joey Armstrong <jarmstrong@linuxfoundation.org>
Change-Id: Ib67bbf1a6389c8d9e9a3ac70911429118228c683
diff --git a/jira/bin/jira-search.sh b/jira/bin/jira-search.sh
index 51959f6..751edb6 100755
--- a/jira/bin/jira-search.sh
+++ b/jira/bin/jira-search.sh
@@ -1,5 +1,5 @@
#!/bin/bash
-# -----------------------------------------------------------------------
+# -------------------------------------------------------------------------
# Copyright 2023-2024 Open Networking Foundation Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -47,6 +47,8 @@
##-------------------##
##---] GLOBALS [---##
##-------------------##
+declare -g -a is_empty=() # label
+
declare -g -a text=()
declare -g -a text_and=()
declare -g -a text_or=()
@@ -60,10 +62,8 @@
declare -g -a projects=()
path="$(realpath $0 --canonicalize-existing)"
-# source "${path%\.sh}/utils.sh"
source "${pgm_lib}/utils.sh"
-#source "$pgmlib/fixversion.sh"
-#source "$pgmlib/resolved.sh"
+source "${pgm_lib}/include.sh"
source "${pgm_lib}/fixversion.sh"
source "${pgm_lib}/resolved.sh"
source "${pgm_lib}/help/utils.sh"
@@ -79,10 +79,43 @@
** ERROR: $@
** -----------------------------------------------------------------------
ERROR
- echo
+ echo
exit 1
}
+## --------------------------------------------------------------------
+## --------------------------------------------------------------------
+function banner()
+{
+ cat <<MSG
+
+** -----------------------------------------------------------------------
+** $@
+** -----------------------------------------------------------------------
+MSG
+ return
+}
+
+## -----------------------------------------------------------------------
+## Intent: Append a conditional token in the list based on context
+## -----------------------------------------------------------------------
+function and_or()
+{
+ local -n ref=$1; shift
+ local val="$1" ; shift
+
+ if [[ ${#ref[@]} -gt 0 ]]; then
+ if [[ -v bool_or ]]; then
+ ref+=('OR')
+ else
+ ref+=('AND')
+ fi
+ fi
+
+ ref+=("$val")
+ return
+}
+
## -----------------------------------------------------------------------
## -----------------------------------------------------------------------
function html_encode()
@@ -165,23 +198,31 @@
## Intent: Query filter by labels assigned to a ticket:
## o pods, failing, testing
## --------------------------------------------------------------------
+## Perform a few string joins
+## - elements {in,not-in} label {excl, incl}
+## - OR: is-empty + in-label
+## - AND: excl + incl
+## --------------------------------------------------------------------
# "project in (UKSCR, COMPRG) AND issuetype = Bug AND labels in (BAT)" and
## --------------------------------------------------------------------
function do_labels()
{
- declare -n incl=$1; shift # was args=
- declare -n excl=$1; shift
- declare -n ans=$1; shift
+ local -n incl=$1; shift # was args=
+ local -n excl=$1; shift
+ local -n ans=$1; shift
- ## --------------------------------
- ## Conjunction if stream tokens > 0
- ## --------------------------------
- conjunction ans
+ ## -------------------------------
+ ## Join #1: is-empty + labels-incl
+ ## -------------------------------
+ local -a tokens=()
- declare -a tokens=()
+ if [[ " ${is_empty[*]} " =~ ' label ' ]]; then
+ tokens+=('(labels IS EMPTY)')
+ fi
- ## -----------------------------
- ## -----------------------------
+ ## ------------------------------
+ ## Construct label include filter
+ ## ------------------------------
if [[ ${#incl[@]} -gt 0 ]]; then
local modifier
@@ -192,25 +233,33 @@
fi
local labels=$(join_by ',' "${incl[@]}")
- local -a tmp=(\
- '('\
- 'label IS EMPTY' \
- 'OR' \
- "labels ${modifier} ($labels)" \
- ')'\
- )
- tokens+=("${tmp[@]}")
+ tokens+=("(labels ${modifier} ($labels))")
fi
- conjunction tokens 'AND'
+ # ------------------------------
+ # JOIN[OR]: is-empty + in-labels
+ # ------------------------------
+ if [[ ${#tokens[@]} -gt 1 ]]; then
+ local combine
+ combine=("$(join_by ' OR ' "${tokens[@]}")")
+ tokens=("$combine")
+ fi
- ## -----------------------------
- ## -----------------------------
+ ## ------------------------------
+ ## Construct label exclude filter
+ ## ------------------------------
if [[ ${#excl[@]} -gt 0 ]]; then
local labels=$(join_by ',' "${excl[@]}")
- tokens+=('(' "labels NOT IN ($labels)" ')')
+ tokens+=("(labels NOT IN ($labels))")
fi
+ # ------------------------------------
+ # JOIN[AND]: labels-excl + labels-incl
+ # ------------------------------------
+ if [[ ${#tokens[@]} -gt 1 ]]; then
+ tokens=("$(join_by ' AND ' "${tokens[@]}")")
+ fi
+
ans+=("${tokens[@]}")
return
}
@@ -220,19 +269,19 @@
## --------------------------------------------------------------------
function do_projects()
{
- declare -n ref=$1; shift
+ local -n ref=$1; shift
[[ ${#projects[@]} -eq 0 ]] && { return; }
local terms="$(join_by ',' "${projects[@]}")"
-# local -a buffer=('(' 'project' 'IN' "($terms)" ')')
-# ref+=("$(join_by '%20' "${buffer[@]}")")
+ # local -a buffer=('(' 'project' 'IN' "($terms)" ')')
+ # ref+=("$(join_by '%20' "${buffer[@]}")")
ref+=("(project IN ($terms))")
return
}
## --------------------------------------------------------------------
-## Intent: Query by compound text filters
+## Intent: Construct query using text field filters
## --------------------------------------------------------------------
function do_text()
{
@@ -252,7 +301,7 @@
## Append terms: AND
if [[ ${#text_and[@]} -gt 0 ]]; then
- declare -a term=()
+ local -a term=()
for val in "${text_and[@]}";
do
term+=("text ~ \"$val\"")
@@ -263,7 +312,7 @@
## Append terms: OR
if [[ ${#text_or[@]} -gt 0 ]]; then
- declare -a term=()
+ local -a term=()
for val in "${text_or[@]}";
do
term+=("text ~ \"$val\"")
@@ -282,7 +331,7 @@
## --------------------------------------------------------------------
function do_user()
{
- declare -n ans=$1; shift
+ local -n ans=$1; shift
[[ -v argv_nobody ]] && return
@@ -307,22 +356,34 @@
## --------------------------------------------------------------------
function gen_filter()
{
- declare -n ans=$1; shift
- declare -n args=$1; shift
+ local -n ans=$1 ; shift
+ local -n args=$1 ; shift
## -----------------------------------
## Begin by joining major search terms
## -----------------------------------
- declare -a _tmp=()
+ local -a _tmp=()
local val
+
+ local -i is_paren=0
+ local -a buffer=()
for val in "${args[@]}";
do
- _tmp+=("$val" 'AND')
+ case "$val" in
+ '(') _tmp+=("$val"); continue ;;
+ ')') _tmp+=("$val"); continue ;;
+ esac
+
+ and_or _tmp "$val"
done
- if [[ ${#_tmp[@]} -gt 0 ]]; then
- unset _tmp[-1]
- fi
+ ## ----------------------------------------------------------------
+ # This was used to remove AND term when a non-query or non-argument
+ # query was needed. Any lingering logic dependent on it ?
+ ## ----------------------------------------------------------------
+ # if [[ ${#_tmp[@]} -gt 0 ]]; then
+ # unset _tmp[-1]
+ # fi
## -----------------------
## Massage with html codes
@@ -336,11 +397,11 @@
## --------------------------------------------------------------------
function gen_url()
{
- declare -n ans=$1; shift
- declare -n args=$1; shift
+ local -n ans=$1; shift
+ local -n args=$1; shift
## Which jira server to query (?)
- [[ ! -v server ]] && declare -g server='jira.opennetworking.org'
+ [[ ! -v server ]] && local -g server='jira.opennetworking.org'
tmp_url="https://${server}/issues/?jql="
tmp="${tmp_url}${args}"
ans="${tmp// /%20}"
@@ -371,16 +432,16 @@
--text Search string(s)
EOH
-# declare -a topics=()
-# topics+=('fixversion.switches')
-# topics+=('resolved.switches')
-#
-# help_switch_show "${topics[@]}"#
+ # local -a topics=()
+ # topics+=('fixversion.switches')
+ # topics+=('resolved.switches')
#
-#[USER(s)]
-# --me Tickets assigned to or reported by me.
-# --user [u] Tickets assigned to this user.
-# --nobody Raw query, no filtering by user
+ # help_switch_show "${topics[@]}"#
+ #
+ #[USER(s)]
+ # --me Tickets assigned to or reported by me.
+ # --user [u] Tickets assigned to this user.
+ # --nobody Raw query, no filtering by user
cat <<EOH
@@ -393,10 +454,34 @@
--in (default) Items belong (--component IN)
--not-in Negate item set (--component NOT IN)
-[Contains]
- --text [t] (join modifer: --and, --or)
- --text-and [t] All of these terms
- --text-or [t] Any of these terms
+[Contains] (join modifer: --and, --or, --is-empty)
+ --text [t]
+ --text-and [t] All list items
+ --text-or [t] Any list items
+ --label [l] Match label field based on modifier criteria
+ --text-and [t] All list items
+ --text-or [t] Any list items
+
+[MODIFIERS]
+ --and Combine query terms using AND keyword
+ --or Combine query terms using OR keyword
+ --in Include ticket field if list item(s) match
+ --not Negate a query
+ --is-empty Include ticket if field is empty
+ --resolved-is-empty
+ --unresolved Query for open/unresolved jira tickets.
+
+[FILTER]
+ --excl Exclude tickets whose fields match this string (filter-out)
+ --incl Include tickets whose fields match this string
+
+[--resolved]
+ --resolved-is-empty
+ --resolved-start Query by date range
+ --resolved-end Query by date range
+ --resolved(-not)-empty Query for (closed)/open tickets
+ --resolved-excl Types to exclude
+ --resolved-incl Types to include
[RANGE]
--newer [d] Search for tickets created < [n] days ago.
@@ -404,6 +489,8 @@
[ALIASES]
--all Query for all unresolved tickets
+ --unresolved Alias for --resolved-is-empty
+ --wip Alias for --resolved-is-empty
[TOPIC]
--fixversion Query by field: fixedversion
@@ -423,8 +510,6 @@
o Search for tickets that contain strings bbsim or release
$0 --cord --text-and 'release' --text-and 'voltctl'
o Search jira.opencord for tickets that contain release and voltctl
- $0 --text 'bitergia' --text 'Jira' -and
- o Search jira.opennetworking for tickets containing string bitergia and Jira
$0 --cord --label failing --label pod
o Search jira.opencord for tests failing due to pod/hardware issuses.
@@ -447,26 +532,54 @@
##----------------##
##---] MAIN [---##
##----------------##
-declare -a suffix0=()
+declare -a suffix0=() # accumulated terms to join
+declare server='jira.opencord.org'
# declare -g -i debug=1
while [ $# -gt 0 ]; do
- if [ ${#suffix0[@]} -gt 0 ]; then
- suffix0+=('AND')
- fi
+# if [ ${#suffix0[@]} -gt 0 ]; then
+# suffix0+=('AND')
+# fi
arg="$1"; shift
+ banner "ARG=[$arg], \$@=[$@]"
+
[[ -v debug ]] && echo "** argv=[$arg] [$*]"
+ getopt_detect_modifiers "$arg"
+
case "$arg" in
- '--help') usage; exit 0 ;;
- '--help-'*) help_with "${arg/--help-/}" ;;
- '--usage-'*) help_usage_show "${arg/--usage-/}"
- ;;
+ '--help') usage; exit 0 ;;
+ '--help-'*) help_with "${arg/--help-/}" ;;
+ '--usage-'*) help_usage_show "${arg/--usage-/}" ;;
+ '--'*'-is-empty')
+ declare -a args=()
+ args+=('--is-empty')
+
+ arg="${arg:2}" # remove prefix --
+ arg="${arg%-is-empty}" # remove suffix token-name
+ args+=("$arg")
+ [[ $# -gt 0 ]] && { args+=("$@"); }
+
+ set -- "${args[@]}"
+ ;;
+
+ '--is-empty')
+ declare val="$1"; shift
+ declare -a valid=()
+ valid+=('label')
+
+ if [[ " ${valid[@]} " =~ " ${val} " ]]; then
+ is_empty+=("$val")
+ else
+ error "Detected invalid --is-empty switch [$arg]"
+ fi
+ ;;
+
##-----------------##
##---] MODES [---##
##-----------------##
@@ -476,7 +589,7 @@
##------------------------##
##---] SWITCH ALIAS [---##
##------------------------##
- --unresolved)
+ --unresolved|--wip)
declare -a args=()
args+=('--resolved-is-empty')
[[ $# -gt 0 ]] && { args+=("$@"); }
@@ -502,12 +615,12 @@
arg="$1"; shift
case "$arg" in
*cord*) server='jira.opencord.org' ;;
- *onf*) server='jira.opennetworking.org' ;;
- *) error "--server [$arg] expected opencord or onf" ;;
+ *onf*) server='jira.opennetworking.org' ;;
+ *) error "--server [$arg] expected opencord or onf" ;;
esac
;;
- --onf) declare server='jira.opennetworking.org' ;;
+ --onf) declare server='jira.opennetworking.org' ;;
--opencord) declare server='jira.opencord.org' ;;
##---------------------##
@@ -519,35 +632,46 @@
components+=("$arg")
;;
- --label-excl)
- arg="$1"; shift
- labels_excl+=("$arg")
- ;;
-
- --label|--label-incl)
- arg="$1"; shift
- labels_incl+=("$arg")
+ '--label-is-empty') is_empty+=('label') ;;
+
+ '--label'|'--lab'*)
+ val="$1"; shift
+ if [[ -v getopt_argv_EXCL ]]; then
+ labels_excl+=("$val")
+ elif [[ -v getopt_argv_INCL ]]; then
+ labels_incl+=("$val")
+ else
+ labels_incl+=("$val")
+ fi
;;
##-----------------------##
##---] Text Search [---##
##-----------------------##
# jsearch.sh --text-and bbsim --text-and release
- -*text-and) text_and+=("$1"); shift ;;
- -*text-or) text_or+=("$1"); shift ;;
+ '--tex'*)
+ [[ $# -gt 0 ]] && { val="$1"; }
+ case "$arg" in
+ '--text-and') text_and+=("$val") ;;
+ '--text-or') text_or+=("$val") ;;
- # % js --and --text jenkins --text cord
- # text ~ "Jira Software"
# [WORDs]
- # text ~ "\"Jira Software\""
# [STRING]
- -*text)
- arg="$1"; shift
- if [[ -v bool_and ]]; then
- text_and+=("$arg")
- elif [[ -v bool_or ]]; then
- text_or+=("$arg")
- else
- text+=("$arg")
- fi
+ # % js --and --text jenkins --text cord
+ # text ~ "Jira Software" # [WORDs]
+ # text ~ "\"Jira Software\"" # [STRING]
+ '--text')
+ if [[ ! -v bool_and_or ]]; then
+ error "Qualify [$arg] using --text-{and,or}"
+ elif [[ bool_and_or -eq 1 ]]; then
+ text_and+=("$1"); shift
+ elif [[ bool_and_or -eq 0 ]]; then
+ text_or+=("$1"); shift
+ else
+ error "Qualify [$arg] using --text-{and,or}"
+ fi
+ ;;
+ *) error "Qualify [$arg] using --text-{and,or}" ;;
+ esac
+ shift # $val
;;
--all) set -- '--resolved-is-none' "$@" ;; # alias: --[un-]resolved
@@ -558,24 +682,24 @@
--fixversion-*)
# function get_jql_fixversion()
case "$arg" in
- *excl)
- [[ ! -v fixversion_excl ]] && { declare -g -a fixversion_excl=(); }
- val="\"$1\""; shift
- html_encode val
- fixversion_excl+=("$val");
- ;;
+ *excl)
+ [[ ! -v fixversion_excl ]] && { declare -g -a fixversion_excl=(); }
+ val="\"$1\""; shift
+ html_encode val
+ fixversion_excl+=("$val");
+ ;;
- *incl)
- [[ ! -v fixversion_incl ]] && { declare -g -a fixversion_incl=(); }
- val="\"$1\""; shift
- html_encode val
- fixversion_incl+=("$val");
- ;;
+ *incl)
+ [[ ! -v fixversion_incl ]] && { declare -g -a fixversion_incl=(); }
+ val="\"$1\""; shift
+ html_encode val
+ fixversion_incl+=("$val");
+ ;;
- *not-empty) declare -g -i fixversion_not_empty=1 ;;
- *is-empty) declare -g -i fixversion_is_empty=1 ;;
+ *not-empty) declare -g -i fixversion_not_empty=1 ;;
+ *is-empty) declare -g -i fixversion_is_empty=1 ;;
- *) error "Detected invalid --fixversion-* modifier" ;;
+ *) error "Detected invalid --fixversion-* modifier" ;;
esac
;;
@@ -584,25 +708,25 @@
case "$arg" in
*start) declare -g resolved_start="$1"; shift ;;
- *end) declare -g resolved_end="$1"; shift ;;
+ *end) declare -g resolved_end="$1"; shift ;;
*not-empty) declare -g resolved_not_empty="$1" ;;
- *empty) declare -g resolved_is_empty="$1" ;;
+ *empty) declare -g resolved_is_empty="$1" ;;
- *excl)
- [[ ! -v resolved_excl ]] && { declare -g -a resolved_excl=(); }
- val="\"$1\""; shift
- html_encode val
- resolved_excl+=("$val");
- ;;
- *incl)
- [[ ! -v resolved_incl ]] && { declare -g -a resolved_incl=(); }
- val="\"$1\""; shift
- html_encode val
- resolved_incl+=("$val");
- ;;
- *) ;;
- *) error "Detected invalid --resolved-* modifier" ;;
+ *excl)
+ [[ ! -v resolved_excl ]] && { declare -g -a resolved_excl=(); }
+ val="\"$1\""; shift
+ html_encode val
+ resolved_excl+=("$val");
+ ;;
+ *incl)
+ [[ ! -v resolved_incl ]] && { declare -g -a resolved_incl=(); }
+ val="\"$1\""; shift
+ html_encode val
+ resolved_incl+=("$val");
+ ;;
+ *) ;;
+ *) error "Detected invalid --resolved-* modifier" ;;
esac
;;
@@ -617,8 +741,14 @@
##----------------##
##---] BOOL [---##
##----------------##
- --[aA][nN][dD]) declare -g -i bool_and=1 ;;
- --[oO][rR]) declare -g -i bool_or=1 ;;
+ --[aA][nN][dD])
+ declare -g -i bool_and=1
+ declare -g -i bool_and_or=1 # ! -v else toggle
+ ;;
+ --[oO][rR])
+ declare -g -i bool_or=1
+ declare -g -i bool_and_or=0
+ ;;
##------------------##
##---] MEMBER [---##
@@ -626,7 +756,7 @@
--[iI][nN]) declare -g -i bool_in=1 ;;
--[nN][oO][tT]) declare -g -i bool_not=1 ;;
- [A-Z][A-Z][A-Z]-[0-9]*)
+ [A-Z][A-Z][A-Z]-[0-9]*) # VOL-xxxx (jira ticket)
case "$arg" in
CORD-[0-9]*)
url="https://jira.opencord.org/browse/${arg}"
@@ -648,6 +778,23 @@
esac
;;
+ ## ---------------------------
+ ## Search all fields for value
+ ## ---------------------------
+ [[:word:]]*)
+
+ echo "MATCHED: [$arg] (LINENO: $LINENO)"
+ declare -a args=()
+ args+=('--OR')
+ args+=('--component' "$arg")
+ args+=('--label-incl' "$arg")
+ args+=('--text-or' "$arg")
+ [[ $# -gt 0 ]] && { args+=("$@"); }
+ set -- "${args[@]}"
+ ;;
+
+ -*) error "Detected unknown switch [$arg]" ;;
+
# -----------------------------------------------------------------------
# https://support.atlassian.com/jira-software-cloud/docs/search-syntax-for-text-fields/
# -----------------------------------------------------------------------
@@ -655,10 +802,12 @@
# -japan -- exclude term
# [STEM] summary ~ "customize" -- finds stem 'custom' in the Summary field
*)
+ echo "MATCHED: [$arg] (LINENO: $LINENO)"
declare -p text_and
error "Detected unknown argument $arg"
;;
esac
+
done
## --------------
@@ -677,6 +826,20 @@
do_resolved suffix0
do_fixversion suffix0
+declare -p suffix0
+
+if [[ -v getopt_argv_AND ]]; then
+ query="$(join_by 'AND' "${suffix0[0]}")"
+elif [[ -v getopt_argv_OR ]]; then
+ query="$(join_by 'OR' "${suffix0[0]}")"
+else
+ error "Ambiguous query [argv needs: --and or --or]"
+fi
+
+# banner "$(declare -p query)"
+
+
+
filter=''
gen_filter filter suffix0
diff --git a/jira/jira-search/getopt/detect-modifiers.sh b/jira/jira-search/getopt/detect-modifiers.sh
new file mode 100644
index 0000000..84a1afd
--- /dev/null
+++ b/jira/jira-search/getopt/detect-modifiers.sh
@@ -0,0 +1,95 @@
+#!/bin/bash
+## -----------------------------------------------------------------------
+## -----------------------------------------------------------------------
+
+## -----------------------------------------------------------------------
+## Intent: Set state flags for parsing based on detection of switch modifiers
+## -----------------------------------------------------------------------
+function getopt_detect_modifiers()
+{
+ local arg="$1"; shift
+
+ local -a patterns=()
+ patterns+=('and' 'or')
+ patterns+=('excl' 'incl')
+ patterns+=('in' 'not')
+ patterns+=('is-empty')
+
+ unset getopt_argv_AND
+ unset getopt_argv_EXCL
+ unset getopt_argv_INCL
+ unset getopt_argv_IN
+ unset getopt_argv_IS_EMPTY
+ unset getopt_argv_NOT
+ unset getopt_argv_OR
+
+ local pattern
+ for pattern in "${patterns[@]}";
+ do
+ # echo "** ${FUNCNAME} pattern=[$pattern], arg=[$arg]"
+ case "$pattern" in
+ 'and')
+ case "$arg" in
+ *'-'[aA][nN][dD]*) declare -g -i getopt_argv_AND=1 ;;
+ esac
+ ;;
+
+ 'or')
+ case "$arg" in
+ *'-'[oO][rR]*)
+ declare -g -i getopt_argv_OR=1
+ ;;
+ esac
+ ;;
+
+ 'excl')
+ case "$arg" in
+ *'-'[eE][xX][cC][lL]*) declare -g -i getopt_argv_EXCL=1 ;;
+ esac
+ ;;
+
+ 'incl')
+ case "$arg" in
+ *'-'[iI][nN][cC][lL]*) declare -g -i getopt_argv_INCL=1 ;;
+ esac
+ ;;
+
+ 'in')
+ if [[ ! -v getopt_argv_INCL ]]; then
+ case "$arg" in
+ *'-'[iI][nN]*) declare -g -i getopt_argv_IN=1 ;;
+ esac
+ fi
+ ;;
+
+ 'is-empty')
+ case "$arg" in
+ *'-'[iI][sS]-[eE][mM][pP][tT][yY]) declare -g -i getopt_argv_IS_EMPTY=1 ;;
+ esac
+ ;;
+
+ 'not')
+ case "$arg" in
+ *'-'[nN][oO][tT]*) declare -g -i getopt_argv_NOT=1 ;;
+ esac
+ ;;
+
+ esac
+
+ done
+
+ if false; then
+ [[ -v getopt_argv_AND ]] && { declare -p getopt_argv_AND; }
+ [[ -v getopt_argv_EXCL ]] && { declare -p getopt_argv_EXCL; }
+ [[ -v getopt_argv_INCL ]] && { declare -p getopt_argv_INCL; }
+ [[ -v getopt_argv_IN ]] && { declare -p getopt_argv_IN; }
+ [[ -v getopt_argv_IS_EMPTY ]] && { declare -p getopt_argv_IS_EMPTY; }
+ [[ -v getopt_argv_NOT ]] && { declare -p getopt_argv_NOT; }
+ [[ -v getopt_argv_OR ]] && { declare -p getopt_argv_OR; }
+ fi
+
+ : # return $?==0
+ return
+}
+
+# [EOF]
diff --git a/jira/jira-search/include.sh b/jira/jira-search/include.sh
new file mode 100644
index 0000000..57405c9
--- /dev/null
+++ b/jira/jira-search/include.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+## -----------------------------------------------------------------------
+## -----------------------------------------------------------------------
+
+## -----------------------------------------------------------------------
+## -----------------------------------------------------------------------
+function init()
+{
+ declare pgm=''
+ pgm="$(realpath --canonicalize-existing "$0")"
+ readonly pgm
+
+ declare -g pgmsrc
+ pgmsrc="$(readlink --canonicalize-existing "${BASH_SOURCE[0]}")"
+ readonly pgmsrc
+
+ # stack-trace-on-error
+ # interrupt handler
+ # mkdir with auto-cleanup at exit
+ declare pgm_root="${pgm%%/jira/jira-search/include.sh}"
+
+ declare root
+ root="${pgm%%/jira/bin/jira-search.sh}"
+ source "$root/lf/onf-common/common.sh" '--common-args-begin--'
+
+ pgm_lib="${root}/jira/jira-search"
+ readonly pgm_lib
+
+ pgm_bin="${root}/bin"
+ readonly pgm_bin
+
+ pgm_help="${root}/jira/jira-search/help"
+ readonly pgm_help
+}
+# init
+# unset init
+
+##--------------------##
+##---] INCLUDES [---##
+##--------------------##
+source "${pgm_lib}/getopt/detect-modifiers.sh"
+
+# source "${pgm_lib}/utils.sh"
+#source "$pgmlib/fixversion.sh"
+#source "$pgmlib/resolved.sh"
+source "${pgm_lib}/fixversion.sh"
+source "${pgm_lib}/resolved.sh"
+source "${pgm_lib}/help/utils.sh"
+
+# [EOF]
diff --git a/jira/makefile b/jira/makefile
index 02f5ad5..ab4c3eb 100644
--- a/jira/makefile
+++ b/jira/makefile
@@ -1,8 +1,37 @@
# -*- makefile -*-
+## -----------------------------------------------------------------------
+## -----------------------------------------------------------------------
+jira-search = bin/jira-search.sh
+
+## -----------------------------------------------------------------------
+## -----------------------------------------------------------------------
all:
+## -----------------------------------------------------------------------
+## -----------------------------------------------------------------------
view :
pandoc README.md | lynx --stdin
+## -----------------------------------------------------------------------
+## -----------------------------------------------------------------------
+query-args := $(null)
+# query-args += --dry-run
+query-args += --opencord
+query-args += --or
+query-args += --label-is-empty
+query-args += --label 'python'
+query-args += --label-excl 'foobar'
+query-args += --text-or 'python'
+
+query :
+ $(jira-search) $(query-args) 2>&1 | tee log
+
+## -----------------------------------------------------------------------
+## -----------------------------------------------------------------------
+help :
+ @printf 'Usage: make [options] [target] ...'
+ @printf ' %-33.33s %s\n' 'query' 'Convenience target for jira queries'
+ @printf ' %-33.33s %s\n' 'view' 'Render README.md for local viewing'
+
# [EOF]