blob: cb0feed23a412ab646f1e29cac66139cba1e64e0 [file] [log] [blame]
#!/bin/sh -efu
# Copyright 2011-2013 Intel Corporation
# Author: Artem Bityutskiy
# License: GPLv2
. shell-args
. shell-error
. shell-ini-config
. shell-quote
__br="
"
if [ -z "${__included_aiaiai_email_sh_functions-}" ]; then
__included_aiaiai_email_sh_functions=1
# All patches we deal with have to have the following prefix
prefix_format="[Prefix PATCH Suffix <m/n>]"
# A single blank character
__blank="[[:blank:]]{1}"
# Reasonable number of blanks
__blanks="[[:blank:]]{0,16}"
# Reasonable number of [blah] prefixes
__blah="(\[[^]]*\]${__blanks}){0,4}"
# The prefix
__prefix="${__blanks}([^[:blank:]]*${__blanks}${__blank})?"
# Patch number pattern
__num="0*([[:digit:]]+)"
# The "PATCH" word for case-insensitive matching
__patch="([Pp][Aa][Tt][Cc][Hh])?"
# Sed regexp matching correct subject
__single="^${__blah}\[${__prefix}${__patch}.*\].*$"
# Sed regexps matching m and n
__series="^${__blah}\[${__prefix}${__patch}.*${__blank}${__num}\/?${__num}${__blanks}\].*$"
__mref="\4"
__nref="\5"
# Get "m" (patch's number in the series) from the subject
# Usage: subject_m <subject>
subject_m()
{
local subj="$1"
printf "%s" "$subj" | LC_ALL=C sed -n -E "s/$__series/$__mref/p"
}
# Get "n" (count of patches in the series) from the subject
# Usage: subject_n <subject>
subject_n()
{
local subj="$1"
printf "%s" "$subj" | LC_ALL=C sed -n -E "s/$__series/$__nref/p"
}
# Check that the subject has the correct format
# Usage: check_subject <subject>
subject_check()
{
[ -n "$(printf "%s" "$1" | LC_ALL=C sed -n -E "/$__single/ p")" ]
}
# Strip an e-mail address from the comma-separated list of e-mail addresses
# Usage: strip_address <list> <email>
strip_address()
{
local list="$1"; shift
local email="$1"; shift
local l d
# Get the local and domain parts of the e-mail address
l="$(printf "%s" "$email" | LC_ALL=C sed "s/@.*//g")"
d="$(printf "%s" "$email" | LC_ALL=C sed "s/.*@//g")"
# Quote special sed symbols
quote_sed_regexp_variable l "$l"
quote_sed_regexp_variable d "$d"
# Strip the email from the list taking into account that local@domain
# address is equivalent to the local+xyz@domain address.
printf "%s" "$list" | LC_ALL=C sed -e "s/[^,]*$l+\{0,1\}[^@]*@$d[^,]*//g" \
-e "s/,,/,/g" -e "s/^,//" -e "s/,$//" \
-e "s/[[:blank:]]\+/ /g"
}
# Fetch project name from a list of e-mail address. The project is specified like
# this: local+project@domain, and the second parameter is the 'local@domain'
# part.
# Usage: fetch_project_name <list> <email>
fetch_project_name()
{
local list="$1"; shift
local email="$1"; shift
local l d
# Get the local and domain parts of the e-mail address
l="$(printf "%s" "$email" | LC_ALL=C sed "s/@.*//g")"
d="$(printf "%s" "$email" | LC_ALL=C sed "s/.*@//g")"
# Quote special sed symbols
quote_sed_regexp_variable l "$l"
quote_sed_regexp_variable d "$d"
printf "%s" "$list" | LC_ALL=C sed -n -e "s/.*$l+\([^@]\+\)@$d.*/\1/p" | head -n1
}
# Merge e-mail addresses into a comma-separated list. This function may have
# any number of arguments. Each input argument is allowed to contain multiple
# lines of comma separated e-mail addresses. This function will merge them all
# into a single line of comma separated e-mail addresses.
# Usage: merge_addresses "addr1" .. "addrN"
merge_addresses()
{
local list
# We set IFS inside the subshell to be a newline, so that expansion of
# $* will properly add blank lines. This enables the trim to replace
# newlines with commas.
list="$(IFS="${__br}" printf "%s" "$*" | LC_ALL=C tr "\n" ",")"
printf "%s" "$list" | LC_ALL=C sed -e "s/,\+/,/g" -e "s/^,\+//" \
-e "s/,\+$//" -e "s/[[:blank:]]\+/ /g"
}
# A helper function for 'ini_config_get()' (from libshell) which fails when
# unable to get the ini file option.
ini_config_get_or_die()
{
local var="$1"; shift
local result="$(ini_config_get "$1" "$2" "$3")"
[ -n "$result" ] ||
die "Could not find config option \"$2.$3\" in \"$1\""
eval "$var=\"\$result\""
}
# Get the list of projects from config file "$1".
get_cfgfile_projects_list()
{
local cfgfile="$1"
LC_ALL=C sed -n -e "s/^\[prj_\(.*\)\]$/\1/p" "$cfgfile"
}
# Like opt_check_number, except doesn't fail on empty number.
# Arguments: $1 is config name, $2 is config value.
# If $2 is a positive decimal, outputs it, otherwise fails.
config_check_number()
{
if [ -n "$2" ]; then
[ -n "${2##0*}" -a -n "${2##*![0-9]*}" ] &&
[ "$2" -gt 0 ] 2>/dev/null ||
fatal "$1: $2: invalid number."
fi
printf %s "$2"
}
# Like opt_check_file, except requires executable permissions
# Arguments: $1 is config name, $2 is config value.
# If $2 is an executable file, outputs canonicalized file name,
# otherwise fails.
config_check_exec()
{
local value
[ -x "$2" ] &&
value="$(readlink -ev "$2")" ||
fatal "$1: $2: not an executable file."
printf %s "$value"
}
# Check whether config value is boolean. Empty string is considered false.
# Arguments: $1 is config name, $2 is config value.
# If $2 is a boolean value, output canonicalized value,
# otherwise, fails.
config_check_boolean()
{
local value
value="$(echo "$2" | tr '[:upper:]' '[:lower:]')"
# Prefixing with _ makes it easier to catch empty string as false
case "_$value" in
_|_0|_false|_no)
printf %s "0"
;;
_1|_true|_yes)
printf %s "1"
;;
*)
fatal "$1: $2: not a boolean value."
;;
esac
}
# Parse the "global" section of the config file. The result is a set of
# per-option variables and their values are exactly as in the configuration
# file:
#
# cfg_ownname, cfg_ownmail, cfg_adminname, cfg_adminmail, cfg_workdir,
# cfg_max_validators, cfg_jobs, cfg_preamble, cfg_signature,
# cfg_built_preamble, cfg_disable_notifications, cfg_preserve_files,
# and cfg_email_hook
#
# Additionally, the following variables are set:
# o cfg_ownmail_local - the local portion of the ownmail address
# o cfg_ownmail_domain - the domain portion of the ownmail address
# o cfg_preamble - the contents of the file pointed to by the preamble file
#
# Usage: parse_config <cfgfile>
parse_config()
{
local cfgfile="$1"
ini_config_get_or_die cfg_ownname "$cfgfile" "global" "ownname"
ini_config_get_or_die cfg_ownmail "$cfgfile" "global" "ownmail"
ini_config_get_or_die cfg_adminmail "$cfgfile" "global" "adminmail"
ini_config_get_or_die cfg_adminname "$cfgfile" "global" "adminname"
ini_config_get_or_die cfg_workdir "$cfgfile" "global" "workdir"
cfg_workdir="$(opt_check_dir "workdir" "$cfg_workdir")"
ini_config_get_or_die cfg_max_validators "$cfgfile" "global" "max_validators"
cfg_max_validators="$(config_check_number "max_validators" "$cfg_max_validators")"
ini_config_get_or_die cfg_jobs "$cfgfile" "global" "jobs"
cfg_jobs="$(config_check_number "jobs" "$cfg_jobs")"
ini_config_get_or_die cfg_preamble "$cfgfile" "global" "preamble"
ini_config_get_or_die cfg_signature "$cfgfile" "global" "signature"
ini_config_get_or_die cfg_built_preamble "$cfgfile" "global" "built_preamble"
# Get Email LDA settings
cfg_lda_reap_archive="$(ini_config_get "$cfgfile" "lda" "reap_archive")"
cfg_lda_reap_archive="$(config_check_number "reap_archive" "$cfg_lda_reap_archive")"
cfg_lda_reap_incomplete="$(ini_config_get "$cfgfile" "lda" "reap_incomplete")"
cfg_lda_reap_incomplete="$(config_check_number "reap_incomplete" "$cfg_lda_reap_incomplete")"
# Get the location of email hook(s)
cfg_email_hook="$(ini_config_get "$cfgfile" "hooks" "email")"
cfg_email_hook="$(config_check_exec "email" "$cfg_email_hook")"
# Debug options
cfg_disable_notifications="$(ini_config_get "$cfgfile" "debug" "disable_notifications")"
cfg_disable_notifications="$(config_check_boolean "disable_notifications" "$cfg_disable_notifications")"
cfg_preserve_files="$(ini_config_get "$cfgfile" "debug" "preserve_files")"
cfg_preserve_files="$(config_check_boolean "preserve_files" "$cfg_preserve_files")"
# Get the contents of the preamble file
cfg_preamble="$(cat "$cfg_preamble")"
# Get the local and domain parts of own e-mail address
cfg_ownmail_local="$(printf "%s" "$cfg_ownmail" | LC_ALL=C sed "s/@.*//g")"
cfg_ownmail_domain="$(printf "%s" "$cfg_ownmail" | LC_ALL=C sed "s/.*@//g")"
}
# Parse the "defaults" section of the config file. The result is a set of
# per-option variables and their values are exactly as in the configuration
# file:
#
# __dcfg_configs, __dcfg_always_cc, __dcfg_reply_to_all, __dcfg_accept_notify,
# __dcfg_unwanted_keywords, __dcfg_kmake_opts, __dcfg_targets,
# __dcfg_defconfigdir, __dcfg_bisectability, __dcfg_sparse, __dcfg_smatch,
# __dcfg_cppcheck, __dcfg_coccinelle, __dcfg_checkpatch
#
# It is expected that this is used internally by the parse_prj_config and
# should not normally be called outside of this file.
#
# Usage: _parse_defaults_config <cfgfile>
__parse_default_config()
{
local cfgfile="$1"; shift
__dcfg_configs="$(ini_config_get "$cfgfile" "defaults" "configs")"
__dcfg_always_cc="$(ini_config_get "$cfgfile" "defaults" "always_cc")"
__dcfg_reply_to_all="$(ini_config_get "$cfgfile" "defaults" "reply_to_all")"
__dcfg_accept_notify="$(ini_config_get "$cfgfile" "defaults" "accept_notify")"
__dcfg_unwanted_keywords="$(ini_config_get "$cfgfile" "defaults" "unwanted_keywords")"
__dcfg_kmake_opts="$(ini_config_get "$cfgfile" "defaults" "kmake_opts")"
__dcfg_targets="$(ini_config_get "$cfgfile" "defaults" "targets")"
__dcfg_defconfigdir="$(ini_config_get "$cfgfile" "defaults" "defconfigdir")"
__dcfg_bisectability="$(ini_config_get "$cfgfile" "defaults" "bisectability")"
__dcfg_sparse="$(ini_config_get "$cfgfile" "defaults" "sparse")"
__dcfg_smatch="$(ini_config_get "$cfgfile" "defaults" "smatch")"
__dcfg_cppcheck="$(ini_config_get "$cfgfile" "defaults" "cppcheck")"
__dcfg_coccinelle="$(ini_config_get "$cfgfile" "defaults" "coccinelle")"
__dcfg_checkpatch="$(ini_config_get "$cfgfile" "defaults" "checkpatch")"
}
# Similar to "parse_config", but parses a project configuration section. If the
# project is found, the following variables are defined:
#
# pcfg_name, pcfg_description, pcfg_path, pcfg_branch and pcfg_canonical_url.
#
# The following variables are defined, but receive default values from the
# [defaults] section, if they are not specified in the project section:
#
# pcfg_configs, pcfg_always_cc, pcfg_reply_to_all, pcfg_accept_notify,
# pcfg_unwanted_keywords, pcfg_kmake_opts, pcfg_targets, pcfg_defconfigdir,
# pcfg_bisectability, pcfg_sparse, pcfg_smatch, pcfg_cppcheck, pcfg_coccinelle
#
# If the project is not found, this function only defined an empty "pcfg_name"
# variable.
#
# Note, this function implicitly uses _parse_defaults_config to grab the
# default configurations, and if the variable is not defined in the project
# section, it will use the default value.
#
# Usage: parse_prj_config <cfgfile> <prj>
parse_prj_config()
{
local cfgfile="$1"; shift
local prj="$1"; shift
pcfg_name="$(ini_config_get "$cfgfile" "prj_$prj" "name")"
[ -n "$pcfg_name" ] || return 0
ini_config_get_or_die pcfg_description "$cfgfile" "prj_$prj" "description"
ini_config_get_or_die pcfg_path "$cfgfile" "prj_$prj" "path"
ini_config_get_or_die pcfg_branch "$cfgfile" "prj_$prj" "branch"
pcfg_canonical_url="$(ini_config_get "$cfgfile" "prj_$prj" "canonical_url")"
# The following options all take default value from the "defaults"
# section, and hence "override" those settings. First we need to populate those.
__parse_default_config "$cfgfile"
# ini_config_is_set is important here, so that defining a value as
# empty in the project section actually does define it as empty, rather
# than using the default. This allows defaults to only be used if the
# project config does not specify anything.
pcfg_configs="$(ini_config_get "$cfgfile" "prj_$prj" "configs")"
ini_config_is_set "$cfgfile" "prj_$prj" "configs" || pcfg_configs="$__dcfg_configs"
pcfg_reply_to_all="$(ini_config_get "$cfgfile" "prj_$prj" "reply_to_all")"
ini_config_is_set "$cfgfile" "prj_$prj" "reply_to_all" || pcfg_reply_to_all="$__dcfg_reply_to_all"
pcfg_reply_to_all="$(config_check_boolean "reply_to_all" "$pcfg_reply_to_all")"
pcfg_accept_notify="$(ini_config_get "$cfgfile" "prj_$prj" "accept_notify")"
ini_config_is_set "$cfgfile" "prj_$prj" "accept_notify" || pcfg_accept_notify="$__dcfg_accept_notify"
pcfg_accept_notify="$(config_check_boolean "accept_notify" "$pcfg_accept_notify")"
pcfg_always_cc="$(ini_config_get "$cfgfile" "prj_$prj" "always_cc")"
ini_config_is_set "$cfgfile" "prj_$prj" "always_cc" || pcfg_always_cc="$__dcfg_always_cc"
pcfg_unwanted_keywords="$(ini_config_get "$cfgfile" "prj_$prj" "unwanted_keywords")"
ini_config_is_set "$cfgfile" "prj_$prj" "unwanted_keywords" || pcfg_unwanted_keywords="$__dcfg_unwanted_keywords"
pcfg_kmake_opts="$(ini_config_get "$cfgfile" "prj_$prj" "kmake_opts")"
ini_config_is_set "$cfgfile" "prj_$prj" "kmake_opts" || pcfg_kmake_opts="$__dcfg_kmake_opts"
pcfg_targets="$(ini_config_get "$cfgfile" "prj_$prj" "targets")"
ini_config_is_set "$cfgfile" "prj_$prj" "targets" || pcfg_targets="$__dcfg_targets"
pcfg_defconfigdir="$(ini_config_get "$cfgfile" "prj_$prj" "defconfigdir")"
ini_config_is_set "$cfgfile" "prj_$prj" "defconfigdir" || pcfg_defconfigdir="$__dcfg_defconfigdir"
[ -z "$pcfg_defconfigdir" ] || pcfg_defconfigdir="$(opt_check_dir "defconfigdir" "$pcfg_defconfigdir")"
pcfg_bisectability="$(ini_config_get "$cfgfile" "prj_$prj" "bisectability")"
ini_config_is_set "$cfgfile" "prj_$prj" "bisectability" || pcfg_bisectability="$__dcfg_bisectability"
pcfg_bisectability="$(config_check_boolean "bisectability" "$pcfg_bisectability")"
pcfg_sparse="$(ini_config_get "$cfgfile" "prj_$prj" "sparse")"
ini_config_is_set "$cfgfile" "prj_$prj" "sparse" || pcfg_sparse="$__dcfg_sparse"
pcfg_sparse="$(config_check_boolean "sparse" "$pcfg_sparse")"
pcfg_smatch="$(ini_config_get "$cfgfile" "prj_$prj" "smatch")"
ini_config_is_set "$cfgfile" "prj_$prj" "smatch" || pcfg_smatch="$__dcfg_smatch"
pcfg_smatch="$(config_check_boolean "smatch" "$pcfg_smatch")"
pcfg_cppcheck="$(ini_config_get "$cfgfile" "prj_$prj" "cppcheck")"
ini_config_is_set "$cfgfile" "prj_$prj" "cppcheck" || pcfg_cppcheck="$__dcfg_cppcheck"
pcfg_cppcheck="$(config_check_boolean "cppcheck" "$pcfg_cppcheck")"
pcfg_coccinelle="$(ini_config_get "$cfgfile" "prj_$prj" "coccinelle")"
ini_config_is_set "$cfgfile" "prj_$prj" "coccinelle" || pcfg_coccinelle="$__dcfg_coccinelle"
pcfg_coccinelle="$(config_check_boolean "coccinelle" "$pcfg_coccinelle")"
pcfg_checkpatch="$(ini_config_get "$cfgfile" "prj_$prj" "checkpatch")"
ini_config_is_set "$cfgfile" "prj_$prj" "checkpatch" || pcfg_checkpatch="$__dcfg_checkpatch"
pcfg_checkpatch="$(config_check_boolean "checkpatch" "$pcfg_checkpatch")"
}
# Compose (but not send) e-mail reply. This function assumes that the following
# variables are defined: cfg_ownname, cfg_ownmail, cfg_adminname,
# cfg_adminmail, cfg_preamble, cfg_signature. See "parse_config()" function.
#
# Usage: compose_email <to> <cc> <subj> <in_reply_to>
compose_email()
{
local to="$1"; shift
local cc="$1"; shift
local subj="$1"; shift
local in_reply_to="$1"; shift
if [ -n "$cc" ]; then
cc="$(LC_ALL=C; printf "%s" "$cc" | tr "," "\n" | \
sed -e "/^$/d" -e "s/^/Cc: /g")"
cc="$__br$cc"
fi
cat <<EOF
To: $to
From: "$cfg_ownname" <$cfg_ownmail>$cc
Subject: Re: $subj
In-Reply-To: $in_reply_to
Reply-To: "$cfg_adminname" <$cfg_adminmail>
$cfg_preamble
$(cat)
--
$cfg_signature
EOF
}
fi #__included_aiaiai_email_sh_functions