| #!/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 |